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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/dot-inference)](https://pypi.org/project/dot-inference/)
31
+ ![Python Version](https://img.shields.io/badge/python-3.12%2B-blue)
32
+ [![Licence: AGPL v3](https://img.shields.io/badge/licence-AGPL--3.0--or--later-blue)](LICENSE.md)
33
+ [![Pipeline](https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-inference/badges/main/pipeline.svg)](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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any