monkeybot 2.1.1__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 (178) hide show
  1. monkeybot/__init__.py +3 -0
  2. monkeybot/cli/__init__.py +3 -0
  3. monkeybot/cli/__main__.py +8 -0
  4. monkeybot/cli/audio_io.py +8 -0
  5. monkeybot/cli/gateway_manager.py +17 -0
  6. monkeybot/cli/main.py +22 -0
  7. monkeybot/cli/push_to_talk.py +12 -0
  8. monkeybot/cli/realtime_client.py +13 -0
  9. monkeybot/core/__init__.py +19 -0
  10. monkeybot/core/attachments/__init__.py +22 -0
  11. monkeybot/core/attachments/catalog.py +62 -0
  12. monkeybot/core/attachments/config.py +52 -0
  13. monkeybot/core/attachments/freeze.py +158 -0
  14. monkeybot/core/attachments/resolve.py +70 -0
  15. monkeybot/core/attachments/store.py +180 -0
  16. monkeybot/core/attachments/text.py +72 -0
  17. monkeybot/core/attachments/tools.py +54 -0
  18. monkeybot/core/bootstrap.py +242 -0
  19. monkeybot/core/config/__init__.py +71 -0
  20. monkeybot/core/config/realtime_config.py +150 -0
  21. monkeybot/core/config/runtime_env.py +262 -0
  22. monkeybot/core/config/settings.py +341 -0
  23. monkeybot/core/config/validation.py +249 -0
  24. monkeybot/core/config/yaml_loader.py +45 -0
  25. monkeybot/core/context/__init__.py +781 -0
  26. monkeybot/core/context/campaign_context.py +8 -0
  27. monkeybot/core/context/common.py +14 -0
  28. monkeybot/core/context/curator.py +255 -0
  29. monkeybot/core/context/epoch.py +226 -0
  30. monkeybot/core/context/memory_prompt.py +222 -0
  31. monkeybot/core/context/tool_output_policy.py +270 -0
  32. monkeybot/core/context/tool_result_ingress.py +290 -0
  33. monkeybot/core/context/tool_shapers.py +361 -0
  34. monkeybot/core/hooks/__init__.py +261 -0
  35. monkeybot/core/llm/__init__.py +4 -0
  36. monkeybot/core/llm/provider.py +296 -0
  37. monkeybot/core/llm/realtime_provider.py +203 -0
  38. monkeybot/core/llm/usage.py +57 -0
  39. monkeybot/core/logging_utils.py +24 -0
  40. monkeybot/core/mcp/__init__.py +1 -0
  41. monkeybot/core/mcp/mcp_client.py +1215 -0
  42. monkeybot/core/mcp/ports_mcp.py +109 -0
  43. monkeybot/core/memory/__init__.py +24 -0
  44. monkeybot/core/memory/hook.py +413 -0
  45. monkeybot/core/memory/index_format.py +104 -0
  46. monkeybot/core/memory/integrity.py +180 -0
  47. monkeybot/core/memory/organizer.py +270 -0
  48. monkeybot/core/memory/storage_ops.py +139 -0
  49. monkeybot/core/memory/subsystem.py +91 -0
  50. monkeybot/core/messages/__init__.py +16 -0
  51. monkeybot/core/messages/convert_provider.py +41 -0
  52. monkeybot/core/messages/tool_integrity.py +262 -0
  53. monkeybot/core/messages/transform_context.py +84 -0
  54. monkeybot/core/path_safety.py +11 -0
  55. monkeybot/core/persistence/__init__.py +17 -0
  56. monkeybot/core/persistence/backends.py +236 -0
  57. monkeybot/core/persistence/db.py +28 -0
  58. monkeybot/core/persistence/durable_runs.py +286 -0
  59. monkeybot/core/persistence/firestore.py +658 -0
  60. monkeybot/core/persistence/firestore_scheduled_loops.py +336 -0
  61. monkeybot/core/persistence/history.py +156 -0
  62. monkeybot/core/persistence/postgres.py +895 -0
  63. monkeybot/core/persistence/runs.py +76 -0
  64. monkeybot/core/persistence/scheduled_loops.py +435 -0
  65. monkeybot/core/persistence/session_turn_locks.py +94 -0
  66. monkeybot/core/persistence/sqlite.py +218 -0
  67. monkeybot/core/persistence/sqlite_backend.py +74 -0
  68. monkeybot/core/persistence/thread_summary.py +61 -0
  69. monkeybot/core/persistence/transcript.py +194 -0
  70. monkeybot/core/persistence/usage.py +149 -0
  71. monkeybot/core/prompts/__init__.py +1 -0
  72. monkeybot/core/prompts/harness_prompt.py +197 -0
  73. monkeybot/core/prompts/prompt.py +215 -0
  74. monkeybot/core/runtime/__init__.py +1 -0
  75. monkeybot/core/runtime/context_budget.py +267 -0
  76. monkeybot/core/runtime/events.py +819 -0
  77. monkeybot/core/runtime/input_admission.py +154 -0
  78. monkeybot/core/runtime/loop.py +2374 -0
  79. monkeybot/core/runtime/provider_stream_mapper.py +159 -0
  80. monkeybot/core/runtime/realtime_loop.py +654 -0
  81. monkeybot/core/runtime/utterance_buffer.py +179 -0
  82. monkeybot/core/subagents/__init__.py +1 -0
  83. monkeybot/core/subagents/subagent_proto.py +331 -0
  84. monkeybot/core/subagents/subagent_worker.py +441 -0
  85. monkeybot/core/subagents/worker_pool.py +403 -0
  86. monkeybot/core/testing/__init__.py +1 -0
  87. monkeybot/core/testing/mocks_provider.py +86 -0
  88. monkeybot/core/testing/mocks_realtime_provider.py +137 -0
  89. monkeybot/core/tools/__init__.py +1 -0
  90. monkeybot/core/tools/core_tool_executor.py +1548 -0
  91. monkeybot/core/tools/inspector.py +226 -0
  92. monkeybot/core/tools/loop_inspector.py +45 -0
  93. monkeybot/core/tools/patch.py +480 -0
  94. monkeybot/core/tools/permission.py +284 -0
  95. monkeybot/core/tools/sandbox_executor.py +255 -0
  96. monkeybot/core/tools/spill_inventory.py +35 -0
  97. monkeybot/core/tools/terminal.py +381 -0
  98. monkeybot/core/tools/text_normalize.py +25 -0
  99. monkeybot/core/tools/types.py +33 -0
  100. monkeybot/core/tools/workspace_service.py +710 -0
  101. monkeybot/core/tools/workspace_tools.py +116 -0
  102. monkeybot/core/types/__init__.py +1 -0
  103. monkeybot/core/types/content_blocks.py +644 -0
  104. monkeybot/core/types/interfaces.py +156 -0
  105. monkeybot/core/types/types_tools.py +29 -0
  106. monkeybot/core/workspace/__init__.py +8 -0
  107. monkeybot/core/workspace/factory.py +45 -0
  108. monkeybot/core/workspace/gcs.py +130 -0
  109. monkeybot/core/workspace/local.py +162 -0
  110. monkeybot/core/workspace/protocol.py +45 -0
  111. monkeybot/core/workspace/s3.py +151 -0
  112. monkeybot/core/workspace_layout.py +27 -0
  113. monkeybot/gateway/__init__.py +1 -0
  114. monkeybot/gateway/bootstrap.py +18 -0
  115. monkeybot/gateway/main.py +47 -0
  116. monkeybot/gateway/realtime/__init__.py +31 -0
  117. monkeybot/gateway/realtime/app.py +321 -0
  118. monkeybot/gateway/realtime/deps.py +52 -0
  119. monkeybot/gateway/realtime/errors.py +81 -0
  120. monkeybot/gateway/realtime/guardrails.py +88 -0
  121. monkeybot/gateway/realtime/manager.py +77 -0
  122. monkeybot/gateway/realtime/metrics.py +144 -0
  123. monkeybot/gateway/realtime/routes.py +864 -0
  124. monkeybot/gateway/realtime/session.py +232 -0
  125. monkeybot/gateway/realtime/wire.py +412 -0
  126. monkeybot/gateway/realtime_main.py +49 -0
  127. monkeybot/gateway/sse/__init__.py +1 -0
  128. monkeybot/gateway/sse/app.py +733 -0
  129. monkeybot/gateway/sse/loop_port.py +31 -0
  130. monkeybot/gateway/sse/models.py +177 -0
  131. monkeybot/gateway/sse/reply_body.py +91 -0
  132. monkeybot/gateway/sse/routes.py +1101 -0
  133. monkeybot/gateway/sse/scheduler_routes.py +200 -0
  134. monkeybot/gateway/sse/scheduler_wiring.py +96 -0
  135. monkeybot/gateway/sse/session_bus.py +226 -0
  136. monkeybot/gateway/sse/sse.py +46 -0
  137. monkeybot/gateway/sse/workspace_layout.py +7 -0
  138. monkeybot/observability/__init__.py +220 -0
  139. monkeybot/observability/_state.py +10 -0
  140. monkeybot/observability/instrumentation.py +153 -0
  141. monkeybot/observability/propagation.py +65 -0
  142. monkeybot/observability/spans.py +455 -0
  143. monkeybot/providers/__init__.py +19 -0
  144. monkeybot/providers/_openai_compat.py +450 -0
  145. monkeybot/providers/_utils.py +473 -0
  146. monkeybot/providers/bedrock.py +145 -0
  147. monkeybot/providers/claude.py +125 -0
  148. monkeybot/providers/gemini.py +677 -0
  149. monkeybot/providers/gemini_live.py +398 -0
  150. monkeybot/providers/huggingface.py +129 -0
  151. monkeybot/providers/nvidia.py +104 -0
  152. monkeybot/providers/ollama.py +152 -0
  153. monkeybot/providers/openai.py +127 -0
  154. monkeybot/providers/pricing.py +60 -0
  155. monkeybot/providers/sampling.py +44 -0
  156. monkeybot/providers/vertex_claude.py +148 -0
  157. monkeybot/scaffold/__init__.py +33 -0
  158. monkeybot/scheduler/__init__.py +13 -0
  159. monkeybot/scheduler/__main__.py +4 -0
  160. monkeybot/scheduler/engine.py +333 -0
  161. monkeybot/scheduler/http_invoker.py +61 -0
  162. monkeybot/scheduler/interval.py +77 -0
  163. monkeybot/scheduler/tick_result.py +34 -0
  164. monkeybot/scheduler/worker.py +87 -0
  165. monkeybot/subagents/__init__.py +1 -0
  166. monkeybot/subagents/worker/__init__.py +1 -0
  167. monkeybot/subagents/worker/__main__.py +22 -0
  168. monkeybot/web_search/__init__.py +82 -0
  169. monkeybot/web_search/backends/__init__.py +5 -0
  170. monkeybot/web_search/backends/duckduckgo.py +32 -0
  171. monkeybot/web_search/backends/firecrawl.py +43 -0
  172. monkeybot/web_search/backends/tavily.py +45 -0
  173. monkeybot/web_search/protocol.py +25 -0
  174. monkeybot/web_search/tool.py +56 -0
  175. monkeybot-2.1.1.dist-info/METADATA +318 -0
  176. monkeybot-2.1.1.dist-info/RECORD +178 -0
  177. monkeybot-2.1.1.dist-info/WHEEL +4 -0
  178. monkeybot-2.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,455 @@
