sofias-sdk-lite 0.1.2__tar.gz → 0.1.3__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.
Files changed (77) hide show
  1. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/PKG-INFO +10 -1
  2. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/README.md +7 -0
  3. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/pyproject.toml +8 -2
  4. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/pyproject.toml.orig +8 -2
  5. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/__init__.py +18 -2
  6. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/__init__.py +2 -0
  7. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/execution_context.py +44 -1
  8. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/config/__init__.py +4 -0
  9. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/config/agent_settings.py +19 -1
  10. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/config/defaults.py +15 -0
  11. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/llm_node.py +334 -64
  12. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/llm_node_config.py +11 -0
  13. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/observability/__init__.py +10 -0
  14. sofias_sdk_lite-0.1.3/src/sofias_sdk_lite/observability/trace_context.py +134 -0
  15. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/__init__.py +2 -1
  16. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/consumer.py +13 -1
  17. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/publisher.py +19 -5
  18. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/types.py +25 -0
  19. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/runner/runner.py +119 -3
  20. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/state/__init__.py +7 -1
  21. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/state/history.py +31 -1
  22. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/workflows/__init__.py +8 -3
  23. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/workflows/chat.py +52 -12
  24. sofias_sdk_lite-0.1.3/src/sofias_sdk_lite/workflows/streaming_chat.py +320 -0
  25. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/agent.py +0 -0
  26. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/agent_builder.py +0 -0
  27. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/agent_config.py +0 -0
  28. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/middleware.py +0 -0
  29. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/response_workflow.py +0 -0
  30. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/agent/streaming.py +0 -0
  31. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/config/runtime_config.py +0 -0
  32. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/config/sdk_config.py +0 -0
  33. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/config/source.py +0 -0
  34. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/contracts/__init__.py +0 -0
  35. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/contracts/agent_contracts.py +0 -0
  36. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/contracts/base_contracts.py +0 -0
  37. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/contracts/node_contracts.py +0 -0
  38. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/contracts/strict_contract.py +0 -0
  39. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/errors/__init__.py +0 -0
  40. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/errors/circuit_breaker.py +0 -0
  41. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/errors/error_handler.py +0 -0
  42. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/errors/exceptions.py +0 -0
  43. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/llm/__init__.py +0 -0
  44. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/llm/protocol.py +0 -0
  45. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/llm/tokens.py +0 -0
  46. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/llm/tools.py +0 -0
  47. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/messaging/__init__.py +0 -0
  48. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/messaging/models.py +0 -0
  49. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/__init__.py +0 -0
  50. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/aggregator_config.py +0 -0
  51. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/aggregator_node.py +0 -0
  52. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/base_node.py +0 -0
  53. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/delegation_config.py +0 -0
  54. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/delegation_node.py +0 -0
  55. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/delegation_transport.py +0 -0
  56. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/function_node.py +0 -0
  57. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/plan_executor.py +0 -0
  58. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/planner_node.py +0 -0
  59. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/planning_models.py +0 -0
  60. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/nodes/prompt_assembler.py +0 -0
  61. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/observability/_log.py +0 -0
  62. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/observability/events.py +0 -0
  63. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/observability/tracing.py +0 -0
  64. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/py.typed +0 -0
  65. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/client.py +0 -0
  66. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/config.py +0 -0
  67. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/delegation_transport.py +0 -0
  68. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/rabbitmq/rpc_client.py +0 -0
  69. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/routing/__init__.py +0 -0
  70. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/routing/router.py +0 -0
  71. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/routing/strategies.py +0 -0
  72. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/runner/__init__.py +0 -0
  73. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/runner/config.py +0 -0
  74. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/state/conversation_state.py +0 -0
  75. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/state/memory.py +0 -0
  76. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/tools.py +0 -0
  77. {sofias_sdk_lite-0.1.2 → sofias_sdk_lite-0.1.3}/src/sofias_sdk_lite/workflows/null.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sofias-sdk-lite
3
- Version: 0.1.2
3
+ Version: 0.1.3
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
@@ -9,8 +9,10 @@ Requires-Dist: rstream>=1.0.0
9
9
  Requires-Dist: nh3>=0.3.6
10
10
  Requires-Dist: mkdocs-material>=9.5 ; extra == 'docs'
11
11
  Requires-Dist: mkdocstrings[python]>=0.25 ; extra == 'docs'
12
+ Requires-Dist: opentelemetry-api>=1.20.0 ; extra == 'otel'
12
13
  Requires-Python: >=3.11
13
14
  Provides-Extra: docs
