sofias-sdk-lite 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.
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/PKG-INFO +61 -11
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/README.md +57 -10
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/pyproject.toml +9 -2
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/pyproject.toml.orig +9 -2
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/__init__.py +35 -2
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/__init__.py +2 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/agent_builder.py +28 -4
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/execution_context.py +44 -1
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/config/__init__.py +4 -0
- sofias_sdk_lite-0.1.4/src/sofias_sdk_lite/config/agent_settings.py +112 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/config/defaults.py +15 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/errors/__init__.py +6 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/errors/exceptions.py +60 -0
- sofias_sdk_lite-0.1.4/src/sofias_sdk_lite/llm/__init__.py +72 -0
- sofias_sdk_lite-0.1.4/src/sofias_sdk_lite/llm/bifrost.py +135 -0
- sofias_sdk_lite-0.1.4/src/sofias_sdk_lite/llm/factory.py +297 -0
- sofias_sdk_lite-0.1.4/src/sofias_sdk_lite/llm/openai_compatible.py +522 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/llm/protocol.py +14 -6
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/llm_node.py +334 -64
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/llm_node_config.py +11 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/observability/__init__.py +10 -0
- sofias_sdk_lite-0.1.4/src/sofias_sdk_lite/observability/trace_context.py +134 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/__init__.py +2 -1
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/consumer.py +13 -1
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/publisher.py +19 -5
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/types.py +25 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/runner/runner.py +151 -4
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/state/__init__.py +7 -1
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/state/history.py +31 -1
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/workflows/__init__.py +8 -3
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/workflows/chat.py +52 -12
- sofias_sdk_lite-0.1.4/src/sofias_sdk_lite/workflows/streaming_chat.py +320 -0
- sofias_sdk_lite-0.1.2/src/sofias_sdk_lite/config/agent_settings.py +0 -72
- sofias_sdk_lite-0.1.2/src/sofias_sdk_lite/llm/__init__.py +0 -27
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/agent.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/agent_config.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/middleware.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/response_workflow.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/streaming.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/config/runtime_config.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/config/sdk_config.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/config/source.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/contracts/__init__.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/contracts/agent_contracts.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/contracts/base_contracts.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/contracts/node_contracts.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/contracts/strict_contract.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/errors/circuit_breaker.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/errors/error_handler.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/llm/tokens.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/llm/tools.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/messaging/__init__.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/messaging/models.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/__init__.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/aggregator_config.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/aggregator_node.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/base_node.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/delegation_config.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/delegation_node.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/delegation_transport.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/function_node.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/plan_executor.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/planner_node.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/planning_models.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/nodes/prompt_assembler.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/observability/_log.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/observability/events.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/observability/tracing.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/py.typed +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/client.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/config.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/delegation_transport.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/rabbitmq/rpc_client.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/routing/__init__.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/routing/router.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/routing/strategies.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/runner/__init__.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/runner/config.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/state/conversation_state.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/state/memory.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/tools.py +0 -0
- {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/workflows/null.py +0 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sofias-sdk-lite
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Build agents on the Sofias agent graph and run them over RabbitMQ.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Dist: pydantic>=2.0
|
|
7
7
|
Requires-Dist: aio-pika>=9.0.0
|
|
8
8
|
Requires-Dist: rstream>=1.0.0
|
|
9
9
|
Requires-Dist: nh3>=0.3.6
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
10
11
|
Requires-Dist: mkdocs-material>=9.5 ; extra == 'docs'
|
|
11
12
|
Requires-Dist: mkdocstrings[python]>=0.25 ; extra == 'docs'
|
|
13
|
+
Requires-Dist: opentelemetry-api>=1.20.0 ; extra == 'otel'
|
|
12
14
|
Requires-Python: >=3.11
|
|
13
15
|
Provides-Extra: docs
|
|
16
|
+
Provides-Extra: otel
|
|
14
17
|
Description-Content-Type: text/markdown
|
|
15
18
|
|
|
16
19
|
# sofias-sdk-lite
|
|
@@ -28,12 +31,14 @@ Build multi-node agents on a declarative graph and run them against RabbitMQ.
|
|
|
28
31
|
- **A runner** — `AgentRunner` consumes tasks from a queue, resolves per-request
|
|
29
32
|
settings, executes your agent, and streams the response back — with graceful
|
|
30
33
|
shutdown, retries, and circuit breakers built in.
|
|
31
|
-
- **
|
|
32
|
-
|
|
34
|
+
- **An LLM that is already wired** — LLM nodes talk to the Sofias Bifrost
|
|
35
|
+
gateway (or any OpenAI-compatible endpoint) using the model, URL and key
|
|
36
|
+
from the agent's settings or the `SOFIAS_LLM_*` environment. `create_llm()`
|
|
37
|
+
builds the client when you need it explicitly; `LLMCallable` stays a protocol
|
|
38
|
+
for anything the bundled clients do not cover.
|
|
33
39
|
|
|
34
|
-
Nothing here depends on a private backend
|
|
35
|
-
|
|
36
|
-
argument or a protocol you implement yourself.
|
|
40
|
+
Nothing here depends on a private backend or an internal config service.
|
|
41
|
+
Where the model lives is deployment configuration, not agent code.
|
|
37
42
|
|
|
38
43
|
## Install
|
|
39
44
|
|
|
@@ -103,6 +108,44 @@ See [`examples/`](examples/) for a runner against a local RabbitMQ
|
|
|
103
108
|
(`docker-compose.yml` included), tool loops, streaming, and agent-to-agent
|
|
104
109
|
delegation.
|
|
105
110
|
|
|
111
|
+
## Connecting to the LLM
|
|
112
|
+
|
|
113
|
+
An agent declares *what* the model should do; *where* the model lives is
|
|
114
|
+
resolved at `build()`:
|
|
115
|
+
|
|
116
|
+
1. Under `AgentRunner`, from the per-task settings the platform sends
|
|
117
|
+
(`model_name`, `router_url`, `router_api_key` on `BaseAgentSettings`).
|
|
118
|
+
2. Otherwise from the environment: `SOFIAS_LLM_MODEL`, `SOFIAS_LLM_BASE_URL`
|
|
119
|
+
(default `http://bifrost:8080/v1`), `SOFIAS_LLM_API_KEY`,
|
|
120
|
+
`SOFIAS_LLM_PROVIDER` (default `bifrost`). The `BIFROST_MODEL` /
|
|
121
|
+
`BIFROST_BASE_URL` / `BIFROST_API_KEY` variables the Sofias Developer
|
|
122
|
+
Portal injects into agent containers are accepted as fallbacks, so a
|
|
123
|
+
portal-deployed agent needs no LLM configuration at all.
|
|
124
|
+
3. Or explicitly: `.with_llm(create_llm(model="default", api_key=...))`.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from sofias_sdk_lite import AgentBuilder, LLMNodeConfig, NodeContract
|
|
128
|
+
|
|
129
|
+
agent = (
|
|
130
|
+
AgentBuilder("qa_agent")
|
|
131
|
+
.with_settings_class(MySettings)
|
|
132
|
+
.with_contract(input_schema=Question, output_schema=Answer)
|
|
133
|
+
.add_llm_node(
|
|
134
|
+
"answerer",
|
|
135
|
+
LLMNodeConfig(name="answerer", input_contract=Question, output_contract=Answer,
|
|
136
|
+
system_prompt="Answer in one sentence."),
|
|
137
|
+
NodeContract(input_schema=Question, output_schema=Answer),
|
|
138
|
+
)
|
|
139
|
+
.set_entry_node("answerer")
|
|
140
|
+
.set_terminal("answerer")
|
|
141
|
+
.build() # no LLM client anywhere in this file
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
See `examples/03_llm_agent.py` and the
|
|
146
|
+
[LLM integration](docs/concepts/llm-integration.md) guide (streaming,
|
|
147
|
+
retries, bringing your own client).
|
|
148
|
+
|
|
106
149
|
## Running an agent against RabbitMQ
|
|
107
150
|
|
|
108
151
|
```python
|
|
@@ -138,13 +181,20 @@ if __name__ == "__main__":
|
|
|
138
181
|
).run()
|
|
139
182
|
```
|
|
140
183
|
|
|
184
|
+
## Optional extras
|
|
185
|
+
|
|
186
|
+
- `sofias-sdk-lite[otel]` — installs `opentelemetry-api` so the runner opens
|
|
187
|
+
an `agent.handle` span per turn and response fragments carry the active
|
|
188
|
+
span's `traceparent`. Without it, the inbound `traceparent` is still echoed
|
|
189
|
+
end to end.
|
|
190
|
+
|
|
141
191
|
## Scope (v1)
|
|
142
192
|
|
|
143
|
-
Core agent graph, RabbitMQ messaging,
|
|
144
|
-
|
|
145
|
-
of scope for v1 — the SDK ships the
|
|
146
|
-
(`MemoryProvider`, `ToolProvider
|
|
147
|
-
own, and these become optional extras in a later release.
|
|
193
|
+
Core agent graph, RabbitMQ messaging, the runner, and LLM clients for the
|
|
194
|
+
Bifrost gateway / any OpenAI-compatible endpoint ship today. Memory and MCP
|
|
195
|
+
tool discovery are intentionally out of scope for v1 — the SDK ships the
|
|
196
|
+
relevant protocols (`MemoryProvider`, `ToolProvider`) so you can plug in
|
|
197
|
+
your own, and these become optional extras in a later release.
|
|
148
198
|
|
|
149
199
|
## License
|
|
150
200
|
|
|
@@ -13,12 +13,14 @@ Build multi-node agents on a declarative graph and run them against RabbitMQ.
|
|
|
13
13
|
- **A runner** — `AgentRunner` consumes tasks from a queue, resolves per-request
|
|
14
14
|
settings, executes your agent, and streams the response back — with graceful
|
|
15
15
|
shutdown, retries, and circuit breakers built in.
|
|
16
|
-
- **
|
|
17
|
-
|
|
16
|
+
- **An LLM that is already wired** — LLM nodes talk to the Sofias Bifrost
|
|
17
|
+
gateway (or any OpenAI-compatible endpoint) using the model, URL and key
|
|
18
|
+
from the agent's settings or the `SOFIAS_LLM_*` environment. `create_llm()`
|
|
19
|
+
builds the client when you need it explicitly; `LLMCallable` stays a protocol
|
|
20
|
+
for anything the bundled clients do not cover.
|
|
18
21
|
|
|
19
|
-
Nothing here depends on a private backend
|
|
20
|
-
|
|
21
|
-
argument or a protocol you implement yourself.
|
|
22
|
+
Nothing here depends on a private backend or an internal config service.
|
|
23
|
+
Where the model lives is deployment configuration, not agent code.
|
|
22
24
|
|
|
23
25
|
## Install
|
|
24
26
|
|
|
@@ -88,6 +90,44 @@ See [`examples/`](examples/) for a runner against a local RabbitMQ
|
|
|
88
90
|
(`docker-compose.yml` included), tool loops, streaming, and agent-to-agent
|
|
89
91
|
delegation.
|
|
90
92
|
|
|
93
|
+
## Connecting to the LLM
|
|
94
|
+
|
|
95
|
+
An agent declares *what* the model should do; *where* the model lives is
|
|
96
|
+
resolved at `build()`:
|
|
97
|
+
|
|
98
|
+
1. Under `AgentRunner`, from the per-task settings the platform sends
|
|
99
|
+
(`model_name`, `router_url`, `router_api_key` on `BaseAgentSettings`).
|
|
100
|
+
2. Otherwise from the environment: `SOFIAS_LLM_MODEL`, `SOFIAS_LLM_BASE_URL`
|
|
101
|
+
(default `http://bifrost:8080/v1`), `SOFIAS_LLM_API_KEY`,
|
|
102
|
+
`SOFIAS_LLM_PROVIDER` (default `bifrost`). The `BIFROST_MODEL` /
|
|
103
|
+
`BIFROST_BASE_URL` / `BIFROST_API_KEY` variables the Sofias Developer
|
|
104
|
+
Portal injects into agent containers are accepted as fallbacks, so a
|
|
105
|
+
portal-deployed agent needs no LLM configuration at all.
|
|
106
|
+
3. Or explicitly: `.with_llm(create_llm(model="default", api_key=...))`.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from sofias_sdk_lite import AgentBuilder, LLMNodeConfig, NodeContract
|
|
110
|
+
|
|
111
|
+
agent = (
|
|
112
|
+
AgentBuilder("qa_agent")
|
|
113
|
+
.with_settings_class(MySettings)
|
|
114
|
+
.with_contract(input_schema=Question, output_schema=Answer)
|
|
115
|
+
.add_llm_node(
|
|
116
|
+
"answerer",
|
|
117
|
+
LLMNodeConfig(name="answerer", input_contract=Question, output_contract=Answer,
|
|
118
|
+
system_prompt="Answer in one sentence."),
|
|
119
|
+
NodeContract(input_schema=Question, output_schema=Answer),
|
|
120
|
+
)
|
|
121
|
+
.set_entry_node("answerer")
|
|
122
|
+
.set_terminal("answerer")
|
|
123
|
+
.build() # no LLM client anywhere in this file
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
See `examples/03_llm_agent.py` and the
|
|
128
|
+
[LLM integration](docs/concepts/llm-integration.md) guide (streaming,
|
|
129
|
+
retries, bringing your own client).
|
|
130
|
+
|
|
91
131
|
## Running an agent against RabbitMQ
|
|
92
132
|
|
|
93
133
|
```python
|
|
@@ -123,13 +163,20 @@ if __name__ == "__main__":
|
|
|
123
163
|
).run()
|
|
124
164
|
```
|
|
125
165
|
|
|
166
|
+
## Optional extras
|
|
167
|
+
|
|
168
|
+
- `sofias-sdk-lite[otel]` — installs `opentelemetry-api` so the runner opens
|
|
169
|
+
an `agent.handle` span per turn and response fragments carry the active
|
|
170
|
+
span's `traceparent`. Without it, the inbound `traceparent` is still echoed
|
|
171
|
+
end to end.
|
|
172
|
+
|
|
126
173
|
## Scope (v1)
|
|
127
174
|
|
|
128
|
-
Core agent graph, RabbitMQ messaging,
|
|
129
|
-
|
|
130
|
-
of scope for v1 — the SDK ships the
|
|
131
|
-
(`MemoryProvider`, `ToolProvider
|
|
132
|
-
own, and these become optional extras in a later release.
|
|
175
|
+
Core agent graph, RabbitMQ messaging, the runner, and LLM clients for the
|
|
176
|
+
Bifrost gateway / any OpenAI-compatible endpoint ship today. Memory and MCP
|
|
177
|
+
tool discovery are intentionally out of scope for v1 — the SDK ships the
|
|
178
|
+
relevant protocols (`MemoryProvider`, `ToolProvider`) so you can plug in
|
|
179
|
+
your own, and these become optional extras in a later release.
|
|
133
180
|
|
|
134
181
|
## License
|
|
135
182
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sofias-sdk-lite"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "Build agents on the Sofias agent graph and run them over RabbitMQ."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -10,9 +10,11 @@ dependencies = [
|
|
|
10
10
|
"aio-pika>=9.0.0",
|
|
11
11
|
"rstream>=1.0.0",
|
|
12
12
|
"nh3>=0.3.6",
|
|
13
|
+
"httpx>=0.27.0",
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
[project.optional-dependencies]
|
|
17
|
+
otel = ["opentelemetry-api>=1.20.0"]
|
|
16
18
|
docs = [
|
|
17
19
|
"mkdocs-material>=9.5",
|
|
18
20
|
"mkdocstrings[python]>=0.25",
|
|
@@ -24,7 +26,10 @@ url = "https://pypi.org/simple"
|
|
|
24
26
|
default = true
|
|
25
27
|
|
|
26
28
|
[tool.pytest.ini_options]
|
|
27
|
-
pythonpath = [
|
|
29
|
+
pythonpath = [
|
|
30
|
+
"src",
|
|
31
|
+
".",
|
|
32
|
+
]
|
|
28
33
|
addopts = "-ra"
|
|
29
34
|
asyncio_mode = "auto"
|
|
30
35
|
markers = ["integration: requires a running RabbitMQ broker"]
|
|
@@ -48,4 +53,6 @@ dev = [
|
|
|
48
53
|
"pytest-cov>=5.0",
|
|
49
54
|
"ruff>=0.4.0",
|
|
50
55
|
"pyright>=1.1.408",
|
|
56
|
+
"opentelemetry-api>=1.20.0",
|
|
57
|
+
"opentelemetry-sdk>=1.20.0",
|
|
51
58
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sofias-sdk-lite"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "Build agents on the Sofias agent graph and run them over RabbitMQ."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -10,9 +10,13 @@ dependencies = [
|
|
|
10
10
|
"aio-pika>=9.0.0",
|
|
11
11
|
"rstream>=1.0.0",
|
|
12
12
|
"nh3>=0.3.6",
|
|
13
|
+
"httpx>=0.27.0",
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
[project.optional-dependencies]
|
|
17
|
+
otel = [
|
|
18
|
+
"opentelemetry-api>=1.20.0",
|
|
19
|
+
]
|
|
16
20
|
docs = [
|
|
17
21
|
"mkdocs-material>=9.5",
|
|
18
22
|
"mkdocstrings[python]>=0.25",
|
|
@@ -34,10 +38,13 @@ dev = [
|
|
|
34
38
|
"pytest-cov>=5.0",
|
|
35
39
|
"ruff>=0.4.0",
|
|
36
40
|
"pyright>=1.1.408",
|
|
41
|
+
# Exercise the optional OpenTelemetry path (the `[otel]` extra) in tests.
|
|
42
|
+
"opentelemetry-api>=1.20.0",
|
|
43
|
+
"opentelemetry-sdk>=1.20.0",
|
|
37
44
|
]
|
|
38
45
|
|
|
39
46
|
[tool.pytest.ini_options]
|
|
40
|
-
pythonpath = ["src"]
|
|
47
|
+
pythonpath = ["src", "."]
|
|
41
48
|
addopts = "-ra"
|
|
42
49
|
asyncio_mode = "auto"
|
|
43
50
|
markers = ["integration: requires a running RabbitMQ broker"]
|
|
@@ -30,6 +30,8 @@ against RabbitMQ, streaming, delegation, and testing.
|
|
|
30
30
|
|
|
31
31
|
from __future__ import annotations
|
|
32
32
|
|
|
33
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
34
|
+
|
|
33
35
|
from sofias_sdk_lite.agent import (
|
|
34
36
|
Agent,
|
|
35
37
|
AgentBuildError,
|
|
@@ -45,6 +47,7 @@ from sofias_sdk_lite.agent import (
|
|
|
45
47
|
StreamingResponseWorkflow,
|
|
46
48
|
get_execution_context,
|
|
47
49
|
set_execution_context,
|
|
50
|
+
usage_state_from_list,
|
|
48
51
|
)
|
|
49
52
|
from sofias_sdk_lite.config import (
|
|
50
53
|
AgentSettings,
|
|
@@ -73,6 +76,9 @@ from sofias_sdk_lite.errors import (
|
|
|
73
76
|
AgentSDKError,
|
|
74
77
|
CircuitBreakerConfig,
|
|
75
78
|
ConfigSourceError,
|
|
79
|
+
EmptyLLMResponseError,
|
|
80
|
+
LLMConfigurationError,
|
|
81
|
+
LLMRequestError,
|
|
76
82
|
ContractValidationError,
|
|
77
83
|
ErrorHandler,
|
|
78
84
|
ErrorHandlerConfig,
|
|
@@ -85,9 +91,14 @@ from sofias_sdk_lite.errors import (
|
|
|
85
91
|
ToolExecutionError,
|
|
86
92
|
)
|
|
87
93
|
from sofias_sdk_lite.llm import (
|
|
94
|
+
BifrostLLM,
|
|
88
95
|
LLMCallable,
|
|
96
|
+
LLMGatewayConfig,
|
|
89
97
|
LLMResponse,
|
|
98
|
+
OpenAICompatibleLLM,
|
|
90
99
|
StreamableLLMCallable,
|
|
100
|
+
create_llm,
|
|
101
|
+
default_llm,
|
|
91
102
|
TokenUsage,
|
|
92
103
|
ToolCall,
|
|
93
104
|
ToolSpec,
|
|
@@ -143,10 +154,20 @@ from sofias_sdk_lite.state import (
|
|
|
143
154
|
InMemoryStateProvider,
|
|
144
155
|
MemoryProvider,
|
|
145
156
|
Message,
|
|
157
|
+
SummarizingHistoryProvider,
|
|
158
|
+
)
|
|
159
|
+
from sofias_sdk_lite.workflows import (
|
|
160
|
+
BaseStreamingResponseWorkflow,
|
|
161
|
+
ChatResponseWorkflow,
|
|
162
|
+
NullWorkflow,
|
|
146
163
|
)
|
|
147
|
-
from sofias_sdk_lite.workflows import ChatResponseWorkflow, NullWorkflow
|
|
148
164
|
|
|
149
|
-
|
|
165
|
+
try:
|
|
166
|
+
# Derived from the installed distribution so it never drifts from
|
|
167
|
+
# pyproject.toml (CI rewrites the version there on tagged releases).
|
|
168
|
+
__version__ = version("sofias-sdk-lite")
|
|
169
|
+
except PackageNotFoundError: # pragma: no cover - source checkout without install
|
|
170
|
+
__version__ = "0.0.0"
|
|
150
171
|
|
|
151
172
|
__all__ = [
|
|
152
173
|
"__version__",
|
|
@@ -161,6 +182,7 @@ __all__ = [
|
|
|
161
182
|
"ExecutionContext",
|
|
162
183
|
"get_execution_context",
|
|
163
184
|
"set_execution_context",
|
|
185
|
+
"usage_state_from_list",
|
|
164
186
|
"ResponseWorkflow",
|
|
165
187
|
"StreamingResponseWorkflow",
|
|
166
188
|
"BaseDelegationResponseWorkflow",
|
|
@@ -196,6 +218,9 @@ __all__ = [
|
|
|
196
218
|
"ToolExecutionError",
|
|
197
219
|
"GraphExecutionError",
|
|
198
220
|
"ConfigSourceError",
|
|
221
|
+
"EmptyLLMResponseError",
|
|
222
|
+
"LLMConfigurationError",
|
|
223
|
+
"LLMRequestError",
|
|
199
224
|
"ErrorHandler",
|
|
200
225
|
"ErrorHandlerConfig",
|
|
201
226
|
"RetryPolicy",
|
|
@@ -204,6 +229,12 @@ __all__ = [
|
|
|
204
229
|
"LLMCallable",
|
|
205
230
|
"StreamableLLMCallable",
|
|
206
231
|
"LLMResponse",
|
|
232
|
+
# LLM clients + factory
|
|
233
|
+
"BifrostLLM",
|
|
234
|
+
"OpenAICompatibleLLM",
|
|
235
|
+
"LLMGatewayConfig",
|
|
236
|
+
"create_llm",
|
|
237
|
+
"default_llm",
|
|
207
238
|
"TokenUsage",
|
|
208
239
|
"ToolCall",
|
|
209
240
|
"ToolSpec",
|
|
@@ -251,12 +282,14 @@ __all__ = [
|
|
|
251
282
|
"RunnerConfig",
|
|
252
283
|
# Workflows
|
|
253
284
|
"ChatResponseWorkflow",
|
|
285
|
+
"BaseStreamingResponseWorkflow",
|
|
254
286
|
"NullWorkflow",
|
|
255
287
|
# State
|
|
256
288
|
"ConversationStateProvider",
|
|
257
289
|
"InMemoryStateProvider",
|
|
258
290
|
"MemoryProvider",
|
|
259
291
|
"HistoryProvider",
|
|
292
|
+
"SummarizingHistoryProvider",
|
|
260
293
|
"InMemoryHistory",
|
|
261
294
|
"Message",
|
|
262
295
|
]
|
|
@@ -8,6 +8,7 @@ from sofias_sdk_lite.agent.execution_context import (
|
|
|
8
8
|
TokenAccumulator,
|
|
9
9
|
get_execution_context,
|
|
10
10
|
set_execution_context,
|
|
11
|
+
usage_state_from_list,
|
|
11
12
|
)
|
|
12
13
|
from sofias_sdk_lite.agent.middleware import (
|
|
13
14
|
AgentMiddleware,
|
|
@@ -39,6 +40,7 @@ __all__ = [
|
|
|
39
40
|
"set_execution_context",
|
|
40
41
|
"ExecutionContext",
|
|
41
42
|
"TokenAccumulator",
|
|
43
|
+
"usage_state_from_list",
|
|
42
44
|
# Builder
|
|
43
45
|
"AgentBuilder",
|
|
44
46
|
"AgentBuildError",
|
|
@@ -24,6 +24,7 @@ from sofias_sdk_lite.errors.error_handler import (
|
|
|
24
24
|
)
|
|
25
25
|
from sofias_sdk_lite.errors.exceptions import AgentSDKError
|
|
26
26
|
from sofias_sdk_lite.llm import LLMCallable
|
|
27
|
+
from sofias_sdk_lite.llm.factory import ENV_LLM_MODEL, resolve_default_llm
|
|
27
28
|
from sofias_sdk_lite.nodes.aggregator_config import AggregatorNodeConfig
|
|
28
29
|
from sofias_sdk_lite.nodes.base_node import BaseNode
|
|
29
30
|
from sofias_sdk_lite.nodes.delegation_config import DelegationNodeConfig
|
|
@@ -230,9 +231,21 @@ class AgentBuilder:
|
|
|
230
231
|
self._output_schema = output_schema
|
|
231
232
|
return self
|
|
232
233
|
|
|
234
|
+
_MISSING_LLM_MESSAGE = (
|
|
235
|
+
"No LLM available for LLM nodes. Either call with_llm(), run the agent through "
|
|
236
|
+
"AgentRunner with model_name/router_url/router_api_key in its settings, or export "
|
|
237
|
+
f"{ENV_LLM_MODEL} (plus SOFIAS_LLM_BASE_URL / SOFIAS_LLM_API_KEY)."
|
|
238
|
+
)
|
|
239
|
+
|
|
233
240
|
def with_llm(self, llm: LLMCallable) -> AgentBuilder:
|
|
234
241
|
"""Set the LLM callable for all LLM nodes.
|
|
235
242
|
|
|
243
|
+
Optional. When omitted, `build()` uses the LLM installed by
|
|
244
|
+
`sofias_sdk_lite.llm.default_llm` (what `AgentRunner` does with the
|
|
245
|
+
per-task settings) or, failing that, one built from the
|
|
246
|
+
``SOFIAS_LLM_*`` environment variables via
|
|
247
|
+
`sofias_sdk_lite.llm.create_llm`.
|
|
248
|
+
|
|
236
249
|
Args:
|
|
237
250
|
llm: LLM implementation for making completions.
|
|
238
251
|
|
|
@@ -242,6 +255,12 @@ class AgentBuilder:
|
|
|
242
255
|
self._llm = llm
|
|
243
256
|
return self
|
|
244
257
|
|
|
258
|
+
def _needs_agent_llm(self) -> bool:
|
|
259
|
+
"""Whether any node still to be built relies on the agent-level LLM."""
|
|
260
|
+
if self._llm_node_configs:
|
|
261
|
+
return True
|
|
262
|
+
return any(cfg["llm"] is None for cfg in self._planner_node_configs.values())
|
|
263
|
+
|
|
245
264
|
def with_agent_llm_config(self, config: AgentLLMConfig) -> AgentBuilder:
|
|
246
265
|
"""Set agent-level LLM configuration.
|
|
247
266
|
|
|
@@ -899,9 +918,9 @@ class AgentBuilder:
|
|
|
899
918
|
)
|
|
900
919
|
errors.extend(graph_errors)
|
|
901
920
|
|
|
902
|
-
# LLM must be
|
|
921
|
+
# LLM must be resolvable if there are LLM nodes to be built (not pre-built)
|
|
903
922
|
if self._llm_node_configs and self._llm is None:
|
|
904
|
-
errors.append(
|
|
923
|
+
errors.append(self._MISSING_LLM_MESSAGE)
|
|
905
924
|
|
|
906
925
|
# Delegation transport must be provided if there are delegation nodes
|
|
907
926
|
if self._delegation_node_configs and self._delegation_transport is None:
|
|
@@ -941,8 +960,8 @@ class AgentBuilder:
|
|
|
941
960
|
for name, config in self._planner_node_configs.items():
|
|
942
961
|
if config["llm"] is None and self._llm is None:
|
|
943
962
|
errors.append(
|
|
944
|
-
f"PlannerNode '{name}' has no LLM and no agent-level LLM
|
|
945
|
-
"
|
|
963
|
+
f"PlannerNode '{name}' has no LLM and no agent-level LLM could be "
|
|
964
|
+
f"resolved. {self._MISSING_LLM_MESSAGE}"
|
|
946
965
|
)
|
|
947
966
|
|
|
948
967
|
# Validate available_nodes references
|
|
@@ -1016,6 +1035,11 @@ class AgentBuilder:
|
|
|
1016
1035
|
# Apply default routes before validation
|
|
1017
1036
|
self._apply_default_routes()
|
|
1018
1037
|
|
|
1038
|
+
# Resolve the agent-level LLM when none was given explicitly: the
|
|
1039
|
+
# runner's per-task default (from settings) first, then the environment.
|
|
1040
|
+
if self._llm is None and self._needs_agent_llm():
|
|
1041
|
+
self._llm = resolve_default_llm()
|
|
1042
|
+
|
|
1019
1043
|
# Validate configuration
|
|
1020
1044
|
errors = self._validate()
|
|
1021
1045
|
if errors:
|
{sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.4}/src/sofias_sdk_lite/agent/execution_context.py
RENAMED
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from contextvars import ContextVar
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
from datetime import datetime
|
|
13
|
-
from typing import TYPE_CHECKING, Any
|
|
13
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
14
14
|
|
|
15
15
|
from pydantic import BaseModel, ConfigDict, Field
|
|
16
16
|
|
|
@@ -22,6 +22,7 @@ __all__ = [
|
|
|
22
22
|
"ExecutionContext",
|
|
23
23
|
"get_execution_context",
|
|
24
24
|
"set_execution_context",
|
|
25
|
+
"usage_state_from_list",
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
|
|
@@ -136,6 +137,48 @@ class TokenAccumulator:
|
|
|
136
137
|
return result
|
|
137
138
|
|
|
138
139
|
|
|
140
|
+
def _usage_entry(entry: Any, key: str, default: Any = 0) -> Any:
|
|
141
|
+
"""Read *key* off a usage entry that may be a dict or an object.
|
|
142
|
+
|
|
143
|
+
``AgentResponse.usage`` entries are typed as ``dict`` (built by
|
|
144
|
+
``TokenAccumulator.to_usage_list``), but callers are free to set the
|
|
145
|
+
field directly with plain objects, so both shapes must work.
|
|
146
|
+
"""
|
|
147
|
+
if isinstance(entry, dict):
|
|
148
|
+
return cast(dict[str, Any], entry).get(key, default)
|
|
149
|
+
return getattr(entry, key, default)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _usage_entry_int(entry: Any, key: str) -> int:
|
|
153
|
+
try:
|
|
154
|
+
return int(_usage_entry(entry, key, 0) or 0)
|
|
155
|
+
except (TypeError, ValueError):
|
|
156
|
+
return 0
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def usage_state_from_list(usage: list[Any] | None) -> dict[str, Any] | None:
|
|
160
|
+
"""Collapse an ``AgentResponse.usage`` list into a single ``StreamFragment``
|
|
161
|
+
``state.usage`` object, or ``None`` when there is nothing to report.
|
|
162
|
+
|
|
163
|
+
Every response workflow that publishes a terminal fragment should call
|
|
164
|
+
this and merge the result into ``state``: it is the one place a billing
|
|
165
|
+
consumer reads token usage from. Sums tokens across every LLM call in the
|
|
166
|
+
turn (a turn can invoke several models: the main chat call plus e.g. an
|
|
167
|
+
embedding call for RAG), and names the entry with the most completion
|
|
168
|
+
tokens as ``model``, which picks the actual generation call over
|
|
169
|
+
near-zero-completion side calls.
|
|
170
|
+
"""
|
|
171
|
+
if not usage:
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
primary = max(usage, key=lambda e: _usage_entry_int(e, "completion_tokens"))
|
|
175
|
+
return {
|
|
176
|
+
"prompt_tokens": sum(_usage_entry_int(e, "prompt_tokens") for e in usage),
|
|
177
|
+
"completion_tokens": sum(_usage_entry_int(e, "completion_tokens") for e in usage),
|
|
178
|
+
"model": _usage_entry(primary, "model_requested", ""),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
139
182
|
class ExecutionContext(BaseModel):
|
|
140
183
|
"""Structured execution context propagated via ContextVar.
|
|
141
184
|
|
|
@@ -6,12 +6,14 @@ from sofias_sdk_lite.config.defaults import (
|
|
|
6
6
|
DEFAULT_MAX_ITERATIONS,
|
|
7
7
|
DEFAULT_MAX_RETRIES,
|
|
8
8
|
DEFAULT_MAX_TOKENS,
|
|
9
|
+
DEFAULT_MAX_TOOL_LOOP_TOKENS,
|
|
9
10
|
DEFAULT_MODEL,
|
|
10
11
|
DEFAULT_RETRY_BACKOFF_MULTIPLIER,
|
|
11
12
|
DEFAULT_RETRY_DELAY_SECONDS,
|
|
12
13
|
DEFAULT_RETRY_MAX_DELAY_SECONDS,
|
|
13
14
|
DEFAULT_STRICT_VALIDATION,
|
|
14
15
|
DEFAULT_TEMPERATURE,
|
|
16
|
+
DEFAULT_TOOL_LOOP_TOKEN_RESERVE,
|
|
15
17
|
DEFAULT_TOP_P,
|
|
16
18
|
DEFAULT_VERBOSE_LOGGING,
|
|
17
19
|
)
|
|
@@ -38,12 +40,14 @@ __all__ = [
|
|
|
38
40
|
"DEFAULT_MAX_ITERATIONS",
|
|
39
41
|
"DEFAULT_MAX_RETRIES",
|
|
40
42
|
"DEFAULT_MAX_TOKENS",
|
|
43
|
+
"DEFAULT_MAX_TOOL_LOOP_TOKENS",
|
|
41
44
|
"DEFAULT_MODEL",
|
|
42
45
|
"DEFAULT_RETRY_BACKOFF_MULTIPLIER",
|
|
43
46
|
"DEFAULT_RETRY_DELAY_SECONDS",
|
|
44
47
|
"DEFAULT_RETRY_MAX_DELAY_SECONDS",
|
|
45
48
|
"DEFAULT_STRICT_VALIDATION",
|
|
46
49
|
"DEFAULT_TEMPERATURE",
|
|
50
|
+
"DEFAULT_TOOL_LOOP_TOKEN_RESERVE",
|
|
47
51
|
"DEFAULT_TOP_P",
|
|
48
52
|
"DEFAULT_VERBOSE_LOGGING",
|
|
49
53
|
# SDK Config
|