dot-inference 2.0.2__py3-none-any.whl
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.
- dot_inference/README.md +83 -0
- dot_inference/__init__.py +86 -0
- dot_inference/_client.py +520 -0
- dot_inference/_cost_tracker.py +71 -0
- dot_inference/_llm_registry.py +302 -0
- dot_inference/_local_check.py +76 -0
- dot_inference/_mistral_compat.py +530 -0
- dot_inference/_openai_compat.py +570 -0
- dot_inference/_rate_limit.py +64 -0
- dot_inference/_retry.py +66 -0
- dot_inference/_sync.py +49 -0
- dot_inference/_types.py +229 -0
- dot_inference/mock.py +151 -0
- dot_inference/models.yaml +481 -0
- dot_inference/settings.py +138 -0
- dot_inference-2.0.2.dist-info/METADATA +351 -0
- dot_inference-2.0.2.dist-info/RECORD +19 -0
- dot_inference-2.0.2.dist-info/WHEEL +4 -0
- dot_inference-2.0.2.dist-info/licenses/LICENSE.md +660 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dot-inference
|
|
3
|
+
Version: 2.0.2
|
|
4
|
+
Summary: Unified async Python library for LLM inference and embeddings across multiple providers
|
|
5
|
+
Project-URL: Homepage, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-inference
|
|
6
|
+
Project-URL: Repository, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-inference
|
|
7
|
+
Project-URL: Issues, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-inference/-/issues
|
|
8
|
+
Author-email: Kannon For Deep Tech <louis.letarnec@deepika.ai>
|
|
9
|
+
License-Expression: AGPL-3.0-or-later
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Keywords: embeddings,inference,llm,providers
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Requires-Python: <3.14,>=3.12
|
|
18
|
+
Requires-Dist: httpx>=0.27.0
|
|
19
|
+
Requires-Dist: openai>=1.0.0
|
|
20
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
21
|
+
Requires-Dist: pydantic>=2.0.0
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Requires-Dist: typing-extensions>=4.16
|
|
24
|
+
Provides-Extra: mistral
|
|
25
|
+
Requires-Dist: mistralai>=2.0.0; extra == 'mistral'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# dot-inference
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/dot-inference/)
|
|
31
|
+