15
+ Provides-Extra: otel
14
16
  Description-Content-Type: text/markdown
15
17
 
16
18
  # sofias-sdk-lite
@@ -138,6 +140,13 @@ if __name__ == "__main__":
138
140
  ).run()
139
141
  ```
140
142
 
143
+ ## Optional extras
144
+
145
+ - `sofias-sdk-lite[otel]` — installs `opentelemetry-api` so the runner opens
146
+ an `agent.handle` span per turn and response fragments carry the active
147
+ span's `traceparent`. Without it, the inbound `traceparent` is still echoed
148
+ end to end.
149
+
141
150
  ## Scope (v1)
142
151
 
143
152
  Core agent graph, RabbitMQ messaging, and the runner ship today. Memory,
@@ -123,6 +123,13 @@ if __name__ == "__main__":
123
123
  ).run()
124
124
  ```
125
125
 
126
+ ## Optional extras
127
+
128
+ - `sofias-sdk-lite[otel]` — installs `opentelemetry-api` so the runner opens
129
+ an `agent.handle` span per turn and response fragments carry the active
130
+ span's `traceparent`. Without it, the inbound `traceparent` is still echoed
131
+ end to end.
132
+
126
133
  ## Scope (v1)
127
134
 
128
135
  Core agent graph, RabbitMQ messaging, and the runner ship today. Memory,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sofias-sdk-lite"
3
- version = "0.1.2"
3
+ version = "0.1.3"
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"
@@ -13,6 +13,7 @@ dependencies = [
13
13
  ]
14
14
 
15
15
  [project.optional-dependencies]
