ellements 0.2.0__py3-none-any.whl

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 (130) hide show
  1. ellements/__init__.py +57 -0
  2. ellements/agents/__init__.py +45 -0
  3. ellements/agents/backend.py +100 -0
  4. ellements/agents/builder.py +303 -0
  5. ellements/agents/claude_backend.py +200 -0
  6. ellements/agents/controller.py +237 -0
  7. ellements/agents/openai_backend.py +187 -0
  8. ellements/agents/py.typed +0 -0
  9. ellements/agents/runner.py +358 -0
  10. ellements/agents/tools.py +30 -0
  11. ellements/benchmarking/__init__.py +52 -0
  12. ellements/benchmarking/harness.py +342 -0
  13. ellements/benchmarking/py.typed +0 -0
  14. ellements/benchmarking/results.py +173 -0
  15. ellements/cli/__init__.py +80 -0
  16. ellements/cli/adapters.py +73 -0
  17. ellements/cli/agent_tui.py +1178 -0
  18. ellements/cli/components.py +411 -0
  19. ellements/cli/printer.py +229 -0
  20. ellements/cli/py.typed +0 -0
  21. ellements/core/__init__.py +112 -0
  22. ellements/core/async_utils.py +42 -0
  23. ellements/core/budgeting/__init__.py +43 -0
  24. ellements/core/budgeting/client.py +276 -0
  25. ellements/core/budgeting/protocol.py +51 -0
  26. ellements/core/budgeting/trackers.py +177 -0
  27. ellements/core/caching/__init__.py +51 -0
  28. ellements/core/caching/cache.py +73 -0
  29. ellements/core/caching/client.py +300 -0
  30. ellements/core/caching/disk.py +128 -0
  31. ellements/core/caching/keys.py +97 -0
  32. ellements/core/caching/memory.py +104 -0
  33. ellements/core/chunking.py +262 -0
  34. ellements/core/config.py +180 -0
  35. ellements/core/exceptions.py +145 -0
  36. ellements/core/llm/__init__.py +46 -0
  37. ellements/core/llm/client.py +1124 -0
  38. ellements/core/llm/images.py +226 -0
  39. ellements/core/llm/messages.py +202 -0
  40. ellements/core/llm/model_params.py +66 -0
  41. ellements/core/llm/protocol.py +146 -0
  42. ellements/core/llm/requests.py +100 -0
  43. ellements/core/llm/structured.py +91 -0
  44. ellements/core/llm/wrapper.py +79 -0
  45. ellements/core/observability/__init__.py +39 -0
  46. ellements/core/observability/events.py +169 -0
  47. ellements/core/observability/jsonl_logger.py +244 -0
  48. ellements/core/observability/markdown_formatter.py +197 -0
  49. ellements/core/observability/observer.py +56 -0
  50. ellements/core/prompting/__init__.py +14 -0
  51. ellements/core/prompting/context.py +185 -0
  52. ellements/core/prompting/guideline.py +133 -0
  53. ellements/core/prompting/persona.py +267 -0
  54. ellements/core/prompting/sources.py +92 -0
  55. ellements/core/py.typed +0 -0
  56. ellements/core/rate_limit/__init__.py +44 -0
  57. ellements/core/rate_limit/bucket.py +85 -0
  58. ellements/core/rate_limit/client.py +216 -0
  59. ellements/core/rate_limit/protocol.py +27 -0
  60. ellements/core/templating.py +126 -0
  61. ellements/core/tools/__init__.py +51 -0
  62. ellements/core/tools/dialects.py +136 -0
  63. ellements/core/tools/executor.py +48 -0
  64. ellements/core/tools/protocol.py +36 -0
  65. ellements/core/tools/records.py +28 -0
  66. ellements/core/tools/registry.py +205 -0
  67. ellements/core/tools/schemas.py +80 -0
  68. ellements/core/tools/simple.py +119 -0
  69. ellements/core/tools/spec.py +33 -0
  70. ellements/domain_specific/__init__.py +7 -0
  71. ellements/domain_specific/finance/__init__.py +188 -0
  72. ellements/domain_specific/finance/calculations.py +837 -0
  73. ellements/domain_specific/finance/charts.py +426 -0
  74. ellements/domain_specific/finance/portfolio.py +129 -0
  75. ellements/domain_specific/finance/quant_analysis.py +279 -0
  76. ellements/domain_specific/finance/risk.py +362 -0
  77. ellements/domain_specific/finance/technical_indicators.py +241 -0
  78. ellements/domain_specific/finance/tools.py +483 -0
  79. ellements/domain_specific/finance/valuation.py +312 -0
  80. ellements/domain_specific/finance/yahoo_finance.py +1523 -0
  81. ellements/domain_specific/finance/yahoo_finance_models.py +321 -0
  82. ellements/domain_specific/py.typed +0 -0
  83. ellements/execution/__init__.py +56 -0
  84. ellements/execution/callbacks.py +149 -0
  85. ellements/execution/catalog.py +70 -0
  86. ellements/execution/collaborative.py +191 -0
  87. ellements/execution/config.py +135 -0
  88. ellements/execution/py.typed +0 -0
  89. ellements/execution/reflection.py +156 -0
  90. ellements/execution/self_consistency.py +189 -0
  91. ellements/execution/single_call.py +48 -0
  92. ellements/execution/strategies.py +237 -0
  93. ellements/execution/tree_of_thought.py +541 -0
  94. ellements/fslm/__init__.py +108 -0
  95. ellements/fslm/builtins.py +103 -0
  96. ellements/fslm/cli.py +199 -0
  97. ellements/fslm/context.py +50 -0
  98. ellements/fslm/definition.py +163 -0
  99. ellements/fslm/det.py +173 -0
  100. ellements/fslm/dsl.py +458 -0
  101. ellements/fslm/errors.py +38 -0
  102. ellements/fslm/evaluators.py +141 -0
  103. ellements/fslm/kernel.py +603 -0
  104. ellements/fslm/loading.py +123 -0
  105. ellements/fslm/models.py +623 -0
  106. ellements/fslm/nl.py +87 -0
  107. ellements/fslm/observers.py +107 -0
  108. ellements/fslm/persistence.py +72 -0
  109. ellements/fslm/py.typed +0 -0
  110. ellements/fslm/rendering.py +551 -0
  111. ellements/fslm/visualization.py +25 -0
  112. ellements/reporting/__init__.py +12 -0
  113. ellements/reporting/charts.py +364 -0
  114. ellements/reporting/html_generation.py +132 -0
  115. ellements/reporting/py.typed +0 -0
  116. ellements/reporting/visualization.py +73 -0
  117. ellements/standard_tools/__init__.py +22 -0
  118. ellements/standard_tools/py.typed +0 -0
  119. ellements/standard_tools/terminal.py +205 -0
  120. ellements/standard_tools/web/__init__.py +37 -0
  121. ellements/standard_tools/web/browser_viewer.py +214 -0
  122. ellements/standard_tools/web/crawler.py +454 -0
  123. ellements/standard_tools/web/search.py +399 -0
  124. ellements/standard_tools/web/youtube.py +1297 -0
  125. ellements-0.2.0.dist-info/METADATA +368 -0
  126. ellements-0.2.0.dist-info/RECORD +130 -0
  127. ellements-0.2.0.dist-info/WHEEL +5 -0
  128. ellements-0.2.0.dist-info/entry_points.txt +2 -0
  129. ellements-0.2.0.dist-info/licenses/LICENSE +42 -0
  130. ellements-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,100 @@
