aihi-models 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.
- aihi_models-0.1.0/.gitignore +25 -0
- aihi_models-0.1.0/PKG-INFO +133 -0
- aihi_models-0.1.0/README.md +120 -0
- aihi_models-0.1.0/README.zh-CN.md +67 -0
- aihi_models-0.1.0/pyproject.toml +24 -0
- aihi_models-0.1.0/src/aihi/models/__init__.py +115 -0
- aihi_models-0.1.0/src/aihi/models/_ids.py +13 -0
- aihi_models-0.1.0/src/aihi/models/base.py +70 -0
- aihi_models-0.1.0/src/aihi/models/errors.py +79 -0
- aihi_models-0.1.0/src/aihi/models/providers/__init__.py +13 -0
- aihi_models-0.1.0/src/aihi/models/providers/anthropic.py +385 -0
- aihi_models-0.1.0/src/aihi/models/providers/deepseek.py +66 -0
- aihi_models-0.1.0/src/aihi/models/providers/fake.py +151 -0
- aihi_models-0.1.0/src/aihi/models/providers/openai.py +423 -0
- aihi_models-0.1.0/src/aihi/models/providers/openai_compatible.py +36 -0
- aihi_models-0.1.0/src/aihi/models/py.typed +1 -0
- aihi_models-0.1.0/src/aihi/models/serialization.py +49 -0
- aihi_models-0.1.0/src/aihi/models/tokens.py +32 -0
- aihi_models-0.1.0/src/aihi/models/transport.py +160 -0
- aihi_models-0.1.0/src/aihi/models/types.py +280 -0
- aihi_models-0.1.0/tests/contract/test_models_public_api.py +31 -0
- aihi_models-0.1.0/tests/contract/test_providers.py +362 -0
- aihi_models-0.1.0/tests/unit/test_serialization.py +48 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
node_modules/
|
|
4
|
+
.pnpm-store/
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
.aihi/settings.local.json
|
|
14
|
+
.aihi/audit.jsonl
|
|
15
|
+
.DS_Store
|
|
16
|
+
|
|
17
|
+
# Local legacy SQLite compatibility fixture and SQLite WAL sidecars.
|
|
18
|
+
/tests/fixtures/session_schema_v1.sqlite3*
|
|
19
|
+
|
|
20
|
+
# Superpowers working documents: local process artefacts, not project docs.
|
|
21
|
+
/docs/superpowers/
|
|
22
|
+
|
|
23
|
+
# Architecture decision records and RFC drafts are local working notes.
|
|
24
|
+
/docs/adr/
|
|
25
|
+
/docs/rfcs/
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: aihi-models
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provider-neutral model contracts and provider adapters for AIHI
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Classifier: Typing :: Typed
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# aihi-models
|
|
15
|
+
|
|
16
|
+
[English] | [简体中文](README.zh-CN.md)
|
|
17
|
+
|
|
18
|
+
Provider-neutral model contracts and provider adapters for AIHI.
|
|
19
|
+
|
|
20
|
+
`aihi-models` is the lowest Python layer in the repository. It normalizes messages, streamed output, tool definitions, usage, provider failures, and wire serialization so the agent runtime does not depend on one vendor SDK.
|
|
21
|
+
|
|
22
|
+
## Why this package exists
|
|
23
|
+
|
|
24
|
+
The package deliberately owns model-facing primitives only:
|
|
25
|
+
|
|
26
|
+
- immutable request/response and content-block contracts;
|
|
27
|
+
- normalized streaming chunks for text and tool input;
|
|
28
|
+
- provider adapters and transport abstractions;
|
|
29
|
+
- typed provider errors and context-length classification;
|
|
30
|
+
- versioned message serialization and token estimation.
|
|
31
|
+
|
|
32
|
+
It does **not** own model routing, a gateway, application configuration, prompt policy, sessions, tools, or an agent loop. Those belong to the application/runtime layers.
|
|
33
|
+
|
|
34
|
+
## Supported providers
|
|
35
|
+
|
|
36
|
+
| Adapter | Use case | Notes |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `OpenAIProvider` | OpenAI Chat Completions | Configure the endpoint and API key explicitly. |
|
|
39
|
+
| `AnthropicProvider` | Anthropic Messages API | Normalizes Anthropic content blocks to the common stream model. |
|
|
40
|
+
| `DeepSeekProvider` | DeepSeek chat models | Uses DeepSeek's OpenAI-compatible API by default. |
|
|
41
|
+
| `OpenAICompatibleProvider` | Other OpenAI-compatible endpoints | Requires an explicit full `base_url` for the chat-completions endpoint. |
|
|
42
|
+
| `FakeProvider` | Tests and local contract fixtures | Deterministic scripted responses; no network access. |
|
|
43
|
+
|
|
44
|
+
Providers are flat modules under `src/aihi/models/providers`. Credentials and model selection are supplied by the application; constructors do not silently read environment variables.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
From the repository workspace:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv sync
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For a local editable install:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv pip install -e packages/aihi/models
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The package requires Python 3.11+ and depends on `httpx` for the default HTTP transport.
|
|
61
|
+
|
|
62
|
+
## Minimal example
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
|
|
67
|
+
from aihi.models import FakeProvider, FakeStep, Message, ModelRequest
|
|
68
|
+
|
|
69
|
+
provider = FakeProvider([
|
|
70
|
+
FakeStep(text="Hello from a deterministic provider."),
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
request = ModelRequest(
|
|
74
|
+
model="fake-model",
|
|
75
|
+
messages=[Message.text("user", "Say hello.")],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def main() -> None:
|
|
79
|
+
chunks = [chunk async for chunk in provider.stream(request)]
|
|
80
|
+
print(chunks[-1])
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
asyncio.run(main())
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Providers also expose normalized asynchronous streaming. A stream is made of typed chunks such as `BlockStart`, `TextDelta`, `ToolInputDelta`, and `MessageEnd`, allowing the runtime to render or persist output without vendor-specific branching.
|
|
87
|
+
|
|
88
|
+
## Public API
|
|
89
|
+
|
|
90
|
+
The package root re-exports the stable building blocks:
|
|
91
|
+
|
|
92
|
+
- Contracts: `Message`, `ModelRequest`, `ModelResponse`, `ModelToolDefinition`, `Capabilities`, content blocks, and `Usage`.
|
|
93
|
+
- Providers: `OpenAIProvider`, `AnthropicProvider`, `DeepSeekProvider`, `OpenAICompatibleProvider`, and `FakeProvider`.
|
|
94
|
+
- Errors: `ProviderError`, `ProviderHTTPError`, `ProviderProtocolError`, `ProviderTimeout`, and `ProviderContextLengthError`.
|
|
95
|
+
- Serialization: `encode_message`, `decode_message`, `ModelMessageEnvelope`, and `MESSAGE_SCHEMA_VERSION`.
|
|
96
|
+
- Transport: `HttpxTransport`, `JsonTransport`, and `HttpRequest`.
|
|
97
|
+
|
|
98
|
+
Import from `aihi.models` rather than reaching into private modules.
|
|
99
|
+
|
|
100
|
+
## Compatibility and errors
|
|
101
|
+
|
|
102
|
+
Provider adapters map vendor responses into one common response/stream vocabulary and classify failures into stable error types. Callers should handle `ProviderTimeout`, HTTP failures, protocol failures, and context-length failures separately when deciding whether to retry, compact context, or surface an error.
|
|
103
|
+
|
|
104
|
+
Message envelopes are versioned. Unknown schema versions raise `UnsupportedMessageSchema`; applications should persist the versioned envelope rather than relying on a provider's native JSON.
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
Run package tests and repository-wide static checks from the root:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
uv run pytest packages/aihi/models/tests
|
|
112
|
+
uv run ruff check packages/aihi/models
|
|
113
|
+
uv run mypy
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Build a wheel without resolving dependencies from the network:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
uv run python -m build --wheel --no-isolation packages/aihi/models
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Security notes
|
|
123
|
+
|
|
124
|
+
- Pass API keys from the application boundary and keep them out of model messages and persisted events.
|
|
125
|
+
- Treat provider response text and tool arguments as untrusted input.
|
|
126
|
+
- Use the default `httpx` transport or provide a transport with equivalent timeout and TLS behavior.
|
|
127
|
+
- The fake provider is intended for tests; it is not a fallback for production failures.
|
|
128
|
+
|
|
129
|
+
## Related packages
|
|
130
|
+
|
|
131
|
+
- [`aihi-agent`](../agent/README.md) builds the provider-neutral runtime on these contracts.
|
|
132
|
+
- [`aihi-code-agent`](../code-agent/README.md) composes providers into a coding-agent application.
|
|
133
|
+
- [Repository architecture](../../../docs/ARCHITECTURE.md)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# aihi-models
|
|
2
|
+
|
|
3
|
+
[English] | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Provider-neutral model contracts and provider adapters for AIHI.
|
|
6
|
+
|
|
7
|
+
`aihi-models` is the lowest Python layer in the repository. It normalizes messages, streamed output, tool definitions, usage, provider failures, and wire serialization so the agent runtime does not depend on one vendor SDK.
|
|
8
|
+
|
|
9
|
+
## Why this package exists
|
|
10
|
+
|
|
11
|
+
The package deliberately owns model-facing primitives only:
|
|
12
|
+
|
|
13
|
+
- immutable request/response and content-block contracts;
|
|
14
|
+
- normalized streaming chunks for text and tool input;
|
|
15
|
+
- provider adapters and transport abstractions;
|
|
16
|
+
- typed provider errors and context-length classification;
|
|
17
|
+
- versioned message serialization and token estimation.
|
|
18
|
+
|
|
19
|
+
It does **not** own model routing, a gateway, application configuration, prompt policy, sessions, tools, or an agent loop. Those belong to the application/runtime layers.
|
|
20
|
+
|
|
21
|
+
## Supported providers
|
|
22
|
+
|
|
23
|
+
| Adapter | Use case | Notes |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `OpenAIProvider` | OpenAI Chat Completions | Configure the endpoint and API key explicitly. |
|
|
26
|
+
| `AnthropicProvider` | Anthropic Messages API | Normalizes Anthropic content blocks to the common stream model. |
|
|
27
|
+
| `DeepSeekProvider` | DeepSeek chat models | Uses DeepSeek's OpenAI-compatible API by default. |
|
|
28
|
+
| `OpenAICompatibleProvider` | Other OpenAI-compatible endpoints | Requires an explicit full `base_url` for the chat-completions endpoint. |
|
|
29
|
+
| `FakeProvider` | Tests and local contract fixtures | Deterministic scripted responses; no network access. |
|
|
30
|
+
|
|
31
|
+
Providers are flat modules under `src/aihi/models/providers`. Credentials and model selection are supplied by the application; constructors do not silently read environment variables.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
From the repository workspace:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For a local editable install:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv pip install -e packages/aihi/models
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The package requires Python 3.11+ and depends on `httpx` for the default HTTP transport.
|
|
48
|
+
|
|
49
|
+
## Minimal example
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
|
|
54
|
+
from aihi.models import FakeProvider, FakeStep, Message, ModelRequest
|
|
55
|
+
|
|
56
|
+
provider = FakeProvider([
|
|
57
|
+
FakeStep(text="Hello from a deterministic provider."),
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
request = ModelRequest(
|
|
61
|
+
model="fake-model",
|
|
62
|
+
messages=[Message.text("user", "Say hello.")],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def main() -> None:
|
|
66
|
+
chunks = [chunk async for chunk in provider.stream(request)]
|
|
67
|
+
print(chunks[-1])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Providers also expose normalized asynchronous streaming. A stream is made of typed chunks such as `BlockStart`, `TextDelta`, `ToolInputDelta`, and `MessageEnd`, allowing the runtime to render or persist output without vendor-specific branching.
|
|
74
|
+
|
|
75
|
+
## Public API
|
|
76
|
+
|
|
77
|
+
The package root re-exports the stable building blocks:
|
|
78
|
+
|
|
79
|
+
- Contracts: `Message`, `ModelRequest`, `ModelResponse`, `ModelToolDefinition`, `Capabilities`, content blocks, and `Usage`.
|
|
80
|
+
- Providers: `OpenAIProvider`, `AnthropicProvider`, `DeepSeekProvider`, `OpenAICompatibleProvider`, and `FakeProvider`.
|
|
81
|
+
- Errors: `ProviderError`, `ProviderHTTPError`, `ProviderProtocolError`, `ProviderTimeout`, and `ProviderContextLengthError`.
|
|
82
|
+
- Serialization: `encode_message`, `decode_message`, `ModelMessageEnvelope`, and `MESSAGE_SCHEMA_VERSION`.
|
|
83
|
+
- Transport: `HttpxTransport`, `JsonTransport`, and `HttpRequest`.
|
|
84
|
+
|
|
85
|
+
Import from `aihi.models` rather than reaching into private modules.
|
|
86
|
+
|
|
87
|
+
## Compatibility and errors
|
|
88
|
+
|
|
89
|
+
Provider adapters map vendor responses into one common response/stream vocabulary and classify failures into stable error types. Callers should handle `ProviderTimeout`, HTTP failures, protocol failures, and context-length failures separately when deciding whether to retry, compact context, or surface an error.
|
|
90
|
+
|
|
91
|
+
Message envelopes are versioned. Unknown schema versions raise `UnsupportedMessageSchema`; applications should persist the versioned envelope rather than relying on a provider's native JSON.
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
Run package tests and repository-wide static checks from the root:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv run pytest packages/aihi/models/tests
|
|
99
|
+
uv run ruff check packages/aihi/models
|
|
100
|
+
uv run mypy
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Build a wheel without resolving dependencies from the network:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv run python -m build --wheel --no-isolation packages/aihi/models
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Security notes
|
|
110
|
+
|
|
111
|
+
- Pass API keys from the application boundary and keep them out of model messages and persisted events.
|
|
112
|
+
- Treat provider response text and tool arguments as untrusted input.
|
|
113
|
+
- Use the default `httpx` transport or provide a transport with equivalent timeout and TLS behavior.
|
|
114
|
+
- The fake provider is intended for tests; it is not a fallback for production failures.
|
|
115
|
+
|
|
116
|
+
## Related packages
|
|
117
|
+
|
|
118
|
+
- [`aihi-agent`](../agent/README.md) builds the provider-neutral runtime on these contracts.
|
|
119
|
+
- [`aihi-code-agent`](../code-agent/README.md) composes providers into a coding-agent application.
|
|
120
|
+
- [Repository architecture](../../../docs/ARCHITECTURE.md)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# aihi-models
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
AIHI 的 Provider-neutral 模型契约和 Provider 适配器。它是仓库中最低层的 Python 包,负责统一
|
|
6
|
+
消息、流式输出、工具定义、用量、Provider 错误、序列化和 token 估算。
|
|
7
|
+
|
|
8
|
+
## 职责边界
|
|
9
|
+
|
|
10
|
+
- 不可变的请求、响应和 Content Block 契约。
|
|
11
|
+
- 文本、工具输入和推理载荷的标准化 Stream Chunk。
|
|
12
|
+
- Provider Protocol、HTTP transport 和 Provider adapters。
|
|
13
|
+
- 类型化 Provider 错误、Context Length 分类和 Message Schema 版本化 codec。
|
|
14
|
+
|
|
15
|
+
本包不负责 ModelRouter、ModelGateway、应用配置、Prompt、Session、ToolSpec 或 Agent loop。
|
|
16
|
+
这些能力由应用层或 `aihi-agent` 负责。
|
|
17
|
+
|
|
18
|
+
## 支持的 Provider
|
|
19
|
+
|
|
20
|
+
| Adapter | 用途 |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `OpenAIProvider` | OpenAI Chat Completions |
|
|
23
|
+
| `AnthropicProvider` | Anthropic Messages API |
|
|
24
|
+
| `DeepSeekProvider` | DeepSeek;复用 OpenAI-compatible 协议 |
|
|
25
|
+
| `OpenAICompatibleProvider` | 其他兼容 OpenAI API 的服务;必须传入完整 endpoint |
|
|
26
|
+
| `FakeProvider` | 测试和离线 fixture |
|
|
27
|
+
|
|
28
|
+
Provider 是扁平模块,凭据、模型选择和多个 Provider 的组合由应用层提供,不会静默读取环境变量。
|
|
29
|
+
|
|
30
|
+
## 安装
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv sync
|
|
34
|
+
uv pip install -e packages/aihi/models
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
要求 Python 3.11+,默认 HTTP transport 依赖 `httpx`。
|
|
38
|
+
|
|
39
|
+
## 最小示例
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from aihi.models import Message, ModelRequest
|
|
43
|
+
from aihi.models.providers.fake import FakeProvider
|
|
44
|
+
|
|
45
|
+
provider = FakeProvider()
|
|
46
|
+
request = ModelRequest(
|
|
47
|
+
model="fake-model",
|
|
48
|
+
messages=(Message.user("Say hello"),),
|
|
49
|
+
)
|
|
50
|
+
async for chunk in provider.stream(request):
|
|
51
|
+
print(chunk)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 公共 API 与兼容性
|
|
55
|
+
|
|
56
|
+
跨包只使用 `aihi.models` 顶层导出。Message codec 使用独立版本号;旧事件缺少版本时按 v1 读取。
|
|
57
|
+
Provider stream 在首个 chunk 之后不得自动切换 Provider,错误必须包含稳定 code 和 `retryable`。
|
|
58
|
+
|
|
59
|
+
## 开发
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pytest packages/aihi/models/tests
|
|
63
|
+
ruff check packages/aihi/models
|
|
64
|
+
mypy packages/aihi/models/src
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
详见仓库 [架构文档](../../../docs/ARCHITECTURE.zh-CN.md)。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aihi-models"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Provider-neutral model contracts and provider adapters for AIHI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.27",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/aihi"]
|
|
24
|
+
artifacts = ["src/aihi/models/py.typed"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Public API for model contracts and Provider adapters."""
|
|
2
|
+
|
|
3
|
+
from aihi.models.base import (
|
|
4
|
+
BlockEnd,
|
|
5
|
+
BlockStart,
|
|
6
|
+
MessageEnd,
|
|
7
|
+
MessageStart,
|
|
8
|
+
Provider,
|
|
9
|
+
StreamChunk,
|
|
10
|
+
TextDelta,
|
|
11
|
+
ThinkingDelta,
|
|
12
|
+
ToolInputDelta,
|
|
13
|
+
)
|
|
14
|
+
from aihi.models.errors import (
|
|
15
|
+
ModelsError,
|
|
16
|
+
ProviderContextLengthError,
|
|
17
|
+
ProviderError,
|
|
18
|
+
ProviderFailure,
|
|
19
|
+
ProviderHTTPError,
|
|
20
|
+
ProviderProtocolError,
|
|
21
|
+
ProviderTimeout,
|
|
22
|
+
)
|
|
23
|
+
from aihi.models.providers import (
|
|
24
|
+
AnthropicProvider,
|
|
25
|
+
DeepSeekProvider,
|
|
26
|
+
OpenAICompatibleProvider,
|
|
27
|
+
OpenAIProvider,
|
|
28
|
+
)
|
|
29
|
+
from aihi.models.providers.anthropic import AnthropicConfig
|
|
30
|
+
from aihi.models.providers.deepseek import DeepSeekConfig
|
|
31
|
+
from aihi.models.providers.fake import FakeProvider, FakeStep
|
|
32
|
+
from aihi.models.providers.openai import OpenAIConfig
|
|
33
|
+
from aihi.models.serialization import (
|
|
34
|
+
MESSAGE_SCHEMA_VERSION,
|
|
35
|
+
ModelMessageEnvelope,
|
|
36
|
+
UnsupportedMessageSchema,
|
|
37
|
+
decode_message,
|
|
38
|
+
encode_message,
|
|
39
|
+
)
|
|
40
|
+
from aihi.models.tokens import estimate_messages_tokens, estimate_text_tokens
|
|
41
|
+
from aihi.models.transport import HttpRequest, HttpxTransport, JsonTransport
|
|
42
|
+
from aihi.models.types import (
|
|
43
|
+
Capabilities,
|
|
44
|
+
ContentBlock,
|
|
45
|
+
ImageBlock,
|
|
46
|
+
JsonObject,
|
|
47
|
+
Message,
|
|
48
|
+
ModelRequest,
|
|
49
|
+
ModelResponse,
|
|
50
|
+
ModelToolDefinition,
|
|
51
|
+
Role,
|
|
52
|
+
StopReason,
|
|
53
|
+
TextBlock,
|
|
54
|
+
ThinkingBlock,
|
|
55
|
+
ToolCallBlock,
|
|
56
|
+
ToolResultBlock,
|
|
57
|
+
Usage,
|
|
58
|
+
block_from_dict,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
"AnthropicConfig",
|
|
63
|
+
"AnthropicProvider",
|
|
64
|
+
"BlockEnd",
|
|
65
|
+
"BlockStart",
|
|
66
|
+
"Capabilities",
|
|
67
|
+
"ContentBlock",
|
|
68
|
+
"DeepSeekConfig",
|
|
69
|
+
"DeepSeekProvider",
|
|
70
|
+
"FakeProvider",
|
|
71
|
+
"FakeStep",
|
|
72
|
+
"HttpRequest",
|
|
73
|
+
"HttpxTransport",
|
|
74
|
+
"ImageBlock",
|
|
75
|
+
"JsonObject",
|
|
76
|
+
"JsonTransport",
|
|
77
|
+
"MESSAGE_SCHEMA_VERSION",
|
|
78
|
+
"Message",
|
|
79
|
+
"MessageEnd",
|
|
80
|
+
"MessageStart",
|
|
81
|
+
"ModelMessageEnvelope",
|
|
82
|
+
"ModelRequest",
|
|
83
|
+
"ModelResponse",
|
|
84
|
+
"ModelToolDefinition",
|
|
85
|
+
"ModelsError",
|
|
86
|
+
"OpenAICompatibleProvider",
|
|
87
|
+
"OpenAIConfig",
|
|
88
|
+
"OpenAIProvider",
|
|
89
|
+
"Provider",
|
|
90
|
+
"ProviderContextLengthError",
|
|
91
|
+
"ProviderError",
|
|
92
|
+
"ProviderFailure",
|
|
93
|
+
"ProviderHTTPError",
|
|
94
|
+
"ProviderProtocolError",
|
|
95
|
+
"ProviderTimeout",
|
|
96
|
+
"Role",
|
|
97
|
+
"StopReason",
|
|
98
|
+
"StreamChunk",
|
|
99
|
+
"TextBlock",
|
|
100
|
+
"TextDelta",
|
|
101
|
+
"ThinkingBlock",
|
|
102
|
+
"ThinkingDelta",
|
|
103
|
+
"ToolCallBlock",
|
|
104
|
+
"ToolInputDelta",
|
|
105
|
+
"ToolResultBlock",
|
|
106
|
+
"UnsupportedMessageSchema",
|
|
107
|
+
"Usage",
|
|
108
|
+
"block_from_dict",
|
|
109
|
+
"decode_message",
|
|
110
|
+
"encode_message",
|
|
111
|
+
"estimate_messages_tokens",
|
|
112
|
+
"estimate_text_tokens",
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Private identifiers for model-protocol value objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import secrets
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def new_id(prefix: str) -> str:
|
|
9
|
+
return f"{prefix}_{secrets.token_hex(12)}"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def new_message_id() -> str:
|
|
13
|
+
return new_id("msg")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Provider protocol and normalized wire-level streaming chunks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Literal, Protocol, TypeAlias
|
|
8
|
+
|
|
9
|
+
from aihi.models.types import Capabilities, ModelRequest, ModelResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class MessageStart:
|
|
14
|
+
model: str
|
|
15
|
+
kind: Literal["message_start"] = "message_start"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class BlockStart:
|
|
20
|
+
index: int
|
|
21
|
+
block_kind: str
|
|
22
|
+
kind: Literal["block_start"] = "block_start"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class TextDelta:
|
|
27
|
+
index: int
|
|
28
|
+
text: str
|
|
29
|
+
kind: Literal["text_delta"] = "text_delta"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class ThinkingDelta:
|
|
34
|
+
index: int
|
|
35
|
+
text: str
|
|
36
|
+
kind: Literal["thinking_delta"] = "thinking_delta"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True, slots=True)
|
|
40
|
+
class ToolInputDelta:
|
|
41
|
+
index: int
|
|
42
|
+
partial_json: str
|
|
43
|
+
kind: Literal["tool_input_delta"] = "tool_input_delta"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True)
|
|
47
|
+
class BlockEnd:
|
|
48
|
+
index: int
|
|
49
|
+
kind: Literal["block_end"] = "block_end"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True)
|
|
53
|
+
class MessageEnd:
|
|
54
|
+
response: ModelResponse
|
|
55
|
+
kind: Literal["message_end"] = "message_end"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
StreamChunk: TypeAlias = (
|
|
59
|
+
MessageStart | BlockStart | TextDelta | ThinkingDelta | ToolInputDelta | BlockEnd | MessageEnd
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Provider(Protocol):
|
|
64
|
+
name: str
|
|
65
|
+
|
|
66
|
+
def capabilities(self, model: str) -> Capabilities: ...
|
|
67
|
+
|
|
68
|
+
def stream(self, request: ModelRequest) -> AsyncIterator[StreamChunk]: ...
|
|
69
|
+
|
|
70
|
+
async def count_tokens(self, request: ModelRequest) -> int: ...
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Stable Provider error taxonomy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ModelsError(Exception):
|
|
7
|
+
code = "models_error"
|
|
8
|
+
retryable = False
|
|
9
|
+
|
|
10
|
+
def __init__(self, message: str, *, details: dict[str, object] | None = None) -> None:
|
|
11
|
+
super().__init__(message)
|
|
12
|
+
self.details = details or {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProviderError(ModelsError):
|
|
16
|
+
code = "provider_failure"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProviderProtocolError(ProviderError):
|
|
20
|
+
code = "provider_protocol_error"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ProviderHTTPError(ProviderError):
|
|
24
|
+
code = "provider_http_error"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProviderContextLengthError(ProviderHTTPError):
|
|
28
|
+
code = "provider_context_length"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProviderTimeout(ProviderError):
|
|
32
|
+
code = "provider_timeout"
|
|
33
|
+
retryable = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_context_length_message(message: str) -> bool:
|
|
37
|
+
normalized = message.casefold().replace("-", "_").replace(" ", "_")
|
|
38
|
+
explicit_markers = (
|
|
39
|
+
"context_length",
|
|
40
|
+
"context_window",
|
|
41
|
+
"maximum_context",
|
|
42
|
+
"max_context",
|
|
43
|
+
"context_exceeded",
|
|
44
|
+
"context_limit",
|
|
45
|
+
"context_size",
|
|
46
|
+
)
|
|
47
|
+
if any(marker in normalized for marker in explicit_markers):
|
|
48
|
+
return True
|
|
49
|
+
if not any(term in normalized for term in ("context", "prompt", "input")):
|
|
50
|
+
return False
|
|
51
|
+
return any(
|
|
52
|
+
marker in normalized
|
|
53
|
+
for marker in (
|
|
54
|
+
"prompt_is_too_long",
|
|
55
|
+
"prompt_too_long",
|
|
56
|
+
"input_is_too_long",
|
|
57
|
+
"input_too_long",
|
|
58
|
+
"too_many_tokens",
|
|
59
|
+
"token_limit",
|
|
60
|
+
"maximum_tokens",
|
|
61
|
+
"max_tokens",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Compatibility name for the stable provider-failure contract.
|
|
67
|
+
ProviderFailure = ProviderError
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"ModelsError",
|
|
72
|
+
"ProviderContextLengthError",
|
|
73
|
+
"ProviderError",
|
|
74
|
+
"ProviderFailure",
|
|
75
|
+
"ProviderHTTPError",
|
|
76
|
+
"ProviderProtocolError",
|
|
77
|
+
"ProviderTimeout",
|
|
78
|
+
"is_context_length_message",
|
|
79
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Built-in model provider adapters."""
|
|
2
|
+
|
|
3
|
+
from aihi.models.providers.anthropic import AnthropicProvider
|
|
4
|
+
from aihi.models.providers.deepseek import DeepSeekProvider
|
|
5
|
+
from aihi.models.providers.openai import OpenAIProvider
|
|
6
|
+
from aihi.models.providers.openai_compatible import OpenAICompatibleProvider
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AnthropicProvider",
|
|
10
|
+
"DeepSeekProvider",
|
|
11
|
+
"OpenAICompatibleProvider",
|
|
12
|
+
"OpenAIProvider",
|
|
13
|
+
]
|