prism-ai-core 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. prism_ai_core-0.1.0/.gitignore +13 -0
  2. prism_ai_core-0.1.0/LICENSE +21 -0
  3. prism_ai_core-0.1.0/PKG-INFO +193 -0
  4. prism_ai_core-0.1.0/README.md +170 -0
  5. prism_ai_core-0.1.0/pyproject.toml +68 -0
  6. prism_ai_core-0.1.0/src/prism/__init__.py +181 -0
  7. prism_ai_core-0.1.0/src/prism/_php.py +90 -0
  8. prism_ai_core-0.1.0/src/prism/audio/__init__.py +15 -0
  9. prism_ai_core-0.1.0/src/prism/audio/pending_request.py +123 -0
  10. prism_ai_core-0.1.0/src/prism/audio/request.py +31 -0
  11. prism_ai_core-0.1.0/src/prism/audio/response.py +46 -0
  12. prism_ai_core-0.1.0/src/prism/batch/__init__.py +39 -0
  13. prism_ai_core-0.1.0/src/prism/batch/batch_job.py +164 -0
  14. prism_ai_core-0.1.0/src/prism/batch/pending_request.py +143 -0
  15. prism_ai_core-0.1.0/src/prism/batch/request.py +85 -0
  16. prism_ai_core-0.1.0/src/prism/canonical.py +70 -0
  17. prism_ai_core-0.1.0/src/prism/embeddings/__init__.py +9 -0
  18. prism_ai_core-0.1.0/src/prism/embeddings/pending_request.py +111 -0
  19. prism_ai_core-0.1.0/src/prism/embeddings/request.py +26 -0
  20. prism_ai_core-0.1.0/src/prism/embeddings/response.py +27 -0
  21. prism_ai_core-0.1.0/src/prism/enums.py +80 -0
  22. prism_ai_core-0.1.0/src/prism/errors.py +132 -0
  23. prism_ai_core-0.1.0/src/prism/files/__init__.py +25 -0
  24. prism_ai_core-0.1.0/src/prism/files/file_data.py +71 -0
  25. prism_ai_core-0.1.0/src/prism/files/pending_request.py +149 -0
  26. prism_ai_core-0.1.0/src/prism/files/request.py +76 -0
  27. prism_ai_core-0.1.0/src/prism/fim/__init__.py +9 -0
  28. prism_ai_core-0.1.0/src/prism/fim/pending_request.py +108 -0
  29. prism_ai_core-0.1.0/src/prism/fim/request.py +32 -0
  30. prism_ai_core-0.1.0/src/prism/fim/response.py +36 -0
  31. prism_ai_core-0.1.0/src/prism/http.py +273 -0
  32. prism_ai_core-0.1.0/src/prism/images/__init__.py +9 -0
  33. prism_ai_core-0.1.0/src/prism/images/pending_request.py +88 -0
  34. prism_ai_core-0.1.0/src/prism/images/request.py +17 -0
  35. prism_ai_core-0.1.0/src/prism/images/response.py +39 -0
  36. prism_ai_core-0.1.0/src/prism/moderation/__init__.py +9 -0
  37. prism_ai_core-0.1.0/src/prism/moderation/pending_request.py +85 -0
  38. prism_ai_core-0.1.0/src/prism/moderation/request.py +18 -0
  39. prism_ai_core-0.1.0/src/prism/moderation/response.py +40 -0
  40. prism_ai_core-0.1.0/src/prism/prism.py +70 -0
  41. prism_ai_core-0.1.0/src/prism/providers/__init__.py +12 -0
  42. prism_ai_core-0.1.0/src/prism/providers/anthropic/__init__.py +8 -0
  43. prism_ai_core-0.1.0/src/prism/providers/anthropic/maps.py +339 -0
  44. prism_ai_core-0.1.0/src/prism/providers/anthropic/provider.py +233 -0
  45. prism_ai_core-0.1.0/src/prism/providers/anthropic/rate_limits.py +68 -0
  46. prism_ai_core-0.1.0/src/prism/providers/anthropic/request_body.py +125 -0
  47. prism_ai_core-0.1.0/src/prism/providers/anthropic/response.py +166 -0
  48. prism_ai_core-0.1.0/src/prism/providers/anthropic/stream_events.py +197 -0
  49. prism_ai_core-0.1.0/src/prism/providers/base.py +82 -0
  50. prism_ai_core-0.1.0/src/prism/providers/mistral/__init__.py +8 -0
  51. prism_ai_core-0.1.0/src/prism/providers/mistral/embeddings.py +66 -0
  52. prism_ai_core-0.1.0/src/prism/providers/mistral/fim.py +65 -0
  53. prism_ai_core-0.1.0/src/prism/providers/mistral/maps.py +243 -0
  54. prism_ai_core-0.1.0/src/prism/providers/mistral/provider.py +291 -0
  55. prism_ai_core-0.1.0/src/prism/providers/mistral/rate_limits.py +68 -0
  56. prism_ai_core-0.1.0/src/prism/providers/mistral/request_body.py +112 -0
  57. prism_ai_core-0.1.0/src/prism/providers/mistral/response.py +227 -0
  58. prism_ai_core-0.1.0/src/prism/providers/mistral/stream_events.py +189 -0
  59. prism_ai_core-0.1.0/src/prism/providers/openai/__init__.py +9 -0
  60. prism_ai_core-0.1.0/src/prism/providers/openai/audio.py +145 -0
  61. prism_ai_core-0.1.0/src/prism/providers/openai/batch.py +297 -0
  62. prism_ai_core-0.1.0/src/prism/providers/openai/embeddings.py +90 -0
  63. prism_ai_core-0.1.0/src/prism/providers/openai/files.py +127 -0
  64. prism_ai_core-0.1.0/src/prism/providers/openai/images.py +90 -0
  65. prism_ai_core-0.1.0/src/prism/providers/openai/maps.py +362 -0
  66. prism_ai_core-0.1.0/src/prism/providers/openai/moderation.py +40 -0
  67. prism_ai_core-0.1.0/src/prism/providers/openai/provider.py +619 -0
  68. prism_ai_core-0.1.0/src/prism/providers/openai/rate_limits.py +85 -0
  69. prism_ai_core-0.1.0/src/prism/providers/openai/request_body.py +77 -0
  70. prism_ai_core-0.1.0/src/prism/providers/openai/response.py +233 -0
  71. prism_ai_core-0.1.0/src/prism/providers/openai/stream_events.py +126 -0
  72. prism_ai_core-0.1.0/src/prism/providers/openai/structured_body.py +64 -0
  73. prism_ai_core-0.1.0/src/prism/providers/openai/structured_mode.py +53 -0
  74. prism_ai_core-0.1.0/src/prism/providers/rate_limits.py +65 -0
  75. prism_ai_core-0.1.0/src/prism/providers/support.py +46 -0
  76. prism_ai_core-0.1.0/src/prism/py.typed +0 -0
  77. prism_ai_core-0.1.0/src/prism/registry.py +66 -0
  78. prism_ai_core-0.1.0/src/prism/schema.py +195 -0
  79. prism_ai_core-0.1.0/src/prism/streaming/__init__.py +31 -0
  80. prism_ai_core-0.1.0/src/prism/streaming/events.py +167 -0
  81. prism_ai_core-0.1.0/src/prism/streaming/sse.py +60 -0
  82. prism_ai_core-0.1.0/src/prism/structured/__init__.py +15 -0
  83. prism_ai_core-0.1.0/src/prism/structured/extract.py +53 -0
  84. prism_ai_core-0.1.0/src/prism/structured/from_text.py +33 -0
  85. prism_ai_core-0.1.0/src/prism/structured/pending_request.py +83 -0
  86. prism_ai_core-0.1.0/src/prism/structured/request.py +30 -0
  87. prism_ai_core-0.1.0/src/prism/structured/response.py +49 -0
  88. prism_ai_core-0.1.0/src/prism/text/__init__.py +11 -0
  89. prism_ai_core-0.1.0/src/prism/text/pending_request.py +249 -0
  90. prism_ai_core-0.1.0/src/prism/text/request.py +51 -0
  91. prism_ai_core-0.1.0/src/prism/text/response.py +47 -0
  92. prism_ai_core-0.1.0/src/prism/text/response_builder.py +82 -0
  93. prism_ai_core-0.1.0/src/prism/text/step.py +58 -0
  94. prism_ai_core-0.1.0/src/prism/tool.py +85 -0
  95. prism_ai_core-0.1.0/src/prism/value_objects/__init__.py +45 -0
  96. prism_ai_core-0.1.0/src/prism/value_objects/_opaque.py +24 -0
  97. prism_ai_core-0.1.0/src/prism/value_objects/embedding.py +62 -0
  98. prism_ai_core-0.1.0/src/prism/value_objects/generated_audio.py +33 -0
  99. prism_ai_core-0.1.0/src/prism/value_objects/generated_image.py +44 -0
  100. prism_ai_core-0.1.0/src/prism/value_objects/media.py +88 -0
  101. prism_ai_core-0.1.0/src/prism/value_objects/media_file.py +363 -0
  102. prism_ai_core-0.1.0/src/prism/value_objects/messages.py +212 -0
  103. prism_ai_core-0.1.0/src/prism/value_objects/meta.py +53 -0
  104. prism_ai_core-0.1.0/src/prism/value_objects/moderation_result.py +68 -0
  105. prism_ai_core-0.1.0/src/prism/value_objects/provider_rate_limit.py +142 -0
  106. prism_ai_core-0.1.0/src/prism/value_objects/provider_tool.py +33 -0
  107. prism_ai_core-0.1.0/src/prism/value_objects/provider_tool_call.py +36 -0
  108. prism_ai_core-0.1.0/src/prism/value_objects/tool_call.py +121 -0
  109. prism_ai_core-0.1.0/src/prism/value_objects/tool_result.py +55 -0
  110. prism_ai_core-0.1.0/src/prism/value_objects/usage.py +48 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .parity/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 TJ MIller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.5
