meshapi 0.1.0__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,519 @@
1
+ Metadata-Version: 2.4
2
+ Name: meshapi
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the MeshAPI AI model gateway
5
+ Project-URL: Homepage, https://meshapi.ai
6
+ Project-URL: Documentation, https://developers.meshapi.ai
7
+ Project-URL: Repository, https://github.com/aifiesta/meshapi-python-sdk
8
+ Project-URL: Issues, https://github.com/aifiesta/meshapi-python-sdk/issues
9
+ Project-URL: Changelog, https://github.com/aifiesta/meshapi-python-sdk/blob/main/CHANGELOG.md
10
+ Author-email: MeshAPI <contact@meshapi.ai>
11
+ License: MIT
12
+ Keywords: ai-gateway,anthropic,batches,embeddings,llm,meshapi,openai,openai-compatible,prompt-templates,sdk
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: httpx>=0.27.0
28
+ Requires-Dist: pydantic>=2.0.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
32
+ Requires-Dist: respx>=0.21.0; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # meshapi
36
+
37
+ Official Python SDK for [Mesh API](https://meshapi.ai), an AI model gateway that gives you instant access to 300+ LLMs through a single OpenAI-compatible API.
38
+
39
+ Code once with the chat completions signature you already know. Switch between OpenAI, Anthropic, Google, Meta, Mistral, DeepSeek, xAI, Alibaba and the rest by changing a model string. Streaming, tool calling, vision, embeddings, multi-model compare, batch jobs and prompt templates from a single client.
40
+
41
+ ```python
42
+ from meshapi import MeshAPI, ChatCompletionParams, ChatMessage
43
+
44
+ client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")
45
+
46
+ reply = client.chat.completions.create(
47
+ ChatCompletionParams(
48
+ model="anthropic/claude-sonnet-4.5",
49
+ messages=[ChatMessage(role="user", content="Write a haiku about Python.")],
50
+ )
51
+ )
52
+
53
+ print(reply.choices[0].message.content)
54
+ ```
55
+
56
+ Python 3.9+. Built on `httpx` and Pydantic v2. Sync and async clients with first-class type hints.
57
+
58
+ ## Install
59
+
60
+ ```bash
61
+ pip install meshapi
62
+ # or
63
+ uv add meshapi
64
+ # or
65
+ poetry add meshapi
66
+ ```
67
+
68
+ Get a key at [meshapi.ai](https://meshapi.ai). Data-plane keys are prefixed `rsk_`.
69
+
70
+ ## What you get
71
+
72
+ | | |
73
+ |---|---|
74
+ | **One Universal API** | Code once. A single `chat.completions.create` call works across 300+ base models. |
75
+ | **Sync and async** | Pick `MeshAPI` for scripts, `AsyncMeshAPI` for servers. Same surface, same params. |
76
+ | **Streaming + tool calling** | SSE streaming via `Iterator` / `AsyncIterator`, function calling with structured tool definitions, vision and audio content parts. |
77
+ | **Reasoning models** | First-class `responses` API with `reasoning.effort` and `max_output_tokens` for o-series and similar models. |
78
+ | **Embeddings** | Drop-in OpenAI-compatible embeddings endpoint. |
79
+ | **Multi-model compare** | Fire one prompt at N models in parallel and stream their replies side by side. |
80
+ | **Batches + Files** | Async bulk inference jobs at discounted rates with file upload, download and lifecycle management. |
81
+ | **Prompt templates** | Server-stored prompts with `{{variable}}` slots. Update prompts without redeploying. |
82
+ | **Provider fallbacks** | If a provider experiences downtime, the gateway falls back to another supported model so your inference stays up. |
83
+ | **Built-in rate limiting** | Per-key RPM and RPD limits to prevent runaway costs. HTTP 429 with `retry_after` surfaced as `MeshAPIError.retry_after_seconds`. |
84
+ | **Unified billing** | One account balance covers every model. No juggling subscriptions. |
85
+ | **Structured errors** | `MeshAPIError` with `error_code`, `status`, `request_id`, `retry_after_seconds`, and provider error details. |
86
+ | **Type-safe** | Every request and response is a Pydantic v2 model. Autocomplete in your editor, validation at the boundary. |
87
+
88
+ ## Configuration
89
+
90
+ ```python
91
+ client = MeshAPI(
92
+ base_url="https://api.meshapi.ai", # required
93
+ token="rsk_...", # required: data-plane key or Supabase JWT
94
+ timeout=60.0, # default 60s
95
+ max_retries=3, # default 3, set 0 to disable
96
+ httpx_client=None, # optional: inject a custom httpx.Client
97
+ )
98
+ ```
99
+
100
+ `AsyncMeshAPI` takes the same arguments (with `async_httpx_client` instead of `httpx_client`) and supports `async with` for clean shutdown.
101
+
102
+ Two auth realms. Use one client per realm.
103
+
104
+ | Realm | Token | Resources |
105
+ |---|---|---|
106
+ | **Data-plane** | `rsk_<ULID>` | `chat`, `responses`, `embeddings`, `compare`, `files`, `batches` |
107
+ | **Control-plane** | Supabase JWT | `templates`, `models` |
108
+
109
+ `models` accepts either token type.
110
+
111
+ ## Chat completions
112
+
113
+ ```python
114
+ from meshapi import MeshAPI, ChatCompletionParams, ChatMessage
115
+
116
+ client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")
117
+
118
+ reply = client.chat.completions.create(
119
+ ChatCompletionParams(
120
+ model="openai/gpt-4o-mini",
121
+ messages=[
122
+ ChatMessage(role="system", content="You are a concise assistant."),
123
+ ChatMessage(role="user", content="What is the capital of France?"),
124
+ ],
125
+ temperature=0.7,
126
+ max_tokens=256,
127
+ )
128
+ )
129
+
130
+ print(reply.choices[0].message.content)
131
+ print(f"Tokens: {reply.usage.total_tokens}")
132
+ ```
133
+
134
+ ### Async
135
+
136
+ ```python
137
+ import asyncio
138
+ from meshapi import AsyncMeshAPI, ChatCompletionParams, ChatMessage
139
+
140
+ async def main():
141
+ async with AsyncMeshAPI(base_url="https://api.meshapi.ai", token="rsk_...") as client:
142
+ reply = await client.chat.completions.create(
143
+ ChatCompletionParams(
144
+ model="openai/gpt-4o-mini",
145
+ messages=[ChatMessage(role="user", content="Hello!")],
146
+ )
147
+ )
148
+ print(reply.choices[0].message.content)
149
+
150
+ asyncio.run(main())
151
+ ```
152
+
153
+ ### Streaming
154
+
155
+ `stream()` is a separate method from `create()`. It returns an iterator (sync) or async iterator (async) of chunks.
156
+
157
+ ```python
158
+ for chunk in client.chat.completions.stream(
159
+ ChatCompletionParams(
160
+ model="openai/gpt-4o-mini",
161
+ messages=[ChatMessage(role="user", content="Write a haiku about Python.")],
162
+ )
163
+ ):
164
+ if chunk.choices and chunk.choices[0].delta:
165
+ print(chunk.choices[0].delta.content or "", end="", flush=True)
166
+ ```
167
+
168
+ Async streaming:
169
+
170
+ ```python
171
+ async for chunk in client.chat.completions.stream(params):
172
+ if chunk.choices and chunk.choices[0].delta:
173
+ print(chunk.choices[0].delta.content or "", end="", flush=True)
174
+ ```
175
+
176
+ ### Tool calling
177
+
178
+ ```python
179
+ from meshapi import ChatCompletionParams, ChatMessage, Tool, ToolFunction
180
+
181
+ params = ChatCompletionParams(
182
+ model="openai/gpt-4o",
183
+ messages=[ChatMessage(role="user", content="What is the weather in Paris?")],
184
+ tools=[
185
+ Tool(
186
+ type="function",
187
+ function=ToolFunction(
188
+ name="get_weather",
189
+ description="Get current weather for a city",
190
+ parameters={
191
+ "type": "object",
192
+ "properties": {"city": {"type": "string"}},
193
+ "required": ["city"],
194
+ },
195
+ ),
196
+ )
197
+ ],
198
+ tool_choice="auto",
199
+ )
200
+
201
+ for chunk in client.chat.completions.stream(params):
202
+ delta = chunk.choices[0].delta if chunk.choices else None
203
+ if delta and delta.tool_calls:
204
+ print("tool call:", delta.tool_calls)
205
+ elif delta and delta.content:
206
+ print(delta.content, end="", flush=True)
207
+ ```
208
+
209
+ ## Responses API (reasoning models)
210
+
211
+ ```python
212
+ from meshapi import ResponsesParams
213
+
214
+ reply = client.responses.create(
215
+ ResponsesParams(
216
+ model="openai/o4-mini",
217
+ input="Explain the halting problem in two sentences.",
218
+ reasoning={"effort": "medium"},
219
+ max_output_tokens=512,
220
+ )
221
+ )
222
+
223
+ print(reply.output)
224
+ ```
225
+
226
+ Streaming works the same way via `client.responses.stream(params)`.
227
+
228
+ ## Embeddings
229
+
230
+ ```python
231
+ from meshapi import EmbeddingsParams
232
+
233
+ result = client.embeddings.create(
234
+ EmbeddingsParams(
235
+ model="openai/text-embedding-3-small",
236
+ input=["hello world", "goodbye world"],
237
+ )
238
+ )
239
+
240
+ print(len(result.data[0].embedding))
241
+ ```
242
+
243
+ ## Compare (multi-model fanout)
244
+
245
+ Fire one prompt at several models and stream their replies in parallel.
246
+
247
+ ```python
248
+ from meshapi import CompareParams, ChatMessage
249
+
250
+ stream = client.compare.stream(
251
+ CompareParams(
252
+ models=[
253
+ "openai/gpt-4o-mini",
254
+ "anthropic/claude-sonnet-4.5",
255
+ "google/gemini-2.5-flash",
256
+ ],
257
+ messages=[ChatMessage(role="user", content="Summarize this paragraph in one sentence: ...")],
258
+ stream=True,
259
+ )
260
+ )
261
+
262
+ for event in stream:
263
+ if event.event == "delta":
264
+ print(event.data)
265
+ ```
266
+
267
+ Use `client.compare.create(params)` for a non-streaming side-by-side response with an optional judge-model comparison via `comparison_model`.
268
+
269
+ ## Files and Batches
270
+
271
+ Upload a list of requests, kick off a batch, poll until done. Batch jobs run at discounted pricing.
272
+
273
+ ```python
274
+ from meshapi import (
275
+ UploadBatchFileParams,
276
+ BatchRequestItem,
277
+ CreateBatchParams,
278
+ )
279
+
280
+ # 1. Upload the batch input
281
+ file = client.files.upload(
282
+ UploadBatchFileParams(
283
+ purpose="batch",
284
+ requests=[
285
+ BatchRequestItem(
286
+ custom_id="req-1",
287
+ body={
288
+ "model": "openai/gpt-4o-mini",
289
+ "messages": [{"role": "user", "content": "Say hi."}],
290
+ },
291
+ ),
292
+ BatchRequestItem(
293
+ custom_id="req-2",
294
+ body={
295
+ "model": "openai/gpt-4o-mini",
296
+ "messages": [{"role": "user", "content": "Say bye."}],
297
+ },
298
+ ),
299
+ ],
300
+ )
301
+ )
302
+
303
+ # 2. Create the batch
304
+ batch = client.batches.create(
305
+ CreateBatchParams(
306
+ input_file_id=file.id,
307
+ endpoint="/v1/chat/completions",
308
+ completion_window="24h",
309
+ )
310
+ )
311
+
312
+ # 3. Poll later
313
+ status = client.batches.get(batch.id)
314
+ if status.status == "completed" and status.output_file_id:
315
+ output_bytes = client.files.content(status.output_file_id)
316
+ # output_bytes is JSONL
317
+ ```
318
+
319
+ ## Models
320
+
321
+ ```python
322
+ all_models = client.models.list()
323
+ free = client.models.free()
324
+ paid = client.models.paid()
325
+
326
+ for m in paid[:5]:
327
+ print(
328
+ f"{m.id}: prompt ${m.pricing.prompt_usd_per_1k}/1k, "
329
+ f"completion ${m.pricing.completion_usd_per_1k}/1k"
330
+ )
331
+ ```
332
+
333
+ Free models (`is_free=True`) cost $0 for both prompt and completion, useful for testing and light tasks. Paid models charge per token against your account balance.
334
+
335
+ ## Prompt templates
336
+
337
+ Server-stored prompts with `{{variable}}` interpolation. Reference them by name from `chat.completions` to skip re-sending system prompts every request.
338
+
339
+ ```python
340
+ from meshapi import MeshAPI, CreateTemplateParams, ChatCompletionParams, ChatMessage
341
+
342
+ # Manage templates with a control-plane JWT
343
+ ctrl = MeshAPI(base_url="https://api.meshapi.ai", token=supabase_session_jwt)
344
+
345
+ ctrl.templates.create(
346
+ CreateTemplateParams(
347
+ name="support-agent",
348
+ system="You are a support agent for {{company}}. Be concise and friendly.",
349
+ model="openai/gpt-4o-mini",
350
+ variables=["company"],
351
+ )
352
+ )
353
+
354
+ # Use the template with a data-plane rsk_ key
355
+ client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")
356
+
357
+ reply = client.chat.completions.create(
358
+ ChatCompletionParams(
359
+ messages=[ChatMessage(role="user", content="How do I reset my password?")],
360
+ template="support-agent",
361
+ variables={"company": "Acme Corp"},
362
+ )
363
+ )
364
+ ```
365
+
366
+ CRUD:
367
+
368
+ ```python
369
+ from meshapi import UpdateTemplateParams
370
+
371
+ templates = ctrl.templates.list()
372
+ t = ctrl.templates.get(template_id)
373
+ ctrl.templates.update(template_id, UpdateTemplateParams(model="openai/gpt-4o"))
374
+ ctrl.templates.delete(template_id)
375
+ ```
376
+
377
+ ## Error handling
378
+
379
+ ```python
380
+ from meshapi import MeshAPIError
381
+
382
+ try:
383
+ client.chat.completions.create(params)
384
+ except MeshAPIError as e:
385
+ print(f"[{e.status}] {e.error_code}: {e}")
386
+ print("Request ID:", e.request_id)
387
+
388
+ if e.error_code == "rate_limit_exceeded":
389
+ print(f"Retry after {e.retry_after_seconds}s")
390
+ elif e.error_code == "spend_limit_exceeded":
391
+ print("Account balance exhausted. Top up to continue.")
392
+ elif e.error_code == "unauthorized":
393
+ print("Invalid API key.")
394
+ elif e.error_code == "model_not_found":
395
+ print("Model not supported.")
396
+ elif e.error_code == "upstream_error":
397
+ print("Provider error:", e.provider_error)
398
+ elif e.error_code == "validation_error":
399
+ print("Invalid request:", e.details)
400
+ ```
401
+
402
+ | Code | HTTP | Meaning |
403
+ |---|---|---|
404
+ | `unauthorized` | 401 | Invalid or missing key |
405
+ | `forbidden` | 403 | Key suspended |
406
+ | `not_found` / `model_not_found` | 404 | Resource or model not found |
407
+ | `spend_limit_exceeded` | 402 | Account balance at zero. Top up to continue. |
408
+ | `validation_error` / `unprocessable_entity` | 422 | Bad request body |
409
+ | `rate_limit_exceeded` | 429 | RPM or RPD limit hit |
410
+ | `upstream_error` / `gateway_timeout` / `internal_error` | 500 | Upstream or server error |
411
+ | `parse_error` | n/a | SDK could not parse response body |
412
+ | `stream_interrupted` | n/a | Mid-stream connection dropped |
413
+
414
+ Mid-stream errors (sent as SSE frames before `[DONE]`) raise the same `MeshAPIError` from inside the iterator.
415
+
416
+ ## Retry and backoff
417
+
418
+ The client automatically retries `GET` and non-streaming `POST` / `PATCH` requests on status codes `429`, `502`, `503`, `504` with exponential backoff (default: 3 retries, base delay 500 ms, max 30 s, with jitter). The `Retry-After` header is respected on 429 responses.
419
+
420
+ ```python
421
+ client = MeshAPI(
422
+ base_url="https://api.meshapi.ai",
423
+ token="rsk_...",
424
+ max_retries=5, # 0 to disable
425
+ timeout=30.0,
426
+ )
427
+ ```
428
+
429
+ ## Streaming failure recovery
430
+
431
+ **Streams do not retry.** If a connection drops mid-stream, a `MeshAPIError` with `error_code="stream_interrupted"` is raised. Catch it and restart a new request:
432
+
433
+ ```python
434
+ try:
435
+ for chunk in client.chat.completions.stream(params):
436
+ process(chunk)
437
+ except MeshAPIError as e:
438
+ if e.error_code == "stream_interrupted":
439
+ # restart from scratch
440
+ ...
441
+ ```
442
+
443
+ ## Type hints
444
+
445
+ Every request and response is a Pydantic v2 model. Import what you need:
446
+
447
+ ```python
448
+ from meshapi import (
449
+ MeshAPI,
450
+ AsyncMeshAPI,
451
+ MeshAPIConfig,
452
+ MeshAPIError,
453
+ # chat
454
+ ChatCompletionParams,
455
+ ChatCompletionResponse,
456
+ ChatCompletionChunk,
457
+ ChatMessage,
458
+ Tool,
459
+ ToolFunction,
460
+ ToolCall,
461
+ # responses
462
+ ResponsesParams,
463
+ ResponsesResponse,
464
+ # embeddings
465
+ EmbeddingsParams,
466
+ EmbeddingsResponse,
467
+ # compare
468
+ CompareParams,
469
+ CompareStreamEvent,
470
+ # batches + files
471
+ UploadBatchFileParams,
472
+ BatchRequestItem,
473
+ CreateBatchParams,
474
+ BatchObject,
475
+ FileObject,
476
+ # models
477
+ ModelInfo,
478
+ ModelPricing,
479
+ # templates
480
+ CreateTemplateParams,
481
+ UpdateTemplateParams,
482
+ TemplateSummary,
483
+ )
484
+ ```
485
+
486
+ ## Versioning
487
+
488
+ This SDK follows [SemVer 2.0](https://semver.org/). Pre-1.0 releases may have breaking changes between minor versions.
489
+
490
+ ```python
491
+ import meshapi
492
+ print(meshapi.__version__) # "0.1.0"
493
+ ```
494
+
495
+ ## About Mesh API
496
+
497
+ [Mesh API](https://meshapi.ai) is an AI model gateway that gives you instant access to a massive variety of LLMs through a single, unified API. Enjoy the developer experience you already know, upgraded with universal model access.
498
+
499
+ | | |
500
+ |---|---|
501
+ | **One Universal API** | A single `chat.completions.create` request works across 300+ base models. |
502
+ | **Unified Billing** | Deposit funds into one account and consume any model. No juggling provider subscriptions. |
503
+ | **Free Tier** | Free models (`is_free=True`) cost $0 for both prompt and completion. Test and ship light workloads without funding. |
504
+ | **Provider Fallbacks** | If a model or provider goes down, the gateway routes to another supported model so your inference stays up. |
505
+ | **Built-in Rate Limiting** | Robust per-key limits prevent runaway costs. |
506
+ | **Prompt Templates** | Manage, version and share prompts via a secure templating system. |
507
+
508
+ Documentation lives at [developers.meshapi.ai](https://developers.meshapi.ai).
509
+
510
+ Built by the founders of [TagMango](https://tagmango.com) (YC W20) and [AI Fiesta](https://aifiesta.ai) (1M+ users).
511
+
512
+ ## Related
513
+
514
+ - [`meshapi-node-sdk`](https://github.com/aifiesta/meshapi-node-sdk): official TypeScript SDK with the same surface
515
+ - [`meshapi-code`](https://github.com/aifiesta/meshapi-code): terminal chat REPL with tool calling
516
+
517
+ ## License
518
+
519
+ [MIT](LICENSE)
@@ -0,0 +1,16 @@
1
+ meshapi/__init__.py,sha256=KhsGNxDxtLe6RTZ5m6UGIOXSMAOibQsqqoIRZPzq0Vo,6734
2
+ meshapi/_errors.py,sha256=JPqUyteKbqWaWF2tIognpEmj-y_cuhXF_Z7rRtLxwNw,3001
3
+ meshapi/_http.py,sha256=erMcpjLE-u4jl6ecB-FU_i0zJO-0bFhH86LPyQAMAHY,15341
4
+ meshapi/_types.py,sha256=HJP4V1a07stX0GHME_XUI-YbMrx1ywVP3dKpZau6nW0,16981
5
+ meshapi/resources/__init__.py,sha256=CRcYDTGDql0LqcJyWH1zHmglD3W6L8_Sm3w48XgzNJs,20
6
+ meshapi/resources/batches.py,sha256=E0Z9clWVIjqclnHnvPE4aoEVlLx35UYXJ79Uo7hzcvU,2274
7
+ meshapi/resources/chat.py,sha256=EX5yVMrHbU3aip7fILmjl_f9argAgT-L9hC3qeN2M5k,2389
8
+ meshapi/resources/compare.py,sha256=8EoCT_h1GsVmbKlG_tAEvpoZxxP-yb_qZ7AcSFenvuU,1691
9
+ meshapi/resources/embeddings.py,sha256=QiJTZnm20YxvZCpfCcY3H8IZx5GHkb1Rg7Pog0zxVrc,858
10
+ meshapi/resources/files.py,sha256=kyhBGsnhzFm8_Lm7D-xWYEumsQt1bryAlk525EdaKyQ,1582
11
+ meshapi/resources/models.py,sha256=oW5M1hNnTxR5VDN8Gs635_VQukzEudYWCAkHwYCv42Y,1709
12
+ meshapi/resources/responses.py,sha256=yeBo8GzGNkpcdl5Zh-1HJK-NDXFWkhxSDyAC_M4-ncE,1716
13
+ meshapi/resources/templates.py,sha256=TCFiKTrFEbW1LJ_l1oQdsskUFI_DPaEFv78NrMa7G5o,2367
14
+ meshapi-0.1.0.dist-info/METADATA,sha256=PQ4ncmVDhQQnlQZR4HLrS4WkPf8nt53noQLQHytOkzg,17018
15
+ meshapi-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
16
+ meshapi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any