clairic 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.
- clairic-0.1.0/.gitignore +22 -0
- clairic-0.1.0/LICENSE +21 -0
- clairic-0.1.0/NOTICE +18 -0
- clairic-0.1.0/PKG-INFO +209 -0
- clairic-0.1.0/README.md +160 -0
- clairic-0.1.0/pyproject.toml +83 -0
- clairic-0.1.0/src/clairic/__init__.py +100 -0
- clairic-0.1.0/src/clairic/_client.py +322 -0
- clairic-0.1.0/src/clairic/_compile.py +196 -0
- clairic-0.1.0/src/clairic/_version.py +1 -0
- clairic-0.1.0/src/clairic/answers.py +374 -0
- clairic-0.1.0/src/clairic/errors.py +99 -0
- clairic-0.1.0/src/clairic/py.typed +0 -0
- clairic-0.1.0/src/clairic/result.py +111 -0
- clairic-0.1.0/src/clairic/specs.py +346 -0
clairic-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Environment variables
|
|
13
|
+
.env
|
|
14
|
+
|
|
15
|
+
# Tooling caches
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
|
|
19
|
+
# Local agent notes and skills
|
|
20
|
+
.cursor/brainstorms/
|
|
21
|
+
.cursor/plans/
|
|
22
|
+
.claude/skills/
|
clairic-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vianney Mixtur
|
|
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.
|
clairic-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
clairic is an unofficial project. It is not affiliated with, endorsed by, or
|
|
2
|
+
supported by Cloudflare, Inc. "Cloudflare", "Workers AI" and "Clairic" are
|
|
3
|
+
names of Cloudflare, Inc. products.
|
|
4
|
+
|
|
5
|
+
Docstrings in this package adapt text from the Cloudflare documentation,
|
|
6
|
+
which Cloudflare publishes under the Creative Commons Attribution 4.0
|
|
7
|
+
International license (CC BY 4.0, https://creativecommons.org/licenses/by/4.0/).
|
|
8
|
+
The adapted text was edited for length and context. Sources:
|
|
9
|
+
|
|
10
|
+
- Clairic model reference
|
|
11
|
+
https://developers.cloudflare.com/workers-ai/models/clairic/
|
|
12
|
+
- Clairic input and output JSON schemas
|
|
13
|
+
https://developers.cloudflare.com/workers-ai/models/clairic/schema-input.json
|
|
14
|
+
https://developers.cloudflare.com/workers-ai/models/clairic/schema-output.json
|
|
15
|
+
- Extraction models concepts
|
|
16
|
+
https://developers.cloudflare.com/workers-ai/concepts/extraction-models/
|
|
17
|
+
- "Clairic" launch post, The Cloudflare Blog
|
|
18
|
+
https://blog.cloudflare.com/clairic-extraction-model/
|
clairic-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: clairic
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial typed Python SDK for Cloudflare's Clairic extraction model, built on Pydantic.
|
|
5
|
+
Project-URL: Homepage, https://github.com/VianneyMI/clairic
|
|
6
|
+
Project-URL: Repository, https://github.com/VianneyMI/clairic
|
|
7
|
+
Project-URL: Issues, https://github.com/VianneyMI/clairic/issues
|
|
8
|
+
Project-URL: Clairic model reference, https://developers.cloudflare.com/workers-ai/models/clairic/
|
|
9
|
+
Author-email: Vianney Mixtur <vianney@consio.ai>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Vianney Mixtur
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
License-File: NOTICE
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
43
|
+
Classifier: Typing :: Typed
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Requires-Dist: httpx>=0.27
|
|
46
|
+
Requires-Dist: pydantic>=2.7
|
|
47
|
+
Requires-Dist: typing-extensions>=4.12
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# clairic
|
|
51
|
+
|
|
52
|
+
**Unofficial** typed Python SDK for [Cloudflare Clairic](https://developers.cloudflare.com/workers-ai/models/clairic/), the Workers AI extraction model. This project is not affiliated with or endorsed by Cloudflare.
|
|
53
|
+
|
|
54
|
+
Clairic reads a text and returns typed values for the fields you name: extracted, computed, estimated or forecast, each with a confidence score, a probability of absence, or a percentile range. `clairic` lets you declare those fields as a Pydantic model and get the answers back as typed Pydantic objects.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from typing import Annotated
|
|
58
|
+
|
|
59
|
+
from pydantic import BaseModel, Field
|
|
60
|
+
|
|
61
|
+
from clairic import Clairic, DateAnswer, Estimate, EstimateAnswer, Number, NumberAnswer, TextAnswer
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class InvoiceExtraction(BaseModel):
|
|
65
|
+
invoice_number: TextAnswer
|
|
66
|
+
due_date: DateAnswer = Field(description="Issue date plus the net payment days")
|
|
67
|
+
total: Annotated[NumberAnswer[float], Number(decimals=2, unit="USD")] = Field(
|
|
68
|
+
description="Amount due including shipping"
|
|
69
|
+
)
|
|
70
|
+
po_number: TextAnswer = Field(description="Purchase order number")
|
|
71
|
+
days_to_pay: Annotated[EstimateAnswer[int], Estimate(min=0, max=120, step=1)] = Field(
|
|
72
|
+
description="Days after issue until this invoice is paid"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
with Clairic() as clairic:
|
|
77
|
+
result = clairic.extract(
|
|
78
|
+
"Invoice INV-2291 from Northwind Supply, issued 2026-09-02, net 30. "
|
|
79
|
+
"4 pallets at $250.00 each, shipping $80.00.",
|
|
80
|
+
InvoiceExtraction,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
result.data.total.value # 1080.0
|
|
84
|
+
result.data.total.confidence # 0.99995
|
|
85
|
+
result.data.po_number.value # None: the invoice has no PO number
|
|
86
|
+
result.data.days_to_pay.p90 # 37
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Install
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
pip install clairic
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Python 3.10 or later. Dependencies: `httpx` and `pydantic` 2.
|
|
96
|
+
|
|
97
|
+
## Credentials
|
|
98
|
+
|
|
99
|
+
The client reads `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`, or takes them as arguments:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
clairic = Clairic(account_id="...", api_token="...")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Create the token in the Cloudflare dashboard with access to Workers AI.
|
|
106
|
+
|
|
107
|
+
## Declaring fields
|
|
108
|
+
|
|
109
|
+
Each field of the extraction model is an answer type. The answer type sets the Clairic field type; an `Annotated` spec sets its options; `Field(description=...)` gives Clairic the instructions; a Pydantic `alias` sets the field name sent to Clairic.
|
|
110
|
+
|
|
111
|
+
| Answer type | Clairic type | Spec (options) |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `TextAnswer` | `text`: a span of the input | `Text(unit, null_threshold)` |
|
|
114
|
+
| `NumberAnswer[float]`, `NumberAnswer[int]` | `number` (`int` requests whole numbers) | `Number(unit, null_threshold, decimals)` |
|
|
115
|
+
| `DateAnswer`, `TimeAnswer`, `DatetimeAnswer` | `date`, `time`, `datetime` | `Date(null_threshold)`, `Time(...)`, `Datetime(...)` |
|
|
116
|
+
| `DurationAnswer[float]`, `DurationAnswer[int]` | `duration` | `Duration(unit, null_threshold, decimals)`, unit defaults to seconds |
|
|
117
|
+
| `TextListAnswer` | `list` of text spans | `List(unit, decimals)` |
|
|
118
|
+
| `ListAnswer[int]`, `ListAnswer[float]`, `ListAnswer[date]` | `list` of numbers or dates | `List(unit, decimals)` |
|
|
119
|
+
| `EstimateAnswer[int]`, `EstimateAnswer[float]` | `estimate` with number bounds | `Estimate(min, max, step, scale, unit)`, required |
|
|
120
|
+
| `DateEstimateAnswer` | `estimate` with date bounds | `Estimate(min=date, max=date, step="month")`, required |
|
|
121
|
+
| `ForecastAnswer` | `forecast` of a series | `Forecast(min, max, horizon, interval, step, scale, unit)`, required |
|
|
122
|
+
|
|
123
|
+
Invalid declarations fail before any request: a non-answer annotation, a missing or mismatched spec, or an option Clairic rejects (such as `decimals=7` or `scale="log"` with `min=0`) raises `ModelDefinitionError` or `ValueError` naming the problem.
|
|
124
|
+
|
|
125
|
+
Set `reference_date` whenever a field depends on today's date or on dates written without a year:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
clairic.extract(text, InvoiceExtraction, reference_date=date(2026, 9, 2))
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Reading answers
|
|
132
|
+
|
|
133
|
+
Every answer holds a `value` and the metadata Clairic attaches to it. Your editor completes the attributes, and a type checker knows their types.
|
|
134
|
+
|
|
135
|
+
- **Single values** (`text`, `number`, `date`, `time`, `datetime`, `duration`): `value`, `guess`, `confidence`, `p_null`. `value` is `None` when `p_null` exceeds the field's `null_threshold` (0.5 by default); `guess` still shows the best candidate.
|
|
136
|
+
- **Text**: `span`, end-exclusive offsets into the input: `text[span.start:span.end]`. Text lists carry `spans`.
|
|
137
|
+
- **Number and date lists**: `confidences`, one per item.
|
|
138
|
+
- **Estimates**: `value` (the mean rounded to the `step` grid), `mean`, `median`, `p10`, `p90`, and for number bounds `p_below` and `p_above`, the probability mass outside `[min, max]`.
|
|
139
|
+
- **Forecasts**: per-step lists `value`, `median`, `p10`, `p90`, and `steps` with the full estimate of each step.
|
|
140
|
+
|
|
141
|
+
The p10 to p90 range is an 80% interval. Cloudflare measures its calibration but does not guarantee it, and intervals can be too narrow on unfamiliar data. Use `confidence`, `p_null` and the interval to decide which answers to act on and which to review.
|
|
142
|
+
|
|
143
|
+
## Plain values
|
|
144
|
+
|
|
145
|
+
`values_as` copies each answer value into the same-named field of a plain Pydantic model, ready for the rest of your program:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
class Invoice(BaseModel):
|
|
149
|
+
invoice_number: str
|
|
150
|
+
total: float
|
|
151
|
+
po_number: str | None
|
|
152
|
+
days_to_pay: int
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
invoice = result.values_as(Invoice)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Pydantic validates the values, so a `None` landing in a field that does not accept `None` raises `ValidationError`.
|
|
159
|
+
|
|
160
|
+
## Storing results
|
|
161
|
+
|
|
162
|
+
A `Result` serializes to JSON and back with its answers, token usage and request id:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
stored = result.model_dump_json()
|
|
166
|
+
result = Result[InvoiceExtraction].model_validate_json(stored)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Async
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from clairic import AsyncClairic
|
|
173
|
+
|
|
174
|
+
async with AsyncClairic() as clairic:
|
|
175
|
+
result = await clairic.extract(text, InvoiceExtraction)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Errors
|
|
179
|
+
|
|
180
|
+
| Exception | When |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `ModelDefinitionError` | The extraction model cannot be sent to Clairic. Raised before any request. |
|
|
183
|
+
| `ClairicConfigurationError` | The account id or token is missing. |
|
|
184
|
+
| `AuthenticationError` | HTTP 401: invalid token or unknown account. |
|
|
185
|
+
| `InvalidRequestError` | HTTP 400 or 422: Cloudflare rejected the request content. |
|
|
186
|
+
| `ContextWindowExceededError` | HTTP 413: the input exceeds the 65,536-token context window. |
|
|
187
|
+
| `APIError` | Any other error response, or answers that do not fit the model. Base class of the API errors. |
|
|
188
|
+
| `APIConnectionError`, `APITimeoutError` | Cloudflare could not be reached, or did not answer within the timeout (30 seconds by default). |
|
|
189
|
+
|
|
190
|
+
API errors carry `status_code`, the Cloudflare error `code`, `message` and `request_id`. When Clairic reports a truncated input, the client emits `ClairicTruncationWarning` and sets `result.truncated`.
|
|
191
|
+
|
|
192
|
+
## Cost
|
|
193
|
+
|
|
194
|
+
Clairic bills input tokens only ($0.10 per million at the time of writing). Fields that do not fit next to the input in one 8,192-token pass are split across parallel passes, and each pass reads the whole input, so `result.usage.input_tokens` counts the input once per pass.
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
uv sync
|
|
200
|
+
uv run ruff format --check && uv run ruff check && uv run pyright && uv run pytest
|
|
201
|
+
uv run pytest -m live # real API, needs credentials
|
|
202
|
+
uv run python scripts/check_schema_drift.py # compare Cloudflare's schemas with the vendored ones
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`examples/invoice.py` runs the invoice extraction end to end.
|
|
206
|
+
|
|
207
|
+
## License and attribution
|
|
208
|
+
|
|
209
|
+
MIT. Docstrings adapt text from the Cloudflare documentation, published under CC BY 4.0; see `NOTICE`.
|
clairic-0.1.0/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# clairic
|
|
2
|
+
|
|
3
|
+
**Unofficial** typed Python SDK for [Cloudflare Clairic](https://developers.cloudflare.com/workers-ai/models/clairic/), the Workers AI extraction model. This project is not affiliated with or endorsed by Cloudflare.
|
|
4
|
+
|
|
5
|
+
Clairic reads a text and returns typed values for the fields you name: extracted, computed, estimated or forecast, each with a confidence score, a probability of absence, or a percentile range. `clairic` lets you declare those fields as a Pydantic model and get the answers back as typed Pydantic objects.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from clairic import Clairic, DateAnswer, Estimate, EstimateAnswer, Number, NumberAnswer, TextAnswer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InvoiceExtraction(BaseModel):
|
|
16
|
+
invoice_number: TextAnswer
|
|
17
|
+
due_date: DateAnswer = Field(description="Issue date plus the net payment days")
|
|
18
|
+
total: Annotated[NumberAnswer[float], Number(decimals=2, unit="USD")] = Field(
|
|
19
|
+
description="Amount due including shipping"
|
|
20
|
+
)
|
|
21
|
+
po_number: TextAnswer = Field(description="Purchase order number")
|
|
22
|
+
days_to_pay: Annotated[EstimateAnswer[int], Estimate(min=0, max=120, step=1)] = Field(
|
|
23
|
+
description="Days after issue until this invoice is paid"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
with Clairic() as clairic:
|
|
28
|
+
result = clairic.extract(
|
|
29
|
+
"Invoice INV-2291 from Northwind Supply, issued 2026-09-02, net 30. "
|
|
30
|
+
"4 pallets at $250.00 each, shipping $80.00.",
|
|
31
|
+
InvoiceExtraction,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
result.data.total.value # 1080.0
|
|
35
|
+
result.data.total.confidence # 0.99995
|
|
36
|
+
result.data.po_number.value # None: the invoice has no PO number
|
|
37
|
+
result.data.days_to_pay.p90 # 37
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
pip install clairic
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Python 3.10 or later. Dependencies: `httpx` and `pydantic` 2.
|
|
47
|
+
|
|
48
|
+
## Credentials
|
|
49
|
+
|
|
50
|
+
The client reads `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`, or takes them as arguments:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
clairic = Clairic(account_id="...", api_token="...")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Create the token in the Cloudflare dashboard with access to Workers AI.
|
|
57
|
+
|
|
58
|
+
## Declaring fields
|
|
59
|
+
|
|
60
|
+
Each field of the extraction model is an answer type. The answer type sets the Clairic field type; an `Annotated` spec sets its options; `Field(description=...)` gives Clairic the instructions; a Pydantic `alias` sets the field name sent to Clairic.
|
|
61
|
+
|
|
62
|
+
| Answer type | Clairic type | Spec (options) |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `TextAnswer` | `text`: a span of the input | `Text(unit, null_threshold)` |
|
|
65
|
+
| `NumberAnswer[float]`, `NumberAnswer[int]` | `number` (`int` requests whole numbers) | `Number(unit, null_threshold, decimals)` |
|
|
66
|
+
| `DateAnswer`, `TimeAnswer`, `DatetimeAnswer` | `date`, `time`, `datetime` | `Date(null_threshold)`, `Time(...)`, `Datetime(...)` |
|
|
67
|
+
| `DurationAnswer[float]`, `DurationAnswer[int]` | `duration` | `Duration(unit, null_threshold, decimals)`, unit defaults to seconds |
|
|
68
|
+
| `TextListAnswer` | `list` of text spans | `List(unit, decimals)` |
|
|
69
|
+
| `ListAnswer[int]`, `ListAnswer[float]`, `ListAnswer[date]` | `list` of numbers or dates | `List(unit, decimals)` |
|
|
70
|
+
| `EstimateAnswer[int]`, `EstimateAnswer[float]` | `estimate` with number bounds | `Estimate(min, max, step, scale, unit)`, required |
|
|
71
|
+
| `DateEstimateAnswer` | `estimate` with date bounds | `Estimate(min=date, max=date, step="month")`, required |
|
|
72
|
+
| `ForecastAnswer` | `forecast` of a series | `Forecast(min, max, horizon, interval, step, scale, unit)`, required |
|
|
73
|
+
|
|
74
|
+
Invalid declarations fail before any request: a non-answer annotation, a missing or mismatched spec, or an option Clairic rejects (such as `decimals=7` or `scale="log"` with `min=0`) raises `ModelDefinitionError` or `ValueError` naming the problem.
|
|
75
|
+
|
|
76
|
+
Set `reference_date` whenever a field depends on today's date or on dates written without a year:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
clairic.extract(text, InvoiceExtraction, reference_date=date(2026, 9, 2))
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Reading answers
|
|
83
|
+
|
|
84
|
+
Every answer holds a `value` and the metadata Clairic attaches to it. Your editor completes the attributes, and a type checker knows their types.
|
|
85
|
+
|
|
86
|
+
- **Single values** (`text`, `number`, `date`, `time`, `datetime`, `duration`): `value`, `guess`, `confidence`, `p_null`. `value` is `None` when `p_null` exceeds the field's `null_threshold` (0.5 by default); `guess` still shows the best candidate.
|
|
87
|
+
- **Text**: `span`, end-exclusive offsets into the input: `text[span.start:span.end]`. Text lists carry `spans`.
|
|
88
|
+
- **Number and date lists**: `confidences`, one per item.
|
|
89
|
+
- **Estimates**: `value` (the mean rounded to the `step` grid), `mean`, `median`, `p10`, `p90`, and for number bounds `p_below` and `p_above`, the probability mass outside `[min, max]`.
|
|
90
|
+
- **Forecasts**: per-step lists `value`, `median`, `p10`, `p90`, and `steps` with the full estimate of each step.
|
|
91
|
+
|
|
92
|
+
The p10 to p90 range is an 80% interval. Cloudflare measures its calibration but does not guarantee it, and intervals can be too narrow on unfamiliar data. Use `confidence`, `p_null` and the interval to decide which answers to act on and which to review.
|
|
93
|
+
|
|
94
|
+
## Plain values
|
|
95
|
+
|
|
96
|
+
`values_as` copies each answer value into the same-named field of a plain Pydantic model, ready for the rest of your program:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
class Invoice(BaseModel):
|
|
100
|
+
invoice_number: str
|
|
101
|
+
total: float
|
|
102
|
+
po_number: str | None
|
|
103
|
+
days_to_pay: int
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
invoice = result.values_as(Invoice)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Pydantic validates the values, so a `None` landing in a field that does not accept `None` raises `ValidationError`.
|
|
110
|
+
|
|
111
|
+
## Storing results
|
|
112
|
+
|
|
113
|
+
A `Result` serializes to JSON and back with its answers, token usage and request id:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
stored = result.model_dump_json()
|
|
117
|
+
result = Result[InvoiceExtraction].model_validate_json(stored)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Async
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from clairic import AsyncClairic
|
|
124
|
+
|
|
125
|
+
async with AsyncClairic() as clairic:
|
|
126
|
+
result = await clairic.extract(text, InvoiceExtraction)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Errors
|
|
130
|
+
|
|
131
|
+
| Exception | When |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `ModelDefinitionError` | The extraction model cannot be sent to Clairic. Raised before any request. |
|
|
134
|
+
| `ClairicConfigurationError` | The account id or token is missing. |
|
|
135
|
+
| `AuthenticationError` | HTTP 401: invalid token or unknown account. |
|
|
136
|
+
| `InvalidRequestError` | HTTP 400 or 422: Cloudflare rejected the request content. |
|
|
137
|
+
| `ContextWindowExceededError` | HTTP 413: the input exceeds the 65,536-token context window. |
|
|
138
|
+
| `APIError` | Any other error response, or answers that do not fit the model. Base class of the API errors. |
|
|
139
|
+
| `APIConnectionError`, `APITimeoutError` | Cloudflare could not be reached, or did not answer within the timeout (30 seconds by default). |
|
|
140
|
+
|
|
141
|
+
API errors carry `status_code`, the Cloudflare error `code`, `message` and `request_id`. When Clairic reports a truncated input, the client emits `ClairicTruncationWarning` and sets `result.truncated`.
|
|
142
|
+
|
|
143
|
+
## Cost
|
|
144
|
+
|
|
145
|
+
Clairic bills input tokens only ($0.10 per million at the time of writing). Fields that do not fit next to the input in one 8,192-token pass are split across parallel passes, and each pass reads the whole input, so `result.usage.input_tokens` counts the input once per pass.
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
uv sync
|
|
151
|
+
uv run ruff format --check && uv run ruff check && uv run pyright && uv run pytest
|
|
152
|
+
uv run pytest -m live # real API, needs credentials
|
|
153
|
+
uv run python scripts/check_schema_drift.py # compare Cloudflare's schemas with the vendored ones
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`examples/invoice.py` runs the invoice extraction end to end.
|
|
157
|
+
|
|
158
|
+
## License and attribution
|
|
159
|
+
|
|
160
|
+
MIT. Docstrings adapt text from the Cloudflare documentation, published under CC BY 4.0; see `NOTICE`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "clairic"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Unofficial typed Python SDK for Cloudflare's Clairic extraction model, built on Pydantic."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "Vianney Mixtur", email = "vianney@consio.ai" }]
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
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
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"httpx>=0.27",
|
|
28
|
+
"pydantic>=2.7",
|
|
29
|
+
"typing-extensions>=4.12",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/VianneyMI/clairic"
|
|
34
|
+
Repository = "https://github.com/VianneyMI/clairic"
|
|
35
|
+
Issues = "https://github.com/VianneyMI/clairic/issues"
|
|
36
|
+
"Clairic model reference" = "https://developers.cloudflare.com/workers-ai/models/clairic/"
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
dev = [
|
|
40
|
+
"anyio>=4.4",
|
|
41
|
+
"pyright>=1.1.400",
|
|
42
|
+
"pytest>=8.3",
|
|
43
|
+
"python-dotenv>=1.0",
|
|
44
|
+
"ruff>=0.8",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.version]
|
|
48
|
+
path = "src/clairic/_version.py"
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["src/clairic"]
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.sdist]
|
|
54
|
+
include = ["/src", "/LICENSE", "/NOTICE", "/README.md", "/pyproject.toml"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff]
|
|
57
|
+
line-length = 100
|
|
58
|
+
target-version = "py310"
|
|
59
|
+
|
|
60
|
+
[tool.ruff.lint]
|
|
61
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF", "D"]
|
|
62
|
+
ignore = [
|
|
63
|
+
"D100", "D104", "D105", "D107", # module, package, magic and __init__ docstrings are optional
|
|
64
|
+
"D203", "D213", # use D211 and D212 (Google style)
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.pydocstyle]
|
|
68
|
+
convention = "google"
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.per-file-ignores]
|
|
71
|
+
"tests/**" = ["D"]
|
|
72
|
+
"scripts/**" = ["D"]
|
|
73
|
+
"examples/**" = ["D"]
|
|
74
|
+
|
|
75
|
+
[tool.pyright]
|
|
76
|
+
include = ["src", "tests", "scripts", "examples"]
|
|
77
|
+
pythonVersion = "3.10"
|
|
78
|
+
typeCheckingMode = "strict"
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
testpaths = ["tests", "src"]
|
|
82
|
+
markers = ["live: calls the real Cloudflare API (needs CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN)"]
|
|
83
|
+
addopts = "-m 'not live' --doctest-modules"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Unofficial typed Python SDK for Cloudflare Clairic.
|
|
2
|
+
|
|
3
|
+
Clairic is a Workers AI extraction model: it reads a text and returns typed values for named
|
|
4
|
+
fields, with confidence scores, probabilities of absence and percentile ranges. This package is
|
|
5
|
+
not affiliated with or endorsed by Cloudflare.
|
|
6
|
+
|
|
7
|
+
See https://developers.cloudflare.com/workers-ai/models/clairic/ for the model reference.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from clairic._client import AsyncClairic, Clairic
|
|
11
|
+
from clairic._version import __version__
|
|
12
|
+
from clairic.answers import (
|
|
13
|
+
Answer,
|
|
14
|
+
DateAnswer,
|
|
15
|
+
DateEstimateAnswer,
|
|
16
|
+
DatetimeAnswer,
|
|
17
|
+
DurationAnswer,
|
|
18
|
+
DurationUnit,
|
|
19
|
+
EstimateAnswer,
|
|
20
|
+
ForecastAnswer,
|
|
21
|
+
ForecastStep,
|
|
22
|
+
ListAnswer,
|
|
23
|
+
NumberAnswer,
|
|
24
|
+
Span,
|
|
25
|
+
TextAnswer,
|
|
26
|
+
TextListAnswer,
|
|
27
|
+
TimeAnswer,
|
|
28
|
+
)
|
|
29
|
+
from clairic.errors import (
|
|
30
|
+
APIConnectionError,
|
|
31
|
+
APIError,
|
|
32
|
+
APITimeoutError,
|
|
33
|
+
AuthenticationError,
|
|
34
|
+
ClairicConfigurationError,
|
|
35
|
+
ClairicError,
|
|
36
|
+
ClairicTruncationWarning,
|
|
37
|
+
ContextWindowExceededError,
|
|
38
|
+
InvalidRequestError,
|
|
39
|
+
ModelDefinitionError,
|
|
40
|
+
)
|
|
41
|
+
from clairic.result import Result, Usage
|
|
42
|
+
from clairic.specs import (
|
|
43
|
+
Date,
|
|
44
|
+
Datetime,
|
|
45
|
+
Duration,
|
|
46
|
+
Estimate,
|
|
47
|
+
Forecast,
|
|
48
|
+
Interval,
|
|
49
|
+
List,
|
|
50
|
+
Number,
|
|
51
|
+
Scale,
|
|
52
|
+
Spec,
|
|
53
|
+
Text,
|
|
54
|
+
Time,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"APIConnectionError",
|
|
59
|
+
"APIError",
|
|
60
|
+
"APITimeoutError",
|
|
61
|
+
"Answer",
|
|
62
|
+
"AsyncClairic",
|
|
63
|
+
"AuthenticationError",
|
|
64
|
+
"Clairic",
|
|
65
|
+
"ClairicConfigurationError",
|
|
66
|
+
"ClairicError",
|
|
67
|
+
"ClairicTruncationWarning",
|
|
68
|
+
"ContextWindowExceededError",
|
|
69
|
+
"Date",
|
|
70
|
+
"DateAnswer",
|
|
71
|
+
"DateEstimateAnswer",
|
|
72
|
+
"Datetime",
|
|
73
|
+
"DatetimeAnswer",
|
|
74
|
+
"Duration",
|
|
75
|
+
"DurationAnswer",
|
|
76
|
+
"DurationUnit",
|
|
77
|
+
"Estimate",
|
|
78
|
+
"EstimateAnswer",
|
|
79
|
+
"Forecast",
|
|
80
|
+
"ForecastAnswer",
|
|
81
|
+
"ForecastStep",
|
|
82
|
+
"Interval",
|
|
83
|
+
"InvalidRequestError",
|
|
84
|
+
"List",
|
|
85
|
+
"ListAnswer",
|
|
86
|
+
"ModelDefinitionError",
|
|
87
|
+
"Number",
|
|
88
|
+
"NumberAnswer",
|
|
89
|
+
"Result",
|
|
90
|
+
"Scale",
|
|
91
|
+
"Span",
|
|
92
|
+
"Spec",
|
|
93
|
+
"Text",
|
|
94
|
+
"TextAnswer",
|
|
95
|
+
"TextListAnswer",
|
|
96
|
+
"Time",
|
|
97
|
+
"TimeAnswer",
|
|
98
|
+
"Usage",
|
|
99
|
+
"__version__",
|
|
100
|
+
]
|