somia 0.1.0a1__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.
- somia-0.1.0a1/LICENSE +21 -0
- somia-0.1.0a1/MANIFEST.in +7 -0
- somia-0.1.0a1/PKG-INFO +174 -0
- somia-0.1.0a1/README.md +149 -0
- somia-0.1.0a1/pyproject.toml +46 -0
- somia-0.1.0a1/setup.cfg +4 -0
- somia-0.1.0a1/src/somia/__init__.py +37 -0
- somia-0.1.0a1/src/somia/_http.py +96 -0
- somia-0.1.0a1/src/somia/client.py +147 -0
- somia-0.1.0a1/src/somia/exceptions.py +105 -0
- somia-0.1.0a1/src/somia/models.py +43 -0
- somia-0.1.0a1/src/somia/streaming.py +42 -0
- somia-0.1.0a1/src/somia.egg-info/PKG-INFO +174 -0
- somia-0.1.0a1/src/somia.egg-info/SOURCES.txt +24 -0
- somia-0.1.0a1/src/somia.egg-info/dependency_links.txt +1 -0
- somia-0.1.0a1/src/somia.egg-info/requires.txt +6 -0
- somia-0.1.0a1/src/somia.egg-info/top_level.txt +1 -0
- somia-0.1.0a1/src/somia_sdk.egg-info/PKG-INFO +179 -0
- somia-0.1.0a1/src/somia_sdk.egg-info/SOURCES.txt +18 -0
- somia-0.1.0a1/src/somia_sdk.egg-info/dependency_links.txt +1 -0
- somia-0.1.0a1/src/somia_sdk.egg-info/requires.txt +6 -0
- somia-0.1.0a1/src/somia_sdk.egg-info/top_level.txt +1 -0
- somia-0.1.0a1/tests/test_client.py +137 -0
- somia-0.1.0a1/tests/test_exceptions.py +60 -0
- somia-0.1.0a1/tests/test_http.py +144 -0
- somia-0.1.0a1/tests/test_streaming.py +52 -0
somia-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Somia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
somia-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: somia
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Python SDK for interacting with Somia Agent API
|
|
5
|
+
Author: Somia
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: somia,agents,api,sdk
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
20
|
+
Requires-Dist: pydantic<3.0.0,>=2.7.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Somia Python SDK
|
|
27
|
+
|
|
28
|
+
> **Alpha release (`0.1.0a1`)** — early SDK for testing and evaluation. APIs may change. Install with `pip install somia --pre` or pin `pip install somia==0.1.0a1`.
|
|
29
|
+
|
|
30
|
+
Official Python client for the [Somia](https://somia-platform.com) Agent API.
|
|
31
|
+
|
|
32
|
+
The SDK is open source under the MIT license. Access to the Somia platform requires a Somia account and API key; usage of the hosted API is subject to Somia's terms and billing.
|
|
33
|
+
|
|
34
|
+
## Supported endpoints
|
|
35
|
+
|
|
36
|
+
- `POST /api/v1/external/agent/{agent_id}/session` — create a session
|
|
37
|
+
- `POST /api/v1/external/agent/{agent_id}/session/{session_id}` — continue a session
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
From PyPI (pre-release):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install somia --pre
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or pin the exact version:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install somia==0.1.0a1
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
From source (development):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/somia-platform/somia-python-sdk.git
|
|
57
|
+
cd somia-python-sdk
|
|
58
|
+
pip install -e ".[dev]"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Requires Python 3.11+.
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from somia import SomiaClient
|
|
67
|
+
|
|
68
|
+
with SomiaClient(
|
|
69
|
+
base_url="https://api.somia-platform.com",
|
|
70
|
+
api_key="your_api_key",
|
|
71
|
+
) as client:
|
|
72
|
+
response = client.sessions.create_session(
|
|
73
|
+
agent_id=123,
|
|
74
|
+
input_data="Hello",
|
|
75
|
+
pipeline_version="production",
|
|
76
|
+
)
|
|
77
|
+
print(response.session_id, response.message)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Continue an existing session
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from somia import SomiaClient
|
|
84
|
+
|
|
85
|
+
with SomiaClient(
|
|
86
|
+
base_url="https://api.somia-platform.com",
|
|
87
|
+
api_key="your_api_key",
|
|
88
|
+
) as client:
|
|
89
|
+
response = client.sessions.interact_session(
|
|
90
|
+
agent_id=123,
|
|
91
|
+
session_id="550e8400-e29b-41d4-a716-446655440000",
|
|
92
|
+
input_data="Can you explain it in one paragraph?",
|
|
93
|
+
)
|
|
94
|
+
print(response.message)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
You can also use `client.sessions.run(...)` with an optional `session_id` to create or continue in one call.
|
|
98
|
+
|
|
99
|
+
## Streaming responses (SSE)
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from somia import SomiaClient
|
|
103
|
+
|
|
104
|
+
with SomiaClient(
|
|
105
|
+
base_url="https://api.somia-platform.com",
|
|
106
|
+
api_key="your_api_key",
|
|
107
|
+
) as client:
|
|
108
|
+
events = client.sessions.create_session(
|
|
109
|
+
agent_id=123,
|
|
110
|
+
input_data="Stream this answer",
|
|
111
|
+
stream=True,
|
|
112
|
+
)
|
|
113
|
+
for event in events:
|
|
114
|
+
if event.event_type == "chunk":
|
|
115
|
+
print("chunk:", event.data)
|
|
116
|
+
elif event.event_type == "error":
|
|
117
|
+
print("error:", event.data)
|
|
118
|
+
elif event.event_type == "end":
|
|
119
|
+
print("stream ended")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Authentication
|
|
123
|
+
|
|
124
|
+
By default the SDK sends your API key in the `x-api-key` header. Override the header name if needed:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
client = SomiaClient(
|
|
128
|
+
base_url="https://api.somia-platform.com",
|
|
129
|
+
api_key="your_api_key",
|
|
130
|
+
api_key_header="apikey",
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Error handling
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from somia import AuthError, RateLimitError, ServiceUnavailableError, SomiaClient
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
with SomiaClient(base_url="https://api.somia-platform.com", api_key="...") as client:
|
|
141
|
+
client.sessions.create_session(agent_id=123, input_data="Hi")
|
|
142
|
+
except AuthError:
|
|
143
|
+
print("Invalid API key")
|
|
144
|
+
except RateLimitError:
|
|
145
|
+
print("Rate limit exceeded")
|
|
146
|
+
except ServiceUnavailableError as exc:
|
|
147
|
+
print(f"Retry later: {exc}")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Common error classes:
|
|
151
|
+
|
|
152
|
+
- `BadRequestError` — invalid payloads
|
|
153
|
+
- `AuthError` — missing or invalid API key
|
|
154
|
+
- `PermissionDeniedError` — access or usage-limit failures
|
|
155
|
+
- `NotFoundError` — missing agents or sessions
|
|
156
|
+
- `RateLimitError` — rate limits exceeded
|
|
157
|
+
- `ServiceUnavailableError` — platform saturation or timeouts
|
|
158
|
+
- `ServerError` — unexpected server-side failures
|
|
159
|
+
- `TransportError` — network-level failures
|
|
160
|
+
- `StreamParseError` — malformed SSE payloads
|
|
161
|
+
|
|
162
|
+
## Session flow notes
|
|
163
|
+
|
|
164
|
+
- Create-session may ignore `input_data` when Begin has no required query fields.
|
|
165
|
+
- Interact-session always uses `session_id` in the path and forwards `input_data` as the turn payload.
|
|
166
|
+
- Non-streaming responses include fields such as `session_id`, `message`, `history`, `pending`, and `finished`.
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, testing, versioning, and release instructions.
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT — see [LICENSE](LICENSE).
|
somia-0.1.0a1/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Somia Python SDK
|
|
2
|
+
|
|
3
|
+
> **Alpha release (`0.1.0a1`)** — early SDK for testing and evaluation. APIs may change. Install with `pip install somia --pre` or pin `pip install somia==0.1.0a1`.
|
|
4
|
+
|
|
5
|
+
Official Python client for the [Somia](https://somia-platform.com) Agent API.
|
|
6
|
+
|
|
7
|
+
The SDK is open source under the MIT license. Access to the Somia platform requires a Somia account and API key; usage of the hosted API is subject to Somia's terms and billing.
|
|
8
|
+
|
|
9
|
+
## Supported endpoints
|
|
10
|
+
|
|
11
|
+
- `POST /api/v1/external/agent/{agent_id}/session` — create a session
|
|
12
|
+
- `POST /api/v1/external/agent/{agent_id}/session/{session_id}` — continue a session
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
From PyPI (pre-release):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install somia --pre
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or pin the exact version:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install somia==0.1.0a1
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
From source (development):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/somia-platform/somia-python-sdk.git
|
|
32
|
+
cd somia-python-sdk
|
|
33
|
+
pip install -e ".[dev]"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires Python 3.11+.
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from somia import SomiaClient
|
|
42
|
+
|
|
43
|
+
with SomiaClient(
|
|
44
|
+
base_url="https://api.somia-platform.com",
|
|
45
|
+
api_key="your_api_key",
|
|
46
|
+
) as client:
|
|
47
|
+
response = client.sessions.create_session(
|
|
48
|
+
agent_id=123,
|
|
49
|
+
input_data="Hello",
|
|
50
|
+
pipeline_version="production",
|
|
51
|
+
)
|
|
52
|
+
print(response.session_id, response.message)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Continue an existing session
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from somia import SomiaClient
|
|
59
|
+
|
|
60
|
+
with SomiaClient(
|
|
61
|
+
base_url="https://api.somia-platform.com",
|
|
62
|
+
api_key="your_api_key",
|
|
63
|
+
) as client:
|
|
64
|
+
response = client.sessions.interact_session(
|
|
65
|
+
agent_id=123,
|
|
66
|
+
session_id="550e8400-e29b-41d4-a716-446655440000",
|
|
67
|
+
input_data="Can you explain it in one paragraph?",
|
|
68
|
+
)
|
|
69
|
+
print(response.message)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can also use `client.sessions.run(...)` with an optional `session_id` to create or continue in one call.
|
|
73
|
+
|
|
74
|
+
## Streaming responses (SSE)
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from somia import SomiaClient
|
|
78
|
+
|
|
79
|
+
with SomiaClient(
|
|
80
|
+
base_url="https://api.somia-platform.com",
|
|
81
|
+
api_key="your_api_key",
|
|
82
|
+
) as client:
|
|
83
|
+
events = client.sessions.create_session(
|
|
84
|
+
agent_id=123,
|
|
85
|
+
input_data="Stream this answer",
|
|
86
|
+
stream=True,
|
|
87
|
+
)
|
|
88
|
+
for event in events:
|
|
89
|
+
if event.event_type == "chunk":
|
|
90
|
+
print("chunk:", event.data)
|
|
91
|
+
elif event.event_type == "error":
|
|
92
|
+
print("error:", event.data)
|
|
93
|
+
elif event.event_type == "end":
|
|
94
|
+
print("stream ended")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Authentication
|
|
98
|
+
|
|
99
|
+
By default the SDK sends your API key in the `x-api-key` header. Override the header name if needed:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
client = SomiaClient(
|
|
103
|
+
base_url="https://api.somia-platform.com",
|
|
104
|
+
api_key="your_api_key",
|
|
105
|
+
api_key_header="apikey",
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Error handling
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from somia import AuthError, RateLimitError, ServiceUnavailableError, SomiaClient
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
with SomiaClient(base_url="https://api.somia-platform.com", api_key="...") as client:
|
|
116
|
+
client.sessions.create_session(agent_id=123, input_data="Hi")
|
|
117
|
+
except AuthError:
|
|
118
|
+
print("Invalid API key")
|
|
119
|
+
except RateLimitError:
|
|
120
|
+
print("Rate limit exceeded")
|
|
121
|
+
except ServiceUnavailableError as exc:
|
|
122
|
+
print(f"Retry later: {exc}")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Common error classes:
|
|
126
|
+
|
|
127
|
+
- `BadRequestError` — invalid payloads
|
|
128
|
+
- `AuthError` — missing or invalid API key
|
|
129
|
+
- `PermissionDeniedError` — access or usage-limit failures
|
|
130
|
+
- `NotFoundError` — missing agents or sessions
|
|
131
|
+
- `RateLimitError` — rate limits exceeded
|
|
132
|
+
- `ServiceUnavailableError` — platform saturation or timeouts
|
|
133
|
+
- `ServerError` — unexpected server-side failures
|
|
134
|
+
- `TransportError` — network-level failures
|
|
135
|
+
- `StreamParseError` — malformed SSE payloads
|
|
136
|
+
|
|
137
|
+
## Session flow notes
|
|
138
|
+
|
|
139
|
+
- Create-session may ignore `input_data` when Begin has no required query fields.
|
|
140
|
+
- Interact-session always uses `session_id` in the path and forwards `input_data` as the turn payload.
|
|
141
|
+
- Non-streaming responses include fields such as `session_id`, `message`, `history`, `pending`, and `finished`.
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, testing, versioning, and release instructions.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "somia"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "Python SDK for interacting with Somia Agent API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Somia" }
|
|
15
|
+
]
|
|
16
|
+
keywords = ["somia", "agents", "api", "sdk"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"httpx>=0.27.0,<1.0.0",
|
|
29
|
+
"pydantic>=2.7.0,<3.0.0"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0.0",
|
|
35
|
+
"pytest-cov>=5.0.0"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
package-dir = { "" = "src" }
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
addopts = "-q"
|
somia-0.1.0a1/setup.cfg
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from somia.client import AgentSessionsClient, SomiaClient
|
|
2
|
+
from somia.exceptions import (
|
|
3
|
+
ApiError,
|
|
4
|
+
AuthError,
|
|
5
|
+
BadRequestError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
PermissionDeniedError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
ServerError,
|
|
10
|
+
ServiceUnavailableError,
|
|
11
|
+
SomiaError,
|
|
12
|
+
StreamParseError,
|
|
13
|
+
TransportError,
|
|
14
|
+
)
|
|
15
|
+
from somia.models import AgentExecutionRequest, AgentSessionResponse, StreamEvent
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0a1"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"AgentExecutionRequest",
|
|
21
|
+
"AgentSessionResponse",
|
|
22
|
+
"AgentSessionsClient",
|
|
23
|
+
"ApiError",
|
|
24
|
+
"AuthError",
|
|
25
|
+
"BadRequestError",
|
|
26
|
+
"NotFoundError",
|
|
27
|
+
"PermissionDeniedError",
|
|
28
|
+
"RateLimitError",
|
|
29
|
+
"ServerError",
|
|
30
|
+
"ServiceUnavailableError",
|
|
31
|
+
"SomiaClient",
|
|
32
|
+
"SomiaError",
|
|
33
|
+
"StreamEvent",
|
|
34
|
+
"StreamParseError",
|
|
35
|
+
"TransportError",
|
|
36
|
+
"__version__",
|
|
37
|
+
]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Dict, Iterator, Optional, Set
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from somia.exceptions import TransportError, raise_for_status
|
|
9
|
+
from somia.models import StreamEvent
|
|
10
|
+
from somia.streaming import parse_sse_events
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HttpTransport:
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
*,
|
|
18
|
+
base_url: str,
|
|
19
|
+
api_key: str,
|
|
20
|
+
timeout: float = 60.0,
|
|
21
|
+
retries: int = 2,
|
|
22
|
+
retry_backoff_seconds: float = 0.5,
|
|
23
|
+
api_key_header: str = "x-api-key",
|
|
24
|
+
transient_status_codes: Optional[Set[int]] = None,
|
|
25
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
self._retries = max(0, retries)
|
|
28
|
+
self._retry_backoff_seconds = max(0.0, retry_backoff_seconds)
|
|
29
|
+
self._transient_status_codes = transient_status_codes or {429, 500, 502, 503, 504}
|
|
30
|
+
client_kwargs: Dict[str, Any] = {
|
|
31
|
+
"base_url": base_url.rstrip("/"),
|
|
32
|
+
"timeout": httpx.Timeout(timeout),
|
|
33
|
+
"headers": {
|
|
34
|
+
api_key_header: api_key,
|
|
35
|
+
"content-type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
if transport is not None:
|
|
39
|
+
client_kwargs["transport"] = transport
|
|
40
|
+
self._client = httpx.Client(**client_kwargs)
|
|
41
|
+
|
|
42
|
+
def close(self) -> None:
|
|
43
|
+
self._client.close()
|
|
44
|
+
|
|
45
|
+
def post_json(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
46
|
+
last_error: Optional[Exception] = None
|
|
47
|
+
for attempt in range(self._retries + 1):
|
|
48
|
+
try:
|
|
49
|
+
response = self._client.post(path, json=payload)
|
|
50
|
+
except httpx.HTTPError as exc:
|
|
51
|
+
if attempt >= self._retries:
|
|
52
|
+
raise TransportError(str(exc)) from exc
|
|
53
|
+
self._sleep_backoff(attempt)
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
if response.status_code in self._transient_status_codes and attempt < self._retries:
|
|
57
|
+
last_error = TransportError(
|
|
58
|
+
f"Transient HTTP status {response.status_code} for {path}",
|
|
59
|
+
)
|
|
60
|
+
self._sleep_backoff(attempt)
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
body = self._decode_json_or_text(response)
|
|
64
|
+
raise_for_status(response.status_code, body)
|
|
65
|
+
if isinstance(body, dict):
|
|
66
|
+
return body
|
|
67
|
+
raise TransportError("Expected JSON object response from API.")
|
|
68
|
+
|
|
69
|
+
if last_error is not None:
|
|
70
|
+
raise last_error
|
|
71
|
+
raise TransportError("Request failed without a recoverable response.")
|
|
72
|
+
|
|
73
|
+
def post_stream(self, path: str, payload: Dict[str, Any]) -> Iterator[StreamEvent]:
|
|
74
|
+
try:
|
|
75
|
+
with self._client.stream("POST", path, json=payload) as response:
|
|
76
|
+
if response.status_code >= 400:
|
|
77
|
+
body = self._decode_json_or_text(response)
|
|
78
|
+
raise_for_status(response.status_code, body)
|
|
79
|
+
for event in parse_sse_events(response.iter_lines()):
|
|
80
|
+
yield event
|
|
81
|
+
except httpx.HTTPError as exc:
|
|
82
|
+
raise TransportError(str(exc)) from exc
|
|
83
|
+
|
|
84
|
+
def _sleep_backoff(self, attempt: int) -> None:
|
|
85
|
+
sleep_seconds = self._retry_backoff_seconds * (2 ** attempt)
|
|
86
|
+
if sleep_seconds > 0:
|
|
87
|
+
time.sleep(sleep_seconds)
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def _decode_json_or_text(response: httpx.Response) -> Any:
|
|
91
|
+
try:
|
|
92
|
+
return response.json()
|
|
93
|
+
except ValueError:
|
|
94
|
+
if response.text:
|
|
95
|
+
return response.text
|
|
96
|
+
return None
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Iterator, Optional, Union
|
|
4
|
+
|
|
5
|
+
from somia._http import HttpTransport
|
|
6
|
+
from somia.models import AgentExecutionRequest, AgentSessionResponse, StreamEvent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentSessionsClient:
|
|
10
|
+
|
|
11
|
+
def __init__(self, transport: HttpTransport) -> None:
|
|
12
|
+
self._transport = transport
|
|
13
|
+
|
|
14
|
+
def create_session(
|
|
15
|
+
self,
|
|
16
|
+
agent_id: int,
|
|
17
|
+
*,
|
|
18
|
+
input_data: Optional[Any] = None,
|
|
19
|
+
stream: bool = False,
|
|
20
|
+
pipeline_version: Union[int, str] = "production",
|
|
21
|
+
trace_enabled: bool = False,
|
|
22
|
+
test: bool = False,
|
|
23
|
+
source: Optional[str] = None,
|
|
24
|
+
) -> Union[AgentSessionResponse, Iterator[StreamEvent]]:
|
|
25
|
+
payload = self._payload(
|
|
26
|
+
input_data=input_data,
|
|
27
|
+
stream=stream,
|
|
28
|
+
pipeline_version=pipeline_version,
|
|
29
|
+
trace_enabled=trace_enabled,
|
|
30
|
+
test=test,
|
|
31
|
+
source=source,
|
|
32
|
+
)
|
|
33
|
+
path = f"/api/v1/external/agent/{agent_id}/session"
|
|
34
|
+
if stream:
|
|
35
|
+
return self._transport.post_stream(path, payload)
|
|
36
|
+
return AgentSessionResponse.model_validate(self._transport.post_json(path, payload))
|
|
37
|
+
|
|
38
|
+
def interact_session(
|
|
39
|
+
self,
|
|
40
|
+
agent_id: int,
|
|
41
|
+
session_id: str,
|
|
42
|
+
*,
|
|
43
|
+
input_data: Optional[Any] = None,
|
|
44
|
+
stream: bool = False,
|
|
45
|
+
pipeline_version: Union[int, str] = "production",
|
|
46
|
+
trace_enabled: bool = False,
|
|
47
|
+
test: bool = False,
|
|
48
|
+
source: Optional[str] = None,
|
|
49
|
+
) -> Union[AgentSessionResponse, Iterator[StreamEvent]]:
|
|
50
|
+
payload = self._payload(
|
|
51
|
+
input_data=input_data,
|
|
52
|
+
stream=stream,
|
|
53
|
+
pipeline_version=pipeline_version,
|
|
54
|
+
trace_enabled=trace_enabled,
|
|
55
|
+
test=test,
|
|
56
|
+
source=source,
|
|
57
|
+
)
|
|
58
|
+
path = f"/api/v1/external/agent/{agent_id}/session/{session_id}"
|
|
59
|
+
if stream:
|
|
60
|
+
return self._transport.post_stream(path, payload)
|
|
61
|
+
return AgentSessionResponse.model_validate(self._transport.post_json(path, payload))
|
|
62
|
+
|
|
63
|
+
def run(
|
|
64
|
+
self,
|
|
65
|
+
agent_id: int,
|
|
66
|
+
*,
|
|
67
|
+
input_data: Optional[Any] = None,
|
|
68
|
+
session_id: Optional[str] = None,
|
|
69
|
+
stream: bool = False,
|
|
70
|
+
pipeline_version: Union[int, str] = "production",
|
|
71
|
+
trace_enabled: bool = False,
|
|
72
|
+
test: bool = False,
|
|
73
|
+
source: Optional[str] = None,
|
|
74
|
+
) -> Union[AgentSessionResponse, Iterator[StreamEvent]]:
|
|
75
|
+
if session_id:
|
|
76
|
+
return self.interact_session(
|
|
77
|
+
agent_id,
|
|
78
|
+
session_id,
|
|
79
|
+
input_data=input_data,
|
|
80
|
+
stream=stream,
|
|
81
|
+
pipeline_version=pipeline_version,
|
|
82
|
+
trace_enabled=trace_enabled,
|
|
83
|
+
test=test,
|
|
84
|
+
source=source,
|
|
85
|
+
)
|
|
86
|
+
return self.create_session(
|
|
87
|
+
agent_id,
|
|
88
|
+
input_data=input_data,
|
|
89
|
+
stream=stream,
|
|
90
|
+
pipeline_version=pipeline_version,
|
|
91
|
+
trace_enabled=trace_enabled,
|
|
92
|
+
test=test,
|
|
93
|
+
source=source,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _payload(
|
|
98
|
+
*,
|
|
99
|
+
input_data: Optional[Any],
|
|
100
|
+
stream: bool,
|
|
101
|
+
pipeline_version: Union[int, str],
|
|
102
|
+
trace_enabled: bool,
|
|
103
|
+
test: bool,
|
|
104
|
+
source: Optional[str],
|
|
105
|
+
) -> Dict[str, Any]:
|
|
106
|
+
request = AgentExecutionRequest(
|
|
107
|
+
input_data=input_data,
|
|
108
|
+
stream=stream,
|
|
109
|
+
pipeline_version=pipeline_version,
|
|
110
|
+
trace_enabled=trace_enabled,
|
|
111
|
+
test=test,
|
|
112
|
+
source=source,
|
|
113
|
+
)
|
|
114
|
+
return request.model_dump(exclude_none=True)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SomiaClient:
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
*,
|
|
122
|
+
base_url: str,
|
|
123
|
+
api_key: str,
|
|
124
|
+
timeout: float = 60.0,
|
|
125
|
+
retries: int = 2,
|
|
126
|
+
retry_backoff_seconds: float = 0.5,
|
|
127
|
+
api_key_header: str = "x-api-key",
|
|
128
|
+
) -> None:
|
|
129
|
+
self._transport = HttpTransport(
|
|
130
|
+
base_url=base_url,
|
|
131
|
+
api_key=api_key,
|
|
132
|
+
timeout=timeout,
|
|
133
|
+
retries=retries,
|
|
134
|
+
retry_backoff_seconds=retry_backoff_seconds,
|
|
135
|
+
api_key_header=api_key_header,
|
|
136
|
+
)
|
|
137
|
+
self.sessions = AgentSessionsClient(self._transport)
|
|
138
|
+
self.agents = self.sessions
|
|
139
|
+
|
|
140
|
+
def close(self) -> None:
|
|
141
|
+
self._transport.close()
|
|
142
|
+
|
|
143
|
+
def __enter__(self) -> "SomiaClient":
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
147
|
+
self.close()
|