16
+ otel = ["opentelemetry-api>=1.20.0"]
16
17
  docs = [
17
18
  "mkdocs-material>=9.5",
18
19
  "mkdocstrings[python]>=0.25",
@@ -24,7 +25,10 @@ url = "https://pypi.org/simple"
24
25
  default = true
25
26
 
26
27
  [tool.pytest.ini_options]
27
- pythonpath = ["src"]
28
+ pythonpath = [
29
+ "src",
30
+ ".",
31
+ ]
28
32
  addopts = "-ra"
29
33
  asyncio_mode = "auto"
30
34
  markers = ["integration: requires a running RabbitMQ broker"]
@@ -48,4 +52,6 @@ dev = [
48
52
  "pytest-cov>=5.0",
49
53
  "ruff>=0.4.0",
50
54
  "pyright>=1.1.408",
55
+ "opentelemetry-api>=1.20.0",
56
+ "opentelemetry-sdk>=1.20.0",
51
57
  ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sofias-sdk-lite"
3
- version = "0.1.2"
3
+ version = "0.1.3"
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"
@@ -13,6 +13,9 @@ dependencies = [
13
13
  ]
14
14
 
15
15
  [project.optional-dependencies]
16
+ otel = [
17
+ "opentelemetry-api>=1.20.0",
18
+ ]
16
19
  docs = [
17
20
  "mkdocs-material>=9.5",
18
21
  "mkdocstrings[python]>=0.25",
@@ -34,10 +37,13 @@ dev = [
34
37
  "pytest-cov>=5.0",
35
38
  "ruff>=0.4.0",
36
39
  "pyright>=1.1.408",
40
+ # Exercise the optional OpenTelemetry path (the `[otel]` extra) in tests.
41
+ "opentelemetry-api>=1.20.0",
42
+ "opentelemetry-sdk>=1.20.0",
37
43
  ]
38
44
 
39
45
  [tool.pytest.ini_options]
40
- pythonpath = ["src"]
46
+ pythonpath = ["src", "."]
41
47
  addopts = "-ra"
42
48
  asyncio_mode = "auto"
43
49
  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,
@@ -143,10 +146,20 @@ from sofias_sdk_lite.state import (
143
146
  InMemoryStateProvider,
144
147
  MemoryProvider,
145
148
  Message,
149
+ SummarizingHistoryProvider,
150
+ )
151
+ from sofias_sdk_lite.workflows import (
152
+ BaseStreamingResponseWorkflow,
153
+ ChatResponseWorkflow,
154
+ NullWorkflow,
146
155
  )
147
- from sofias_sdk_lite.workflows import ChatResponseWorkflow, NullWorkflow
148
156
 
149
- __version__ = "0.1.0"
157
+ try:
158
+ # Derived from the installed distribution so it never drifts from
159
+ # pyproject.toml (CI rewrites the version there on tagged releases).
160
+ __version__ = version("sofias-sdk-lite")
161
+ except PackageNotFoundError: # pragma: no cover - source checkout without install
162
+ __version__ = "0.0.0"
150
163
 
151
164
  __all__ = [
152
165
  "__version__",
@@ -161,6 +174,7 @@ __all__ = [
161
174
  "ExecutionContext",
162
175
  "get_execution_context",
163
176
  "set_execution_context",
177
+ "usage_state_from_list",
164
178
  "ResponseWorkflow",
165
179
  "StreamingResponseWorkflow",
166
180
  "BaseDelegationResponseWorkflow",
@@ -251,12 +265,14 @@ __all__ = [
251
265
  "RunnerConfig",
252
266
  # Workflows
253
267
  "ChatResponseWorkflow",
268
+ "BaseStreamingResponseWorkflow",
254
269
  "NullWorkflow",
255
270
  # State
256
271
  "ConversationStateProvider",
257
272
  "InMemoryStateProvider",
258
273
  "MemoryProvider",
259
274
  "HistoryProvider",
275
+ "SummarizingHistoryProvider",
260
276
  "InMemoryHistory",
261
277
  "Message",
262
278
  ]
@@ -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",
@@ -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
@@ -15,7 +15,7 @@ whatever fields their own agent actually needs:
15
15
  from __future__ import annotations
16
16
 
17
17
  import difflib
18
- from typing import Any
18
+ from typing import Any, ClassVar
19
19
 
20
20
  from pydantic import ConfigDict, model_validator
21
21
 
@@ -41,6 +41,24 @@ class BaseAgentSettings(AgentSettings):
41
41
 
42
42
  model_config = ConfigDict(extra="ignore", validate_default=True)
43
43
 
44
+ CREDENTIAL_FIELDS: ClassVar[frozenset[str]] = frozenset()
45
+ """Names of fields that hold credentials (API keys, tokens, secrets).
46
+
47
+ Declare them on your subclass, ideally typed as ``pydantic.SecretStr`` so
48
+ ``repr``/``model_dump`` never leak the raw value. The SDK excludes these
49
+ fields wherever settings are dumped into a dict that reaches an LLM prompt
50
+ (``LLMNode`` template variables), so a ``{api_key}`` placeholder can never
51
+ inline a secret. Read them at the point of use with ``.get_secret_value()``.
52
+
53
+ Example::
54
+
55
+ class MySettings(BaseAgentSettings):
56
+ CREDENTIAL_FIELDS = frozenset({"api_key"})
57
+
58
+ model_name: str = "gpt-4o-mini"
59
+ api_key: SecretStr = SecretStr("")
60
+ """
61
+
44
62
  @model_validator(mode="before")
45
63
  @classmethod
46
64
  def _warn_on_typo_keys(cls, data: Any) -> Any:
@@ -29,6 +29,19 @@ DEFAULT_TOP_P: Final[float] = 1.0
29
29
  DEFAULT_MAX_ITERATIONS: Final[int] = 10
30
30
  """Default maximum iterations for a node's tool loop."""
31
31
 
32
+ DEFAULT_MAX_TOOL_LOOP_TOKENS: Final[int] = 128_000
33
+ """Token budget for the WHOLE request of a tool-loop iteration.
34
+
35
+ The budget means the model's context window, not "what is left for the
36
+ history". The fixed prefix (system prompt + user prompt + tool schemas) is
37
+ *measured* on every iteration and counted against this number, together with
38
+ the accumulated tool-loop history. An agent running a model with a larger
39
+ window should raise it via ``NodeToolLoopConfig.max_tool_loop_tokens``.
40
+ """
41
+
42
+ DEFAULT_TOOL_LOOP_TOKEN_RESERVE: Final[int] = 16_384
43
+ """Tokens reserved for the final LLM response within the tool-loop budget."""
44
+
32
45
  DEFAULT_ITERATION_TIMEOUT_SECONDS: Final[float] = 60.0
33
46
  """Default timeout in seconds for a single tool loop iteration."""
34
47
 
@@ -68,6 +81,8 @@ __all__ = [
68
81
  "DEFAULT_MAX_TOKENS",
69
82
  "DEFAULT_TOP_P",
70
83
  "DEFAULT_MAX_ITERATIONS",
84
+ "DEFAULT_MAX_TOOL_LOOP_TOKENS",
85
+ "DEFAULT_TOOL_LOOP_TOKEN_RESERVE",
71
86
  "DEFAULT_ITERATION_TIMEOUT_SECONDS",
72
87
  "DEFAULT_MAX_RETRIES",
73
88
  "DEFAULT_RETRY_DELAY_SECONDS",