1
+ """Span helpers for the agent loop (lazy OpenTelemetry imports)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from collections.abc import AsyncGenerator
8
+ from contextlib import asynccontextmanager
9
+ from dataclasses import dataclass
10
+ from typing import Any
11
+
12
+ from monkeybot.core.context import TurnContext
13
+ from monkeybot.observability._state import is_observability_enabled
14
+
15
+ _TRUNC_SUFFIX = "…[truncated]"
16
+ _DEFAULT_MAX_BYTES = 8192
17
+
18
+ _DENYLIST_SUBSTRINGS = (
19
+ "api_key",
20
+ "secret",
21
+ "password",
22
+ "token",
23
+ "authorization",
24
+ "credential",
25
+ "private_key",
26
+ )
27
+
28
+ _ATTR_ALLOWLIST_EXACT = frozenset(
29
+ {
30
+ "tool.name",
31
+ "gen_ai.request.model",
32
+ "gen_ai.operation.name",
33
+ "gen_ai.prompt",
34
+ "gen_ai.completion",
35
+ "gen_ai.usage.input_tokens",
36
+ "gen_ai.usage.output_tokens",
37
+ "gen_ai.usage.cached_tokens",
38
+ "gen_ai.usage.cache_read_tokens",
39
+ "gen_ai.usage.cache_creation_tokens",
40
+ "gen_ai.usage.total_tokens",
41
+ "turn.index",
42
+ "turns.summarized",
43
+ "trace.input",
44
+ "trace.output",
45
+ "langfuse.trace.input",
46
+ "langfuse.trace.output",
47
+ "langfuse.observation.input",
48
+ "langfuse.observation.output",
49
+ "input.value",
50
+ "output.value",
51
+ "openinference.span.kind",
52
+ }
53
+ )
54
+
55
+ # OpenInference span kinds (Phoenix UI): https://arize.com/docs/phoenix/tracing
56
+ _OI_KIND_AGENT = "AGENT"
57
+ _OI_KIND_CHAIN = "CHAIN"
58
+ _OI_KIND_LLM = "LLM"
59
+ _OI_KIND_TOOL = "TOOL"
60
+
61
+ _tool_outcome: dict[str, tuple[str | None, str | None] | None] = {"value": None}
62
+
63
+
64
+ @dataclass
65
+ class _SpanHandle:
66
+ span: Any | None
67
+ token: Any | None
68
+
69
+
70
+ def truncate(value: str, *, max_bytes: int = _DEFAULT_MAX_BYTES) -> str:
71
+ if not value:
72
+ return value
73
+ encoded = value.encode("utf-8")
74
+ if len(encoded) <= max_bytes:
75
+ return value
76
+ clipped = encoded[:max_bytes].decode("utf-8", errors="ignore")
77
+ return clipped + _TRUNC_SUFFIX
78
+
79
+
80
+ def _key_denied(key: str) -> bool:
81
+ lower = key.lower()
82
+ if lower.startswith("gen_ai."):
83
+ return False
84
+ return any(part in lower for part in _DENYLIST_SUBSTRINGS)
85
+
86
+
87
+ def set_span_attribute_safe(
88
+ span: Any,
89
+ key: str,
90
+ value: str | int | bool | float,
91
+ ) -> None:
92
+ if _key_denied(key):
93
+ return
94
+ if isinstance(value, str) and key not in _ATTR_ALLOWLIST_EXACT:
95
+ value = truncate(value)
96
+ span.set_attribute(key, value)
97
+
98
+
99
+ def _current_span() -> Any | None:
100
+ if not is_observability_enabled():
101
+ return None
102
+ from opentelemetry import trace
103
+
104
+ span = trace.get_current_span()
105
+ if not span.get_span_context().is_valid:
106
+ return None
107
+ return span
108
+
109
+
110
+ def _set_openinference_span_kind(span: Any, kind: str) -> None:
111
+ set_span_attribute_safe(span, "openinference.span.kind", kind)
112
+
113
+
114
+ def _mirror_observation_io(
115
+ span: Any,
116
+ *,
117
+ input_value: str | None = None,
118
+ output_value: str | None = None,
119
+ trace_level: bool = False,
120
+ ) -> None:
121
+ """Mirror I/O for Langfuse (langfuse.*) and Phoenix (input.value / output.value)."""
122
+ if input_value is not None:
123
+ msg = truncate(input_value)
124
+ set_span_attribute_safe(span, "langfuse.observation.input", msg)
125
+ set_span_attribute_safe(span, "input.value", msg)
126
+ if trace_level:
127
+ set_span_attribute_safe(span, "langfuse.trace.input", msg)
128
+ if output_value is not None:
129
+ msg = truncate(output_value)
130
+ set_span_attribute_safe(span, "langfuse.observation.output", msg)
131
+ set_span_attribute_safe(span, "output.value", msg)
132
+ if trace_level:
133
+ set_span_attribute_safe(span, "langfuse.trace.output", msg)
134
+
135
+
136
+ def set_run_output(text: str) -> None:
137
+ span = _current_span()
138
+ if span is None:
139
+ return
140
+ out = truncate(text)
141
+ set_span_attribute_safe(span, "trace.output", out)
142
+ _mirror_observation_io(span, output_value=text, trace_level=True)
143
+
144
+
145
+ def set_llm_io(*, prompt: str, completion: str) -> None:
146
+ """Set LLM span I/O for Langfuse generations (call inside ``span_llm``)."""
147
+ span = _current_span()
148
+ if span is None:
149
+ return
150
+ prompt_t = truncate(prompt)
151
+ completion_t = truncate(completion)
152
+ set_span_attribute_safe(span, "gen_ai.prompt", prompt_t)
153
+ set_span_attribute_safe(span, "gen_ai.completion", completion_t)
154
+ _mirror_observation_io(span, input_value=prompt, output_value=completion)
155
+
156
+
157
+ def set_turn_io(
158
+ *,
159
+ input_value: str | None = None,
160
+ output_value: str | None = None,
161
+ ) -> None:
162
+ """Set turn span I/O (call on the active ``monkeybot.turn`` span before ``end_turn_span``)."""
163
+ span = _current_span()
164
+ if span is None:
165
+ return
166
+ if input_value is not None:
167
+ set_span_attribute_safe(span, "trace.input", truncate(input_value))
168
+ _mirror_observation_io(span, input_value=input_value, output_value=output_value)
169
+
170
+
171
+ def set_turn_prompt_tokens(count: int) -> None:
172
+ span = _current_span()
173
+ if span is None:
174
+ return
175
+ set_span_attribute_safe(span, "estimated_prompt_tokens", count)
176
+
177
+
178
+ def set_llm_usage(
179
+ *,
180
+ input_tokens: int,
181
+ output_tokens: int,
182
+ cached_tokens: int = 0,
183
+ cache_read_tokens: int = 0,
184
+ cache_creation_tokens: int = 0,
185
+ ) -> None:
186
+ span = _current_span()
187
+ if span is None:
188
+ return
189
+ set_span_attribute_safe(span, "gen_ai.usage.input_tokens", input_tokens)
190
+ set_span_attribute_safe(span, "gen_ai.usage.output_tokens", output_tokens)
191
+ if cached_tokens:
192
+ set_span_attribute_safe(span, "gen_ai.usage.cached_tokens", cached_tokens)
193
+ if cache_read_tokens:
194
+ set_span_attribute_safe(span, "gen_ai.usage.cache_read_tokens", cache_read_tokens)
195
+ if cache_creation_tokens:
196
+ set_span_attribute_safe(
197
+ span, "gen_ai.usage.cache_creation_tokens", cache_creation_tokens
198
+ )
199
+ total = input_tokens + output_tokens
200
+ if total:
201
+ set_span_attribute_safe(span, "gen_ai.usage.total_tokens", total)
202
+
203
+
204
+ def set_summarize_turns(count: int) -> None:
205
+ span = _current_span()
206
+ if span is None:
207
+ return
208
+ set_span_attribute_safe(span, "turns.summarized", count)
209
+
210
+
211
+ def _record_tool_outcome(result_text: str | None, error_text: str | None) -> None:
212
+ _tool_outcome["value"] = (result_text, error_text)
213
+
214
+
215
+ def _agent_name() -> str:
216
+ return os.environ.get("AGENT_NAME", "monkeybot")
217
+
218
+
219
+ @asynccontextmanager
220
+ async def span_run(
221
+ ctx: TurnContext,
222
+ *,
223
+ user_message: str,
224
+ agent_name: str | None = None,
225
+ ) -> AsyncGenerator[None, None]:
226
+ if not is_observability_enabled():
227
+ yield
228
+ return
229
+
230
+ from monkeybot.observability import get_tracer
231
+
232
+ tracer = get_tracer()
233
+ msg = truncate(user_message)
234
+ with tracer.start_as_current_span("monkeybot.run") as span:
235
+ _set_openinference_span_kind(span, _OI_KIND_AGENT)
236
+ set_span_attribute_safe(span, "thread.id", ctx.thread_id)
237
+ set_span_attribute_safe(span, "request.id", ctx.request_id)
238
+ set_span_attribute_safe(span, "user.message", msg)
239
+ set_span_attribute_safe(span, "trace.input", msg)
240
+ _mirror_observation_io(span, input_value=user_message, trace_level=True)
241
+ set_span_attribute_safe(span, "agent.name", agent_name or _agent_name())
242
+ try:
243
+ yield
244
+ except Exception as exc:
245
+ span.record_exception(exc)
246
+ trace_api = __import__("opentelemetry.trace", fromlist=["Status", "StatusCode"])
247
+ span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, str(exc)))
248
+ raise
249
+
250
+
251
+ def begin_turn_span(
252
+ *,
253
+ turn_index: int,
254
+ thread_id: str,
255
+ request_id: str,
256
+ ) -> _SpanHandle:
257
+ if not is_observability_enabled():
258
+ return _SpanHandle(None, None)
259
+
260
+ from opentelemetry import trace as trace_api
261
+ from opentelemetry.context import attach
262
+
263
+ from monkeybot.observability import get_tracer
264
+
265
+ tracer = get_tracer()
266
+ span = tracer.start_span("monkeybot.turn")
267
+ _set_openinference_span_kind(span, _OI_KIND_CHAIN)
268
+ set_span_attribute_safe(span, "turn.index", turn_index)
269
+ set_span_attribute_safe(span, "thread.id", thread_id)
270
+ set_span_attribute_safe(span, "request.id", request_id)
271
+ otel_ctx = trace_api.set_span_in_context(span)
272
+ token = attach(otel_ctx)
273
+ return _SpanHandle(span, token)
274
+
275
+
276
+ def end_turn_span(handle: _SpanHandle) -> None:
277
+ if handle.span is None:
278
+ return
279
+ from opentelemetry.context import detach
280
+
281
+ if handle.token is not None:
282
+ detach(handle.token)
283
+ handle.span.end()
284
+
285
+
286
+ @asynccontextmanager
287
+ async def span_turn(
288
+ *,
289
+ turn_index: int,
290
+ thread_id: str,
291
+ request_id: str,
292
+ ) -> AsyncGenerator[None, None]:
293
+ handle = begin_turn_span(
294
+ turn_index=turn_index,
295
+ thread_id=thread_id,
296
+ request_id=request_id,
297
+ )
298
+ try:
299
+ yield
300
+ except Exception as exc:
301
+ if handle.span is not None:
302
+ handle.span.record_exception(exc)
303
+ trace_api = __import__("opentelemetry.trace", fromlist=["Status", "StatusCode"])
304
+ handle.span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, str(exc)))
305
+ raise
306
+ finally:
307
+ end_turn_span(handle)
308
+
309
+
310
+ @asynccontextmanager
311
+ async def span_llm(
312
+ *,
313
+ ctx: TurnContext,
314
+ model: str | None = None,
315
+ vertex_google_search: bool = False,
316
+ ) -> AsyncGenerator[None, None]:
317
+ if not is_observability_enabled():
318
+ yield
319
+ return
320
+
321
+ from monkeybot.observability import get_tracer
322
+
323
+ tracer = get_tracer()
324
+ model_id = model or ctx.model
325
+ with tracer.start_as_current_span("monkeybot.llm.stream") as span:
326
+ _set_openinference_span_kind(span, _OI_KIND_LLM)
327
+ set_span_attribute_safe(span, "gen_ai.request.model", model_id)
328
+ set_span_attribute_safe(span, "gen_ai.operation.name", "chat")
329
+ set_span_attribute_safe(span, "thread.id", ctx.thread_id)
330
+ set_span_attribute_safe(span, "request.id", ctx.request_id)
331
+ if vertex_google_search:
332
+ set_span_attribute_safe(span, "monkeybot.vertex_google_search", True)
333
+ try:
334
+ yield
335
+ except Exception as exc:
336
+ span.record_exception(exc)
337
+ trace_api = __import__("opentelemetry.trace", fromlist=["Status", "StatusCode"])
338
+ span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, str(exc)))
339
+ raise
340
+
341
+
342
+ @asynccontextmanager
343
+ async def span_tool(
344
+ *,
345
+ tool_name: str,
346
+ tool_call_id: str,
347
+ thread_id: str,
348
+ request_id: str,
349
+ args: dict[str, object] | None = None,
350
+ ) -> AsyncGenerator[None, None]:
351
+ if not is_observability_enabled():
352
+ yield
353
+ return
354
+
355
+ from monkeybot.observability import get_tracer
356
+
357
+ _tool_outcome["value"] = None
358
+ tracer = get_tracer()
359
+ with tracer.start_as_current_span("monkeybot.tool") as span:
360
+ _set_openinference_span_kind(span, _OI_KIND_TOOL)
361
+ set_span_attribute_safe(span, "tool.name", tool_name)
362
+ set_span_attribute_safe(span, "tool.call_id", tool_call_id)
363
+ set_span_attribute_safe(span, "thread.id", thread_id)
364
+ set_span_attribute_safe(span, "request.id", request_id)
365
+ if args is not None:
366
+ args_json = truncate(json.dumps(args, default=str))
367
+ set_span_attribute_safe(span, "tool.input", args_json)
368
+ _mirror_observation_io(span, input_value=args_json)
369
+ try:
370
+ yield
371
+ outcome = _tool_outcome["value"]
372
+ if outcome is not None:
373
+ result_text, error_text = outcome
374
+ if error_text:
375
+ set_span_attribute_safe(span, "error", error_text)
376
+ trace_api = __import__("opentelemetry.trace", fromlist=["Status", "StatusCode"])
377
+ span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, error_text))
378
+ elif result_text is not None:
379
+ out = truncate(result_text)
380
+ set_span_attribute_safe(span, "tool.output", out)
381
+ _mirror_observation_io(span, output_value=out)
382
+ except Exception as exc:
383
+ set_span_attribute_safe(span, "error", truncate(str(exc)))
384
+ span.record_exception(exc)
385
+ trace_api = __import__("opentelemetry.trace", fromlist=["Status", "StatusCode"])
386
+ span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, str(exc)))
387
+ raise
388
+ finally:
389
+ _tool_outcome["value"] = None
390
+
391
+
392
+ def record_tool_outcome(result_text: str | None, error_text: str | None) -> None:
393
+ """Called from the loop after ``tool_executor.execute`` inside ``span_tool``."""
394
+ _record_tool_outcome(result_text, error_text)
395
+
396
+
397
+ @asynccontextmanager
398
+ async def span_subagent(
399
+ *,
400
+ thread_id: str,
401
+ request_id: str,
402
+ parent_run_id: str,
403
+ subagent_type: str | None = None,
404
+ agent_md: str | None = None,
405
+ ) -> AsyncGenerator[None, None]:
406
+ """Child span for subprocess ``task`` run; nests under propagated context when attached."""
407
+ if not is_observability_enabled():
408
+ yield
409
+ return
410
+
411
+ from monkeybot.observability import get_tracer
412
+
413
+ tracer = get_tracer()
414
+ with tracer.start_as_current_span("monkeybot.subagent") as span:
415
+ _set_openinference_span_kind(span, _OI_KIND_AGENT)
416
+ set_span_attribute_safe(span, "thread.id", thread_id)
417
+ set_span_attribute_safe(span, "request.id", request_id)
418
+ set_span_attribute_safe(span, "parent.run.id", parent_run_id)
419
+ if subagent_type:
420
+ set_span_attribute_safe(span, "subagent.type", subagent_type)
421
+ if agent_md:
422
+ set_span_attribute_safe(span, "subagent.agent_md", agent_md)
423
+ try:
424
+ yield
425
+ except Exception as exc:
426
+ span.record_exception(exc)
427
+ trace_api = __import__("opentelemetry.trace", fromlist=["Status", "StatusCode"])
428
+ span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, str(exc)))
429
+ raise
430
+
431
+
432
+ @asynccontextmanager
433
+ async def span_summarize(
434
+ *,
435
+ thread_id: str,
436
+ request_id: str,
437
+ ) -> AsyncGenerator[None, None]:
438
+ if not is_observability_enabled():
439
+ yield
440
+ return
441
+
442
+ from monkeybot.observability import get_tracer
443
+
444
+ tracer = get_tracer()
445
+ with tracer.start_as_current_span("monkeybot.context.summarize") as span:
446
+ _set_openinference_span_kind(span, _OI_KIND_CHAIN)
447
+ set_span_attribute_safe(span, "thread.id", thread_id)
448
+ set_span_attribute_safe(span, "request.id", request_id)
449
+ try:
450
+ yield
451
+ except Exception as exc:
452
+ span.record_exception(exc)
453
+ trace_api = __import__("opentelemetry.trace", fromlist=["Status", "StatusCode"])
454
+ span.set_status(trace_api.Status(trace_api.StatusCode.ERROR, str(exc)))
455
+ raise
@@ -0,0 +1,19 @@
1
+ """Optional LLM providers (lazy-import heavy SDKs inside ``stream()``)."""
2
+
3
+ from monkeybot.providers.claude import ClaudeProvider
4
+ from monkeybot.providers.gemini import GeminiProvider
5
+ from monkeybot.providers.huggingface import HuggingFaceProvider
6
+ from monkeybot.providers.nvidia import NvidiaProvider
7
+ from monkeybot.providers.ollama import OllamaProvider
8
+ from monkeybot.providers.openai import OpenAIProvider
9
+ from monkeybot.providers.vertex_claude import VertexClaudeProvider
10
+
11
+ __all__ = [
12
+ "ClaudeProvider",
13
+ "GeminiProvider",
14
+ "HuggingFaceProvider",
15
+ "NvidiaProvider",
16
+ "OllamaProvider",
17
+ "OpenAIProvider",
18
+ "VertexClaudeProvider",
19
+ ]