meshapi 0.1.2__tar.gz → 0.1.4__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.
- meshapi-0.1.4/CLAUDE.md +159 -0
- meshapi-0.1.4/PKG-INFO +458 -0
- meshapi-0.1.4/README.md +421 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/TESTING.md +2 -2
- meshapi-0.1.4/livetests/compare.py +34 -0
- meshapi-0.1.4/livetests/config.py +43 -0
- meshapi-0.1.4/livetests/conftest.py +98 -0
- meshapi-0.1.4/livetests/pytest.ini +16 -0
- meshapi-0.1.4/livetests/requirements.txt +2 -0
- meshapi-0.1.4/livetests/responses.py +23 -0
- meshapi-0.1.4/livetests/test_chat.py +83 -0
- meshapi-0.1.4/livetests/test_errors.py +45 -0
- meshapi-0.1.4/livetests/test_feature_matrix.py +158 -0
- meshapi-0.1.4/livetests/test_inference_resources.py +156 -0
- meshapi-0.1.4/livetests/test_models.py +38 -0
- meshapi-0.1.4/livetests/test_rag.py +114 -0
- meshapi-0.1.4/livetests/test_realtime.py +128 -0
- meshapi-0.1.4/livetests/test_stream.py +73 -0
- meshapi-0.1.4/livetests/test_templates.py +61 -0
- meshapi-0.1.4/livetests/tool_call.py +101 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/__init__.py +54 -7
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/_http.py +8 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/_types.py +158 -26
- meshapi-0.1.4/meshapi/resources/images.py +38 -0
- meshapi-0.1.4/meshapi/resources/rag.py +117 -0
- meshapi-0.1.4/meshapi/resources/realtime.py +325 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/pyproject.toml +3 -2
- meshapi-0.1.2/PKG-INFO +0 -519
- meshapi-0.1.2/README.md +0 -485
- meshapi-0.1.2/meshapi/resources/files.py +0 -44
- {meshapi-0.1.2 → meshapi-0.1.4}/.gitignore +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/CHANGELOG.md +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/_errors.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/__init__.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/batches.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/chat.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/compare.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/embeddings.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/models.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/responses.py +0 -0
- {meshapi-0.1.2 → meshapi-0.1.4}/meshapi/resources/templates.py +0 -0
meshapi-0.1.4/CLAUDE.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# MeshAPI Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python client for the MeshAPI AI model gateway.
|
|
4
|
+
|
|
5
|
+
- **Package**: `meshapi`
|
|
6
|
+
- **Python**: 3.9+
|
|
7
|
+
- **Runtime dependencies**: `httpx>=0.27`, `pydantic>=2`
|
|
8
|
+
- **Build backend**: hatchling
|
|
9
|
+
|
|
10
|
+
## Project layout
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
python/
|
|
14
|
+
├── meshapi/
|
|
15
|
+
│ ├── __init__.py # MeshAPI, AsyncMeshAPI, all public exports
|
|
16
|
+
│ ├── _types.py # All Pydantic request/response models
|
|
17
|
+
│ ├── _http.py # SyncHttpClient / AsyncHttpClient (httpx-based)
|
|
18
|
+
│ ├── _errors.py # MeshAPIError exception
|
|
19
|
+
│ └── resources/
|
|
20
|
+
│ ├── chat.py # /v1/chat/completions
|
|
21
|
+
│ ├── responses.py # /v1/responses
|
|
22
|
+
│ ├── embeddings.py # /v1/embeddings
|
|
23
|
+
│ ├── compare.py # /v1/compare
|
|
24
|
+
│ ├── files.py # /v1/files (batch file objects)
|
|
25
|
+
│ ├── rag.py # /v1/files RAG endpoints (upload, embed, search)
|
|
26
|
+
│ ├── batches.py # /v1/batches
|
|
27
|
+
│ ├── models.py # /v1/models
|
|
28
|
+
│ ├── templates.py # /v1/templates
|
|
29
|
+
│ └── images.py # /v1/images/generations
|
|
30
|
+
├── tests/
|
|
31
|
+
│ ├── unit/ # Fast, no-network tests
|
|
32
|
+
│ ├── contract/ # Pydantic model parsing against local fixtures
|
|
33
|
+
│ └── integration/ # Full-stack tests against a running backend
|
|
34
|
+
├── livetests/ # Live tests against a real backend
|
|
35
|
+
└── pyproject.toml
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Common tasks
|
|
39
|
+
|
|
40
|
+
### Set up development environment
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python -m venv .venv
|
|
44
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
45
|
+
pip install -e ".[dev]"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Unit and contract tests (no network)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pytest tests/unit/ tests/contract/ -v
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Integration tests (requires a running backend)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
MESHAPI_BASE_URL=http://localhost:8000 MESHAPI_TOKEN=rsk_... pytest tests/integration/ -v
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Adding a new resource
|
|
61
|
+
|
|
62
|
+
1. Add Pydantic models to `_types.py` under a clearly labelled section.
|
|
63
|
+
2. Create `resources/<name>.py` with `<Name>Resource` and `Async<Name>Resource` classes.
|
|
64
|
+
3. Both classes take their respective `SyncHttpClient` / `AsyncHttpClient` in `__init__`.
|
|
65
|
+
4. Wire the resource into both `MeshAPI` and `AsyncMeshAPI` in `__init__.py`.
|
|
66
|
+
5. Import the new types and resource classes in `__init__.py` and add them to `__all__`.
|
|
67
|
+
6. Follow the pattern in `resources/templates.py`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Live tests
|
|
72
|
+
|
|
73
|
+
Live tests hit a real MeshAPI backend and live in `livetests/`. They use pytest with a shared `client` fixture from `conftest.py`.
|
|
74
|
+
|
|
75
|
+
### Prerequisites
|
|
76
|
+
|
|
77
|
+
- A running MeshAPI instance (default `http://localhost:8000`), **or** point at the dev API.
|
|
78
|
+
- A valid data-plane API key (`rsk_...`).
|
|
79
|
+
|
|
80
|
+
### Environment variables
|
|
81
|
+
|
|
82
|
+
Create `python/.env.livetest` (read automatically by the test harness) or export the variables in your shell before running tests.
|
|
83
|
+
|
|
84
|
+
| Variable | Required | Default | Description |
|
|
85
|
+
|----------|----------|---------|-------------|
|
|
86
|
+
| `MESHAPI_BASE_URL` | No | `http://localhost:8000` | Base URL of the MeshAPI gateway |
|
|
87
|
+
| `MESHAPI_TOKEN` | **Yes** | hardcoded dev key | Data-plane API key (`rsk_...`) |
|
|
88
|
+
| `MESHAPI_MODEL` | No | `openai/gpt-4o-mini` | Primary model used in chat/stream tests |
|
|
89
|
+
| `MESHAPI_SECOND_MODEL` | No | `anthropic/claude-haiku-4.5` | Second model for compare tests |
|
|
90
|
+
| `MESHAPI_EMBEDDINGS_MODEL` | No | `openai/text-embedding-3-small` | Model used in embeddings tests |
|
|
91
|
+
| `MESHAPI_IMAGE_GEN_MODEL` | No | _(skipped if unset)_ | Image generation model; test skipped if blank |
|
|
92
|
+
| `MESHAPI_IMAGE_URL` | No | _(skipped if unset)_ | Publicly accessible image URL for vision tests |
|
|
93
|
+
| `MESHAPI_INPUT_AUDIO_B64` | No | _(skipped if unset)_ | Base64-encoded audio for audio-input tests |
|
|
94
|
+
| `MESHAPI_INPUT_AUDIO_FORMAT` | No | `wav` | Format of the base64 audio (`wav`, `mp3`, etc.) |
|
|
95
|
+
| `MESHAPI_AUDIO_OUT_MODEL` | No | _(skipped if unset)_ | Model for audio-output tests; skipped if blank |
|
|
96
|
+
| `MESHAPI_REALTIME_MODEL` | No | `openai/gpt-realtime-mini` | Realtime-capable model used in WebSocket live tests |
|
|
97
|
+
|
|
98
|
+
Example `python/.env.livetest`:
|
|
99
|
+
|
|
100
|
+
```env
|
|
101
|
+
MESHAPI_BASE_URL=https://api-dev.meshapi.ai
|
|
102
|
+
MESHAPI_TOKEN=rsk_your_key_here
|
|
103
|
+
MESHAPI_MODEL=openai/gpt-4o-mini
|
|
104
|
+
MESHAPI_EMBEDDINGS_MODEL=openai/text-embedding-3-small
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Install dependencies
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# From the python/ directory
|
|
111
|
+
pip install -e ".[dev]"
|
|
112
|
+
pip install httpx # required by the RAG live test for direct signed-URL PUT
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Run all live tests
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd livetests
|
|
119
|
+
pytest -v
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Run a single live test file
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
cd livetests
|
|
126
|
+
pytest test_rag.py -v
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Run a specific test function
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
cd livetests
|
|
133
|
+
pytest test_rag.py::test_rag_upload_embed_search -v
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Available live test files
|
|
137
|
+
|
|
138
|
+
| File | What it tests |
|
|
139
|
+
|------|---------------|
|
|
140
|
+
| `test_chat.py` | Chat completions (basic, tools, multi-turn) |
|
|
141
|
+
| `test_stream.py` | Streaming chat and responses |
|
|
142
|
+
| `test_models.py` | Model listing |
|
|
143
|
+
| `test_templates.py` | Template CRUD lifecycle |
|
|
144
|
+
| `test_inference_resources.py` | Embeddings, responses |
|
|
145
|
+
| `test_errors.py` | 401/404 error handling |
|
|
146
|
+
| `test_feature_matrix.py` | Cross-model feature matrix |
|
|
147
|
+
| `test_rag.py` | RAG upload → embed → list → search |
|
|
148
|
+
| `test_realtime.py` | WebSocket connect/close, session.created, session.update, error envelopes, iterator API, async variants |
|
|
149
|
+
|
|
150
|
+
### RAG live test notes
|
|
151
|
+
|
|
152
|
+
`test_rag_upload_embed_search` does the following:
|
|
153
|
+
1. Calls `client.rag.init_upload` with `embed=False`.
|
|
154
|
+
2. PUTs the file bytes directly to the returned `signed_url` via `httpx.put`.
|
|
155
|
+
3. Waits up to 30 s for `upload_status=ready`.
|
|
156
|
+
4. Calls `client.rag.embed` to trigger embedding.
|
|
157
|
+
5. Polls up to 90 s for `embedding_status=ready`.
|
|
158
|
+
6. Calls `client.rag.list` and asserts the file appears.
|
|
159
|
+
7. Calls `client.rag.search` scoped to the file ID and asserts non-empty results.
|
meshapi-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meshapi
|
|
3
|
+
Version: 0.1.4
|
|
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
|
+
Requires-Dist: websockets>=12.0; extra == 'dev'
|
|
34
|
+
Provides-Extra: realtime
|
|
35
|
+
Requires-Dist: websockets>=12.0; extra == 'realtime'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# meshapi
|
|
39
|
+
|
|
40
|
+
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.
|
|
41
|
+
|
|
42
|
+
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, RAG and prompt templates from a single client.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from meshapi import MeshAPI, ChatCompletionParams, ChatMessage
|
|
46
|
+
|
|
47
|
+
client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")
|
|
48
|
+
|
|
49
|
+
reply = client.chat.completions.create(
|
|
50
|
+
ChatCompletionParams(
|
|
51
|
+
model="anthropic/claude-sonnet-4.5",
|
|
52
|
+
messages=[ChatMessage(role="user", content="Write a haiku about Python.")],
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
print(reply.choices[0].message.content)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Python 3.9+. Built on `httpx` and Pydantic v2. Sync and async clients with first-class type hints.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install meshapi
|
|
65
|
+
# or
|
|
66
|
+
uv add meshapi
|
|
67
|
+
# or
|
|
68
|
+
poetry add meshapi
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Get a key at [meshapi.ai](https://meshapi.ai). Data-plane keys are prefixed `rsk_`.
|
|
72
|
+
|
|
73
|
+
## What you get
|
|
74
|
+
|
|
75
|
+
| | |
|
|
76
|
+
|---|---|
|
|
77
|
+
| **One Universal API** | Code once. A single `chat.completions.create` call works across 300+ base models. |
|
|
78
|
+
| **Sync and async** | Pick `MeshAPI` for scripts, `AsyncMeshAPI` for servers. Same surface, same params. |
|
|
79
|
+
| **Streaming + tool calling** | SSE streaming via `Iterator` / `AsyncIterator`, function calling, vision and audio content parts. |
|
|
80
|
+
| **Reasoning models** | First-class `responses` API with `reasoning.effort` and `max_output_tokens`. |
|
|
81
|
+
| **Embeddings** | Drop-in OpenAI-compatible embeddings endpoint. |
|
|
82
|
+
| **Multi-model compare** | Fire one prompt at N models in parallel and stream their replies side by side. |
|
|
83
|
+
| **RAG** | Upload files, embed them, and run vector search — all through the same client. |
|
|
84
|
+
| **Batches** | Async bulk inference jobs at discounted rates with inline request submission. |
|
|
85
|
+
| **Prompt templates** | Server-stored prompts with `{{variable}}` slots. Update prompts without redeploying. |
|
|
86
|
+
| **Provider fallbacks** | If a provider experiences downtime, the gateway falls back to another supported model. |
|
|
87
|
+
| **Structured errors** | `MeshAPIError` with `error_code`, `status`, `request_id`, `retry_after_seconds`. |
|
|
88
|
+
| **Type-safe** | Every request and response is a Pydantic v2 model. |
|
|
89
|
+
|
|
90
|
+
## Authentication
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Chat completions
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from meshapi import MeshAPI, ChatCompletionParams, ChatMessage
|
|
100
|
+
|
|
101
|
+
reply = client.chat.completions.create(
|
|
102
|
+
ChatCompletionParams(
|
|
103
|
+
model="openai/gpt-4o-mini",
|
|
104
|
+
messages=[
|
|
105
|
+
ChatMessage(role="system", content="You are a concise assistant."),
|
|
106
|
+
ChatMessage(role="user", content="What is the capital of France?"),
|
|
107
|
+
],
|
|
108
|
+
temperature=0.7,
|
|
109
|
+
max_tokens=256,
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
print(reply.choices[0].message.content)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Async
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import asyncio
|
|
119
|
+
from meshapi import AsyncMeshAPI, ChatCompletionParams, ChatMessage
|
|
120
|
+
|
|
121
|
+
async def main():
|
|
122
|
+
async with AsyncMeshAPI(base_url="https://api.meshapi.ai", token="rsk_...") as client:
|
|
123
|
+
reply = await client.chat.completions.create(
|
|
124
|
+
ChatCompletionParams(
|
|
125
|
+
model="openai/gpt-4o-mini",
|
|
126
|
+
messages=[ChatMessage(role="user", content="Hello!")],
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
print(reply.choices[0].message.content)
|
|
130
|
+
|
|
131
|
+
asyncio.run(main())
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Streaming
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
for chunk in client.chat.completions.stream(
|
|
138
|
+
ChatCompletionParams(
|
|
139
|
+
model="openai/gpt-4o-mini",
|
|
140
|
+
messages=[ChatMessage(role="user", content="Write a haiku about Python.")],
|
|
141
|
+
)
|
|
142
|
+
):
|
|
143
|
+
if chunk.choices and chunk.choices[0].delta:
|
|
144
|
+
print(chunk.choices[0].delta.content or "", end="", flush=True)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Tool calling
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from meshapi import Tool, ToolFunction
|
|
151
|
+
|
|
152
|
+
params = ChatCompletionParams(
|
|
153
|
+
model="openai/gpt-4o",
|
|
154
|
+
messages=[ChatMessage(role="user", content="What is the weather in Paris?")],
|
|
155
|
+
tools=[
|
|
156
|
+
Tool(
|
|
157
|
+
type="function",
|
|
158
|
+
function=ToolFunction(
|
|
159
|
+
name="get_weather",
|
|
160
|
+
description="Get current weather for a city",
|
|
161
|
+
parameters={
|
|
162
|
+
"type": "object",
|
|
163
|
+
"properties": {"city": {"type": "string"}},
|
|
164
|
+
"required": ["city"],
|
|
165
|
+
},
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
],
|
|
169
|
+
tool_choice="auto",
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Responses API (reasoning models)
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from meshapi import ResponsesParams
|
|
177
|
+
|
|
178
|
+
reply = client.responses.create(
|
|
179
|
+
ResponsesParams(
|
|
180
|
+
model="openai/o4-mini",
|
|
181
|
+
input="Explain the halting problem in two sentences.",
|
|
182
|
+
reasoning={"effort": "medium"},
|
|
183
|
+
max_output_tokens=512,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Embeddings
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from meshapi import EmbeddingsParams
|
|
192
|
+
|
|
193
|
+
result = client.embeddings.create(
|
|
194
|
+
EmbeddingsParams(
|
|
195
|
+
model="openai/text-embedding-3-small",
|
|
196
|
+
input=["hello world", "goodbye world"],
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
print(len(result.data[0].embedding))
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Image generation
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from meshapi import ImageGenerationParams
|
|
206
|
+
|
|
207
|
+
result = client.images.generate(
|
|
208
|
+
ImageGenerationParams(
|
|
209
|
+
model="openai/gpt-image-1",
|
|
210
|
+
prompt="A watercolor of a fox in a snowy forest",
|
|
211
|
+
n=1, size="1024x1024", quality="high", output_format="webp",
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
print(result.data[0].url)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Compare (multi-model fanout)
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from meshapi import CompareParams, ChatMessage
|
|
221
|
+
|
|
222
|
+
for event in client.compare.stream(
|
|
223
|
+
CompareParams(
|
|
224
|
+
models=["openai/gpt-4o-mini", "anthropic/claude-sonnet-4.5"],
|
|
225
|
+
messages=[ChatMessage(role="user", content="Summarise this in one sentence: ...")],
|
|
226
|
+
)
|
|
227
|
+
):
|
|
228
|
+
if event.event == "delta":
|
|
229
|
+
print(event.data)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Batches
|
|
233
|
+
|
|
234
|
+
Batch jobs accept inline requests — no separate file upload step required.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from meshapi import CreateBatchParams, BatchRequestItem
|
|
238
|
+
|
|
239
|
+
batch = client.batches.create(
|
|
240
|
+
CreateBatchParams(
|
|
241
|
+
requests=[
|
|
242
|
+
BatchRequestItem(
|
|
243
|
+
custom_id="req-1",
|
|
244
|
+
body={"model": "openai/gpt-5-nano",
|
|
245
|
+
"messages": [{"role": "user", "content": "Say hi."}]},
|
|
246
|
+
),
|
|
247
|
+
BatchRequestItem(
|
|
248
|
+
custom_id="req-2",
|
|
249
|
+
body={"model": "openai/gpt-5-nano",
|
|
250
|
+
"messages": [{"role": "user", "content": "Say bye."}]},
|
|
251
|
+
),
|
|
252
|
+
],
|
|
253
|
+
metadata={"job": "my-batch"},
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Poll
|
|
258
|
+
status = client.batches.get(batch.id)
|
|
259
|
+
print(status.status)
|
|
260
|
+
|
|
261
|
+
# Cancel
|
|
262
|
+
client.batches.cancel(batch.id)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## RAG (Retrieval-Augmented Generation)
|
|
266
|
+
|
|
267
|
+
Upload files, embed them, and run vector search.
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from meshapi import InitUploadRequest, BulkEmbedRequest, SearchRequest
|
|
271
|
+
import httpx, time
|
|
272
|
+
|
|
273
|
+
# 1. Initialise upload — get a signed URL
|
|
274
|
+
upload = client.rag.init_upload(
|
|
275
|
+
InitUploadRequest(file_name="handbook.pdf", mime_type="application/pdf")
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# 2a. PUT file bytes to the signed URL yourself…
|
|
279
|
+
httpx.put(upload.signed_url, content=pdf_bytes,
|
|
280
|
+
headers={"Content-Type": "application/pdf"}).raise_for_status()
|
|
281
|
+
|
|
282
|
+
# 2b. …or use the convenience wrapper that does both steps:
|
|
283
|
+
upload = client.rag.upload_file(
|
|
284
|
+
file_name="handbook.pdf",
|
|
285
|
+
mime_type="application/pdf",
|
|
286
|
+
content=pdf_bytes,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# 3. Trigger embedding
|
|
290
|
+
client.rag.embed(BulkEmbedRequest(file_ids=[upload.file_id]))
|
|
291
|
+
|
|
292
|
+
# 4. Poll until ready
|
|
293
|
+
while True:
|
|
294
|
+
s = client.rag.get(upload.file_id)
|
|
295
|
+
if s.embedding_status == "ready":
|
|
296
|
+
break
|
|
297
|
+
time.sleep(3)
|
|
298
|
+
|
|
299
|
+
# 5. Search
|
|
300
|
+
results = client.rag.search(
|
|
301
|
+
SearchRequest(query="onboarding process", top_k=5)
|
|
302
|
+
)
|
|
303
|
+
for r in results.results:
|
|
304
|
+
print(f"{r.score:.4f} {r.text}")
|
|
305
|
+
|
|
306
|
+
# List files (paginated)
|
|
307
|
+
page = client.rag.list(limit=50)
|
|
308
|
+
print(f"{page.total} total files")
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Realtime (Speech-to-Speech WebSocket)
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
from meshapi import MeshAPI
|
|
315
|
+
|
|
316
|
+
client = MeshAPI(base_url="...", token="rsk_...")
|
|
317
|
+
|
|
318
|
+
# Sync — use as a context manager
|
|
319
|
+
with client.realtime.connect(model="openai/gpt-4o-realtime-preview") as session:
|
|
320
|
+
session.send({
|
|
321
|
+
"type": "session.update",
|
|
322
|
+
"session": {"instructions": "You are a helpful assistant."},
|
|
323
|
+
})
|
|
324
|
+
session.send_audio(pcm_bytes) # binary audio frame
|
|
325
|
+
|
|
326
|
+
for msg in session: # iterate until connection closes
|
|
327
|
+
print(msg.event["type"]) # "session.created", "response.done", …
|
|
328
|
+
if msg.audio: # binary audio frame from server
|
|
329
|
+
process_audio(msg.audio)
|
|
330
|
+
if msg.event and msg.event["type"] == "response.done":
|
|
331
|
+
break
|
|
332
|
+
|
|
333
|
+
# Async — identical API with await
|
|
334
|
+
from meshapi import AsyncMeshAPI
|
|
335
|
+
|
|
336
|
+
async with AsyncMeshAPI(base_url="...", token="rsk_...") as client:
|
|
337
|
+
async with client.realtime.connect(model="openai/gpt-4o-realtime-preview") as session:
|
|
338
|
+
await session.send({"type": "session.update", "session": {...}})
|
|
339
|
+
async for msg in session:
|
|
340
|
+
print(msg.event["type"])
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`RealtimeError` is raised for server-sent error envelopes (e.g. `insufficient_quota`, `idle_timeout`). Requires `websockets>=12.0` (included as a dependency).
|
|
344
|
+
|
|
345
|
+
## Models
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
all_models = client.models.list()
|
|
349
|
+
free = client.models.free()
|
|
350
|
+
paid = client.models.paid()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Prompt templates
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
from meshapi import CreateTemplateParams, UpdateTemplateParams
|
|
357
|
+
|
|
358
|
+
client.templates.create(
|
|
359
|
+
CreateTemplateParams(
|
|
360
|
+
name="support-agent",
|
|
361
|
+
system="You are a support agent for {{company}}. Be concise and friendly.",
|
|
362
|
+
model="openai/gpt-4o-mini",
|
|
363
|
+
variables=["company"],
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Use via chat
|
|
368
|
+
reply = client.chat.completions.create(
|
|
369
|
+
ChatCompletionParams(
|
|
370
|
+
messages=[ChatMessage(role="user", content="How do I reset my password?")],
|
|
371
|
+
template="support-agent",
|
|
372
|
+
variables={"company": "Acme Corp"},
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# CRUD
|
|
377
|
+
templates = client.templates.list()
|
|
378
|
+
client.templates.update(template_id, UpdateTemplateParams(model="openai/gpt-4o"))
|
|
379
|
+
client.templates.delete(template_id)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Error handling
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
from meshapi import MeshAPIError
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
client.chat.completions.create(params)
|
|
389
|
+
except MeshAPIError as e:
|
|
390
|
+
print(f"[{e.status}] {e.error_code}: {e}")
|
|
391
|
+
print("Request ID:", e.request_id)
|
|
392
|
+
if e.error_code == "rate_limit_exceeded":
|
|
393
|
+
print(f"Retry after {e.retry_after_seconds}s")
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
| Code | HTTP | Meaning |
|
|
397
|
+
|---|---|---|
|
|
398
|
+
| `unauthorized` | 401 | Invalid or missing key |
|
|
399
|
+
| `forbidden` | 403 | Key suspended |
|
|
400
|
+
| `not_found` / `model_not_found` | 404 | Resource or model not found |
|
|
401
|
+
| `spend_limit_exceeded` | 402 | Account balance at zero |
|
|
402
|
+
| `validation_error` | 422 | Bad request body |
|
|
403
|
+
| `rate_limit_exceeded` | 429 | RPM or RPD limit hit |
|
|
404
|
+
| `upstream_error` | 500 | Upstream or server error |
|
|
405
|
+
| `stream_interrupted` | n/a | Mid-stream connection dropped |
|
|
406
|
+
|
|
407
|
+
## Retry and backoff
|
|
408
|
+
|
|
409
|
+
Retries on 429/502/503/504 with exponential backoff (default 3 retries, 500 ms base, 30 s max). **Streams do not retry.**
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
client = MeshAPI(base_url="...", token="rsk_...", max_retries=5, timeout=30.0)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Type hints
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
from meshapi import (
|
|
419
|
+
MeshAPI, AsyncMeshAPI, MeshAPIError,
|
|
420
|
+
# chat
|
|
421
|
+
ChatCompletionParams, ChatCompletionResponse, ChatCompletionChunk,
|
|
422
|
+
ChatMessage, Tool, ToolFunction, ToolCall,
|
|
423
|
+
# responses
|
|
424
|
+
ResponsesParams, ResponsesResponse,
|
|
425
|
+
# embeddings
|
|
426
|
+
EmbeddingsParams, EmbeddingsResponse,
|
|
427
|
+
# compare
|
|
428
|
+
CompareParams, CompareStreamEvent,
|
|
429
|
+
# batches
|
|
430
|
+
BatchRequestItem, CreateBatchParams, BatchObject,
|
|
431
|
+
# RAG
|
|
432
|
+
InitUploadRequest, InitUploadResponse, UploadFileParams,
|
|
433
|
+
RagFileStatus, RagFileListResponse,
|
|
434
|
+
BulkEmbedRequest, BulkEmbedResponse,
|
|
435
|
+
SearchRequest, SearchResponse, SearchResult,
|
|
436
|
+
# models
|
|
437
|
+
ModelInfo, ModelPricing,
|
|
438
|
+
# templates
|
|
439
|
+
CreateTemplateParams, UpdateTemplateParams, TemplateSummary,
|
|
440
|
+
)
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Versioning
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
import meshapi
|
|
447
|
+
print(meshapi.__version__) # "0.1.0"
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## About Mesh API
|
|
451
|
+
|
|
452
|
+
[Mesh API](https://meshapi.ai) is an AI model gateway that gives you instant access to 300+ LLMs through a single, unified API.
|
|
453
|
+
|
|
454
|
+
Documentation: [developers.meshapi.ai](https://developers.meshapi.ai)
|
|
455
|
+
|
|
456
|
+
## License
|
|
457
|
+
|
|
458
|
+
[MIT](LICENSE)
|