2
+ Name: prism-ai-core
3
+ Version: 0.1.0
4
+ Summary: Python port of Prism — a unified API layer over LLM providers.
5
+ Author: Particle Academy
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: ai,llm,openai,prism
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.10
18
+ Provides-Extra: dev
19
+ Requires-Dist: mypy>=1.11; extra == 'dev'
20
+ Requires-Dist: pytest>=8.0; extra == 'dev'
21
+ Requires-Dist: ruff>=0.6; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # prism-ai-core
25
+
26
+ A unified API layer over LLM providers — the Python port of
27
+ [Prism](https://github.com/Particle-Academy/prism).
28
+
29
+ Zero runtime dependencies. Python 3.10+.
30
+
31
+ ```python
32
+ from prism import Prism
33
+
34
+ response = (
35
+ Prism.text()
36
+ .using("openai", "gpt-4o")
37
+ .with_prompt("Who are you?")
38
+ .as_text()
39
+ )
40
+
41
+ print(response.text)
42
+ ```
43
+
44
+ > **Working on this package?** Read **[`AGENTS.md`](AGENTS.md)** first — the boundary
45
+ > this package has to hold, the gates that must be green, and the traps that have
46
+ > already caught someone.
47
+ > `@link AGENTS.md`
48
+
49
+ ## Scope
50
+
51
+ **Every capability the reference has**, and three of its eighteen providers.
52
+ That is the shape of the remaining drift: one axis, not two.
53
+
54
+ - **Capabilities — all of them.** `text`, `structured`, `stream`, `embeddings`,
55
+ `images`, `moderation`, `audio` (`text_to_speech` and `speech_to_text`),
56
+ `files`, `batch`, `fim`. A user message carries images and documents, and each
57
+ provider spells them its own way.
58
+ - **Providers — `anthropic`, `mistral`, `openai`.** The OpenAI provider speaks
59
+ the **Responses API**; Mistral brings the chat-completions shape, which is a
60
+ different wire format rather than a variant of it.
61
+
62
+ Not in this port: **the tool-execution loop** — a response that finishes on tool
63
+ calls is refused with `tool_loop_not_supported` rather than half-executed — and
64
+ the fifteen providers the reference has and this one does not. An unsupported
65
+ capability raises a coded error rather than a missing attribute.
66
+
67
+ Not every capability reaches every provider, because not every provider has one.
68
+ `images`, `moderation`, `text_to_speech`, `files` and `batch` are OpenAI-only
69
+ here, and `fim` is Mistral-only because Mistral is the only provider that
70
+ implements it at all — in the reference as well. See the envelope's port gaps
71
+ register for which of those are work someone could do and which are facts about
72
+ the provider.
73
+
74
+ Two things this port has that the reference does not:
75
+
76
+ - **Every failure carries a stable `code`** (`prism.ErrorCode`). The reference
77
+ identifies failures by an English sentence, which forces consumers to match on
78
+ prose. The prose here is explicitly not part of the contract; the code is.
79
+ - **Every value object rebuilds.** `to_dict()` *and* `from_dict()`. The
80
+ reference can write its value objects and cannot read them back, a gap that
81
+ forced a downstream package to invent its own rehydration and ship a defect
82
+ with it.
83
+
84
+ ## Install
85
+
86
+ ```
87
+ pip install prism-ai-core
88
+ ```
89
+
90
+ The distribution is `prism-ai-core`; the package it installs is imported as
91
+ `prism`. To track `main` instead:
92
+
93
+ ```
94
+ pip install "git+https://github.com/Particle-Academy/prism-py"
95
+ ```
96
+
97
+ **Do not `pip install prism-ai`. That name on PyPI is somebody else's package**
98
+ — author `gwhite`, shipping a `prism_ai` module, unrelated to this project. It
99
+ is not a squat to route around later: it publishes **the same version number**
100
+ this port carries, so a pin of `prism-ai==0.1.0` resolves to the stranger's
101
+ distribution with nothing to tell you apart.
102
+
103
+ It is not inert either. It declares `httpx`, `requests` and `tqdm`, where this
104
+ port declares none, and installing it has been reported ROLLING HTTPX BACK
105
+ (0.28.1 → 0.25.2) and breaking an MCP client sharing the environment. pip prints
106
+ that conflict as a warning *after* the words "Successfully installed", so a CI
107
+ or otherwise non-interactive install reports success and the damage surfaces
108
+ somewhere else entirely.
109
+
110
+ Pin the git URL, and leave a comment where you pin it — this is the kind of line
111
+ a later tidy-up "corrects" into a version specifier.
112
+
113
+ Configuration comes from explicit constructor arguments, falling back to
114
+ `OPENAI_API_KEY`, `OPENAI_URL`, `OPENAI_ORGANIZATION` and `OPENAI_PROJECT`. The
115
+ HTTP transport is injectable, so nothing has to reach the network in a test:
116
+
117
+ ```python
118
+ from prism import OpenAI, Prism
119
+
120
+ pending = Prism.text().using("openai", "gpt-4o", {"transport": my_transport})
121
+ ```
122
+
123
+ ## Parity
124
+
125
+ What this port must do is pinned by
126
+ [**prism-parity**](https://github.com/Particle-Academy/prism-parity), not by this
127
+ README. The corpus is the contract — the cases, the goldens, the per-language
128
+ skips and the discrimination probes all live there, each with its own notes
129
+ saying what it exists to catch. Restating them here would only give them a
130
+ second copy to drift from.
131
+
132
+ The corpus and its loader install as the `prism-conformance` package:
133
+
134
+ ```
135
+ git clone https://github.com/Particle-Academy/prism-parity .parity
136
+ pip install ./.parity/loaders/py
137
+ ```
138
+
139
+ Install it **last**: a path install can be pruned by a later install step. The
140
+ loader finds its fixtures by walking up from its own installed location, so
141
+ nothing here ever resolves a path into a sibling checkout.
142
+
143
+ Run every suite:
144
+
145
+ ```
146
+ python conformance/runner.py
147
+ ```
148
+
149
+ Or one suite, or under a probe:
150
+
151
+ ```
152
+ python conformance/runner.py --suite openai-text-request
153
+ python conformance/runner.py --probe omit-null-keys
154
+ ```
155
+
156
+ stdout is JSON and nothing else; the corpus version, digest and root go to
157
+ stderr on every run. Exit 0 when every case passed or skipped, 1 on a failure,
158
+ 2 when the corpus failed to load, 3 when the runner could not start.
159
+
160
+ ### Probes
161
+
162
+ `conformance/mutations.py` implements each probe the corpus declares as an
163
+ injected defect, and `tests/test_probes.py` asserts that every one fails
164
+ *exactly* the set of case ids the corpus names — not a superset, not "at least
165
+ one" — and that the faithful control fails nothing. That is what makes the
166
+ conformance table a measurement rather than decoration.
167
+
168
+ Nothing in `src/prism` knows any of this exists. A defect a port can switch on
169
+ is a defect a port can ship, so the mutants are installed from outside, over the
170
+ real library's output, and removed again.
171
+
172
+ ## Development
173
+
174
+ `src/` layout, so install the package before running the tests:
175
+
176
+ ```
177
+ python -m venv .venv
178
+ .venv/Scripts/python -m pip install -e ".[dev]"
179
+ .venv/Scripts/python -m pip install ../prism-parity/loaders/py
180
+
181
+ python -m pytest
182
+ python -m mypy --strict
183
+ python -m ruff check .
184
+ python -m ruff format --check .
185
+ ```
186
+
187
+ Set `PRISM_CORPUS_ROOT` to run the conformance tests against a parity checkout
188
+ whose loader copy has not been re-synced yet. Unset — the normal case — the
189
+ loader discovers its own fixtures.
190
+
191
+ ## License
192
+
193
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,170 @@
1
+ # prism-ai-core
2
+
3
+ A unified API layer over LLM providers — the Python port of
4
+ [Prism](https://github.com/Particle-Academy/prism).
5
+
6
+ Zero runtime dependencies. Python 3.10+.
7
+
8
+ ```python
9
+ from prism import Prism
10
+
11
+ response = (
12
+ Prism.text()
13
+ .using("openai", "gpt-4o")
14
+ .with_prompt("Who are you?")
15
+ .as_text()
16
+ )
17
+
18
+ print(response.text)
19
+ ```
20
+
21
+ > **Working on this package?** Read **[`AGENTS.md`](AGENTS.md)** first — the boundary
22
+ > this package has to hold, the gates that must be green, and the traps that have
23
+ > already caught someone.
24
+ > `@link AGENTS.md`
25
+
26
+ ## Scope
27
+
28
+ **Every capability the reference has**, and three of its eighteen providers.
29
+ That is the shape of the remaining drift: one axis, not two.
30
+
31
+ - **Capabilities — all of them.** `text`, `structured`, `stream`, `embeddings`,
32
+ `images`, `moderation`, `audio` (`text_to_speech` and `speech_to_text`),
33
+ `files`, `batch`, `fim`. A user message carries images and documents, and each
34
+ provider spells them its own way.
35
+ - **Providers — `anthropic`, `mistral`, `openai`.** The OpenAI provider speaks
36
+ the **Responses API**; Mistral brings the chat-completions shape, which is a
37
+ different wire format rather than a variant of it.
38
+
39
+ Not in this port: **the tool-execution loop** — a response that finishes on tool
40
+ calls is refused with `tool_loop_not_supported` rather than half-executed — and
41
+ the fifteen providers the reference has and this one does not. An unsupported
42
+ capability raises a coded error rather than a missing attribute.
43
+
44
+ Not every capability reaches every provider, because not every provider has one.
45
+ `images`, `moderation`, `text_to_speech`, `files` and `batch` are OpenAI-only
46
+ here, and `fim` is Mistral-only because Mistral is the only provider that
47
+ implements it at all — in the reference as well. See the envelope's port gaps
48
+ register for which of those are work someone could do and which are facts about
49
+ the provider.
50
+
51
+ Two things this port has that the reference does not:
52
+
53
+ - **Every failure carries a stable `code`** (`prism.ErrorCode`). The reference
54
+ identifies failures by an English sentence, which forces consumers to match on
55
+ prose. The prose here is explicitly not part of the contract; the code is.
56
+ - **Every value object rebuilds.** `to_dict()` *and* `from_dict()`. The
57
+ reference can write its value objects and cannot read them back, a gap that
58
+ forced a downstream package to invent its own rehydration and ship a defect
59
+ with it.
60
+
61
+ ## Install
62
+
63
+ ```
64
+ pip install prism-ai-core
65
+ ```
66
+
67
+ The distribution is `prism-ai-core`; the package it installs is imported as
68
+ `prism`. To track `main` instead:
69
+
70
+ ```
71
+ pip install "git+https://github.com/Particle-Academy/prism-py"
72
+ ```
73
+
74
+ **Do not `pip install prism-ai`. That name on PyPI is somebody else's package**
75
+ — author `gwhite`, shipping a `prism_ai` module, unrelated to this project. It
76
+ is not a squat to route around later: it publishes **the same version number**
77
+ this port carries, so a pin of `prism-ai==0.1.0` resolves to the stranger's
78
+ distribution with nothing to tell you apart.
79
+
80
+ It is not inert either. It declares `httpx`, `requests` and `tqdm`, where this
81
+ port declares none, and installing it has been reported ROLLING HTTPX BACK
82
+ (0.28.1 → 0.25.2) and breaking an MCP client sharing the environment. pip prints
83
+ that conflict as a warning *after* the words "Successfully installed", so a CI
84
+ or otherwise non-interactive install reports success and the damage surfaces
85
+ somewhere else entirely.
86
+
87
+ Pin the git URL, and leave a comment where you pin it — this is the kind of line
88
+ a later tidy-up "corrects" into a version specifier.
89
+
90
+ Configuration comes from explicit constructor arguments, falling back to
91
+ `OPENAI_API_KEY`, `OPENAI_URL`, `OPENAI_ORGANIZATION` and `OPENAI_PROJECT`. The
92
+ HTTP transport is injectable, so nothing has to reach the network in a test:
93
+
94
+ ```python
95
+ from prism import OpenAI, Prism
96
+
97
+ pending = Prism.text().using("openai", "gpt-4o", {"transport": my_transport})
98
+ ```
99
+
100
+ ## Parity
101
+
102
+ What this port must do is pinned by
103
+ [**prism-parity**](https://github.com/Particle-Academy/prism-parity), not by this
104
+ README. The corpus is the contract — the cases, the goldens, the per-language
105
+ skips and the discrimination probes all live there, each with its own notes
106
+ saying what it exists to catch. Restating them here would only give them a
107
+ second copy to drift from.
108
+
109
+ The corpus and its loader install as the `prism-conformance` package:
110
+
111
+ ```
112
+ git clone https://github.com/Particle-Academy/prism-parity .parity
113
+ pip install ./.parity/loaders/py
114
+ ```
115
+
116
+ Install it **last**: a path install can be pruned by a later install step. The
117
+ loader finds its fixtures by walking up from its own installed location, so
118
+ nothing here ever resolves a path into a sibling checkout.
119
+
120
+ Run every suite:
121
+
122
+ ```
123
+ python conformance/runner.py
124
+ ```
125
+
126
+ Or one suite, or under a probe:
127
+
128
+ ```
129
+ python conformance/runner.py --suite openai-text-request
130
+ python conformance/runner.py --probe omit-null-keys
131
+ ```
132
+
133
+ stdout is JSON and nothing else; the corpus version, digest and root go to
134
+ stderr on every run. Exit 0 when every case passed or skipped, 1 on a failure,
135
+ 2 when the corpus failed to load, 3 when the runner could not start.
136
+
137
+ ### Probes
138
+
139
+ `conformance/mutations.py` implements each probe the corpus declares as an
140
+ injected defect, and `tests/test_probes.py` asserts that every one fails
141
+ *exactly* the set of case ids the corpus names — not a superset, not "at least
142
+ one" — and that the faithful control fails nothing. That is what makes the
143
+ conformance table a measurement rather than decoration.
144
+
145
+ Nothing in `src/prism` knows any of this exists. A defect a port can switch on
146
+ is a defect a port can ship, so the mutants are installed from outside, over the
147
+ real library's output, and removed again.
148
+
149
+ ## Development
150
+
151
+ `src/` layout, so install the package before running the tests:
152
+
153
+ ```
154
+ python -m venv .venv
155
+ .venv/Scripts/python -m pip install -e ".[dev]"
156
+ .venv/Scripts/python -m pip install ../prism-parity/loaders/py
157
+
158
+ python -m pytest
159
+ python -m mypy --strict
160
+ python -m ruff check .
161
+ python -m ruff format --check .
162
+ ```
163
+
164
+ Set `PRISM_CORPUS_ROOT` to run the conformance tests against a parity checkout
165
+ whose loader copy has not been re-synced yet. Unset — the normal case — the
166
+ loader discovers its own fixtures.
167
+
168
+ ## License
169
+
170
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "prism-ai-core"
7
+ version = "0.1.0"
8
+ description = "Python port of Prism — a unified API layer over LLM providers."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Particle Academy" }]
14
+ keywords = ["llm", "openai", "prism", "ai"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = []
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["pytest>=8.0", "mypy>=1.11", "ruff>=0.6"]
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["src/prism"]
32
+
33
+ # The source archive carries the package and its licence, and nothing else: not the Lab's
34
+ # loopback agent, CI workflows or contributor notes. Anchored with a leading slash, since an
35
+ # unanchored README.md also matches agent/README.md.
36
+ [tool.hatch.build.targets.sdist]
37
+ include = ["/src/prism", "/README.md", "/LICENSE", "/pyproject.toml"]
38
+
39
+ [tool.ruff]
40
+ line-length = 100
41
+ target-version = "py310"
42
+ src = ["src", "conformance", "tests"]
43
+ # Prose, not source. Ruff would collapse the README's chained-builder examples
44
+ # onto one line, which is exactly the shape they exist to show.
45
+ extend-exclude = ["*.md"]
46
+
47
+ [tool.ruff.lint]
48
+ select = ["E", "F", "W", "I", "B", "UP", "N", "RUF"]
49
+ ignore = [
50
+ # `Tool.format` / schema `format` mirror the reference field names.
51
+ "A003",
52
+ ]
53
+
54
+ [tool.ruff.lint.per-file-ignores]
55
+ "tests/*" = ["E501"]
56
+
57
+ [tool.mypy]
58
+ python_version = "3.10"
59
+ strict = true
60
+ files = ["src", "conformance", "tests"]
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = ["tests"]
64
+ # `-ra` so skips and their reasons are always printed. A skip nobody sees is a
65
+ # suite that quietly shrank.
66
+ addopts = "-q -ra"
67
+ # The conformance runner is test support outside src/, and the tests import it.
68
+ pythonpath = ["."]
@@ -0,0 +1,181 @@
1
+ """prism — a unified API layer over LLM providers.
2
+
3
+ This is the Python port of Prism:
4
+
5
+ >>> from prism import Prism
6
+ >>> response = ( # doctest: +SKIP
7
+ ... Prism.text()
8
+ ... .using("openai", "gpt-4o")
9
+ ... .with_prompt("Who are you?")
10
+ ... .as_text()
11
+ ... )
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from prism import canonical
17
+ from prism.audio import (
18
+ AudioPendingRequest,
19
+ AudioResponse,
20
+ AudioTextResponse,
21
+ SpeechToTextRequest,
22
+ TextToSpeechRequest,
23
+ )
24
+ from prism.batch import (
25
+ BatchJob,
26
+ BatchJobError,
27
+ BatchJobRequestCounts,
28
+ BatchListResult,
29
+ BatchPendingRequest,
30
+ BatchRequest,
31
+ BatchRequestItem,
32
+ BatchResultItem,
33
+ BatchResultStatus,
34
+ BatchStatus,
35
+ CancelBatchRequest,
36
+ GetBatchResultsRequest,
37
+ ListBatchesRequest,
38
+ RetrieveBatchRequest,
39
+ )
40
+ from prism.canonical import encode as canonical_encode
41
+ from prism.enums import FinishReason, ToolChoice
42
+ from prism.enums import Provider as ProviderName
43
+ from prism.errors import ErrorCode, PrismError
44
+ from prism.files import (
45
+ DeleteFileRequest,
46
+ DeleteFileResult,
47
+ DownloadFileRequest,
48
+ FileData,
49
+ FileListResult,
50
+ FilesPendingRequest,
51
+ GetFileMetadataRequest,
52
+ ListFilesRequest,
53
+ UploadFileRequest,
54
+ )
55
+ from prism.fim import FimPendingRequest, FimRequest, FimResponse
56
+ from prism.http import (
57
+ HttpRequest,
58
+ HttpResponse,
59
+ MultipartBody,
60
+ MultipartFile,
61
+ Transport,
62
+ UrllibTransport,
63
+ encode_multipart,
64
+ )
65
+ from prism.prism import Prism
66
+ from prism.providers.base import Provider
67
+ from prism.providers.mistral import Mistral, MistralStreamMapper
68
+ from prism.providers.openai import OpenAI, build_request_body, build_tools, parse_text_response
69
+ from prism.registry import register_provider, resolve_provider
70
+ from prism.schema import BooleanSchema, NumberSchema, Schema, StringSchema
71
+ from prism.text import PendingRequest, Request, Response, ResponseBuilder, Step
72
+ from prism.tool import Tool
73
+ from prism.value_objects import (
74
+ AssistantMessage,
75
+ Message,
76
+ Meta,
77
+ ProviderRateLimit,
78
+ ProviderTool,
79
+ ProviderToolCall,
80
+ SystemMessage,
81
+ Text,
82
+ ToolCall,
83
+ ToolResult,
84
+ ToolResultMessage,
85
+ Usage,
86
+ UserMessage,
87
+ message_from_dict,
88
+ )
89
+ from prism.value_objects.generated_audio import GeneratedAudio
90
+ from prism.value_objects.media_file import Audio, Document, Image, Media, Video
91
+
92
+ __version__ = "0.1.0"
93
+
94
+ __all__ = [
95
+ "AssistantMessage",
96
+ "Audio",
97
+ "AudioPendingRequest",
98
+ "AudioResponse",
99
+ "AudioTextResponse",
100
+ "BatchJob",
101
+ "BatchJobError",
102
+ "BatchJobRequestCounts",
103
+ "BatchListResult",
104
+ "BatchPendingRequest",
105
+ "BatchRequest",
106
+ "BatchRequestItem",
107
+ "BatchResultItem",
108
+ "BatchResultStatus",
109
+ "BatchStatus",
110
+ "BooleanSchema",
111
+ "CancelBatchRequest",
112
+ "DeleteFileRequest",
113
+ "DeleteFileResult",
114
+ "Document",
115
+ "DownloadFileRequest",
116
+ "ErrorCode",
117
+ "FileData",
118
+ "FileListResult",
119
+ "FilesPendingRequest",
120
+ "FimPendingRequest",
121
+ "FimRequest",
122
+ "FimResponse",
123
+ "FinishReason",
124
+ "GeneratedAudio",
125
+ "GetBatchResultsRequest",
126
+ "GetFileMetadataRequest",
127
+ "HttpRequest",
128
+ "HttpResponse",
129
+ "Image",
130
+ "ListBatchesRequest",
131
+ "ListFilesRequest",
132
+ "Media",
133
+ "Message",
134
+ "Meta",
135
+ "Mistral",
136
+ "MistralStreamMapper",
137
+ "MultipartBody",
138
+ "MultipartFile",
139
+ "NumberSchema",
140
+ "OpenAI",
141
+ "PendingRequest",
142
+ "Prism",
143
+ "PrismError",
144
+ "Provider",
145
+ "ProviderName",
146
+ "ProviderRateLimit",
147
+ "ProviderTool",
148
+ "ProviderToolCall",
149
+ "Request",
150
+ "Response",
151
+ "ResponseBuilder",
152
+ "RetrieveBatchRequest",
153
+ "Schema",
154
+ "SpeechToTextRequest",
155
+ "Step",
156
+ "StringSchema",
157
+ "SystemMessage",
158
+ "Text",
159
+ "TextToSpeechRequest",
160
+ "Tool",
161
+ "ToolCall",
162
+ "ToolChoice",
163
+ "ToolResult",
164
+ "ToolResultMessage",
165
+ "Transport",
166
+ "UploadFileRequest",
167
+ "UrllibTransport",
168
+ "Usage",
169
+ "UserMessage",
170
+ "Video",
171
+ "__version__",
172
+ "build_request_body",
173
+ "build_tools",
174
+ "canonical",
175
+ "canonical_encode",
176
+ "encode_multipart",
177
+ "message_from_dict",
178
+ "parse_text_response",
179
+ "register_provider",
180
+ "resolve_provider",
181
+ ]