hud-python 0.4.45__py3-none-any.whl → 0.5.13__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.
- hud/__init__.py +27 -7
- hud/agents/__init__.py +70 -5
- hud/agents/base.py +238 -500
- hud/agents/claude.py +236 -247
- hud/agents/gateway.py +42 -0
- hud/agents/gemini.py +264 -0
- hud/agents/gemini_cua.py +324 -0
- hud/agents/grounded_openai.py +98 -100
- hud/agents/misc/integration_test_agent.py +51 -20
- hud/agents/misc/response_agent.py +48 -36
- hud/agents/openai.py +282 -296
- hud/agents/{openai_chat_generic.py → openai_chat.py} +63 -33
- hud/agents/operator.py +199 -0
- hud/agents/resolver.py +70 -0
- hud/agents/tests/conftest.py +133 -0
- hud/agents/tests/test_base.py +300 -622
- hud/agents/tests/test_base_runtime.py +233 -0
- hud/agents/tests/test_claude.py +381 -214
- hud/agents/tests/test_client.py +9 -10
- hud/agents/tests/test_gemini.py +369 -0
- hud/agents/tests/test_grounded_openai_agent.py +65 -50
- hud/agents/tests/test_openai.py +377 -140
- hud/agents/tests/test_operator.py +362 -0
- hud/agents/tests/test_resolver.py +192 -0
- hud/agents/tests/test_run_eval.py +179 -0
- hud/agents/types.py +148 -0
- hud/cli/__init__.py +493 -546
- hud/cli/analyze.py +43 -5
- hud/cli/build.py +699 -113
- hud/cli/debug.py +8 -5
- hud/cli/dev.py +889 -732
- hud/cli/eval.py +793 -667
- hud/cli/flows/dev.py +167 -0
- hud/cli/flows/init.py +191 -0
- hud/cli/flows/tasks.py +153 -56
- hud/cli/flows/templates.py +151 -0
- hud/cli/flows/tests/__init__.py +1 -0
- hud/cli/flows/tests/test_dev.py +126 -0
- hud/cli/init.py +60 -58
- hud/cli/pull.py +1 -1
- hud/cli/push.py +38 -13
- hud/cli/rft.py +311 -0
- hud/cli/rft_status.py +145 -0
- hud/cli/tests/test_analyze.py +5 -5
- hud/cli/tests/test_analyze_metadata.py +3 -2
- hud/cli/tests/test_analyze_module.py +120 -0
- hud/cli/tests/test_build.py +110 -8
- hud/cli/tests/test_build_failure.py +41 -0
- hud/cli/tests/test_build_module.py +50 -0
- hud/cli/tests/test_cli_init.py +6 -1
- hud/cli/tests/test_cli_more_wrappers.py +30 -0
- hud/cli/tests/test_cli_root.py +140 -0
- hud/cli/tests/test_convert.py +361 -0
- hud/cli/tests/test_debug.py +12 -10
- hud/cli/tests/test_dev.py +197 -0
- hud/cli/tests/test_eval.py +251 -0
- hud/cli/tests/test_eval_bedrock.py +51 -0
- hud/cli/tests/test_init.py +124 -0
- hud/cli/tests/test_main_module.py +11 -5
- hud/cli/tests/test_mcp_server.py +12 -100
- hud/cli/tests/test_push.py +1 -1
- hud/cli/tests/test_push_happy.py +74 -0
- hud/cli/tests/test_push_wrapper.py +23 -0
- hud/cli/tests/test_registry.py +1 -1
- hud/cli/tests/test_utils.py +1 -1
- hud/cli/{rl → utils}/celebrate.py +14 -12
- hud/cli/utils/config.py +18 -1
- hud/cli/utils/docker.py +130 -4
- hud/cli/utils/env_check.py +9 -9
- hud/cli/utils/git.py +136 -0
- hud/cli/utils/interactive.py +39 -5
- hud/cli/utils/metadata.py +70 -1
- hud/cli/utils/runner.py +1 -1
- hud/cli/utils/server.py +2 -2
- hud/cli/utils/source_hash.py +3 -3
- hud/cli/utils/tasks.py +4 -1
- hud/cli/utils/tests/__init__.py +0 -0
- hud/cli/utils/tests/test_config.py +58 -0
- hud/cli/utils/tests/test_docker.py +93 -0
- hud/cli/utils/tests/test_docker_hints.py +71 -0
- hud/cli/utils/tests/test_env_check.py +74 -0
- hud/cli/utils/tests/test_environment.py +42 -0
- hud/cli/utils/tests/test_git.py +142 -0
- hud/cli/utils/tests/test_interactive_module.py +60 -0
- hud/cli/utils/tests/test_local_runner.py +50 -0
- hud/cli/utils/tests/test_logging_utils.py +23 -0
- hud/cli/utils/tests/test_metadata.py +49 -0
- hud/cli/utils/tests/test_package_runner.py +35 -0
- hud/cli/utils/tests/test_registry_utils.py +49 -0
- hud/cli/utils/tests/test_remote_runner.py +25 -0
- hud/cli/utils/tests/test_runner_modules.py +52 -0
- hud/cli/utils/tests/test_source_hash.py +36 -0
- hud/cli/utils/tests/test_tasks.py +80 -0
- hud/cli/utils/version_check.py +258 -0
- hud/cli/{rl → utils}/viewer.py +2 -2
- hud/clients/README.md +12 -11
- hud/clients/__init__.py +4 -3
- hud/clients/base.py +166 -26
- hud/clients/environment.py +51 -0
- hud/clients/fastmcp.py +13 -6
- hud/clients/mcp_use.py +45 -15
- hud/clients/tests/test_analyze_scenarios.py +206 -0
- hud/clients/tests/test_protocol.py +9 -3
- hud/datasets/__init__.py +23 -20
- hud/datasets/loader.py +326 -0
- hud/datasets/runner.py +198 -105
- hud/datasets/tests/__init__.py +0 -0
- hud/datasets/tests/test_loader.py +221 -0
- hud/datasets/tests/test_utils.py +315 -0
- hud/datasets/utils.py +270 -90
- hud/environment/__init__.py +52 -0
- hud/environment/connection.py +258 -0
- hud/environment/connectors/__init__.py +33 -0
- hud/environment/connectors/base.py +68 -0
- hud/environment/connectors/local.py +177 -0
- hud/environment/connectors/mcp_config.py +137 -0
- hud/environment/connectors/openai.py +101 -0
- hud/environment/connectors/remote.py +172 -0
- hud/environment/environment.py +835 -0
- hud/environment/integrations/__init__.py +45 -0
- hud/environment/integrations/adk.py +67 -0
- hud/environment/integrations/anthropic.py +196 -0
- hud/environment/integrations/gemini.py +92 -0
- hud/environment/integrations/langchain.py +82 -0
- hud/environment/integrations/llamaindex.py +68 -0
- hud/environment/integrations/openai.py +238 -0
- hud/environment/mock.py +306 -0
- hud/environment/router.py +263 -0
- hud/environment/scenarios.py +620 -0
- hud/environment/tests/__init__.py +1 -0
- hud/environment/tests/test_connection.py +317 -0
- hud/environment/tests/test_connectors.py +205 -0
- hud/environment/tests/test_environment.py +593 -0
- hud/environment/tests/test_integrations.py +257 -0
- hud/environment/tests/test_local_connectors.py +242 -0
- hud/environment/tests/test_scenarios.py +1086 -0
- hud/environment/tests/test_tools.py +208 -0
- hud/environment/types.py +23 -0
- hud/environment/utils/__init__.py +35 -0
- hud/environment/utils/formats.py +215 -0
- hud/environment/utils/schema.py +171 -0
- hud/environment/utils/tool_wrappers.py +113 -0
- hud/eval/__init__.py +67 -0
- hud/eval/context.py +727 -0
- hud/eval/display.py +299 -0
- hud/eval/instrument.py +187 -0
- hud/eval/manager.py +533 -0
- hud/eval/parallel.py +268 -0
- hud/eval/task.py +372 -0
- hud/eval/tests/__init__.py +1 -0
- hud/eval/tests/test_context.py +178 -0
- hud/eval/tests/test_eval.py +210 -0
- hud/eval/tests/test_manager.py +152 -0
- hud/eval/tests/test_parallel.py +168 -0
- hud/eval/tests/test_task.py +291 -0
- hud/eval/types.py +65 -0
- hud/eval/utils.py +194 -0
- hud/patches/__init__.py +19 -0
- hud/patches/mcp_patches.py +308 -0
- hud/patches/warnings.py +54 -0
- hud/samples/browser.py +4 -4
- hud/server/__init__.py +2 -1
- hud/server/low_level.py +2 -1
- hud/server/router.py +164 -0
- hud/server/server.py +567 -80
- hud/server/tests/test_mcp_server_integration.py +11 -11
- hud/server/tests/test_mcp_server_more.py +1 -1
- hud/server/tests/test_server_extra.py +2 -0
- hud/settings.py +45 -3
- hud/shared/exceptions.py +36 -10
- hud/shared/hints.py +26 -1
- hud/shared/requests.py +15 -3
- hud/shared/tests/test_exceptions.py +40 -31
- hud/shared/tests/test_hints.py +167 -0
- hud/telemetry/__init__.py +20 -19
- hud/telemetry/exporter.py +201 -0
- hud/telemetry/instrument.py +165 -253
- hud/telemetry/tests/test_eval_telemetry.py +356 -0
- hud/telemetry/tests/test_exporter.py +258 -0
- hud/telemetry/tests/test_instrument.py +401 -0
- hud/tools/__init__.py +18 -2
- hud/tools/agent.py +223 -0
- hud/tools/apply_patch.py +639 -0
- hud/tools/base.py +54 -4
- hud/tools/bash.py +2 -2
- hud/tools/computer/__init__.py +36 -3
- hud/tools/computer/anthropic.py +2 -2
- hud/tools/computer/gemini.py +385 -0
- hud/tools/computer/hud.py +23 -6
- hud/tools/computer/openai.py +20 -21
- hud/tools/computer/qwen.py +434 -0
- hud/tools/computer/settings.py +37 -0
- hud/tools/edit.py +3 -7
- hud/tools/executors/base.py +4 -2
- hud/tools/executors/pyautogui.py +1 -1
- hud/tools/grounding/grounded_tool.py +13 -18
- hud/tools/grounding/grounder.py +10 -31
- hud/tools/grounding/tests/test_grounded_tool.py +26 -44
- hud/tools/jupyter.py +330 -0
- hud/tools/playwright.py +18 -3
- hud/tools/shell.py +308 -0
- hud/tools/tests/test_agent_tool.py +355 -0
- hud/tools/tests/test_apply_patch.py +718 -0
- hud/tools/tests/test_computer.py +4 -9
- hud/tools/tests/test_computer_actions.py +24 -2
- hud/tools/tests/test_jupyter_tool.py +181 -0
- hud/tools/tests/test_shell.py +596 -0
- hud/tools/tests/test_submit.py +85 -0
- hud/tools/tests/test_types.py +193 -0
- hud/tools/types.py +21 -1
- hud/types.py +194 -56
- hud/utils/__init__.py +2 -0
- hud/utils/env.py +67 -0
- hud/utils/hud_console.py +89 -18
- hud/utils/mcp.py +15 -58
- hud/utils/strict_schema.py +162 -0
- hud/utils/tests/test_init.py +1 -2
- hud/utils/tests/test_mcp.py +1 -28
- hud/utils/tests/test_pretty_errors.py +186 -0
- hud/utils/tests/test_tool_shorthand.py +154 -0
- hud/utils/tests/test_version.py +1 -1
- hud/utils/types.py +20 -0
- hud/version.py +1 -1
- hud_python-0.5.13.dist-info/METADATA +264 -0
- hud_python-0.5.13.dist-info/RECORD +305 -0
- {hud_python-0.4.45.dist-info → hud_python-0.5.13.dist-info}/WHEEL +1 -1
- hud/agents/langchain.py +0 -261
- hud/agents/lite_llm.py +0 -72
- hud/cli/rl/__init__.py +0 -180
- hud/cli/rl/config.py +0 -101
- hud/cli/rl/display.py +0 -133
- hud/cli/rl/gpu.py +0 -63
- hud/cli/rl/gpu_utils.py +0 -321
- hud/cli/rl/local_runner.py +0 -595
- hud/cli/rl/presets.py +0 -96
- hud/cli/rl/remote_runner.py +0 -463
- hud/cli/rl/rl_api.py +0 -150
- hud/cli/rl/vllm.py +0 -177
- hud/cli/rl/wait_utils.py +0 -89
- hud/datasets/parallel.py +0 -687
- hud/misc/__init__.py +0 -1
- hud/misc/claude_plays_pokemon.py +0 -292
- hud/otel/__init__.py +0 -35
- hud/otel/collector.py +0 -142
- hud/otel/config.py +0 -181
- hud/otel/context.py +0 -570
- hud/otel/exporters.py +0 -369
- hud/otel/instrumentation.py +0 -135
- hud/otel/processors.py +0 -121
- hud/otel/tests/__init__.py +0 -1
- hud/otel/tests/test_processors.py +0 -197
- hud/rl/README.md +0 -30
- hud/rl/__init__.py +0 -1
- hud/rl/actor.py +0 -176
- hud/rl/buffer.py +0 -405
- hud/rl/chat_template.jinja +0 -101
- hud/rl/config.py +0 -192
- hud/rl/distributed.py +0 -132
- hud/rl/learner.py +0 -637
- hud/rl/tests/__init__.py +0 -1
- hud/rl/tests/test_learner.py +0 -186
- hud/rl/train.py +0 -382
- hud/rl/types.py +0 -101
- hud/rl/utils/start_vllm_server.sh +0 -30
- hud/rl/utils.py +0 -524
- hud/rl/vllm_adapter.py +0 -143
- hud/telemetry/job.py +0 -352
- hud/telemetry/replay.py +0 -74
- hud/telemetry/tests/test_replay.py +0 -40
- hud/telemetry/tests/test_trace.py +0 -63
- hud/telemetry/trace.py +0 -158
- hud/utils/agent_factories.py +0 -86
- hud/utils/async_utils.py +0 -65
- hud/utils/group_eval.py +0 -223
- hud/utils/progress.py +0 -149
- hud/utils/tasks.py +0 -127
- hud/utils/tests/test_async_utils.py +0 -173
- hud/utils/tests/test_progress.py +0 -261
- hud_python-0.4.45.dist-info/METADATA +0 -552
- hud_python-0.4.45.dist-info/RECORD +0 -228
- {hud_python-0.4.45.dist-info → hud_python-0.5.13.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.45.dist-info → hud_python-0.5.13.dist-info}/licenses/LICENSE +0 -0
hud/otel/context.py
DELETED
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
"""OpenTelemetry context utilities for HUD telemetry.
|
|
2
|
-
|
|
3
|
-
This module provides internal utilities for managing OpenTelemetry contexts.
|
|
4
|
-
User-facing APIs are in hud.telemetry.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import contextvars
|
|
10
|
-
import logging
|
|
11
|
-
from contextlib import contextmanager
|
|
12
|
-
from typing import TYPE_CHECKING, Any
|
|
13
|
-
|
|
14
|
-
from opentelemetry import baggage, context
|
|
15
|
-
from opentelemetry import trace as otel_trace
|
|
16
|
-
from opentelemetry.trace import Status, StatusCode
|
|
17
|
-
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
from collections.abc import Generator
|
|
20
|
-
from types import TracebackType
|
|
21
|
-
|
|
22
|
-
from hud.settings import settings
|
|
23
|
-
from hud.shared import make_request, make_request_sync
|
|
24
|
-
from hud.utils.async_utils import fire_and_forget
|
|
25
|
-
|
|
26
|
-
logger = logging.getLogger(__name__)
|
|
27
|
-
|
|
28
|
-
# Context variables for task tracking
|
|
29
|
-
current_task_run_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
30
|
-
"current_task_run_id", default=None
|
|
31
|
-
)
|
|
32
|
-
is_root_trace_var: contextvars.ContextVar[bool] = contextvars.ContextVar(
|
|
33
|
-
"is_root_trace", default=False
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
# Step counters for different types
|
|
37
|
-
current_base_mcp_steps: contextvars.ContextVar[int] = contextvars.ContextVar(
|
|
38
|
-
"current_base_mcp_steps", default=0
|
|
39
|
-
)
|
|
40
|
-
current_mcp_tool_steps: contextvars.ContextVar[int] = contextvars.ContextVar(
|
|
41
|
-
"current_mcp_tool_steps", default=0
|
|
42
|
-
)
|
|
43
|
-
current_agent_steps: contextvars.ContextVar[int] = contextvars.ContextVar(
|
|
44
|
-
"current_agent_steps", default=0
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# Keys for OpenTelemetry baggage
|
|
48
|
-
TASK_RUN_ID_KEY = "hud.task_run_id"
|
|
49
|
-
IS_ROOT_TRACE_KEY = "hud.is_root_trace"
|
|
50
|
-
BASE_MCP_STEPS_KEY = "hud.base_mcp_steps"
|
|
51
|
-
MCP_TOOL_STEPS_KEY = "hud.mcp_tool_steps"
|
|
52
|
-
AGENT_STEPS_KEY = "hud.agent_steps"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def set_current_task_run_id(task_run_id: str | None) -> contextvars.Token:
|
|
56
|
-
"""Set the current task run ID."""
|
|
57
|
-
return current_task_run_id.set(task_run_id)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def get_current_task_run_id() -> str | None:
|
|
61
|
-
"""Get current task_run_id from either contextvars or OTel baggage."""
|
|
62
|
-
# First try OTel baggage
|
|
63
|
-
task_run_id = baggage.get_baggage(TASK_RUN_ID_KEY)
|
|
64
|
-
if task_run_id and isinstance(task_run_id, str):
|
|
65
|
-
return task_run_id
|
|
66
|
-
|
|
67
|
-
# Fallback to contextvars
|
|
68
|
-
return current_task_run_id.get()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def is_root_trace() -> bool:
|
|
72
|
-
"""Check if current context is a root trace."""
|
|
73
|
-
# First try OTel baggage
|
|
74
|
-
is_root = baggage.get_baggage(IS_ROOT_TRACE_KEY)
|
|
75
|
-
if isinstance(is_root, str):
|
|
76
|
-
return is_root.lower() == "true"
|
|
77
|
-
|
|
78
|
-
# Fallback to contextvars
|
|
79
|
-
return is_root_trace_var.get()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def get_base_mcp_steps() -> int:
|
|
83
|
-
"""Get current base MCP step count from either contextvars or OTel baggage."""
|
|
84
|
-
# First try OTel baggage
|
|
85
|
-
step_count = baggage.get_baggage(BASE_MCP_STEPS_KEY)
|
|
86
|
-
if step_count and isinstance(step_count, str):
|
|
87
|
-
try:
|
|
88
|
-
return int(step_count)
|
|
89
|
-
except ValueError:
|
|
90
|
-
pass
|
|
91
|
-
|
|
92
|
-
# Fallback to contextvars
|
|
93
|
-
return current_base_mcp_steps.get()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def get_mcp_tool_steps() -> int:
|
|
97
|
-
"""Get current MCP tool step count from either contextvars or OTel baggage."""
|
|
98
|
-
# First try OTel baggage
|
|
99
|
-
step_count = baggage.get_baggage(MCP_TOOL_STEPS_KEY)
|
|
100
|
-
if step_count and isinstance(step_count, str):
|
|
101
|
-
try:
|
|
102
|
-
return int(step_count)
|
|
103
|
-
except ValueError:
|
|
104
|
-
pass
|
|
105
|
-
|
|
106
|
-
# Fallback to contextvars
|
|
107
|
-
return current_mcp_tool_steps.get()
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def get_agent_steps() -> int:
|
|
111
|
-
"""Get current agent step count from either contextvars or OTel baggage."""
|
|
112
|
-
# First try OTel baggage
|
|
113
|
-
step_count = baggage.get_baggage(AGENT_STEPS_KEY)
|
|
114
|
-
if step_count and isinstance(step_count, str):
|
|
115
|
-
try:
|
|
116
|
-
return int(step_count)
|
|
117
|
-
except ValueError:
|
|
118
|
-
pass
|
|
119
|
-
|
|
120
|
-
# Fallback to contextvars
|
|
121
|
-
return current_agent_steps.get()
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def increment_base_mcp_steps() -> int:
|
|
125
|
-
"""Increment the base MCP step count and update baggage.
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
The new base MCP step count after incrementing
|
|
129
|
-
"""
|
|
130
|
-
current = get_base_mcp_steps()
|
|
131
|
-
new_count = current + 1
|
|
132
|
-
|
|
133
|
-
# Update contextvar
|
|
134
|
-
current_base_mcp_steps.set(new_count)
|
|
135
|
-
|
|
136
|
-
# Update baggage for propagation
|
|
137
|
-
ctx = baggage.set_baggage(BASE_MCP_STEPS_KEY, str(new_count))
|
|
138
|
-
context.attach(ctx)
|
|
139
|
-
|
|
140
|
-
# Update current span if one exists
|
|
141
|
-
span = otel_trace.get_current_span()
|
|
142
|
-
if span and span.is_recording():
|
|
143
|
-
span.set_attribute("hud.base_mcp_steps", new_count)
|
|
144
|
-
|
|
145
|
-
return new_count
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def increment_mcp_tool_steps() -> int:
|
|
149
|
-
"""Increment the MCP tool step count and update baggage.
|
|
150
|
-
|
|
151
|
-
Returns:
|
|
152
|
-
The new MCP tool step count after incrementing
|
|
153
|
-
"""
|
|
154
|
-
current = get_mcp_tool_steps()
|
|
155
|
-
new_count = current + 1
|
|
156
|
-
|
|
157
|
-
# Update contextvar
|
|
158
|
-
current_mcp_tool_steps.set(new_count)
|
|
159
|
-
|
|
160
|
-
# Update baggage for propagation
|
|
161
|
-
ctx = baggage.set_baggage(MCP_TOOL_STEPS_KEY, str(new_count))
|
|
162
|
-
context.attach(ctx)
|
|
163
|
-
|
|
164
|
-
# Update current span if one exists
|
|
165
|
-
span = otel_trace.get_current_span()
|
|
166
|
-
if span and span.is_recording():
|
|
167
|
-
span.set_attribute("hud.mcp_tool_steps", new_count)
|
|
168
|
-
|
|
169
|
-
return new_count
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def increment_agent_steps() -> int:
|
|
173
|
-
"""Increment the agent step count and update baggage.
|
|
174
|
-
|
|
175
|
-
Returns:
|
|
176
|
-
The new agent step count after incrementing
|
|
177
|
-
"""
|
|
178
|
-
current = get_agent_steps()
|
|
179
|
-
new_count = current + 1
|
|
180
|
-
|
|
181
|
-
# Update contextvar
|
|
182
|
-
current_agent_steps.set(new_count)
|
|
183
|
-
|
|
184
|
-
# Update baggage for propagation
|
|
185
|
-
ctx = baggage.set_baggage(AGENT_STEPS_KEY, str(new_count))
|
|
186
|
-
context.attach(ctx)
|
|
187
|
-
|
|
188
|
-
# Update current span if one exists
|
|
189
|
-
span = otel_trace.get_current_span()
|
|
190
|
-
if span and span.is_recording():
|
|
191
|
-
span.set_attribute("hud.agent_steps", new_count)
|
|
192
|
-
|
|
193
|
-
return new_count
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@contextmanager
|
|
197
|
-
def span_context(
|
|
198
|
-
name: str,
|
|
199
|
-
attributes: dict[str, Any] | None = None,
|
|
200
|
-
kind: otel_trace.SpanKind = otel_trace.SpanKind.INTERNAL,
|
|
201
|
-
) -> Generator[otel_trace.Span, None, None]:
|
|
202
|
-
"""Create a child span within the current trace context.
|
|
203
|
-
|
|
204
|
-
This is a simple wrapper around OpenTelemetry's span creation that
|
|
205
|
-
ensures the span inherits the current HUD context (task_run_id, etc).
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
name: Name for the span
|
|
209
|
-
attributes: Additional attributes to add to the span
|
|
210
|
-
kind: OpenTelemetry span kind
|
|
211
|
-
|
|
212
|
-
Example:
|
|
213
|
-
with span_context("process_data", {"items": 100}) as span:
|
|
214
|
-
# Process data...
|
|
215
|
-
span.set_attribute("processed", True)
|
|
216
|
-
"""
|
|
217
|
-
tracer = otel_trace.get_tracer("hud-sdk")
|
|
218
|
-
|
|
219
|
-
# Current task_run_id will be added by HudEnrichmentProcessor
|
|
220
|
-
with tracer.start_as_current_span(
|
|
221
|
-
name,
|
|
222
|
-
attributes=attributes,
|
|
223
|
-
kind=kind,
|
|
224
|
-
) as span:
|
|
225
|
-
yield span
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
async def _update_task_status_async(
|
|
229
|
-
task_run_id: str,
|
|
230
|
-
status: str,
|
|
231
|
-
job_id: str | None = None,
|
|
232
|
-
error_message: str | None = None,
|
|
233
|
-
trace_name: str | None = None,
|
|
234
|
-
task_id: str | None = None,
|
|
235
|
-
) -> None:
|
|
236
|
-
"""Async task status update."""
|
|
237
|
-
if not settings.telemetry_enabled:
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
try:
|
|
241
|
-
data: dict[str, Any] = {"status": status}
|
|
242
|
-
|
|
243
|
-
# Resolve effective job_id from explicit param, OTel baggage, or current job context
|
|
244
|
-
effective_job_id: str | None = job_id
|
|
245
|
-
if not effective_job_id:
|
|
246
|
-
bj = baggage.get_baggage("hud.job_id")
|
|
247
|
-
if isinstance(bj, str) and bj:
|
|
248
|
-
effective_job_id = bj
|
|
249
|
-
if not effective_job_id:
|
|
250
|
-
try:
|
|
251
|
-
from hud.telemetry.job import get_current_job # Local import to avoid cycles
|
|
252
|
-
|
|
253
|
-
current_job = get_current_job()
|
|
254
|
-
if current_job:
|
|
255
|
-
effective_job_id = current_job.id
|
|
256
|
-
except Exception:
|
|
257
|
-
effective_job_id = None
|
|
258
|
-
|
|
259
|
-
if effective_job_id:
|
|
260
|
-
data["job_id"] = effective_job_id
|
|
261
|
-
if error_message:
|
|
262
|
-
data["error_message"] = error_message
|
|
263
|
-
|
|
264
|
-
# Build metadata with trace name and step counts
|
|
265
|
-
metadata = {}
|
|
266
|
-
if trace_name:
|
|
267
|
-
metadata["trace_name"] = trace_name
|
|
268
|
-
|
|
269
|
-
# Include all three step counts in metadata
|
|
270
|
-
metadata["base_mcp_steps"] = get_base_mcp_steps()
|
|
271
|
-
metadata["mcp_tool_steps"] = get_mcp_tool_steps()
|
|
272
|
-
metadata["agent_steps"] = get_agent_steps()
|
|
273
|
-
|
|
274
|
-
if metadata:
|
|
275
|
-
data["metadata"] = metadata
|
|
276
|
-
|
|
277
|
-
if task_id:
|
|
278
|
-
data["task_id"] = task_id
|
|
279
|
-
|
|
280
|
-
await make_request(
|
|
281
|
-
method="POST",
|
|
282
|
-
url=f"{settings.hud_telemetry_url}/trace/{task_run_id}/status",
|
|
283
|
-
json=data,
|
|
284
|
-
api_key=settings.api_key,
|
|
285
|
-
)
|
|
286
|
-
logger.debug("Updated task %s status to %s", task_run_id, status)
|
|
287
|
-
except Exception as e:
|
|
288
|
-
# Suppress warnings about interpreter shutdown
|
|
289
|
-
if "interpreter shutdown" not in str(e):
|
|
290
|
-
logger.warning("Failed to update task status: %s", e)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def _fire_and_forget_status_update(
|
|
294
|
-
task_run_id: str,
|
|
295
|
-
status: str,
|
|
296
|
-
job_id: str | None = None,
|
|
297
|
-
error_message: str | None = None,
|
|
298
|
-
trace_name: str | None = None,
|
|
299
|
-
task_id: str | None = None,
|
|
300
|
-
) -> None:
|
|
301
|
-
"""Fire and forget status update - works in any context including Jupyter."""
|
|
302
|
-
fire_and_forget(
|
|
303
|
-
_update_task_status_async(task_run_id, status, job_id, error_message, trace_name, task_id),
|
|
304
|
-
f"update task {task_run_id} status to {status}",
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def _update_task_status_sync(
|
|
309
|
-
task_run_id: str,
|
|
310
|
-
status: str,
|
|
311
|
-
job_id: str | None = None,
|
|
312
|
-
error_message: str | None = None,
|
|
313
|
-
trace_name: str | None = None,
|
|
314
|
-
task_id: str | None = None,
|
|
315
|
-
) -> None:
|
|
316
|
-
"""Synchronous task status update."""
|
|
317
|
-
if not settings.telemetry_enabled:
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
try:
|
|
321
|
-
data: dict[str, Any] = {"status": status}
|
|
322
|
-
|
|
323
|
-
# Resolve effective job_id from explicit param, OTel baggage, or current job context
|
|
324
|
-
effective_job_id: str | None = job_id
|
|
325
|
-
if not effective_job_id:
|
|
326
|
-
bj = baggage.get_baggage("hud.job_id")
|
|
327
|
-
if isinstance(bj, str) and bj:
|
|
328
|
-
effective_job_id = bj
|
|
329
|
-
if not effective_job_id:
|
|
330
|
-
try:
|
|
331
|
-
from hud.telemetry.job import get_current_job # Local import to avoid cycles
|
|
332
|
-
|
|
333
|
-
current_job = get_current_job()
|
|
334
|
-
if current_job:
|
|
335
|
-
effective_job_id = current_job.id
|
|
336
|
-
except Exception:
|
|
337
|
-
effective_job_id = None
|
|
338
|
-
|
|
339
|
-
if effective_job_id:
|
|
340
|
-
data["job_id"] = effective_job_id
|
|
341
|
-
if error_message:
|
|
342
|
-
data["error_message"] = error_message
|
|
343
|
-
|
|
344
|
-
# Build metadata with trace name and step counts
|
|
345
|
-
metadata = {}
|
|
346
|
-
if trace_name:
|
|
347
|
-
metadata["trace_name"] = trace_name
|
|
348
|
-
|
|
349
|
-
# Include all three step counts in metadata
|
|
350
|
-
metadata["base_mcp_steps"] = get_base_mcp_steps()
|
|
351
|
-
metadata["mcp_tool_steps"] = get_mcp_tool_steps()
|
|
352
|
-
metadata["agent_steps"] = get_agent_steps()
|
|
353
|
-
|
|
354
|
-
if metadata:
|
|
355
|
-
data["metadata"] = metadata
|
|
356
|
-
|
|
357
|
-
if task_id:
|
|
358
|
-
data["task_id"] = task_id
|
|
359
|
-
|
|
360
|
-
make_request_sync(
|
|
361
|
-
method="POST",
|
|
362
|
-
url=f"{settings.hud_telemetry_url}/trace/{task_run_id}/status",
|
|
363
|
-
json=data,
|
|
364
|
-
api_key=settings.api_key,
|
|
365
|
-
)
|
|
366
|
-
logger.debug("Updated task %s status to %s", task_run_id, status)
|
|
367
|
-
except Exception as e:
|
|
368
|
-
# Suppress warnings about interpreter shutdown
|
|
369
|
-
if "interpreter shutdown" not in str(e):
|
|
370
|
-
logger.warning("Failed to update task status: %s", e)
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
def _print_trace_url(task_run_id: str) -> None:
|
|
374
|
-
"""Print the trace URL in a colorful box."""
|
|
375
|
-
# Only print HUD URL if HUD telemetry is enabled and has API key
|
|
376
|
-
if not (settings.telemetry_enabled and settings.api_key):
|
|
377
|
-
return
|
|
378
|
-
|
|
379
|
-
url = f"https://hud.so/trace/{task_run_id}"
|
|
380
|
-
header = "🚀 See your agent live at:"
|
|
381
|
-
|
|
382
|
-
# ANSI color codes
|
|
383
|
-
DIM = "\033[90m" # Dim/Gray for border (visible on both light and dark terminals)
|
|
384
|
-
GOLD = "\033[33m" # Gold/Yellow for URL
|
|
385
|
-
RESET = "\033[0m"
|
|
386
|
-
BOLD = "\033[1m"
|
|
387
|
-
|
|
388
|
-
# Calculate box width based on the longest line
|
|
389
|
-
box_width = max(len(url), len(header)) + 6
|
|
390
|
-
|
|
391
|
-
# Box drawing characters
|
|
392
|
-
top_border = "╔" + "═" * (box_width - 2) + "╗"
|
|
393
|
-
bottom_border = "╚" + "═" * (box_width - 2) + "╝"
|
|
394
|
-
divider = "╟" + "─" * (box_width - 2) + "╢"
|
|
395
|
-
|
|
396
|
-
# Center the content
|
|
397
|
-
header_padding = (box_width - len(header) - 2) // 2
|
|
398
|
-
url_padding = (box_width - len(url) - 2) // 2
|
|
399
|
-
|
|
400
|
-
# Print the box
|
|
401
|
-
print(f"\n{DIM}{top_border}{RESET}") # noqa: T201
|
|
402
|
-
print( # noqa: T201
|
|
403
|
-
f"{DIM}║{RESET}{' ' * header_padding}{header}{' ' * (box_width - len(header) - header_padding - 3)}{DIM}║{RESET}" # noqa: E501
|
|
404
|
-
)
|
|
405
|
-
print(f"{DIM}{divider}{RESET}") # noqa: T201
|
|
406
|
-
print( # noqa: T201
|
|
407
|
-
f"{DIM}║{RESET}{' ' * url_padding}{BOLD}{GOLD}{url}{RESET}{' ' * (box_width - len(url) - url_padding - 2)}{DIM}║{RESET}" # noqa: E501
|
|
408
|
-
)
|
|
409
|
-
print(f"{DIM}{bottom_border}{RESET}\n") # noqa: T201
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
def _print_trace_complete_url(task_run_id: str, error_occurred: bool = False) -> None:
|
|
413
|
-
"""Print the trace completion URL with appropriate messaging."""
|
|
414
|
-
# Only print HUD URL if HUD telemetry is enabled and has API key
|
|
415
|
-
if not (settings.telemetry_enabled and settings.api_key):
|
|
416
|
-
return
|
|
417
|
-
|
|
418
|
-
url = f"https://hud.so/trace/{task_run_id}"
|
|
419
|
-
|
|
420
|
-
# ANSI color codes
|
|
421
|
-
GREEN = "\033[92m"
|
|
422
|
-
RED = "\033[91m"
|
|
423
|
-
GOLD = "\033[33m"
|
|
424
|
-
RESET = "\033[0m"
|
|
425
|
-
DIM = "\033[2m"
|
|
426
|
-
BOLD = "\033[1m"
|
|
427
|
-
|
|
428
|
-
if error_occurred:
|
|
429
|
-
print( # noqa: T201
|
|
430
|
-
f"\n{RED}✗ Trace errored!{RESET} {DIM}More error details available at:{RESET} {BOLD}{GOLD}{url}{RESET}\n" # noqa: E501
|
|
431
|
-
)
|
|
432
|
-
else:
|
|
433
|
-
print(f"\n{GREEN}✓ Trace complete!{RESET} {DIM}View at:{RESET} {BOLD}{GOLD}{url}{RESET}\n") # noqa: T201
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
class trace:
|
|
437
|
-
"""Internal OpenTelemetry trace context manager.
|
|
438
|
-
|
|
439
|
-
This is the implementation class. Users should use hud.trace() instead.
|
|
440
|
-
"""
|
|
441
|
-
|
|
442
|
-
def __init__(
|
|
443
|
-
self,
|
|
444
|
-
task_run_id: str,
|
|
445
|
-
is_root: bool = True,
|
|
446
|
-
span_name: str = "hud.task",
|
|
447
|
-
attributes: dict[str, Any] | None = None,
|
|
448
|
-
job_id: str | None = None,
|
|
449
|
-
task_id: str | None = None,
|
|
450
|
-
) -> None:
|
|
451
|
-
self.task_run_id = task_run_id
|
|
452
|
-
self.job_id = job_id
|
|
453
|
-
self.task_id = task_id
|
|
454
|
-
self.is_root = is_root
|
|
455
|
-
self.span_name = span_name
|
|
456
|
-
self.attributes = attributes or {}
|
|
457
|
-
self._span: otel_trace.Span | None = None
|
|
458
|
-
self._span_manager: Any | None = None
|
|
459
|
-
self._otel_token: object | None = None
|
|
460
|
-
self._task_run_token = None
|
|
461
|
-
self._root_token = None
|
|
462
|
-
|
|
463
|
-
def __enter__(self) -> str:
|
|
464
|
-
"""Enter the trace context and return the task_run_id."""
|
|
465
|
-
# Set context variables
|
|
466
|
-
self._task_run_token = set_current_task_run_id(self.task_run_id)
|
|
467
|
-
self._root_token = is_root_trace_var.set(self.is_root)
|
|
468
|
-
|
|
469
|
-
# Set OpenTelemetry baggage for propagation
|
|
470
|
-
ctx = baggage.set_baggage(TASK_RUN_ID_KEY, self.task_run_id)
|
|
471
|
-
ctx = baggage.set_baggage(IS_ROOT_TRACE_KEY, str(self.is_root), context=ctx)
|
|
472
|
-
if self.job_id:
|
|
473
|
-
ctx = baggage.set_baggage("hud.job_id", self.job_id, context=ctx)
|
|
474
|
-
if self.task_id:
|
|
475
|
-
ctx = baggage.set_baggage("hud.task_id", self.task_id, context=ctx)
|
|
476
|
-
self._otel_token = context.attach(ctx)
|
|
477
|
-
|
|
478
|
-
# Start a span as current
|
|
479
|
-
tracer = otel_trace.get_tracer("hud-sdk")
|
|
480
|
-
span_attrs = {
|
|
481
|
-
"hud.task_run_id": self.task_run_id,
|
|
482
|
-
"hud.is_root_trace": self.is_root,
|
|
483
|
-
**self.attributes,
|
|
484
|
-
}
|
|
485
|
-
if self.job_id:
|
|
486
|
-
span_attrs["hud.job_id"] = self.job_id
|
|
487
|
-
if self.task_id:
|
|
488
|
-
span_attrs["hud.task_id"] = self.task_id
|
|
489
|
-
|
|
490
|
-
# Use start_as_current_span context manager
|
|
491
|
-
self._span_manager = tracer.start_as_current_span(
|
|
492
|
-
self.span_name,
|
|
493
|
-
attributes=span_attrs,
|
|
494
|
-
)
|
|
495
|
-
self._span = self._span_manager.__enter__()
|
|
496
|
-
|
|
497
|
-
# Update task status to running if root (only for HUD backend)
|
|
498
|
-
if self.is_root and settings.telemetry_enabled and settings.api_key:
|
|
499
|
-
_fire_and_forget_status_update(
|
|
500
|
-
self.task_run_id,
|
|
501
|
-
"running",
|
|
502
|
-
job_id=self.job_id,
|
|
503
|
-
trace_name=self.span_name,
|
|
504
|
-
task_id=self.task_id,
|
|
505
|
-
)
|
|
506
|
-
# Print the nice trace URL box (only if not part of a job)
|
|
507
|
-
if not self.job_id:
|
|
508
|
-
_print_trace_url(self.task_run_id)
|
|
509
|
-
|
|
510
|
-
logger.debug("Started HUD trace context for task_run_id=%s", self.task_run_id)
|
|
511
|
-
return self.task_run_id
|
|
512
|
-
|
|
513
|
-
def __exit__(
|
|
514
|
-
self,
|
|
515
|
-
exc_type: type[BaseException] | None,
|
|
516
|
-
exc_val: BaseException | None,
|
|
517
|
-
exc_tb: TracebackType | None,
|
|
518
|
-
) -> None:
|
|
519
|
-
"""Exit the trace context."""
|
|
520
|
-
# Update task status if root (only for HUD backend)
|
|
521
|
-
if self.is_root and settings.telemetry_enabled and settings.api_key:
|
|
522
|
-
if exc_type is not None:
|
|
523
|
-
# Use synchronous update to ensure it completes before process exit
|
|
524
|
-
_update_task_status_sync(
|
|
525
|
-
self.task_run_id,
|
|
526
|
-
"error",
|
|
527
|
-
job_id=self.job_id,
|
|
528
|
-
error_message=str(exc_val),
|
|
529
|
-
trace_name=self.span_name,
|
|
530
|
-
task_id=self.task_id,
|
|
531
|
-
)
|
|
532
|
-
# Print error completion message (only if not part of a job)
|
|
533
|
-
if not self.job_id:
|
|
534
|
-
_print_trace_complete_url(self.task_run_id, error_occurred=True)
|
|
535
|
-
else:
|
|
536
|
-
# Use synchronous update to ensure it completes before process exit
|
|
537
|
-
_update_task_status_sync(
|
|
538
|
-
self.task_run_id,
|
|
539
|
-
"completed",
|
|
540
|
-
job_id=self.job_id,
|
|
541
|
-
trace_name=self.span_name,
|
|
542
|
-
task_id=self.task_id,
|
|
543
|
-
)
|
|
544
|
-
# Print success completion message (only if not part of a job)
|
|
545
|
-
if not self.job_id:
|
|
546
|
-
_print_trace_complete_url(self.task_run_id, error_occurred=False)
|
|
547
|
-
|
|
548
|
-
# End the span
|
|
549
|
-
if self._span and self._span_manager is not None:
|
|
550
|
-
if exc_type is not None and exc_val is not None:
|
|
551
|
-
self._span.record_exception(exc_val)
|
|
552
|
-
self._span.set_status(Status(StatusCode.ERROR, str(exc_val)))
|
|
553
|
-
else:
|
|
554
|
-
self._span.set_status(Status(StatusCode.OK))
|
|
555
|
-
self._span_manager.__exit__(exc_type, exc_val, exc_tb)
|
|
556
|
-
|
|
557
|
-
# Detach OpenTelemetry context
|
|
558
|
-
if self._otel_token is not None:
|
|
559
|
-
try:
|
|
560
|
-
context.detach(self._otel_token) # type: ignore[arg-type]
|
|
561
|
-
except Exception:
|
|
562
|
-
logger.warning("Failed to detach OpenTelemetry context")
|
|
563
|
-
|
|
564
|
-
# Reset context variables
|
|
565
|
-
if self._task_run_token is not None:
|
|
566
|
-
current_task_run_id.reset(self._task_run_token) # type: ignore
|
|
567
|
-
if self._root_token is not None:
|
|
568
|
-
is_root_trace_var.reset(self._root_token) # type: ignore
|
|
569
|
-
|
|
570
|
-
logger.debug("Ended HUD trace context for task_run_id=%s", self.task_run_id)
|