|
|
32
|
+
[](LICENSE.md)
|
|
33
|
+
[](https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-inference/-/pipelines)
|
|
34
|
+
|
|
35
|
+
**One async interface for LLM inference and embeddings, across every provider.**
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from dot_inference import InferenceSettings, Message, Role, get_llm_client
|
|
39
|
+
|
|
40
|
+
client = get_llm_client(InferenceSettings()) # provider and model come from env
|
|
41
|
+
response = client.call_sync([Message(role=Role.USER, content="How tall is the Eiffel Tower?")])
|
|
42
|
+
print(response.content)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why dot-inference
|
|
46
|
+
|
|
47
|
+
Switching LLM provider usually means rewriting call sites: each SDK has its own
|
|
48
|
+
client, its own message shape, its own way of doing structured output, streaming
|
|
49
|
+
and tool calls. Retries, cost accounting and rate limiting end up reinvented in
|
|
50
|
+
every project.
|
|
51
|
+
|
|
52
|
+
dot-inference puts one async interface in front of OpenAI, Mistral, OpenRouter
|
|
53
|
+
and any OpenAI-compatible server. Changing provider is an environment variable,
|
|
54
|
+
not a refactor. Retries honouring `Retry-After`, USD cost tracking, rate limiting
|
|
55
|
+
and structured output come with it, and a model registry records what each model
|
|
56
|
+
can actually do — so an unsupported `reasoning_effort` fails with a clear error
|
|
57
|
+
instead of silently doing nothing.
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
- Multi-provider: OpenAI, Mistral, OpenRouter, and any OpenAI-compatible server (vLLM, SGLang, Ollama)
|
|
62
|
+
- Structured outputs backed by Pydantic models
|
|
63
|
+
- Rich event streaming: text, reasoning, incremental tool-call arguments, tool calls, usage and cost
|
|
64
|
+
- Native tool calling with `tool_choice` control (`auto` / `required` / `none`)
|
|
65
|
+
- Embeddings with automatic batching
|
|
66
|
+
- Retries on transient errors, honouring `Retry-After`, with exponential backoff
|
|
67
|
+
- Cost tracking in tokens and USD
|
|
68
|
+
- Rate limiting through an async sliding window
|
|
69
|
+
- YAML model registry carrying pricing and per-model capabilities
|
|
70
|
+
- Per-model default provider resolution — pick a model, let the registry choose the provider
|
|
71
|
+
- Configurable HTTP timeouts (`DOTI_GENERATION__TIMEOUT`, `DOTI_EMBEDDING__TIMEOUT`)
|
|
72
|
+
- Mock clients for testing without API calls
|
|
73
|
+
- Sync wrappers (`call_sync`, `embed_sync`) for non-async codebases
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install dot-inference
|
|
79
|
+
|
|
80
|
+
# With the native Mistral SDK:
|
|
81
|
+
pip install 'dot-inference[mistral]'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Requires Python 3.12+.
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
Settings come from `DOTI_`-prefixed environment variables, using `__` as the
|
|
89
|
+
nesting delimiter — or from an env file, modelled on `.env.template`:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
DOTI_GENERATION__PROVIDER=openai
|
|
93
|
+
DOTI_GENERATION__OPENAI__MODEL_NAME=gpt-5-mini
|
|
94
|
+
DOTI_GENERATION__OPENAI__API_KEY=sk-...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
API keys are held as `SecretStr`, so a settings object caught in a traceback or
|
|
98
|
+
an error report never carries the key in clear text.
|
|
99
|
+
|
|
100
|
+
## Quick start
|
|
101
|
+
|
|
102
|
+
### From settings
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import asyncio
|
|
106
|
+
from dot_inference import InferenceSettings, Message, Role, get_llm_client
|
|
107
|
+
|
|
108
|
+
settings = InferenceSettings() # reads from DOTI_* env vars
|
|
109
|
+
client = get_llm_client(settings)
|
|
110
|
+
|
|
111
|
+
messages = [Message(role=Role.USER, content="What is the height of the Eiffel Tower?")]
|
|
112
|
+
response = asyncio.run(client.call(messages))
|
|
113
|
+
# or synchronously:
|
|
114
|
+
response = client.call_sync(messages)
|
|
115
|
+
print(response.content)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Explicit provider and model
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from dot_inference import InferenceSettings, Message, Provider, Role, get_llm_client
|
|
122
|
+
|
|
123
|
+
settings = InferenceSettings()
|
|
124
|
+
client = get_llm_client(settings, provider=Provider.OPENAI, model="gpt-5-mini")
|
|
125
|
+
response = asyncio.run(client.call([Message(role=Role.USER, content="Hello!")]))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Reasoning models
|
|
129
|
+
|
|
130
|
+
Use `ReasoningEffort` to control thinking budget on reasoning models (e.g. `gpt-5*`, `o-series`):
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from dot_inference import (
|
|
134
|
+
InferenceSettings,
|
|
135
|
+
Message,
|
|
136
|
+
Provider,
|
|
137
|
+
ReasoningEffort,
|
|
138
|
+
Role,
|
|
139
|
+
get_llm_client,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
settings = InferenceSettings()
|
|
143
|
+
client = get_llm_client(
|
|
144
|
+
settings,
|
|
145
|
+
provider=Provider.OPENAI,
|
|
146
|
+
model="gpt-5-mini",
|
|
147
|
+
reasoning_effort=ReasoningEffort.HIGH,
|
|
148
|
+
)
|
|
149
|
+
response = asyncio.run(client.call([Message(role=Role.USER, content="Solve this step by step...")]))
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Structured output
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from pydantic import BaseModel
|
|
156
|
+
from dot_inference import InferenceSettings, Message, Provider, Role, get_llm_client
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Answer(BaseModel):
|
|
160
|
+
answer: str
|
|
161
|
+
confidence: float
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
settings = InferenceSettings()
|
|
165
|
+
client = get_llm_client(settings, provider=Provider.OPENAI, model="gpt-5-mini")
|
|
166
|
+
response = asyncio.run(
|
|
167
|
+
client.call(
|
|
168
|
+
[Message(role=Role.USER, content="What is 2+2?")],
|
|
169
|
+
response_format=Answer,
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
print(response.structured_output) # Answer(answer='4', confidence=1.0)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Streaming
|
|
176
|
+
|
|
177
|
+
`stream()` yields typed `StreamEvent` objects:
|
|
178
|
+
|
|
179
|
+
| Event | Description |
|
|
180
|
+
| ----- | ----------- |
|
|
181
|
+
| `TextDelta` | A chunk of text content (`content: str`) |
|
|
182
|
+
| `ReasoningDelta` | A chunk of reasoning text, when the provider exposes it |
|
|
183
|
+
| `InputJsonDelta` | A chunk of tool-call argument JSON (`tool_call_index`, `tool_name`, `partial_json`) — emitted incrementally before `ToolCallDone` |
|
|
184
|
+
| `ToolCallDone` | A fully assembled tool call (`tool_call: ToolCall`) |
|
|
185
|
+
| `StreamUsage` | Token counts and cost (`prompt_tokens`, `completion_tokens`, `cost`) |
|
|
186
|
+
| `StreamDone` | Terminal event carrying the final `LLMResponse` — identical to what `call()` returns |
|
|
187
|
+
|
|
188
|
+
**Simple text streaming** — use `stream_text()` for plain `str` chunks:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from dot_inference import InferenceSettings, Message, Role, get_llm_client
|
|
192
|
+
|
|
193
|
+
settings = InferenceSettings()
|
|
194
|
+
client = get_llm_client(settings)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
async def main():
|
|
198
|
+
async for chunk in client.stream_text([Message(role=Role.USER, content="Tell me a story")]):
|
|
199
|
+
print(chunk, end="", flush=True)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
asyncio.run(main())
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Rich event streaming** — consume all events for text + usage + tool calls:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from dot_inference import (
|
|
209
|
+
InferenceSettings,
|
|
210
|
+
Message,
|
|
211
|
+
Role,
|
|
212
|
+
get_llm_client,
|
|
213
|
+
ToolDef,
|
|
214
|
+
TextDelta,
|
|
215
|
+
InputJsonDelta,
|
|
216
|
+
ToolCallDone,
|
|
217
|
+
StreamUsage,
|
|
218
|
+
StreamDone,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def main():
|
|
223
|
+
settings = InferenceSettings()
|
|
224
|
+
client = get_llm_client(settings)
|
|
225
|
+
|
|
226
|
+
async for event in client.stream(
|
|
227
|
+
[Message(role=Role.USER, content="What's the weather in Paris?")],
|
|
228
|
+
tools=[ToolDef(name="get_weather", description="Get weather", parameters={...})],
|
|
229
|
+
):
|
|
230
|
+
if isinstance(event, TextDelta):
|
|
231
|
+
print(event.content, end="", flush=True)
|
|
232
|
+
elif isinstance(event, InputJsonDelta):
|
|
233
|
+
print(event.partial_json, end="", flush=True)
|
|
234
|
+
elif isinstance(event, ToolCallDone):
|
|
235
|
+
print(f"Tool call: {event.tool_call.name}({event.tool_call.arguments})")
|
|
236
|
+
elif isinstance(event, StreamUsage):
|
|
237
|
+
print(f"Tokens: {event.prompt_tokens} in / {event.completion_tokens} out")
|
|
238
|
+
elif isinstance(event, StreamDone):
|
|
239
|
+
# event.response is a full LLMResponse, works with CostTracker
|
|
240
|
+
tracker.record(event.response)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
asyncio.run(main())
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Embeddings
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from dot_inference import InferenceSettings, get_embedding_client
|
|
250
|
+
|
|
251
|
+
settings = InferenceSettings()
|
|
252
|
+
client = get_embedding_client(settings)
|
|
253
|
+
vectors = asyncio.run(client.embed(["Hello world", "Bonjour le monde"]))
|
|
254
|
+
print(len(vectors), len(vectors[0])) # 2, 1536
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Cost tracking
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from dot_inference import CostTracker, InferenceSettings, Message, Provider, Role, get_llm_client
|
|
261
|
+
|
|
262
|
+
settings = InferenceSettings()
|
|
263
|
+
tracker = CostTracker()
|
|
264
|
+
client = get_llm_client(settings, provider=Provider.OPENROUTER, model="openai/gpt-5-mini")
|
|
265
|
+
|
|
266
|
+
response = asyncio.run(client.call([Message(role=Role.USER, content="Hi")]))
|
|
267
|
+
tracker.record(response)
|
|
268
|
+
print(f"Tokens: {tracker.total_tokens}, Cost: ${tracker.total_cost:.4f}")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Retries
|
|
272
|
+
|
|
273
|
+
Transient provider errors (5xx, rate limits, timeouts) are retried automatically with exponential backoff. When the provider returns a `Retry-After` header, the wait is honored (clamped to 60s). Client-side errors (400, 401, 403, 404, 422) are not retried.
|
|
274
|
+
|
|
275
|
+
Attach `on_retry` to observe retry events — useful for UI feedback:
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
client = get_llm_client(settings)
|
|
279
|
+
client.on_retry = lambda attempt, max_attempts, err: print(f"Retry {attempt}/{max_attempts}: {err}")
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## On-premise inference (vLLM, SGLang, Ollama)
|
|
283
|
+
|
|
284
|
+
Point the `local` provider at any OpenAI-compatible endpoint:
|
|
285
|
+
|
|
286
|
+
```shell
|
|
287
|
+
export DOTI_GENERATION__PROVIDER=local
|
|
288
|
+
export DOTI_GENERATION__LOCAL__MODEL_NAME=openai/gpt-oss-120b
|
|
289
|
+
export DOTI_GENERATION__LOCAL__BASE_URL=http://localhost:8000/v1
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from dot_inference import InferenceSettings, Message, Role, get_llm_client
|
|
294
|
+
|
|
295
|
+
client = get_llm_client(InferenceSettings())
|
|
296
|
+
response = client.call_sync([Message(role=Role.USER, content="How tall is the Eiffel Tower?")])
|
|
297
|
+
print(response.content)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Testing without API calls
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from dot_inference.mock import MockLLMClient, MockEmbeddingClient
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Both return deterministic responses — including structured output built from
|
|
307
|
+
your Pydantic schema — so tests never reach a provider.
|
|
308
|
+
|
|
309
|
+
## Stability
|
|
310
|
+
|
|
311
|
+
`dot-inference` follows semantic versioning: everything exported from the top-level
|
|
312
|
+
package is covered, anything underscore-prefixed is internal and may change in
|
|
313
|
+
any release. Public names are never removed without a deprecation period.
|
|
314
|
+
|
|
315
|
+
```toml
|
|
316
|
+
dependencies = ["dot-inference>=1.0,<2"]
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
See [docs/VERSIONING.md](docs/VERSIONING.md) for the full policy.
|
|
320
|
+
|
|
321
|
+
## Roadmap
|
|
322
|
+
|
|
323
|
+
- [ ] Azure OpenAI provider
|
|
324
|
+
- [ ] Logging integration (dot-logger)
|
|
325
|
+
- [ ] Jinja templates for prompt management
|
|
326
|
+
|
|
327
|
+
## Documentation
|
|
328
|
+
|
|
329
|
+
| Document | Contents |
|
|
330
|
+
|---|---|
|
|
331
|
+
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Internal design and module layout |
|
|
332
|
+
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Environment setup, tests, code style |
|
|
333
|
+
| [docs/VERSIONING.md](docs/VERSIONING.md) | Versioning, deprecation policy, how to depend on this package |
|
|
334
|
+
| [docs/PUBLISHING.md](docs/PUBLISHING.md) | Cutting a release |
|
|
335
|
+
| [CHANGELOG.md](CHANGELOG.md) | Release history |
|
|
336
|
+
|
|
337
|
+
## Contributing
|
|
338
|
+
|
|
339
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the DCO
|
|
340
|
+
sign-off requirement, the licensing terms that apply to contributions, and how to
|
|
341
|
+
submit a change.
|
|
342
|
+
|
|
343
|
+
## Licence
|
|
344
|
+
|
|
345
|
+
Copyright (C) 2026 Kannon For Deep Tech (deepika)
|
|
346
|
+
|
|
347
|
+
This software is distributed under the GNU Affero General Public License,
|
|
348
|
+
version 3 or later — see [LICENSE.md](LICENSE.md).
|
|
349
|
+
|
|
350
|
+
A commercial licence is available for use in proprietary environments.
|
|
351
|
+
Contact: louis.letarnec@deepika.ai
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
dot_inference/README.md,sha256=2kGOQCZE9pTmaU3Xy0WAKR2duCAGVVXD4VU0g8rzP5o,3361
|
|
2
|
+
dot_inference/__init__.py,sha256=VyKDfo4A50ub_4AcJBK3Iuv6dbnXblvzEJiwHzqF2pM,1949
|
|
3
|
+
dot_inference/_client.py,sha256=sDvleCbipeoEpNPDoMrR0vylPeTjn0VThuQWvkeY_fY,19537
|
|
4
|
+
dot_inference/_cost_tracker.py,sha256=YWRaTa5vBvsIH5e0tDxB4wsowEgcqMTJyjzRSuL6R3M,2401
|
|
5
|
+
dot_inference/_llm_registry.py,sha256=OzlMaw-Oum3-_J7zzSQQQiJgx9cgdEskWnY17cvKHu0,11928
|
|
6
|
+
dot_inference/_local_check.py,sha256=dPEhChhmRTqS6GXJsqCTOfIoMJU6TJIGKmOBzuIc3dI,2807
|
|
7
|
+
dot_inference/_mistral_compat.py,sha256=spYmHwvG61ILh-8TpxuIUKQpcyzC1o_54pluDGKDLkg,19636
|
|
8
|
+
dot_inference/_openai_compat.py,sha256=BNTsMixgDdIKZDOJ-mY3j35Eybf9F5jfRhetYWHuul4,21576
|
|
9
|
+
dot_inference/_rate_limit.py,sha256=RSJtBdBYw1wFI2WdpPi14mIAHXLm4aXyF1nwo5G81n8,2081
|
|
10
|
+
dot_inference/_retry.py,sha256=npyEmZqZFScxoDlJDf_c9JsoGERMjRIv0eq8e2MXvQY,2355
|
|
11
|
+
dot_inference/_sync.py,sha256=AGiGc_Wbqiga75oYqtgjv24WUlTD_B1ugCV8horYK4I,1554
|
|
12
|
+
dot_inference/_types.py,sha256=B_IS8CRFB0eI9TRRkEw1CszRW6_9kuSLzHSR2G_HZ3E,7001
|
|
13
|
+
dot_inference/mock.py,sha256=7ksDCjYZ7RrZf1kEtI_gcox7_cGdPFPUJ0pEjIY8_uU,5120
|
|
14
|
+
dot_inference/models.yaml,sha256=-dugBbjbErEZ6PCvQcRGDaIiaUeS08ahvyEt1n4Xw9E,16250
|
|
15
|
+
dot_inference/settings.py,sha256=_biGkq-CSFEQXuA_X9ELjED__qjI_PC15fOWrjEMOzw,4641
|
|
16
|
+
dot_inference-2.0.2.dist-info/METADATA,sha256=EiHVFEULCWIymQY6H78xqkg0BWegUBiuJm37w1IeBWg,11900
|
|
17
|
+
dot_inference-2.0.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
18
|
+
dot_inference-2.0.2.dist-info/licenses/LICENSE.md,sha256=ADUqsZhl4juwq34PRTMiBqumpm11s_PMli_dZQjWPqQ,34260
|
|
19
|
+
dot_inference-2.0.2.dist-info/RECORD,,
|