1
+ """Shared LiteLLM request normalization helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ import litellm
10
+
11
+ from .model_params import filter_parameters, get_unsupported_parameters
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class CompletionRequest:
16
+ """Normalized chat-completion request settings."""
17
+
18
+ model: str
19
+ params: dict[str, Any]
20
+ logged_max_tokens: int | None
21
+
22
+
23
+ def configure_litellm_globals(config: Mapping[str, Any]) -> None:
24
+ """Apply top-level LiteLLM configuration from a client config mapping."""
25
+ if "api_key" in config:
26
+ litellm.api_key = config["api_key"]
27
+ if "base_url" in config:
28
+ litellm.api_base = config["base_url"]
29
+
30
+
31
+ def resolve_model_name(
32
+ model: str,
33
+ *,
34
+ use_responses_api: bool = False,
35
+ ) -> tuple[str, bool]:
36
+ """Resolve provider-prefixed model names for special GPT-5 routing."""
37
+ is_gpt5 = "gpt-5" in model.lower()
38
+ target_model = model
39
+
40
+ if is_gpt5 and not model.startswith("openai/"):
41
+ if use_responses_api:
42
+ target_model = f"openai/responses/{model}"
43
+ else:
44
+ target_model = f"openai/{model}"
45
+
46
+ return target_model, is_gpt5
47
+
48
+
49
+ def prepare_completion_request(
50
+ *,
51
+ model: str,
52
+ temperature: float | None,
53
+ max_tokens: int | None,
54
+ use_responses_api: bool = False,
55
+ extra_params: Mapping[str, Any] | None = None,
56
+ default_reasoning_effort: str | None = "medium",
57
+ ) -> CompletionRequest:
58
+ """Normalize model routing and provider parameters for a chat completion."""
59
+ params: dict[str, Any] = dict(extra_params or {})
60
+ target_model, is_gpt5 = resolve_model_name(
61
+ model,
62
+ use_responses_api=use_responses_api,
63
+ )
64
+ unsupported = get_unsupported_parameters(target_model)
65
+ logged_max_tokens = max_tokens
66
+ target_max_tokens = max_tokens
67
+
68
+ if (
69
+ is_gpt5
70
+ and use_responses_api
71
+ and default_reasoning_effort
72
+ and "reasoning_effort" not in params
73
+ ):
74
+ params["reasoning_effort"] = default_reasoning_effort
75
+
76
+ if is_gpt5 and target_max_tokens is not None:
77
+ if use_responses_api:
78
+ params["max_output_tokens"] = target_max_tokens
79
+ else:
80
+ params["max_completion_tokens"] = target_max_tokens
81
+ target_max_tokens = None
82
+
83
+ if temperature is not None and "temperature" not in unsupported:
84
+ params["temperature"] = temperature
85
+ if target_max_tokens is not None:
86
+ params["max_tokens"] = target_max_tokens
87
+
88
+ return CompletionRequest(
89
+ model=target_model,
90
+ params=filter_parameters(target_model, **params),
91
+ logged_max_tokens=logged_max_tokens,
92
+ )
93
+
94
+
95
+ def extract_response_text(response: Any) -> str:
96
+ """Read text content from a LiteLLM response object."""
97
+ content = response.choices[0].message.content
98
+ if content is None:
99
+ return ""
100
+ return str(content)
@@ -0,0 +1,91 @@
1
+ """Structured-output helpers built on litellm's native ``response_format``.
2
+
3
+ Modern litellm supports passing a Pydantic ``BaseModel`` subclass to the
4
+ ``response_format`` parameter of ``litellm.acompletion``. Providers that
5
+ support JSON-mode (OpenAI, Anthropic, Mistral, Gemini, …) will return a
6
+ JSON document validating against the schema; providers that don't will
7
+ raise an exception, which we surface as
8
+ :class:`StructuredOutputUnsupportedError`.
9
+
10
+ This module deliberately does *not* re-implement schema-to-prompt
11
+ conversion. Hand-rolled "please respond with JSON like this…" prompts
12
+ are inferior — they don't constrain generation, only post-hoc parsing.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import re
19
+ from typing import TypeVar
20
+
21
+ import litellm
22
+ from pydantic import BaseModel
23
+ from pydantic import ValidationError as PydanticValidationError
24
+
25
+ from ..exceptions import LLMError, StructuredOutputUnsupportedError
26
+
27
+ T = TypeVar("T", bound=BaseModel)
28
+
29
+
30
+ def supports_structured_output(model: str) -> bool:
31
+ """Return whether *model* supports litellm's native ``response_format``."""
32
+ try:
33
+ return bool(litellm.supports_response_schema(model=model))
34
+ except Exception:
35
+ # Older litellm versions or unknown models — be conservative and
36
+ # fall through to the attempt, which will raise if unsupported.
37
+ return True
38
+
39
+
40
+ def ensure_structured_support(model: str) -> None:
41
+ """Hard-fail when *model* cannot produce native structured outputs."""
42
+ if not supports_structured_output(model):
43
+ raise StructuredOutputUnsupportedError(
44
+ f"Model {model!r} does not support native structured outputs. "
45
+ "Choose a model with JSON-mode / response_format support."
46
+ )
47
+
48
+
49
+ def extract_json_payload(content: str) -> str:
50
+ """Extract a JSON document from a response that may include code fences."""
51
+ stripped = content.strip()
52
+ if "```json" in stripped:
53
+ match = re.search(r"```json\s*\n(.*?)\n```", stripped, re.DOTALL)
54
+ if match:
55
+ return match.group(1).strip()
56
+ if "```" in stripped:
57
+ match = re.search(r"```\s*\n(.*?)\n```", stripped, re.DOTALL)
58
+ if match:
59
+ return match.group(1).strip()
60
+ return stripped
61
+
62
+
63
+ def parse_structured_content(
64
+ content: str,
65
+ response_model: type[T],
66
+ *,
67
+ error_context: str = "Failed to parse structured response",
68
+ ) -> T:
69
+ """Parse JSON *content* into an instance of *response_model*.
70
+
71
+ Raises:
72
+ LLMError: If *content* is not valid JSON or does not validate
73
+ against the model's schema.
74
+ """
75
+ payload = extract_json_payload(content)
76
+ try:
77
+ data = json.loads(payload)
78
+ return response_model.model_validate(data)
79
+ except (json.JSONDecodeError, PydanticValidationError, ValueError) as exc:
80
+ preview = payload[:200] if len(payload) > 200 else payload
81
+ raise LLMError(
82
+ f"{error_context}: {exc}\nContent preview: {preview}"
83
+ ) from exc
84
+
85
+
86
+ __all__ = [
87
+ "ensure_structured_support",
88
+ "extract_json_payload",
89
+ "parse_structured_content",
90
+ "supports_structured_output",
91
+ ]
@@ -0,0 +1,79 @@
1
+ """Shared base class for LLM-client wrappers.
2
+
3
+ The composition layers in :mod:`ellements.core.caching`,
4
+ :mod:`ellements.core.rate_limit`, and :mod:`ellements.core.budgeting`
5
+ all decorate a :class:`LLMClientProtocol`. They share two boring
6
+ pieces: storing the wrapped client and proxying the ``model``
7
+ attribute. This module centralizes those, plus a default
8
+ :meth:`continue_conversation` passthrough.
9
+
10
+ The base intentionally does **not** provide default passthroughs for
11
+ :meth:`complete`, :meth:`complete_structured`, :meth:`complete_with_tools`,
12
+ :meth:`stream`, :meth:`loglikelihood`, or :meth:`generate_image`.
13
+ A half-decorated wrapper that silently forwards methods it forgot to
14
+ override would defeat the purpose of the wrapper. Subclasses must
15
+ implement every method they care about, and the type checker will flag
16
+ any that are missing from :class:`LLMClientProtocol`.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import TYPE_CHECKING, Any
22
+
23
+ if TYPE_CHECKING:
24
+ from .messages import Conversation
25
+ from .protocol import LLMClientProtocol
26
+
27
+
28
+ class LLMClientWrapper:
29
+ """Storage + minimal proxying for LLM-client decorators.
30
+
31
+ Args:
32
+ inner: The wrapped client. Must satisfy
33
+ :class:`~ellements.core.llm.LLMClientProtocol`.
34
+
35
+ Subclasses are expected to expose the same methods as
36
+ :class:`LLMClientProtocol`, overriding the ones they intercept
37
+ and letting :meth:`continue_conversation` use the default
38
+ passthrough provided here (or override it too if they want to
39
+ charge/throttle multi-turn calls).
40
+ """
41
+
42
+ def __init__(self, *, inner: LLMClientProtocol) -> None:
43
+ self._inner = inner
44
+
45
+ @property
46
+ def inner(self) -> LLMClientProtocol:
47
+ """The wrapped client (read-only access for debugging/inspection)."""
48
+ return self._inner
49
+
50
+ @property
51
+ def model(self) -> str:
52
+ """Proxy the wrapped client's model identifier."""
53
+ return self._inner.model
54
+
55
+ async def continue_conversation(
56
+ self,
57
+ conversation: Conversation,
58
+ user_message: str,
59
+ *,
60
+ temperature: float = 0.7,
61
+ max_tokens: int | None = None,
62
+ **kwargs: Any,
63
+ ) -> str:
64
+ """Forward to the wrapped client unchanged.
65
+
66
+ Subclasses may override to intercept multi-turn calls (e.g. to
67
+ charge the budget once per turn, or to count the turn toward a
68
+ rate limit).
69
+ """
70
+ return await self._inner.continue_conversation(
71
+ conversation,
72
+ user_message,
73
+ temperature=temperature,
74
+ max_tokens=max_tokens,
75
+ **kwargs,
76
+ )
77
+
78
+
79
+ __all__ = ["LLMClientWrapper"]
@@ -0,0 +1,39 @@
1
+ """Observability primitives for ellements.
2
+
3
+ This subpackage centralises everything related to *seeing what
4
+ happened* inside an ellements run:
5
+
6
+ - :mod:`.events` — the event vocabulary
7
+ (:class:`LLMRequestEvent`, :class:`LLMResponseEvent`,
8
+ :class:`LLMErrorEvent`, :class:`AgentEvent`).
9
+ - :mod:`.observer` — the :class:`LLMObserver` Protocol.
10
+ - :mod:`.jsonl_logger` — :class:`JsonlPromptLogger`, the canonical
11
+ observer implementation, and its :class:`LoggedCall` record schema.
12
+ - :mod:`.markdown_formatter` — :func:`format_log_markdown`, a
13
+ human-readable view over any ``.jsonl`` log file.
14
+
15
+ This ``__init__`` re-exports the full public surface so callers can
16
+ ``from ellements.core.observability import …`` without reaching into
17
+ specific submodules.
18
+ """
19
+
20
+ from .events import (
21
+ AgentEvent,
22
+ LLMErrorEvent,
23
+ LLMRequestEvent,
24
+ LLMResponseEvent,
25
+ )
26
+ from .jsonl_logger import JsonlPromptLogger, LoggedCall
27
+ from .markdown_formatter import format_log_markdown
28
+ from .observer import LLMObserver
29
+
30
+ __all__ = [
31
+ "AgentEvent",
32
+ "JsonlPromptLogger",
33
+ "LLMErrorEvent",
34
+ "LLMObserver",
35
+ "LLMRequestEvent",
36
+ "LLMResponseEvent",
37
+ "LoggedCall",
38
+ "format_log_markdown",
39
+ ]
@@ -0,0 +1,169 @@
1
+ """Event records emitted across ellements layers.
2
+
3
+ This module owns the **vocabulary** observers consume. We keep it
4
+ deliberately small and immutable-by-default so that every observer
5
+ implementation knows the exact shape of every field, with no surprises
6
+ from subclassing.
7
+
8
+ Two families of events live here:
9
+
10
+ LLM call events (consumed by :class:`LLMObserver`)
11
+ Every method on :class:`~ellements.core.LLMClient` (``complete``,
12
+ ``complete_structured``, ``complete_with_tools``, ``stream``,
13
+ ``generate_image``, ``loglikelihood``) fires the same three events:
14
+ :class:`LLMRequestEvent` before the call leaves the client,
15
+ :class:`LLMResponseEvent` on success, :class:`LLMErrorEvent` on
16
+ failure. The triple shares a ``call_id`` so observers can correlate
17
+ them.
18
+
19
+ Agent run events (consumed by streaming agent backends)
20
+ :class:`AgentEvent` is the uniform shape yielded by
21
+ :meth:`~ellements.agents.AgentBackend.stream_run`. Backends
22
+ translate their native event streams (OpenAI Agents SDK,
23
+ Claude Agents SDK, etc.) onto this small vocabulary so UIs can
24
+ consume them without backend-specific code.
25
+
26
+ Co-locating both families here makes ellements' observability surface
27
+ discoverable from a single place. Layers that need only one family
28
+ import only what they need (events are independent).
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from dataclasses import dataclass, field
34
+ from typing import Any
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # LLM call events
38
+ # ---------------------------------------------------------------------------
39
+
40
+
41
+ @dataclass(slots=True)
42
+ class LLMRequestEvent:
43
+ """Event fired before an LLM call leaves the client.
44
+
45
+ Observers receive this event before the network request hits the
46
+ provider, which makes it the right place to record the exact
47
+ payload (messages, parameters, available tools) that will be sent.
48
+
49
+ Attributes:
50
+ call_id: UUID that identifies this call across the
51
+ ``request``/``response``/``error`` triple.
52
+ method: The :class:`LLMClient` method name that initiated this
53
+ call (e.g. ``"complete"``, ``"stream"``).
54
+ model: The model identifier the call is targeting.
55
+ messages: The serialized message list as sent to the provider.
56
+ temperature: The temperature parameter, if specified.
57
+ max_tokens: The max-tokens parameter, if specified.
58
+ tools: Provider-formatted tool definitions, if any.
59
+ extra_params: Additional provider-specific parameters.
60
+ metadata: Free-form, observer-supplied annotation channel.
61
+ Observers may carry context (e.g. workstream id) here from
62
+ request through response/error.
63
+ """
64
+
65
+ call_id: str
66
+ method: str
67
+ model: str
68
+ messages: list[dict[str, Any]]
69
+ temperature: float | None
70
+ max_tokens: int | None
71
+ tools: list[dict[str, Any]] = field(default_factory=list)
72
+ extra_params: dict[str, Any] = field(default_factory=dict)
73
+ metadata: dict[str, Any] = field(default_factory=dict)
74
+
75
+
76
+ @dataclass(slots=True)
77
+ class LLMResponseEvent:
78
+ """Event fired after a successful LLM call returns.
79
+
80
+ Attributes:
81
+ call_id: Matches the originating :class:`LLMRequestEvent`.
82
+ method: Same as on the request event.
83
+ model: Same as on the request event.
84
+ response: The final assistant text. For streaming calls this
85
+ is the concatenated content.
86
+ duration_ms: Wall-clock latency in milliseconds.
87
+ tool_calls: Tool calls the model issued during this turn,
88
+ already merged with any tool-call results the client
89
+ looped back internally.
90
+ usage: Provider-reported token usage, when available.
91
+ metadata: Free-form, observer-supplied annotation channel.
92
+ """
93
+
94
+ call_id: str
95
+ method: str
96
+ model: str
97
+ response: str
98
+ duration_ms: int
99
+ tool_calls: list[dict[str, Any]] = field(default_factory=list)
100
+ usage: dict[str, Any] | None = None
101
+ metadata: dict[str, Any] = field(default_factory=dict)
102
+
103
+
104
+ @dataclass(slots=True)
105
+ class LLMErrorEvent:
106
+ """Event fired when an LLM call raises (after any retries).
107
+
108
+ Attributes:
109
+ call_id: Matches the originating :class:`LLMRequestEvent`.
110
+ method: Same as on the request event.
111
+ model: Same as on the request event.
112
+ error: The actual exception instance that ended the call.
113
+ Observers should not assume the exception is JSON-safe;
114
+ persistent loggers usually record ``type(error).__name__``
115
+ and ``str(error)`` instead of the live object.
116
+ duration_ms: Wall-clock time spent before the failure.
117
+ metadata: Free-form, observer-supplied annotation channel.
118
+ """
119
+
120
+ call_id: str
121
+ method: str
122
+ model: str
123
+ error: BaseException
124
+ duration_ms: int
125
+ metadata: dict[str, Any] = field(default_factory=dict)
126
+
127
+
128
+ # ---------------------------------------------------------------------------
129
+ # Agent run events
130
+ # ---------------------------------------------------------------------------
131
+
132
+
133
+ @dataclass(slots=True)
134
+ class AgentEvent:
135
+ """One incremental event surfaced by streaming agent runs.
136
+
137
+ Backends map their native event types onto a small,
138
+ backend-agnostic vocabulary so UIs can consume agent streams
139
+ without knowing which SDK powers the backend:
140
+
141
+ - ``"agent_active"``: a new sub-agent took the floor.
142
+ Payload: ``{"name": str}``.
143
+ - ``"tool_call"``: the agent invoked a tool.
144
+ Payload: ``{"name": str, "arguments": Mapping[str, Any] | str}``.
145
+ - ``"tool_output"``: a tool produced an output.
146
+ Payload: ``{"output": Any}``.
147
+ - ``"message"``: the agent produced a (possibly partial) message.
148
+ Payload: ``{"text": str, "final": bool}``.
149
+
150
+ Backends MAY surface additional event types under
151
+ backend-namespaced keys (e.g. ``"openai.raw_response"``).
152
+ Consumers should ignore unknown types.
153
+
154
+ Attributes:
155
+ type: One of the type strings above (or a backend-namespaced
156
+ extension).
157
+ payload: Type-specific payload as documented above.
158
+ """
159
+
160
+ type: str
161
+ payload: dict[str, Any] = field(default_factory=dict)
162
+
163
+
164
+ __all__ = [
165
+ "AgentEvent",
166
+ "LLMErrorEvent",
167
+ "LLMRequestEvent",
168
+ "LLMResponseEvent",
169
+ ]