aiyallm 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.
aiyallm-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,300 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiyallm
3
+ Version: 0.1.0
4
+ Summary: A small, provider-agnostic gateway for OpenAI/Anthropic-style language and vision models.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: openai>=1.66
8
+ Requires-Dist: anthropic>=0.49
9
+ Requires-Dist: httpx>=0.27
10
+ Provides-Extra: test
11
+ Requires-Dist: pytest>=8.3; extra == "test"
12
+ Requires-Dist: pytest-asyncio>=0.24; extra == "test"
13
+
14
+ # Aiya
15
+
16
+ Aiya is a lightweight Python gateway for OpenAI/Anthropic-style language and vision models.
17
+
18
+ > Aiya does not ship a built-in model catalog and does not read API keys implicitly. Every provider, `base_url`, API key, and model is declared by the application.
19
+
20
+ ## Table of Contents
21
+
22
+ - [Features](#features)
23
+ - [Install](#install)
24
+ - [Quick Start](#quick-start)
25
+ - [Provider Configuration](#provider-configuration)
26
+ - [Routing](#routing)
27
+ - [Vision and Capability Filtering](#vision-and-capability-filtering)
28
+ - [Usage Ledger](#usage-ledger)
29
+ - [Cost Calculation](#cost-calculation)
30
+ - [Streaming and Async](#streaming-and-async)
31
+ - [Extending Providers](#extending-providers)
32
+
33
+ ## Features
34
+
35
+ - OpenAI and OpenAI-compatible providers
36
+ - Anthropic and Anthropic-compatible providers
37
+ - Chinese OpenAI-compatible providers such as DeepSeek, Qwen/DashScope, Moonshot/Kimi, Zhipu GLM, and SiliconFlow
38
+ - Explicit model selection, `provider/model` references, and provider route lists
39
+ - Routing policies: `fallback`, `first`, `round_robin`, `random`, and `least_used`
40
+ - Vision and multimodal message passthrough with capability filtering
41
+ - Sync and async chat and streaming
42
+ - Detailed token accounting with cache hit/miss fields
43
+ - Optional SQLite or JSONL usage persistence
44
+ - Optional cost calculation from an application-owned price book
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install -e .
50
+ ```
51
+
52
+ Runtime dependencies are:
53
+
54
+ - `openai`
55
+ - `anthropic`
56
+ - `httpx`
57
+
58
+ ## Quick Start
59
+
60
+ There are no default providers. Pass them explicitly.
61
+
62
+ ```python
63
+ from aiya import Aiya
64
+
65
+ client = Aiya(
66
+ providers=[
67
+ {
68
+ "name": "deepseek",
69
+ "account": "billing-deepseek",
70
+ "type": "openai_compatible",
71
+ "base_url": "https://api.deepseek.com",
72
+ "api_key": "your-deepseek-key",
73
+ "models": ["deepseek-chat", "deepseek-reasoner"],
74
+ }
75
+ ]
76
+ )
77
+
78
+ response = client.chat(
79
+ "用一句话解释什么是路由。",
80
+ model="deepseek/deepseek-chat",
81
+ )
82
+
83
+ print(response.text)
84
+ print(response.usage)
85
+ ```
86
+
87
+ > `models` is optional when the provider allows unknown models. Configured providers default to `allow_unknown_models=True`, but the provider itself must still be declared.
88
+
89
+ ## Provider Configuration
90
+
91
+ Each provider accepts these common fields.
92
+
93
+ | Field | Meaning |
94
+ | --- | --- |
95
+ | `name` | Unique provider name used by `provider/model` routes |
96
+ | `account` | Optional billing or account name recorded in usage |
97
+ | `type` | `openai`, `openai_compatible`, `anthropic`, or `anthropic_compatible` |
98
+ | `base_url` | Endpoint for compatible services |
99
+ | `api_key` | API key, or use `api_key_env` to read an environment variable |
100
+ | `models` | Optional model names or `{"id", "capabilities", ...}` mappings |
101
+ | `allow_unknown_models` | Allow model IDs not listed in `models`; defaults to `True` |
102
+ | `timeout` | SDK timeout |
103
+
104
+ ### OpenAI-Compatible Examples
105
+
106
+ ```python
107
+ providers = [
108
+ {
109
+ "name": "deepseek",
110
+ "type": "openai_compatible",
111
+ "base_url": "https://api.deepseek.com",
112
+ "api_key_env": "DEEPSEEK_API_KEY",
113
+ },
114
+ {
115
+ "name": "qwen",
116
+ "type": "openai_compatible",
117
+ "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
118
+ "api_key_env": "DASHSCOPE_API_KEY",
119
+ },
120
+ {
121
+ "name": "moonshot",
122
+ "type": "openai_compatible",
123
+ "base_url": "https://api.moonshot.cn/v1",
124
+ "api_key_env": "MOONSHOT_API_KEY",
125
+ },
126
+ {
127
+ "name": "glm",
128
+ "type": "openai_compatible",
129
+ "base_url": "https://open.bigmodel.cn/api/paas/v4",
130
+ "api_key_env": "ZHIPU_API_KEY",
131
+ },
132
+ ]
133
+ ```
134
+
135
+ Use the `base_url` from each provider's current documentation. Aiya does not hard-code any endpoint or model name.
136
+
137
+ ## Routing
138
+
139
+ Use `provider/model` to target a provider.
140
+
141
+ ```python
142
+ response = client.chat("Hello", model="qwen/qwen-plus")
143
+ ```
144
+
145
+ Use a route list for fallback.
146
+
147
+ ```python
148
+ response = client.chat(
149
+ "Hello",
150
+ model=["deepseek/deepseek-chat", "qwen/qwen-plus"],
151
+ route_policy="fallback",
152
+ )
153
+ ```
154
+
155
+ Available policies:
156
+
157
+ - `fallback` - try candidates in order
158
+ - `first` - use only the first candidate
159
+ - `round_robin` - rotate through candidates
160
+ - `random` - choose a random candidate
161
+ - `least_used` - choose the candidate with the lowest recorded token usage
162
+
163
+ The default policy is `fallback`.
164
+
165
+ ## Vision and Capability Filtering
166
+
167
+ Declare capabilities in `models` and pass `require_vision=True` when a request needs image input.
168
+
169
+ ```python
170
+ from aiya import Aiya
171
+
172
+ client = Aiya(
173
+ providers=[
174
+ {
175
+ "name": "qwen",
176
+ "type": "openai_compatible",
177
+ "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
178
+ "api_key": "your-key",
179
+ "models": [
180
+ {"id": "qwen-vl-plus", "capabilities": ["text", "vision"]}
181
+ ],
182
+ }
183
+ ]
184
+ )
185
+
186
+ response = client.chat(
187
+ [
188
+ {
189
+ "role": "user",
190
+ "content": [
191
+ {"type": "text", "text": "What is in this image?"},
192
+ {
193
+ "type": "image_url",
194
+ "image_url": {"url": "https://example.com/image.jpg"},
195
+ },
196
+ ],
197
+ }
198
+ ],
199
+ model="qwen/qwen-vl-plus",
200
+ require_vision=True,
201
+ )
202
+ ```
203
+
204
+ ## Usage Ledger
205
+
206
+ Every successful completion is recorded.
207
+
208
+ ```python
209
+ response = client.chat("Hello", model="deepseek/deepseek-chat")
210
+
211
+ print(response.usage.input_tokens)
212
+ print(response.usage.output_tokens)
213
+ print(response.usage.input_cache_hit_tokens)
214
+ print(response.usage.input_cache_miss_tokens)
215
+ print(response.usage.output_cache_hit_tokens)
216
+ print(response.usage.output_cache_miss_tokens)
217
+ print(response.usage.account)
218
+ print(response.usage.reasoning_tokens)
219
+
220
+ print(client.ledger.total())
221
+ print(client.ledger.by_model())
222
+ print(client.ledger.by_provider())
223
+ print(client.ledger.by_account())
224
+
225
+ client.ledger.export_jsonl("usage.jsonl")
226
+ ```
227
+
228
+ For persistent accounting, pass a SQLite path.
229
+
230
+ ```python
231
+ from aiya import UsageLedger
232
+
233
+ ledger = UsageLedger(sqlite_path="data/usage.sqlite3")
234
+ client = Aiya(providers=providers, ledger=ledger)
235
+ ```
236
+
237
+ ## Cost Calculation
238
+
239
+ Token counts are always recorded. Cost is calculated only when the application supplies a price.
240
+
241
+ ```python
242
+ from aiya import Aiya, Price, PriceBook
243
+
244
+ price_book = PriceBook(
245
+ {
246
+ "deepseek-chat": Price(
247
+ input_per_mtok=0.27,
248
+ output_per_mtok=1.10,
249
+ )
250
+ }
251
+ )
252
+
253
+ client = Aiya(providers=providers, price_book=price_book)
254
+ response = client.chat("Hello", model="deepseek/deepseek-chat")
255
+
256
+ print(response.usage.cost_usd)
257
+ ```
258
+
259
+ Prices are US dollars per million tokens.
260
+
261
+ ## Streaming and Async
262
+
263
+ ```python
264
+ for chunk in client.stream("Hello", model="deepseek/deepseek-chat"):
265
+ print(chunk.content, end="", flush=True)
266
+ ```
267
+
268
+ ```python
269
+ import asyncio
270
+
271
+
272
+ async def main():
273
+ response = await client.achat("Hello", model="deepseek/deepseek-chat")
274
+ print(response.text)
275
+
276
+ async for chunk in client.astream("Hello", model="deepseek/deepseek-chat"):
277
+ print(chunk.content, end="", flush=True)
278
+
279
+
280
+ asyncio.run(main())
281
+ ```
282
+
283
+ ## Extending Providers
284
+
285
+ For a non-standard provider, subclass `aiya.BaseProvider` and implement `complete`, `acomplete`, `stream`, and `astream`.
286
+
287
+ ```python
288
+ from aiya import Aiya, BaseProvider
289
+
290
+
291
+ class MyProvider(BaseProvider):
292
+ def complete(self, request):
293
+ ...
294
+ ```
295
+
296
+ Then register the instance directly.
297
+
298
+ ```python
299
+ client = Aiya(providers=[MyProvider(name="my-provider", models=["my-model"])])
300
+ ```
@@ -0,0 +1,287 @@
1
+ # Aiya
2
+
3
+ Aiya is a lightweight Python gateway for OpenAI/Anthropic-style language and vision models.
4
+
5
+ > Aiya does not ship a built-in model catalog and does not read API keys implicitly. Every provider, `base_url`, API key, and model is declared by the application.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Install](#install)
11
+ - [Quick Start](#quick-start)
12
+ - [Provider Configuration](#provider-configuration)
13
+ - [Routing](#routing)
14
+ - [Vision and Capability Filtering](#vision-and-capability-filtering)
15
+ - [Usage Ledger](#usage-ledger)
16
+ - [Cost Calculation](#cost-calculation)
17
+ - [Streaming and Async](#streaming-and-async)
18
+ - [Extending Providers](#extending-providers)
19
+
20
+ ## Features
21
+
22
+ - OpenAI and OpenAI-compatible providers
23
+ - Anthropic and Anthropic-compatible providers
24
+ - Chinese OpenAI-compatible providers such as DeepSeek, Qwen/DashScope, Moonshot/Kimi, Zhipu GLM, and SiliconFlow
25
+ - Explicit model selection, `provider/model` references, and provider route lists
26
+ - Routing policies: `fallback`, `first`, `round_robin`, `random`, and `least_used`
27
+ - Vision and multimodal message passthrough with capability filtering
28
+ - Sync and async chat and streaming
29
+ - Detailed token accounting with cache hit/miss fields
30
+ - Optional SQLite or JSONL usage persistence
31
+ - Optional cost calculation from an application-owned price book
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install -e .
37
+ ```
38
+
39
+ Runtime dependencies are:
40
+
41
+ - `openai`
42
+ - `anthropic`
43
+ - `httpx`
44
+
45
+ ## Quick Start
46
+
47
+ There are no default providers. Pass them explicitly.
48
+
49
+ ```python
50
+ from aiya import Aiya
51
+
52
+ client = Aiya(
53
+ providers=[
54
+ {
55
+ "name": "deepseek",
56
+ "account": "billing-deepseek",
57
+ "type": "openai_compatible",
58
+ "base_url": "https://api.deepseek.com",
59
+ "api_key": "your-deepseek-key",
60
+ "models": ["deepseek-chat", "deepseek-reasoner"],
61
+ }
62
+ ]
63
+ )
64
+
65
+ response = client.chat(
66
+ "用一句话解释什么是路由。",
67
+ model="deepseek/deepseek-chat",
68
+ )
69
+
70
+ print(response.text)
71
+ print(response.usage)
72
+ ```
73
+
74
+ > `models` is optional when the provider allows unknown models. Configured providers default to `allow_unknown_models=True`, but the provider itself must still be declared.
75
+
76
+ ## Provider Configuration
77
+
78
+ Each provider accepts these common fields.
79
+
80
+ | Field | Meaning |
81
+ | --- | --- |
82
+ | `name` | Unique provider name used by `provider/model` routes |
83
+ | `account` | Optional billing or account name recorded in usage |
84
+ | `type` | `openai`, `openai_compatible`, `anthropic`, or `anthropic_compatible` |
85
+ | `base_url` | Endpoint for compatible services |
86
+ | `api_key` | API key, or use `api_key_env` to read an environment variable |
87
+ | `models` | Optional model names or `{"id", "capabilities", ...}` mappings |
88
+ | `allow_unknown_models` | Allow model IDs not listed in `models`; defaults to `True` |
89
+ | `timeout` | SDK timeout |
90
+
91
+ ### OpenAI-Compatible Examples
92
+
93
+ ```python
94
+ providers = [
95
+ {
96
+ "name": "deepseek",
97
+ "type": "openai_compatible",
98
+ "base_url": "https://api.deepseek.com",
99
+ "api_key_env": "DEEPSEEK_API_KEY",
100
+ },
101
+ {
102
+ "name": "qwen",
103
+ "type": "openai_compatible",
104
+ "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
105
+ "api_key_env": "DASHSCOPE_API_KEY",
106
+ },
107
+ {
108
+ "name": "moonshot",
109
+ "type": "openai_compatible",
110
+ "base_url": "https://api.moonshot.cn/v1",
111
+ "api_key_env": "MOONSHOT_API_KEY",
112
+ },
113
+ {
114
+ "name": "glm",
115
+ "type": "openai_compatible",
116
+ "base_url": "https://open.bigmodel.cn/api/paas/v4",
117
+ "api_key_env": "ZHIPU_API_KEY",
118
+ },
119
+ ]
120
+ ```
121
+
122
+ Use the `base_url` from each provider's current documentation. Aiya does not hard-code any endpoint or model name.
123
+
124
+ ## Routing
125
+
126
+ Use `provider/model` to target a provider.
127
+
128
+ ```python
129
+ response = client.chat("Hello", model="qwen/qwen-plus")
130
+ ```
131
+
132
+ Use a route list for fallback.
133
+
134
+ ```python
135
+ response = client.chat(
136
+ "Hello",
137
+ model=["deepseek/deepseek-chat", "qwen/qwen-plus"],
138
+ route_policy="fallback",
139
+ )
140
+ ```
141
+
142
+ Available policies:
143
+
144
+ - `fallback` - try candidates in order
145
+ - `first` - use only the first candidate
146
+ - `round_robin` - rotate through candidates
147
+ - `random` - choose a random candidate
148
+ - `least_used` - choose the candidate with the lowest recorded token usage
149
+
150
+ The default policy is `fallback`.
151
+
152
+ ## Vision and Capability Filtering
153
+
154
+ Declare capabilities in `models` and pass `require_vision=True` when a request needs image input.
155
+
156
+ ```python
157
+ from aiya import Aiya
158
+
159
+ client = Aiya(
160
+ providers=[
161
+ {
162
+ "name": "qwen",
163
+ "type": "openai_compatible",
164
+ "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
165
+ "api_key": "your-key",
166
+ "models": [
167
+ {"id": "qwen-vl-plus", "capabilities": ["text", "vision"]}
168
+ ],
169
+ }
170
+ ]
171
+ )
172
+
173
+ response = client.chat(
174
+ [
175
+ {
176
+ "role": "user",
177
+ "content": [
178
+ {"type": "text", "text": "What is in this image?"},
179
+ {
180
+ "type": "image_url",
181
+ "image_url": {"url": "https://example.com/image.jpg"},
182
+ },
183
+ ],
184
+ }
185
+ ],
186
+ model="qwen/qwen-vl-plus",
187
+ require_vision=True,
188
+ )
189
+ ```
190
+
191
+ ## Usage Ledger
192
+
193
+ Every successful completion is recorded.
194
+
195
+ ```python
196
+ response = client.chat("Hello", model="deepseek/deepseek-chat")
197
+
198
+ print(response.usage.input_tokens)
199
+ print(response.usage.output_tokens)
200
+ print(response.usage.input_cache_hit_tokens)
201
+ print(response.usage.input_cache_miss_tokens)
202
+ print(response.usage.output_cache_hit_tokens)
203
+ print(response.usage.output_cache_miss_tokens)
204
+ print(response.usage.account)
205
+ print(response.usage.reasoning_tokens)
206
+
207
+ print(client.ledger.total())
208
+ print(client.ledger.by_model())
209
+ print(client.ledger.by_provider())
210
+ print(client.ledger.by_account())
211
+
212
+ client.ledger.export_jsonl("usage.jsonl")
213
+ ```
214
+
215
+ For persistent accounting, pass a SQLite path.
216
+
217
+ ```python
218
+ from aiya import UsageLedger
219
+
220
+ ledger = UsageLedger(sqlite_path="data/usage.sqlite3")
221
+ client = Aiya(providers=providers, ledger=ledger)
222
+ ```
223
+
224
+ ## Cost Calculation
225
+
226
+ Token counts are always recorded. Cost is calculated only when the application supplies a price.
227
+
228
+ ```python
229
+ from aiya import Aiya, Price, PriceBook
230
+
231
+ price_book = PriceBook(
232
+ {
233
+ "deepseek-chat": Price(
234
+ input_per_mtok=0.27,
235
+ output_per_mtok=1.10,
236
+ )
237
+ }
238
+ )
239
+
240
+ client = Aiya(providers=providers, price_book=price_book)
241
+ response = client.chat("Hello", model="deepseek/deepseek-chat")
242
+
243
+ print(response.usage.cost_usd)
244
+ ```
245
+
246
+ Prices are US dollars per million tokens.
247
+
248
+ ## Streaming and Async
249
+
250
+ ```python
251
+ for chunk in client.stream("Hello", model="deepseek/deepseek-chat"):
252
+ print(chunk.content, end="", flush=True)
253
+ ```
254
+
255
+ ```python
256
+ import asyncio
257
+
258
+
259
+ async def main():
260
+ response = await client.achat("Hello", model="deepseek/deepseek-chat")
261
+ print(response.text)
262
+
263
+ async for chunk in client.astream("Hello", model="deepseek/deepseek-chat"):
264
+ print(chunk.content, end="", flush=True)
265
+
266
+
267
+ asyncio.run(main())
268
+ ```
269
+
270
+ ## Extending Providers
271
+
272
+ For a non-standard provider, subclass `aiya.BaseProvider` and implement `complete`, `acomplete`, `stream`, and `astream`.
273
+
274
+ ```python
275
+ from aiya import Aiya, BaseProvider
276
+
277
+
278
+ class MyProvider(BaseProvider):
279
+ def complete(self, request):
280
+ ...
281
+ ```
282
+
283
+ Then register the instance directly.
284
+
285
+ ```python
286
+ client = Aiya(providers=[MyProvider(name="my-provider", models=["my-model"])])
287
+ ```
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aiyallm"
7
+ version = "0.1.0"
8
+ description = "A small, provider-agnostic gateway for OpenAI/Anthropic-style language and vision models."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "openai>=1.66",
13
+ "anthropic>=0.49",
14
+ "httpx>=0.27",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ test = [
19
+ "pytest>=8.3",
20
+ "pytest-asyncio>=0.24",
21
+ ]
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
25
+
26
+ [tool.pytest.ini_options]
27
+ pythonpath = ["src"]
28
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,49 @@
1
+ """Aiya: provider-agnostic LLM and vision model gateway."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .client import Aiya
6
+ from .exceptions import (
7
+ AiyaError,
8
+ AuthenticationError,
9
+ ConfigurationError,
10
+ ModelNotSupportedError,
11
+ NoAvailableProviderError,
12
+ ProviderError,
13
+ RateLimitError,
14
+ )
15
+ from .models import Capability, ChatChunk, ChatResponse, ModelInfo, ToolCall
16
+ from .providers import (
17
+ AnthropicCompatibleProvider,
18
+ BaseProvider,
19
+ OpenAICompatibleProvider,
20
+ )
21
+ from .registry import RouteTarget
22
+ from .router import RoutePolicy, Router
23
+ from .usage import Price, PriceBook, Usage, UsageLedger
24
+
25
+ __all__ = [
26
+ "Aiya",
27
+ "AiyaError",
28
+ "AnthropicCompatibleProvider",
29
+ "AuthenticationError",
30
+ "BaseProvider",
31
+ "Capability",
32
+ "ChatChunk",
33
+ "ChatResponse",
34
+ "ConfigurationError",
35
+ "ModelInfo",
36
+ "ModelNotSupportedError",
37
+ "NoAvailableProviderError",
38
+ "OpenAICompatibleProvider",
39
+ "Price",
40
+ "PriceBook",
41
+ "ProviderError",
42
+ "RateLimitError",
43
+ "RoutePolicy",
44
+ "RouteTarget",
45
+ "Router",
46
+ "ToolCall",
47
+ "Usage",
48
+ "UsageLedger",
49
+ ]