ainative-ai-sdk-provider 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AINative Studio
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,304 @@
1
+ Metadata-Version: 2.4
2
+ Name: ainative-ai-sdk-provider
3
+ Version: 0.1.0
4
+ Summary: Python SDK for AINative's multi-provider AI inference API: chat completions, streaming, and agentic tool-calling over one interface, with server-side routing and automatic fallback across Cerebras, Meta Llama, NVIDIA NIM & DigitalOcean.
5
+ Author-email: AINative Studio <dev@ainative.studio>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AINative-Studio/core
8
+ Project-URL: Documentation, https://docs.ainative.studio
9
+ Project-URL: Repository, https://github.com/AINative-Studio/core
10
+ Project-URL: Issues, https://github.com/AINative-Studio/core/issues
11
+ Keywords: ainative,ai-sdk,llm,llm-client,llm-sdk,python-sdk,chat-completion,chat-completions,inference,inference-api,ai-gateway,model-router,llm-router,multi-provider,openai-compatible,openai-alternative,httpx,async,streaming,sse,server-sent-events,tool-calling,function-calling,agentic,ai-agent,llama,meta-llama,qwen,deepseek,kimi,cerebras,nvidia-nim,digitalocean,zerodb
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: httpx>=0.24
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
+ Requires-Dist: respx>=0.20; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # ainative-ai-sdk-provider
36
+
37
+ **Python SDK for multi-provider AI inference** — chat completions, streaming, and agentic tool-calling over one interface, with server-side routing and automatic fallback across Cerebras, Meta Llama, NVIDIA NIM & DigitalOcean.
38
+
39
+ AINative's backend routes every chat completion across Cerebras, Meta Llama,
40
+ NVIDIA NIM, DigitalOcean and BYOK based on your plan tier, live per-provider
41
+ rate-limit state, daily token and cost caps, and model availability — then
42
+ transparently falls back through its chain when a provider is exhausted or
43
+ geo-blocked. This SDK is the client for that. You ask for a model; the platform
44
+ picks the upstream.
45
+
46
+ ```bash
47
+ pip install ainative-ai-sdk-provider
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from ainative_ai_sdk_provider import AINativeProvider
54
+
55
+ provider = AINativeProvider() # reads AINATIVE_API_KEY
56
+
57
+ result = provider.complete(
58
+ messages=[{"role": "user", "content": "Explain SSE in one sentence."}],
59
+ model="deepseek-4-flash",
60
+ )
61
+ print(result.text)
62
+ print(result.total_tokens, "tokens")
63
+ ```
64
+
65
+ ## Why there is no `provider=` parameter
66
+
67
+ This is the deliberate design of the SDK, not a missing feature.
68
+
69
+ The server-side router picks an upstream per request. Pinning a provider from
70
+ the client would switch that off — no tier-aware selection, no rate-limit
71
+ awareness, no cost-aware ordering, no automatic fallback when an upstream 429s
72
+ or geo-blocks you. So the SDK never sends a `provider` field, which is exactly
73
+ the signal the endpoint needs to do its job.
74
+
75
+ Two consequences worth knowing:
76
+
77
+ - **`result.provider` is informational.** The backend intentionally reports
78
+ several internal upstreams as `"ainative"`. Don't branch on it.
79
+ - **The SDK does not retry rate limits for you.** The backend already walked
80
+ its own fallback chain before returning a 429 or 503, and tells you how long
81
+ to wait. Retrying immediately would fight its circuit breaker — honour
82
+ `err.retry_after` instead.
83
+
84
+ ## Authentication
85
+
86
+ ```python
87
+ # 1. Environment variable (recommended)
88
+ # export AINATIVE_API_KEY=your_key
89
+ provider = AINativeProvider()
90
+
91
+ # 2. Explicit
92
+ provider = AINativeProvider(api_key="your_key")
93
+
94
+ # 3. Point at another environment
95
+ provider = AINativeProvider(api_key="k", base_url="http://localhost:8000/api/v1")
96
+ # or: export AINATIVE_BASE_URL=...
97
+ ```
98
+
99
+ The client sends your key as both `X-API-Key` and `Authorization: Bearer`,
100
+ since the platform's unified auth accepts either — so the same client works
101
+ against the public gateway and a directly-addressed backend without
102
+ reconfiguration.
103
+
104
+ ## Streaming
105
+
106
+ ```python
107
+ for chunk in provider.stream(
108
+ messages=[{"role": "user", "content": "Count to five."}],
109
+ model="deepseek-4-flash",
110
+ ):
111
+ print(chunk.content, end="", flush=True)
112
+ ```
113
+
114
+ Streaming is served by upstreams that speak OpenAI SSE natively. If the router
115
+ lands your request on one that doesn't, the API returns a normal JSON body and
116
+ this method yields a single chunk with the full completion — so your loop keeps
117
+ working either way.
118
+
119
+ If an upstream fails mid-stream, the API injects an error frame; the SDK raises
120
+ `UpstreamUnavailableError` rather than silently truncating your output.
121
+
122
+ ## Async
123
+
124
+ Every method has an async counterpart. `acomplete` mirrors `complete`,
125
+ `astream` mirrors `stream`.
126
+
127
+ ```python
128
+ import asyncio
129
+ from ainative_ai_sdk_provider import AINativeProvider
130
+
131
+ async def main():
132
+ async with AINativeProvider() as provider:
133
+ result = await provider.acomplete(
134
+ messages=[{"role": "user", "content": "Hello!"}],
135
+ )
136
+ print(result.text)
137
+
138
+ async for chunk in provider.astream(
139
+ messages=[{"role": "user", "content": "Count to three."}],
140
+ ):
141
+ print(chunk.content, end="")
142
+
143
+ asyncio.run(main())
144
+ ```
145
+
146
+ ## Tool calling
147
+
148
+ The API runs an agentic tool-calling loop server-side, up to `max_iterations`.
149
+
150
+ ```python
151
+ tools = [{
152
+ "type": "function",
153
+ "function": {
154
+ "name": "get_weather",
155
+ "description": "Get current weather for a city",
156
+ "parameters": {
157
+ "type": "object",
158
+ "properties": {"city": {"type": "string"}},
159
+ "required": ["city"],
160
+ },
161
+ },
162
+ }]
163
+
164
+ result = provider.complete(
165
+ messages=[{"role": "user", "content": "Weather in Oakland?"}],
166
+ model="deepseek-4-flash",
167
+ tools=tools,
168
+ tool_choice="auto",
169
+ max_iterations=3,
170
+ )
171
+
172
+ if result.tool_calls:
173
+ for call in result.tool_calls:
174
+ print(call["function"]["name"], call["function"]["arguments"])
175
+ else:
176
+ print(result.text)
177
+ ```
178
+
179
+ `tool_subset=["get_weather"]` restricts which registered server-side tools get
180
+ loaded for the request.
181
+
182
+ ## Error handling
183
+
184
+ Errors map onto the API's real failure modes, each carrying the payload the API
185
+ supplies with it.
186
+
187
+ ```python
188
+ from ainative_ai_sdk_provider import (
189
+ AINativeAPIError,
190
+ AuthenticationError,
191
+ InsufficientCreditsError,
192
+ ModelDeprecatedError,
193
+ RateLimitError,
194
+ RegionUnavailableError,
195
+ UpstreamUnavailableError,
196
+ )
197
+
198
+ try:
199
+ result = provider.complete(messages=[{"role": "user", "content": "hi"}])
200
+ except InsufficientCreditsError as err:
201
+ # 402 — chat completions need a paid plan
202
+ print(err.details["upgrade"]["action"])
203
+ except RateLimitError as err:
204
+ # 429 — the backend already tried its fallback chain
205
+ print(f"Retry in {err.retry_after}s")
206
+ except RegionUnavailableError as err:
207
+ # 403 — model geo-blocked; the API names alternatives
208
+ print("Try instead:", err.details["suggested_models"])
209
+ except ModelDeprecatedError as err:
210
+ # 410 — model decommissioned, migrate
211
+ print(err.message)
212
+ except UpstreamUnavailableError as err:
213
+ # 502/503/504 — upstream overloaded, erroring, or timed out
214
+ print(f"Retry in {err.retry_after}s")
215
+ except AINativeAPIError as err:
216
+ print(err.status_code, err.code, err.message)
217
+ ```
218
+
219
+ | Exception | Status | Meaning |
220
+ |-----------|--------|---------|
221
+ | `AuthenticationError` | 401, 403 | Missing, invalid, or unauthorized API key |
222
+ | `InvalidRequestError` | 400, 404, 422 | Malformed or rejected request |
223
+ | `InsufficientCreditsError` | 402 | Chat completions require a paid plan |
224
+ | `RegionUnavailableError` | 403 | Model geo-blocked; backend fallback also failed |
225
+ | `ModelDeprecatedError` | 410 | Model decommissioned — migrate |
226
+ | `RateLimitError` | 429 | Rate limited; honour `retry_after` |
227
+ | `UpstreamUnavailableError` | 502, 503, 504 | Upstream overloaded/erroring/timed out |
228
+ | `ServerError` | 500 | Unexpected server-side failure |
229
+ | `AINativeConnectionError` | — | API unreachable (DNS/TCP/TLS) |
230
+ | `AINativeTimeoutError` | — | Client-side timeout |
231
+
232
+ All of them subclass `AINativeError`; all HTTP ones subclass
233
+ `AINativeAPIError`, which exposes `.status_code`, `.code`, `.message`,
234
+ `.details`, `.retry_after` and `.response`.
235
+
236
+ ## API reference
237
+
238
+ ### `AINativeProvider(api_key=None, *, base_url=None, timeout=180.0, client=None, async_client=None)`
239
+
240
+ Pass `client` / `async_client` to reuse your own `httpx` client (connection
241
+ pooling, proxies, custom transports). A client you supply is yours to close —
242
+ the SDK won't close it for you.
243
+
244
+ The 180s default outlasts the endpoint's own 150s budget across provider
245
+ fallbacks, so you receive the API's structured timeout error instead of the
246
+ client aborting first.
247
+
248
+ ### `complete(messages, *, model=None, temperature=None, max_tokens=None, system=None, tools=None, tool_choice=None, tool_subset=None, max_iterations=None, user_id=None, extra_body=None) -> ChatCompletion`
249
+
250
+ `messages` is required and non-empty. `temperature` is bounded 0.0-2.0 and
251
+ `max_iterations` 1-10, both validated locally so mistakes fail before a round
252
+ trip. Unset parameters are omitted entirely, letting the API's own defaults
253
+ apply. `extra_body` merges extra fields for endpoint parameters newer than this
254
+ SDK version.
255
+
256
+ ### `stream(...) -> Iterator[StreamChunk]`
257
+
258
+ Same parameters minus the tool-calling ones — use `complete` for agentic loops.
259
+
260
+ ### `acomplete(...)` / `astream(...)`
261
+
262
+ Async equivalents.
263
+
264
+ ### `ChatCompletion`
265
+
266
+ | Field | Description |
267
+ |-------|-------------|
268
+ | `text` | Assistant text from the first choice (`""` on a tool-call-only reply) |
269
+ | `tool_calls` | Tool calls the model requested, or `[]` |
270
+ | `id`, `model`, `created` | Completion identity |
271
+ | `provider` | Provider label — informational only, see above |
272
+ | `finish_reason` | `stop`, `tool_calls`, … |
273
+ | `prompt_tokens`, `completion_tokens`, `total_tokens` | Usage |
274
+ | `iterations`, `total_tokens_used` | Tool-loop metadata, when reported |
275
+ | `model_substituted` | Set when the backend swapped your model for an equivalent |
276
+ | `choices`, `usage`, `raw` | Unprocessed response data |
277
+
278
+ ### `StreamChunk`
279
+
280
+ `content` (text fragment), `finish_reason` (final frame only), `raw`.
281
+
282
+ ## Scope (v0.1)
283
+
284
+ **Implemented:** chat completions — non-streaming and streaming, sync and
285
+ async, server-side tool calling, the full error surface of the completions
286
+ endpoint, and typed results.
287
+
288
+ **Not yet:** embeddings, the chat *session* endpoints (`/chat/sessions/...`
289
+ persistence and history), client-side BYOK key passthrough, and a published
290
+ model catalog. Those are separate endpoints and a separate surface — see
291
+ `core#8066` for what a v2 would cover.
292
+
293
+ ## Development
294
+
295
+ ```bash
296
+ pip install -e ".[dev]"
297
+ pytest tests/ -v --cov=ainative_ai_sdk_provider --cov-report=term-missing
298
+ ```
299
+
300
+ Tests are fully mocked with `respx` — no live API calls and no API key needed.
301
+
302
+ ## License
303
+
304
+ MIT
@@ -0,0 +1,270 @@
1
+ # ainative-ai-sdk-provider
2
+
3
+ **Python SDK for multi-provider AI inference** — chat completions, streaming, and agentic tool-calling over one interface, with server-side routing and automatic fallback across Cerebras, Meta Llama, NVIDIA NIM & DigitalOcean.
4
+
5
+ AINative's backend routes every chat completion across Cerebras, Meta Llama,
6
+ NVIDIA NIM, DigitalOcean and BYOK based on your plan tier, live per-provider
7
+ rate-limit state, daily token and cost caps, and model availability — then
8
+ transparently falls back through its chain when a provider is exhausted or
9
+ geo-blocked. This SDK is the client for that. You ask for a model; the platform
10
+ picks the upstream.
11
+
12
+ ```bash
13
+ pip install ainative-ai-sdk-provider
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```python
19
+ from ainative_ai_sdk_provider import AINativeProvider
20
+
21
+ provider = AINativeProvider() # reads AINATIVE_API_KEY
22
+
23
+ result = provider.complete(
24
+ messages=[{"role": "user", "content": "Explain SSE in one sentence."}],
25
+ model="deepseek-4-flash",
26
+ )
27
+ print(result.text)
28
+ print(result.total_tokens, "tokens")
29
+ ```
30
+
31
+ ## Why there is no `provider=` parameter
32
+
33
+ This is the deliberate design of the SDK, not a missing feature.
34
+
35
+ The server-side router picks an upstream per request. Pinning a provider from
36
+ the client would switch that off — no tier-aware selection, no rate-limit
37
+ awareness, no cost-aware ordering, no automatic fallback when an upstream 429s
38
+ or geo-blocks you. So the SDK never sends a `provider` field, which is exactly
39
+ the signal the endpoint needs to do its job.
40
+
41
+ Two consequences worth knowing:
42
+
43
+ - **`result.provider` is informational.** The backend intentionally reports
44
+ several internal upstreams as `"ainative"`. Don't branch on it.
45
+ - **The SDK does not retry rate limits for you.** The backend already walked
46
+ its own fallback chain before returning a 429 or 503, and tells you how long
47
+ to wait. Retrying immediately would fight its circuit breaker — honour
48
+ `err.retry_after` instead.
49
+
50
+ ## Authentication
51
+
52
+ ```python
53
+ # 1. Environment variable (recommended)
54
+ # export AINATIVE_API_KEY=your_key
55
+ provider = AINativeProvider()
56
+
57
+ # 2. Explicit
58
+ provider = AINativeProvider(api_key="your_key")
59
+
60
+ # 3. Point at another environment
61
+ provider = AINativeProvider(api_key="k", base_url="http://localhost:8000/api/v1")
62
+ # or: export AINATIVE_BASE_URL=...
63
+ ```
64
+
65
+ The client sends your key as both `X-API-Key` and `Authorization: Bearer`,
66
+ since the platform's unified auth accepts either — so the same client works
67
+ against the public gateway and a directly-addressed backend without
68
+ reconfiguration.
69
+
70
+ ## Streaming
71
+
72
+ ```python
73
+ for chunk in provider.stream(
74
+ messages=[{"role": "user", "content": "Count to five."}],
75
+ model="deepseek-4-flash",
76
+ ):
77
+ print(chunk.content, end="", flush=True)
78
+ ```
79
+
80
+ Streaming is served by upstreams that speak OpenAI SSE natively. If the router
81
+ lands your request on one that doesn't, the API returns a normal JSON body and
82
+ this method yields a single chunk with the full completion — so your loop keeps
83
+ working either way.
84
+
85
+ If an upstream fails mid-stream, the API injects an error frame; the SDK raises
86
+ `UpstreamUnavailableError` rather than silently truncating your output.
87
+
88
+ ## Async
89
+
90
+ Every method has an async counterpart. `acomplete` mirrors `complete`,
91
+ `astream` mirrors `stream`.
92
+
93
+ ```python
94
+ import asyncio
95
+ from ainative_ai_sdk_provider import AINativeProvider
96
+
97
+ async def main():
98
+ async with AINativeProvider() as provider:
99
+ result = await provider.acomplete(
100
+ messages=[{"role": "user", "content": "Hello!"}],
101
+ )
102
+ print(result.text)
103
+
104
+ async for chunk in provider.astream(
105
+ messages=[{"role": "user", "content": "Count to three."}],
106
+ ):
107
+ print(chunk.content, end="")
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ## Tool calling
113
+
114
+ The API runs an agentic tool-calling loop server-side, up to `max_iterations`.
115
+
116
+ ```python
117
+ tools = [{
118
+ "type": "function",
119
+ "function": {
120
+ "name": "get_weather",
121
+ "description": "Get current weather for a city",
122
+ "parameters": {
123
+ "type": "object",
124
+ "properties": {"city": {"type": "string"}},
125
+ "required": ["city"],
126
+ },
127
+ },
128
+ }]
129
+
130
+ result = provider.complete(
131
+ messages=[{"role": "user", "content": "Weather in Oakland?"}],
132
+ model="deepseek-4-flash",
133
+ tools=tools,
134
+ tool_choice="auto",
135
+ max_iterations=3,
136
+ )
137
+
138
+ if result.tool_calls:
139
+ for call in result.tool_calls:
140
+ print(call["function"]["name"], call["function"]["arguments"])
141
+ else:
142
+ print(result.text)
143
+ ```
144
+
145
+ `tool_subset=["get_weather"]` restricts which registered server-side tools get
146
+ loaded for the request.
147
+
148
+ ## Error handling
149
+
150
+ Errors map onto the API's real failure modes, each carrying the payload the API
151
+ supplies with it.
152
+
153
+ ```python
154
+ from ainative_ai_sdk_provider import (
155
+ AINativeAPIError,
156
+ AuthenticationError,
157
+ InsufficientCreditsError,
158
+ ModelDeprecatedError,
159
+ RateLimitError,
160
+ RegionUnavailableError,
161
+ UpstreamUnavailableError,
162
+ )
163
+
164
+ try:
165
+ result = provider.complete(messages=[{"role": "user", "content": "hi"}])
166
+ except InsufficientCreditsError as err:
167
+ # 402 — chat completions need a paid plan
168
+ print(err.details["upgrade"]["action"])
169
+ except RateLimitError as err:
170
+ # 429 — the backend already tried its fallback chain
171
+ print(f"Retry in {err.retry_after}s")
172
+ except RegionUnavailableError as err:
173
+ # 403 — model geo-blocked; the API names alternatives
174
+ print("Try instead:", err.details["suggested_models"])
175
+ except ModelDeprecatedError as err:
176
+ # 410 — model decommissioned, migrate
177
+ print(err.message)
178
+ except UpstreamUnavailableError as err:
179
+ # 502/503/504 — upstream overloaded, erroring, or timed out
180
+ print(f"Retry in {err.retry_after}s")
181
+ except AINativeAPIError as err:
182
+ print(err.status_code, err.code, err.message)
183
+ ```
184
+
185
+ | Exception | Status | Meaning |
186
+ |-----------|--------|---------|
187
+ | `AuthenticationError` | 401, 403 | Missing, invalid, or unauthorized API key |
188
+ | `InvalidRequestError` | 400, 404, 422 | Malformed or rejected request |
189
+ | `InsufficientCreditsError` | 402 | Chat completions require a paid plan |
190
+ | `RegionUnavailableError` | 403 | Model geo-blocked; backend fallback also failed |
191
+ | `ModelDeprecatedError` | 410 | Model decommissioned — migrate |
192
+ | `RateLimitError` | 429 | Rate limited; honour `retry_after` |
193
+ | `UpstreamUnavailableError` | 502, 503, 504 | Upstream overloaded/erroring/timed out |
194
+ | `ServerError` | 500 | Unexpected server-side failure |
195
+ | `AINativeConnectionError` | — | API unreachable (DNS/TCP/TLS) |
196
+ | `AINativeTimeoutError` | — | Client-side timeout |
197
+
198
+ All of them subclass `AINativeError`; all HTTP ones subclass
199
+ `AINativeAPIError`, which exposes `.status_code`, `.code`, `.message`,
200
+ `.details`, `.retry_after` and `.response`.
201
+
202
+ ## API reference
203
+
204
+ ### `AINativeProvider(api_key=None, *, base_url=None, timeout=180.0, client=None, async_client=None)`
205
+
206
+ Pass `client` / `async_client` to reuse your own `httpx` client (connection
207
+ pooling, proxies, custom transports). A client you supply is yours to close —
208
+ the SDK won't close it for you.
209
+
210
+ The 180s default outlasts the endpoint's own 150s budget across provider
211
+ fallbacks, so you receive the API's structured timeout error instead of the
212
+ client aborting first.
213
+
214
+ ### `complete(messages, *, model=None, temperature=None, max_tokens=None, system=None, tools=None, tool_choice=None, tool_subset=None, max_iterations=None, user_id=None, extra_body=None) -> ChatCompletion`
215
+
216
+ `messages` is required and non-empty. `temperature` is bounded 0.0-2.0 and
217
+ `max_iterations` 1-10, both validated locally so mistakes fail before a round
218
+ trip. Unset parameters are omitted entirely, letting the API's own defaults
219
+ apply. `extra_body` merges extra fields for endpoint parameters newer than this
220
+ SDK version.
221
+
222
+ ### `stream(...) -> Iterator[StreamChunk]`
223
+
224
+ Same parameters minus the tool-calling ones — use `complete` for agentic loops.
225
+
226
+ ### `acomplete(...)` / `astream(...)`
227
+
228
+ Async equivalents.
229
+
230
+ ### `ChatCompletion`
231
+
232
+ | Field | Description |
233
+ |-------|-------------|
234
+ | `text` | Assistant text from the first choice (`""` on a tool-call-only reply) |
235
+ | `tool_calls` | Tool calls the model requested, or `[]` |
236
+ | `id`, `model`, `created` | Completion identity |
237
+ | `provider` | Provider label — informational only, see above |
238
+ | `finish_reason` | `stop`, `tool_calls`, … |
239
+ | `prompt_tokens`, `completion_tokens`, `total_tokens` | Usage |
240
+ | `iterations`, `total_tokens_used` | Tool-loop metadata, when reported |
241
+ | `model_substituted` | Set when the backend swapped your model for an equivalent |
242
+ | `choices`, `usage`, `raw` | Unprocessed response data |
243
+
244
+ ### `StreamChunk`
245
+
246
+ `content` (text fragment), `finish_reason` (final frame only), `raw`.
247
+
248
+ ## Scope (v0.1)
249
+
250
+ **Implemented:** chat completions — non-streaming and streaming, sync and
251
+ async, server-side tool calling, the full error surface of the completions
252
+ endpoint, and typed results.
253
+
254
+ **Not yet:** embeddings, the chat *session* endpoints (`/chat/sessions/...`
255
+ persistence and history), client-side BYOK key passthrough, and a published
256
+ model catalog. Those are separate endpoints and a separate surface — see
257
+ `core#8066` for what a v2 would cover.
258
+
259
+ ## Development
260
+
261
+ ```bash
262
+ pip install -e ".[dev]"
263
+ pytest tests/ -v --cov=ainative_ai_sdk_provider --cov-report=term-missing
264
+ ```
265
+
266
+ Tests are fully mocked with `respx` — no live API calls and no API key needed.
267
+
268
+ ## License
269
+
270
+ MIT
@@ -0,0 +1,66 @@
1
+ """
2
+ ainative-ai-sdk-provider — Python client for AINative's inference API.
3
+
4
+ One interface, many providers — chosen server-side. AINative's backend routes
5
+ each request across Cerebras, Meta Llama, NVIDIA NIM, DigitalOcean and BYOK
6
+ based on your plan tier, live rate-limit state and cost caps. This SDK is the
7
+ client for that: you ask for a model, the platform picks the upstream.
8
+
9
+ Usage:
10
+ from ainative_ai_sdk_provider import AINativeProvider
11
+
12
+ provider = AINativeProvider() # reads AINATIVE_API_KEY
13
+
14
+ result = provider.complete(
15
+ messages=[{"role": "user", "content": "Explain SSE in one sentence."}],
16
+ model="deepseek-4-flash",
17
+ )
18
+ print(result.text)
19
+
20
+ # Streaming
21
+ for chunk in provider.stream(messages=[{"role": "user", "content": "Count to 5."}]):
22
+ print(chunk.content, end="")
23
+ """
24
+
25
+ __version__ = "0.1.0"
26
+
27
+ from .client import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, AINativeProvider
28
+ from .errors import (
29
+ AINativeAPIError,
30
+ AINativeConnectionError,
31
+ AINativeError,
32
+ AINativeTimeoutError,
33
+ AuthenticationError,
34
+ InsufficientCreditsError,
35
+ InvalidRequestError,
36
+ ModelDeprecatedError,
37
+ RateLimitError,
38
+ RegionUnavailableError,
39
+ ServerError,
40
+ UpstreamUnavailableError,
41
+ )
42
+ from .models import ChatCompletion, StreamChunk
43
+
44
+ __all__ = [
45
+ "__version__",
46
+ # Client
47
+ "AINativeProvider",
48
+ "DEFAULT_BASE_URL",
49
+ "DEFAULT_TIMEOUT",
50
+ # Models
51
+ "ChatCompletion",
52
+ "StreamChunk",
53
+ # Errors
54
+ "AINativeError",
55
+ "AINativeAPIError",
56
+ "AINativeConnectionError",
57
+ "AINativeTimeoutError",
58
+ "AuthenticationError",
59
+ "InsufficientCreditsError",
60
+ "InvalidRequestError",
61
+ "ModelDeprecatedError",
62
+ "RateLimitError",
63
+ "RegionUnavailableError",
64
+ "ServerError",
65
+ "UpstreamUnavailableError",
66
+ ]