hud 0.6.18.dev1__tar.gz → 0.6.18.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/PKG-INFO +1 -1
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/agent.py +3 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_gemini_agent.py +16 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_tool_agent.py +17 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tool_agent.py +5 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tools/ssh.py +1 -1
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/types.py +1 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/eval.py +17 -8
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_eval.py +269 -3
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/__init__.py +21 -7
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/namespace.py +77 -29
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_loader.py +140 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_workspace.py +96 -1
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/utils.py +16 -2
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/workspace.py +66 -58
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/local.py +25 -23
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_local_runtime.py +114 -3
- hud-0.6.18.dev2/hud/utils/modules.py +122 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/process.py +57 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/version.py +1 -1
- hud-0.6.18.dev1/hud/utils/modules.py +0 -79
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/.gitignore +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/LICENSE +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/README.md +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/__main__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/browser_use/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/browser_use/agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/sdk/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/sdk/agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/sdk/computer_mcp.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/coding.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/computer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/hosted.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/mcp_proxy.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/settings.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/claude/tools/tests/test_computer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/settings.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/coding.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/computer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/filesystem.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/hosted.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/mcp_proxy.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/gemini/tools/tests/test_computer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/misc/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/misc/response_automation.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/apply_patch.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/coding.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/computer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/hosted.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/mcp_proxy.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/strict_schema.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/tests/test_computer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai/tools/tests/test_strict_schema.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai_compatible/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai_compatible/agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/filesystem.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/openai_compatible/tools/mcp_proxy.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/robot/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/robot/adapter.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/robot/agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/robot/batching.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/robot/dataset.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/robot/model.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_apply_patch.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_claude_agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_claude_sdk_agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_openai_agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_openai_compatible_agent.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_provider_native_tools.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_robot_dataset_writer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tests/test_trace.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tools/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tools/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tools/hosted.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tools/mcp.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/agents/tools/rfb.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/cdp.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/mcp.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/rfb.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/robot.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/ssh.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/tests/test_rfb.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/capabilities/tests/test_ssh.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/__main__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/deploy.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/init.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/jobs.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/models.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/project.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/qa.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/serve.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/sync.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/task.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_cli.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_deploy.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_init.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_jobs.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_main.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_project.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_qa.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_sync.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_task.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/tests/test_trace.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/cli/trace.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/clients/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/clients/client.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/clients/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/clients/tests/test_connect.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/conftest.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/arguments.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/egress.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/env.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/file_tracker.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/robot/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/robot/bridge.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/robot/endpoint.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/robot/gym.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/server.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/conftest.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_arguments.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_capability_backing.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_file_tracker.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_file_tracking.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_manifest.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_platform.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_robot_regressions.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_server.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_sessions.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/environment/tests/test_tunnel.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/chat.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/docker-seccomp.json +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/file_tracking.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/job.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/run.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/compose.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/core.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/daytona.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/docker.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/hosted.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/hud.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/runtime/modal.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/sync.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/task.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/taskset.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_chat.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_docker_provider.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_file_tracking_observer.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_hosted.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_job.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_rollout.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_shared.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_sync.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/eval/tests/test_task.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/graders/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/graders/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/graders/bash.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/graders/combine.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/graders/judge.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/graders/results.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/graders/text.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/adapt.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/env.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/export.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/install.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/conftest.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/environment/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/instruction.md +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/solution/solve.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/task.toml +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/agent-lifecycle/tests/test.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/docker-compose.yaml +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/environment/mcp-server/server.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/instruction.md +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/solution/solve.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/task.toml +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/hello-mcp/tests/test.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/environment/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/environment/entrypoint.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/instruction.md +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/solution/solve.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/task.toml +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/phase-boundary/tests/test.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/environment/docker-compose.yaml +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/instruction.md +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/solution/solve.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/task.toml +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/sidecar-reachability/tests/test.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/Dockerfile +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/environment/entrypoint.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/instruction.md +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/solution/solve.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/task.toml +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/tasks/verifier-lifecycle/tests/test.sh +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/test_contract.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/test_harbor.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/integrations/harbor/tests/test_integration.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/patches/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/patches/mcp_patches.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/patches/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/patches/tests/test_warnings.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/patches/warnings.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/py.typed +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/settings.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/context.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/exporter.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/instrument.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/robot/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/robot/recorder.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/robot/video.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/span.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/tests/test_exporter.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/telemetry/tests/test_instrument.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/train/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/train/base.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/train/client.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/train/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/train/tests/test_client.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/train/types.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/types.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/docker.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/exceptions.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/gateway.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/hints.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/hud_console.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/naming.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/platform.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/requests.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/serialization.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/__init__.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_exceptions.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_gateway.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_hints.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_hud_console.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_naming.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_platform.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_process.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_requests.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/tests/test_serialization.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/hud/utils/time.py +0 -0
- {hud-0.6.18.dev1 → hud-0.6.18.dev2}/pyproject.toml +0 -0
|
@@ -224,6 +224,9 @@ class GeminiAgent(ToolAgent[genai_types.Content, GeminiConfig]):
|
|
|
224
224
|
|
|
225
225
|
if candidate.finish_reason is not None:
|
|
226
226
|
result.finish_reason = candidate.finish_reason.name
|
|
227
|
+
if result.finish_reason == "MALFORMED_FUNCTION_CALL":
|
|
228
|
+
result.error = "Provider returned a malformed function call"
|
|
229
|
+
result.stop_reason = "malformed_tool_call"
|
|
227
230
|
|
|
228
231
|
return result
|
|
229
232
|
|
|
@@ -145,3 +145,19 @@ def test_grounding_citations() -> None:
|
|
|
145
145
|
assert len(cites) == 1
|
|
146
146
|
assert cites[0].source == "http://x"
|
|
147
147
|
assert cites[0].type == "grounding"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def test_malformed_function_call_is_a_normalized_error() -> None:
|
|
151
|
+
response = _api_response(
|
|
152
|
+
SimpleNamespace(
|
|
153
|
+
content=None,
|
|
154
|
+
grounding_metadata=None,
|
|
155
|
+
finish_reason=SimpleNamespace(name="MALFORMED_FUNCTION_CALL"),
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
agent = _agent(response)
|
|
159
|
+
result = await agent.get_response(_state(agent))
|
|
160
|
+
assert result.error == "Provider returned a malformed function call"
|
|
161
|
+
assert result.stop_reason == "malformed_tool_call"
|
|
162
|
+
assert result.finish_reason == "MALFORMED_FUNCTION_CALL"
|
|
163
|
+
assert result.usage is not None
|
|
@@ -788,3 +788,20 @@ async def test_loop_stops_on_length_when_configured() -> None:
|
|
|
788
788
|
assert run.trace.stop_reason == "length"
|
|
789
789
|
assert run.trace.is_truncated is True
|
|
790
790
|
assert all(not isinstance(step, ToolStep) for step in run.trace.steps)
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
@pytest.mark.parametrize("auto_respond", [False, True])
|
|
794
|
+
@pytest.mark.parametrize("reason", [None, "malformed_tool_call"])
|
|
795
|
+
async def test_provider_error_stops_without_auto_response(
|
|
796
|
+
auto_respond: bool, reason: Literal["malformed_tool_call"] | None
|
|
797
|
+
) -> None:
|
|
798
|
+
agent = DictAgent(
|
|
799
|
+
[AgentStep(error="provider failure", stop_reason=reason)],
|
|
800
|
+
auto_respond=auto_respond,
|
|
801
|
+
)
|
|
802
|
+
run = cast("Run", _FakeRun())
|
|
803
|
+
await agent._loop(run, RunState(), max_steps=3)
|
|
804
|
+
assert run.trace.status == "error"
|
|
805
|
+
assert run.trace.stop_reason == reason
|
|
806
|
+
assert len(run.trace.steps) == 1
|
|
807
|
+
assert run.trace.steps[0].error == "provider failure"
|
|
@@ -253,6 +253,9 @@ class ToolAgent(Agent, Generic[MessageT, ConfigT]):
|
|
|
253
253
|
step.started_at = step.started_at or started_at
|
|
254
254
|
step.model = step.model or self.config.model
|
|
255
255
|
run.record(step)
|
|
256
|
+
if step.error:
|
|
257
|
+
stopped = step.stop_reason
|
|
258
|
+
break
|
|
256
259
|
|
|
257
260
|
if step.tool_calls:
|
|
258
261
|
logger.info(" → %s", ", ".join(c.name for c in step.tool_calls))
|
|
@@ -304,6 +307,8 @@ class ToolAgent(Agent, Generic[MessageT, ConfigT]):
|
|
|
304
307
|
trace.status = "error" if step is not None and step.error else "completed"
|
|
305
308
|
if stopped is not None:
|
|
306
309
|
trace.stop_reason = stopped
|
|
310
|
+
elif step is not None and step.error:
|
|
311
|
+
trace.stop_reason = None
|
|
307
312
|
elif hit_max:
|
|
308
313
|
trace.stop_reason = "max_steps"
|
|
309
314
|
elif step is not None and step.finish_reason in TRUNCATION_FINISH_REASONS:
|
|
@@ -65,7 +65,7 @@ class SSHTool(AgentTool[SSHClient]):
|
|
|
65
65
|
await self.client.write_text(path, content)
|
|
66
66
|
except asyncssh.ProcessError as e:
|
|
67
67
|
return tool_err(_remote_error(e))
|
|
68
|
-
return tool_ok(f"wrote {len(content)} bytes to {path}")
|
|
68
|
+
return tool_ok(f"wrote {len(content.encode('utf-8'))} bytes to {path}")
|
|
69
69
|
|
|
70
70
|
async def file_list(self, path: str = "/") -> MCPToolResult:
|
|
71
71
|
"""List directory entries through SSH exec."""
|
|
@@ -288,6 +288,7 @@ class AgentStep(Step):
|
|
|
288
288
|
#: No further tool calls expected — the loop's stop signal.
|
|
289
289
|
done: bool = False
|
|
290
290
|
finish_reason: str | None = None
|
|
291
|
+
stop_reason: StopCondition | None = None
|
|
291
292
|
refusal: str | None = None
|
|
292
293
|
citations: list[Citation] = Field(default_factory=list[Citation])
|
|
293
294
|
raw: Any | None = None
|
|
@@ -102,7 +102,7 @@ class EvalConfig(BaseModel):
|
|
|
102
102
|
group_size: int = 1
|
|
103
103
|
gateway: bool = False
|
|
104
104
|
#: ``LOCAL`` spawns each row's env (Docker for container rows, a subprocess
|
|
105
|
-
#:
|
|
105
|
+
#: loading the task source otherwise); other names hand rows to that
|
|
106
106
|
#: provider; a ``tcp://`` url attaches to an already-served env. ``None``
|
|
107
107
|
#: infers from the source: a file on disk runs locally, a platform taskset hosted.
|
|
108
108
|
runtime: Placement | TcpUrl | None = None
|
|
@@ -417,24 +417,33 @@ def eval_command(
|
|
|
417
417
|
case AnyUrl():
|
|
418
418
|
placement = Runtime(str(cfg.runtime))
|
|
419
419
|
case Placement.LOCAL:
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
420
|
+
rows = list(taskset)
|
|
421
|
+
rows.extend(
|
|
422
|
+
task.verifier
|
|
423
|
+
for task in taskset
|
|
424
|
+
if task.verifier is not None and not task.shares_verifier_runtime
|
|
425
|
+
)
|
|
426
|
+
if not Path(source).exists() and any(
|
|
427
|
+
not (
|
|
428
|
+
task.runtime_config
|
|
429
|
+
and (task.runtime_config.image or task.runtime_config.compose)
|
|
430
|
+
)
|
|
431
|
+
for task in rows
|
|
432
|
+
):
|
|
424
433
|
raise ValueError(
|
|
425
434
|
f"{source} is a platform taskset, so there is no env source to spawn "
|
|
426
435
|
"locally. Run it with --remote, --runtime hud, or --runtime tcp://host:port."
|
|
427
436
|
)
|
|
428
437
|
docker = DockerRuntime()
|
|
429
|
-
|
|
430
|
-
beside = SubprocessRuntime(
|
|
438
|
+
source_path = Path(source).resolve()
|
|
439
|
+
beside = SubprocessRuntime(source_path if source_path.is_dir() else source_path.parent)
|
|
431
440
|
|
|
432
441
|
def spawn(task: Task) -> AbstractAsyncContextManager[Runtime]:
|
|
433
442
|
config = task.runtime_config
|
|
434
443
|
if config and (config.image or config.compose):
|
|
435
444
|
return docker(task)
|
|
436
445
|
if task._env is not None:
|
|
437
|
-
return SubprocessRuntime(
|
|
446
|
+
return SubprocessRuntime(source_path)(task)
|
|
438
447
|
return beside(task)
|
|
439
448
|
|
|
440
449
|
placement = spawn
|
|
@@ -5,12 +5,14 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import sys
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
8
9
|
from typing import TYPE_CHECKING, Any
|
|
9
10
|
from unittest.mock import MagicMock
|
|
10
11
|
|
|
11
12
|
import pytest
|
|
12
13
|
from typer.testing import CliRunner
|
|
13
14
|
|
|
15
|
+
from hud.agents import OpenAIAgent
|
|
14
16
|
from hud.agents.types import ClaudeConfig
|
|
15
17
|
from hud.cli import eval as eval_mod
|
|
16
18
|
from hud.cli.__main__ import app
|
|
@@ -34,8 +36,7 @@ from hud.utils.gateway import GatewayModelInfo
|
|
|
34
36
|
from hud.utils.hud_console import HUDConsole
|
|
35
37
|
|
|
36
38
|
if TYPE_CHECKING:
|
|
37
|
-
from collections.abc import Callable
|
|
38
|
-
from pathlib import Path
|
|
39
|
+
from collections.abc import Callable, Iterator
|
|
39
40
|
|
|
40
41
|
_TASKS_PY = """\
|
|
41
42
|
from hud import Environment
|
|
@@ -405,7 +406,7 @@ def test_local_placement_routes_each_row(eval_cli: _EvalCli, tmp_path: Path, mon
|
|
|
405
406
|
assert eval_cli.taskset is not None
|
|
406
407
|
bound = next(iter(eval_cli.taskset))
|
|
407
408
|
|
|
408
|
-
assert placement(bound) is subprocesses[
|
|
409
|
+
assert placement(bound) is subprocesses[(tmp_path / "mixed.py").resolve()].return_value
|
|
409
410
|
assert placement(image) is docker.return_value
|
|
410
411
|
# A row without a bound env is served from the tasks file's directory.
|
|
411
412
|
assert placement(data_row) is subprocesses[tmp_path.resolve()].return_value
|
|
@@ -685,3 +686,268 @@ def test_result_payload_uses_job_metrics(eval_cli: _EvalCli) -> None:
|
|
|
685
686
|
assert payload["error_count"] == 1
|
|
686
687
|
assert [run["slug"] for run in payload["runs"]] == ["solve", None]
|
|
687
688
|
assert [run["is_error"] for run in payload["runs"]] == [False, False]
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@pytest.fixture
|
|
692
|
+
def local_eval(
|
|
693
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
694
|
+
) -> Iterator[Callable[..., dict[str, Any]]]:
|
|
695
|
+
async def answer(self: OpenAIAgent, run: Run) -> None:
|
|
696
|
+
run.trace.content = "ok"
|
|
697
|
+
|
|
698
|
+
monkeypatch.setattr(OpenAIAgent, "__call__", answer)
|
|
699
|
+
monkeypatch.setattr(settings, "openai_api_key", "test-provider-key")
|
|
700
|
+
monkeypatch.setenv("HUD_API_KEY", "")
|
|
701
|
+
monkeypatch.setenv("HUD_TELEMETRY_ENABLED", "false")
|
|
702
|
+
monkeypatch.chdir(tmp_path)
|
|
703
|
+
original = dict(sys.modules)
|
|
704
|
+
|
|
705
|
+
def invoke(source: Path | str, *flags: str) -> dict[str, Any]:
|
|
706
|
+
result = CliRunner().invoke(
|
|
707
|
+
app, ["eval", str(source), "openai", "--all", "--yes", "--json", *flags]
|
|
708
|
+
)
|
|
709
|
+
assert result.exit_code == 0, result.output
|
|
710
|
+
payload = json.loads(result.stdout)
|
|
711
|
+
assert payload["error_count"] == 0, result.output
|
|
712
|
+
assert payload["mean_reward"] == 1.0, result.output
|
|
713
|
+
return payload
|
|
714
|
+
|
|
715
|
+
yield invoke
|
|
716
|
+
for name, module in list(sys.modules.items()):
|
|
717
|
+
file = getattr(module, "__file__", None)
|
|
718
|
+
if file and Path(file).is_relative_to(tmp_path):
|
|
719
|
+
if name in original:
|
|
720
|
+
sys.modules[name] = original[name]
|
|
721
|
+
else:
|
|
722
|
+
sys.modules.pop(name, None)
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
@pytest.mark.parametrize(
|
|
726
|
+
"layout",
|
|
727
|
+
[
|
|
728
|
+
"single",
|
|
729
|
+
"standalone",
|
|
730
|
+
"split",
|
|
731
|
+
"hooks",
|
|
732
|
+
"hooks_source",
|
|
733
|
+
"assembled",
|
|
734
|
+
"assembled_source",
|
|
735
|
+
"json",
|
|
736
|
+
"jsonl",
|
|
737
|
+
"data_python",
|
|
738
|
+
"directory",
|
|
739
|
+
"assembled_directory",
|
|
740
|
+
"package",
|
|
741
|
+
"lazy_package",
|
|
742
|
+
],
|
|
743
|
+
)
|
|
744
|
+
def test_local_eval_project_layouts(
|
|
745
|
+
local_eval: Callable[..., dict[str, Any]], tmp_path: Path, layout: str
|
|
746
|
+
) -> None:
|
|
747
|
+
project = tmp_path / "project"
|
|
748
|
+
project.mkdir()
|
|
749
|
+
events = project / "events"
|
|
750
|
+
events.mkdir()
|
|
751
|
+
(project / "asset.txt").write_text("ok")
|
|
752
|
+
package = layout in {"package", "lazy_package"}
|
|
753
|
+
prefix = "." if package else ""
|
|
754
|
+
if package:
|
|
755
|
+
(project / "__init__.py").write_text("")
|
|
756
|
+
template = (
|
|
757
|
+
"from pathlib import Path\n"
|
|
758
|
+
'@env.template(id="solve")\n'
|
|
759
|
+
"async def solve():\n"
|
|
760
|
+
' answer = yield "answer ok"\n'
|
|
761
|
+
' yield 1.0 if answer == Path("asset.txt").read_text() else 0.0\n'
|
|
762
|
+
)
|
|
763
|
+
hooks = (
|
|
764
|
+
"import os\nfrom pathlib import Path\n"
|
|
765
|
+
"@env.initialize\nasync def start():\n"
|
|
766
|
+
' Path(f"events/{os.getpid()}").write_text("started")\n'
|
|
767
|
+
"@env.shutdown\nasync def stop():\n"
|
|
768
|
+
' Path(f"events/{os.getpid()}").write_text("stopped")\n'
|
|
769
|
+
)
|
|
770
|
+
if layout == "lazy_package":
|
|
771
|
+
(project / "expected.py").write_text(
|
|
772
|
+
'from pathlib import Path\nexpected = Path("asset.txt").read_text()\n'
|
|
773
|
+
)
|
|
774
|
+
template = template.replace(
|
|
775
|
+
' answer = yield "answer ok"\n',
|
|
776
|
+
' from .expected import expected\n answer = yield "answer ok"\n',
|
|
777
|
+
).replace('Path("asset.txt").read_text()', "expected")
|
|
778
|
+
if layout in {
|
|
779
|
+
"hooks",
|
|
780
|
+
"hooks_source",
|
|
781
|
+
"assembled",
|
|
782
|
+
"assembled_source",
|
|
783
|
+
"assembled_directory",
|
|
784
|
+
"package",
|
|
785
|
+
}:
|
|
786
|
+
(project / "local_core.py").write_text(
|
|
787
|
+
'from hud import Environment\nenv = Environment("local-test")\n'
|
|
788
|
+
)
|
|
789
|
+
(project / "local_templates.py").write_text(
|
|
790
|
+
f"from {prefix}local_core import env\n" + template
|
|
791
|
+
)
|
|
792
|
+
(project / "local_extra.py").write_text(
|
|
793
|
+
f"from {prefix}local_core import env\n"
|
|
794
|
+
'@env.template(id="extra")\nasync def extra():\n yield "extra"\n yield 1.0\n'
|
|
795
|
+
)
|
|
796
|
+
env_source = (
|
|
797
|
+
f"from {prefix}local_core import env\n"
|
|
798
|
+
f"from {prefix}local_templates import solve\n"
|
|
799
|
+
+ (f"from {prefix}local_extra import extra\n" if not layout.startswith("hooks") else "")
|
|
800
|
+
+ hooks
|
|
801
|
+
)
|
|
802
|
+
else:
|
|
803
|
+
env_source = (
|
|
804
|
+
'from hud import Environment\nenv = Environment("local-test")\n' + template + hooks
|
|
805
|
+
)
|
|
806
|
+
if layout in {"single", "standalone", "lazy_package", "hooks_source", "assembled_source"}:
|
|
807
|
+
env_source += "tasks = [solve()]\n"
|
|
808
|
+
source = project / "env.py"
|
|
809
|
+
else:
|
|
810
|
+
(project / "tasks.py").write_text(
|
|
811
|
+
f"from {prefix}env import env, solve\ntasks = [solve()]\n"
|
|
812
|
+
)
|
|
813
|
+
source = project / "tasks.py"
|
|
814
|
+
(project / "env.py").write_text(env_source)
|
|
815
|
+
if layout == "standalone":
|
|
816
|
+
source = project / "standalone.py"
|
|
817
|
+
source.write_text(env_source)
|
|
818
|
+
(project / "env.py").write_text(
|
|
819
|
+
'from hud import Environment\nenv = Environment("unrelated")\n'
|
|
820
|
+
)
|
|
821
|
+
if layout in {"json", "jsonl"}:
|
|
822
|
+
source = project / f"tasks.{layout}"
|
|
823
|
+
row = {"env": "local-test", "id": "solve"}
|
|
824
|
+
source.write_text(json.dumps([row] if layout == "json" else row))
|
|
825
|
+
if layout == "data_python":
|
|
826
|
+
source.write_text(
|
|
827
|
+
'from hud.eval import Task\ntasks = [Task(env="local-test", id="solve")]\n'
|
|
828
|
+
)
|
|
829
|
+
if layout in {"directory", "assembled_directory"}:
|
|
830
|
+
source = project
|
|
831
|
+
payload = local_eval(source, "--group", "2", "--max-concurrent", "2")
|
|
832
|
+
assert payload["run_count"] == 2
|
|
833
|
+
assert len(list(events.iterdir())) == 2
|
|
834
|
+
assert {event.read_text() for event in events.iterdir()} == {"stopped"}
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
@pytest.mark.parametrize(
|
|
838
|
+
"source,same_name",
|
|
839
|
+
[
|
|
840
|
+
("tasks.py", False),
|
|
841
|
+
("tasks.py", True),
|
|
842
|
+
(".", False),
|
|
843
|
+
(".", True),
|
|
844
|
+
("rows.json", False),
|
|
845
|
+
("rows.json", True),
|
|
846
|
+
("rows.jsonl", False),
|
|
847
|
+
],
|
|
848
|
+
)
|
|
849
|
+
def test_local_eval_environment_selection(
|
|
850
|
+
local_eval: Callable[..., dict[str, Any]], tmp_path: Path, source: str, same_name: bool
|
|
851
|
+
) -> None:
|
|
852
|
+
project = tmp_path / "project"
|
|
853
|
+
project.mkdir()
|
|
854
|
+
(project / "bound_env.py").write_text(
|
|
855
|
+
'from hud import Environment\nenv = Environment("bound")\n'
|
|
856
|
+
'@env.template(id="solve")\nasync def solve():\n'
|
|
857
|
+
' answer = yield "answer ok"\n yield float(answer == "ok")\n'
|
|
858
|
+
)
|
|
859
|
+
name = "bound" if same_name else "unrelated"
|
|
860
|
+
(project / "env.py").write_text(
|
|
861
|
+
f"from hud import Environment\nenv = Environment({name!r})\n"
|
|
862
|
+
'@env.template(id="solve")\nasync def solve():\n'
|
|
863
|
+
' yield "unrelated"\n yield 0.0\n'
|
|
864
|
+
)
|
|
865
|
+
(project / "tasks.py").write_text("from bound_env import solve\ntasks = [solve()]\n")
|
|
866
|
+
if source in {"rows.json", "rows.jsonl"}:
|
|
867
|
+
(project / source).write_text('{"env": "bound", "id": "solve"}\n')
|
|
868
|
+
if same_name and source != "tasks.py":
|
|
869
|
+
result = CliRunner().invoke(
|
|
870
|
+
app, ["eval", str(project / source), "openai", "--all", "--yes", "--json"]
|
|
871
|
+
)
|
|
872
|
+
assert result.exit_code == 0, result.output
|
|
873
|
+
assert json.loads(result.stdout)["error_count"] == 1
|
|
874
|
+
assert "multiple Environments" in result.output
|
|
875
|
+
else:
|
|
876
|
+
assert local_eval(project / source)["run_count"] == 1
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
@pytest.mark.parametrize("shared", [True, False])
|
|
880
|
+
def test_local_eval_places_nested_verifier(
|
|
881
|
+
local_eval: Callable[..., dict[str, Any]], tmp_path: Path, shared: bool
|
|
882
|
+
) -> None:
|
|
883
|
+
project = tmp_path / "project"
|
|
884
|
+
project.mkdir()
|
|
885
|
+
(project / "local_core.py").write_text(
|
|
886
|
+
'from hud import Environment\nactor = Environment("actor")\n'
|
|
887
|
+
+ ("judge = actor\n" if shared else 'judge = Environment("judge")\n')
|
|
888
|
+
)
|
|
889
|
+
(project / "local_actor.py").write_text(
|
|
890
|
+
"from local_core import actor\n"
|
|
891
|
+
'@actor.template(id="solve")\nasync def solve():\n'
|
|
892
|
+
' answer = yield "answer ok"\n yield {"score": 0.0, "answer": answer}\n'
|
|
893
|
+
)
|
|
894
|
+
(project / "local_judge.py").write_text(
|
|
895
|
+
"from local_core import judge\n"
|
|
896
|
+
'@judge.template(id="verify")\nasync def verify():\n'
|
|
897
|
+
' result = yield ""\n yield 1.0 if result["answer"] == "ok" else 0.0\n'
|
|
898
|
+
)
|
|
899
|
+
(project / "env.py").write_text(
|
|
900
|
+
"from local_core import actor, judge\n"
|
|
901
|
+
"from local_actor import solve\nfrom local_judge import verify\n"
|
|
902
|
+
)
|
|
903
|
+
(project / "tasks.py").write_text(
|
|
904
|
+
"from env import solve, verify\ntasks = [solve()]\ntasks[0].verifier = verify()\n"
|
|
905
|
+
)
|
|
906
|
+
assert local_eval(project / "tasks.py")["run_count"] == 1
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
@pytest.mark.parametrize("container", ["image", "compose"])
|
|
910
|
+
def test_platform_container_rows_can_use_local_placement(
|
|
911
|
+
eval_cli: _EvalCli, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, container: str
|
|
912
|
+
) -> None:
|
|
913
|
+
compose = tmp_path / "compose.yaml"
|
|
914
|
+
compose.write_text("services:\n main:\n image: example:latest\n")
|
|
915
|
+
config = (
|
|
916
|
+
RuntimeConfig(image="example:latest")
|
|
917
|
+
if container == "image"
|
|
918
|
+
else RuntimeConfig.model_validate(
|
|
919
|
+
{"compose": {"document": str(compose), "root": str(tmp_path)}}
|
|
920
|
+
)
|
|
921
|
+
)
|
|
922
|
+
task = Task(env="demo", id="solve", runtime_config=config)
|
|
923
|
+
monkeypatch.setattr(Taskset, "from_api", classmethod(lambda cls, name: Taskset(name, [task])))
|
|
924
|
+
docker = MagicMock()
|
|
925
|
+
monkeypatch.setattr(eval_mod, "DockerRuntime", lambda: docker)
|
|
926
|
+
eval_cli.invoke("Platform Tasks", "openai", "--runtime", "local", "--yes")
|
|
927
|
+
assert eval_cli.kwargs["runtime"](task) is docker.return_value
|
|
928
|
+
docker.assert_called_once_with(task)
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
@pytest.mark.parametrize("shared,configured", [(True, False), (False, True), (False, False)])
|
|
932
|
+
def test_platform_local_placement_checks_separate_verifier(
|
|
933
|
+
eval_cli: _EvalCli, monkeypatch: pytest.MonkeyPatch, shared: bool, configured: bool
|
|
934
|
+
) -> None:
|
|
935
|
+
config = RuntimeConfig(image="example:latest")
|
|
936
|
+
verifier = Task(
|
|
937
|
+
env="actor" if shared else "judge",
|
|
938
|
+
id="verify",
|
|
939
|
+
runtime_config=config if configured else None,
|
|
940
|
+
)
|
|
941
|
+
task = Task(env="actor", id="solve", runtime_config=config, verifier=verifier)
|
|
942
|
+
monkeypatch.setattr(Taskset, "from_api", classmethod(lambda cls, name: Taskset(name, [task])))
|
|
943
|
+
payload = eval_cli.invoke(
|
|
944
|
+
"Platform Tasks",
|
|
945
|
+
"openai",
|
|
946
|
+
"--runtime",
|
|
947
|
+
"local",
|
|
948
|
+
"--yes",
|
|
949
|
+
exit_code=0 if shared or configured else 2,
|
|
950
|
+
)
|
|
951
|
+
if not shared and not configured:
|
|
952
|
+
assert "no env source to spawn locally" in payload["message"]
|
|
953
|
+
assert eval_cli.taskset is None
|
|
@@ -37,8 +37,9 @@ def load_environment(
|
|
|
37
37
|
"""Resolve an environment reference to the one :class:`Environment` it names.
|
|
38
38
|
|
|
39
39
|
A source path (``.py`` file or directory) is scanned for ``Environment``
|
|
40
|
-
instances; *name* selects among
|
|
41
|
-
|
|
40
|
+
instances and environments bound to exported tasks; *name* selects among
|
|
41
|
+
them, matching the module attribute or ``Environment.name``.
|
|
42
|
+
A dotted importable module reads attribute *name*
|
|
42
43
|
(default ``env``): an ``Environment`` directly, or a factory called with
|
|
43
44
|
*args* to build one — how programmatic envs (integrations, adapted
|
|
44
45
|
images) are served without a source file. Raises when nothing resolves.
|
|
@@ -63,19 +64,32 @@ def load_environment(
|
|
|
63
64
|
and (path / "__init__.py").is_file()
|
|
64
65
|
)
|
|
65
66
|
if path.exists() and not package_attribute:
|
|
67
|
+
# Avoid the environment -> eval -> runtime import cycle.
|
|
68
|
+
from hud.eval.taskset import Taskset
|
|
69
|
+
|
|
66
70
|
if args:
|
|
67
71
|
raise ValueError(f"args= applies to factory targets, not source path {target}")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
modules = list(iter_modules(path))
|
|
73
|
+
matched = {
|
|
74
|
+
id(env): env
|
|
75
|
+
for module in modules
|
|
71
76
|
for attr, env in vars(module).items()
|
|
72
77
|
if isinstance(env, Environment) and (name is None or name in (attr, env.name))
|
|
73
|
-
|
|
78
|
+
}
|
|
79
|
+
for module in modules:
|
|
80
|
+
for task in Taskset._scan_tasks(module):
|
|
81
|
+
for row in (task, task.verifier):
|
|
82
|
+
if (
|
|
83
|
+
row is not None
|
|
84
|
+
and (env := row._env) is not None
|
|
85
|
+
and (name is None or name == env.name)
|
|
86
|
+
):
|
|
87
|
+
matched[id(env)] = env
|
|
74
88
|
if not matched:
|
|
75
89
|
raise ValueError(f"no Environment{f' named {name!r}' if name else ''} found in {path}")
|
|
76
90
|
if len(matched) > 1:
|
|
77
91
|
raise ValueError(f"multiple Environments in {path}; select one by name")
|
|
78
|
-
return matched
|
|
92
|
+
return next(iter(matched.values()))
|
|
79
93
|
|
|
80
94
|
if path.is_file() or "/" in str(target):
|
|
81
95
|
raise FileNotFoundError(f"no environment source at {target}")
|