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.
- monkeybot/__init__.py +3 -0
- monkeybot/cli/__init__.py +3 -0
- monkeybot/cli/__main__.py +8 -0
- monkeybot/cli/audio_io.py +8 -0
- monkeybot/cli/gateway_manager.py +17 -0
- monkeybot/cli/main.py +22 -0
- monkeybot/cli/push_to_talk.py +12 -0
- monkeybot/cli/realtime_client.py +13 -0
- monkeybot/core/__init__.py +19 -0
- monkeybot/core/attachments/__init__.py +22 -0
- monkeybot/core/attachments/catalog.py +62 -0
- monkeybot/core/attachments/config.py +52 -0
- monkeybot/core/attachments/freeze.py +158 -0
- monkeybot/core/attachments/resolve.py +70 -0
- monkeybot/core/attachments/store.py +180 -0
- monkeybot/core/attachments/text.py +72 -0
- monkeybot/core/attachments/tools.py +54 -0
- monkeybot/core/bootstrap.py +242 -0
- monkeybot/core/config/__init__.py +71 -0
- monkeybot/core/config/realtime_config.py +150 -0
- monkeybot/core/config/runtime_env.py +262 -0
- monkeybot/core/config/settings.py +341 -0
- monkeybot/core/config/validation.py +249 -0
- monkeybot/core/config/yaml_loader.py +45 -0
- monkeybot/core/context/__init__.py +781 -0
- monkeybot/core/context/campaign_context.py +8 -0
- monkeybot/core/context/common.py +14 -0
- monkeybot/core/context/curator.py +255 -0
- monkeybot/core/context/epoch.py +226 -0
- monkeybot/core/context/memory_prompt.py +222 -0
- monkeybot/core/context/tool_output_policy.py +270 -0
- monkeybot/core/context/tool_result_ingress.py +290 -0
- monkeybot/core/context/tool_shapers.py +361 -0
- monkeybot/core/hooks/__init__.py +261 -0
- monkeybot/core/llm/__init__.py +4 -0
- monkeybot/core/llm/provider.py +296 -0
- monkeybot/core/llm/realtime_provider.py +203 -0
- monkeybot/core/llm/usage.py +57 -0
- monkeybot/core/logging_utils.py +24 -0
- monkeybot/core/mcp/__init__.py +1 -0
- monkeybot/core/mcp/mcp_client.py +1215 -0
- monkeybot/core/mcp/ports_mcp.py +109 -0
- monkeybot/core/memory/__init__.py +24 -0
- monkeybot/core/memory/hook.py +413 -0
- monkeybot/core/memory/index_format.py +104 -0
- monkeybot/core/memory/integrity.py +180 -0
- monkeybot/core/memory/organizer.py +270 -0
- monkeybot/core/memory/storage_ops.py +139 -0
- monkeybot/core/memory/subsystem.py +91 -0
- monkeybot/core/messages/__init__.py +16 -0
- monkeybot/core/messages/convert_provider.py +41 -0
- monkeybot/core/messages/tool_integrity.py +262 -0
- monkeybot/core/messages/transform_context.py +84 -0
- monkeybot/core/path_safety.py +11 -0
- monkeybot/core/persistence/__init__.py +17 -0
- monkeybot/core/persistence/backends.py +236 -0
- monkeybot/core/persistence/db.py +28 -0
- monkeybot/core/persistence/durable_runs.py +286 -0
- monkeybot/core/persistence/firestore.py +658 -0
- monkeybot/core/persistence/firestore_scheduled_loops.py +336 -0
- monkeybot/core/persistence/history.py +156 -0
- monkeybot/core/persistence/postgres.py +895 -0
- monkeybot/core/persistence/runs.py +76 -0
- monkeybot/core/persistence/scheduled_loops.py +435 -0
- monkeybot/core/persistence/session_turn_locks.py +94 -0
- monkeybot/core/persistence/sqlite.py +218 -0
- monkeybot/core/persistence/sqlite_backend.py +74 -0
- monkeybot/core/persistence/thread_summary.py +61 -0
- monkeybot/core/persistence/transcript.py +194 -0
- monkeybot/core/persistence/usage.py +149 -0
- monkeybot/core/prompts/__init__.py +1 -0
- monkeybot/core/prompts/harness_prompt.py +197 -0
- monkeybot/core/prompts/prompt.py +215 -0
- monkeybot/core/runtime/__init__.py +1 -0
- monkeybot/core/runtime/context_budget.py +267 -0
- monkeybot/core/runtime/events.py +819 -0
- monkeybot/core/runtime/input_admission.py +154 -0
- monkeybot/core/runtime/loop.py +2374 -0
- monkeybot/core/runtime/provider_stream_mapper.py +159 -0
- monkeybot/core/runtime/realtime_loop.py +654 -0
- monkeybot/core/runtime/utterance_buffer.py +179 -0
- monkeybot/core/subagents/__init__.py +1 -0
- monkeybot/core/subagents/subagent_proto.py +331 -0
- monkeybot/core/subagents/subagent_worker.py +441 -0
- monkeybot/core/subagents/worker_pool.py +403 -0
- monkeybot/core/testing/__init__.py +1 -0
- monkeybot/core/testing/mocks_provider.py +86 -0
- monkeybot/core/testing/mocks_realtime_provider.py +137 -0
- monkeybot/core/tools/__init__.py +1 -0
- monkeybot/core/tools/core_tool_executor.py +1548 -0
- monkeybot/core/tools/inspector.py +226 -0
- monkeybot/core/tools/loop_inspector.py +45 -0
- monkeybot/core/tools/patch.py +480 -0
- monkeybot/core/tools/permission.py +284 -0
- monkeybot/core/tools/sandbox_executor.py +255 -0
- monkeybot/core/tools/spill_inventory.py +35 -0
- monkeybot/core/tools/terminal.py +381 -0
- monkeybot/core/tools/text_normalize.py +25 -0
- monkeybot/core/tools/types.py +33 -0
- monkeybot/core/tools/workspace_service.py +710 -0
- monkeybot/core/tools/workspace_tools.py +116 -0
- monkeybot/core/types/__init__.py +1 -0
- monkeybot/core/types/content_blocks.py +644 -0
- monkeybot/core/types/interfaces.py +156 -0
- monkeybot/core/types/types_tools.py +29 -0
- monkeybot/core/workspace/__init__.py +8 -0
- monkeybot/core/workspace/factory.py +45 -0
- monkeybot/core/workspace/gcs.py +130 -0
- monkeybot/core/workspace/local.py +162 -0
- monkeybot/core/workspace/protocol.py +45 -0
- monkeybot/core/workspace/s3.py +151 -0
- monkeybot/core/workspace_layout.py +27 -0
- monkeybot/gateway/__init__.py +1 -0
- monkeybot/gateway/bootstrap.py +18 -0
- monkeybot/gateway/main.py +47 -0
- monkeybot/gateway/realtime/__init__.py +31 -0
- monkeybot/gateway/realtime/app.py +321 -0
- monkeybot/gateway/realtime/deps.py +52 -0
- monkeybot/gateway/realtime/errors.py +81 -0
- monkeybot/gateway/realtime/guardrails.py +88 -0
- monkeybot/gateway/realtime/manager.py +77 -0
- monkeybot/gateway/realtime/metrics.py +144 -0
- monkeybot/gateway/realtime/routes.py +864 -0
- monkeybot/gateway/realtime/session.py +232 -0
- monkeybot/gateway/realtime/wire.py +412 -0
- monkeybot/gateway/realtime_main.py +49 -0
- monkeybot/gateway/sse/__init__.py +1 -0
- monkeybot/gateway/sse/app.py +733 -0
- monkeybot/gateway/sse/loop_port.py +31 -0
- monkeybot/gateway/sse/models.py +177 -0
- monkeybot/gateway/sse/reply_body.py +91 -0
- monkeybot/gateway/sse/routes.py +1101 -0
- monkeybot/gateway/sse/scheduler_routes.py +200 -0
- monkeybot/gateway/sse/scheduler_wiring.py +96 -0
- monkeybot/gateway/sse/session_bus.py +226 -0
- monkeybot/gateway/sse/sse.py +46 -0
- monkeybot/gateway/sse/workspace_layout.py +7 -0
- monkeybot/observability/__init__.py +220 -0
- monkeybot/observability/_state.py +10 -0
- monkeybot/observability/instrumentation.py +153 -0
- monkeybot/observability/propagation.py +65 -0
- monkeybot/observability/spans.py +455 -0
- monkeybot/providers/__init__.py +19 -0
- monkeybot/providers/_openai_compat.py +450 -0
- monkeybot/providers/_utils.py +473 -0
- monkeybot/providers/bedrock.py +145 -0
- monkeybot/providers/claude.py +125 -0
- monkeybot/providers/gemini.py +677 -0
- monkeybot/providers/gemini_live.py +398 -0
- monkeybot/providers/huggingface.py +129 -0
- monkeybot/providers/nvidia.py +104 -0
- monkeybot/providers/ollama.py +152 -0
- monkeybot/providers/openai.py +127 -0
- monkeybot/providers/pricing.py +60 -0
- monkeybot/providers/sampling.py +44 -0
- monkeybot/providers/vertex_claude.py +148 -0
- monkeybot/scaffold/__init__.py +33 -0
- monkeybot/scheduler/__init__.py +13 -0
- monkeybot/scheduler/__main__.py +4 -0
- monkeybot/scheduler/engine.py +333 -0
- monkeybot/scheduler/http_invoker.py +61 -0
- monkeybot/scheduler/interval.py +77 -0
- monkeybot/scheduler/tick_result.py +34 -0
- monkeybot/scheduler/worker.py +87 -0
- monkeybot/subagents/__init__.py +1 -0
- monkeybot/subagents/worker/__init__.py +1 -0
- monkeybot/subagents/worker/__main__.py +22 -0
- monkeybot/web_search/__init__.py +82 -0
- monkeybot/web_search/backends/__init__.py +5 -0
- monkeybot/web_search/backends/duckduckgo.py +32 -0
- monkeybot/web_search/backends/firecrawl.py +43 -0
- monkeybot/web_search/backends/tavily.py +45 -0
- monkeybot/web_search/protocol.py +25 -0
- monkeybot/web_search/tool.py +56 -0
- monkeybot-2.1.1.dist-info/METADATA +318 -0
- monkeybot-2.1.1.dist-info/RECORD +178 -0
- monkeybot-2.1.1.dist-info/WHEEL +4 -0
- 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
|
+
]
|