hud-python 0.4.45__py3-none-any.whl → 0.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. hud/__init__.py +27 -7
  2. hud/agents/__init__.py +11 -5
  3. hud/agents/base.py +220 -500
  4. hud/agents/claude.py +200 -240
  5. hud/agents/gemini.py +275 -0
  6. hud/agents/gemini_cua.py +335 -0
  7. hud/agents/grounded_openai.py +98 -100
  8. hud/agents/misc/integration_test_agent.py +51 -20
  9. hud/agents/misc/response_agent.py +41 -36
  10. hud/agents/openai.py +291 -292
  11. hud/agents/{openai_chat_generic.py → openai_chat.py} +80 -34
  12. hud/agents/operator.py +211 -0
  13. hud/agents/tests/conftest.py +133 -0
  14. hud/agents/tests/test_base.py +300 -622
  15. hud/agents/tests/test_base_runtime.py +233 -0
  16. hud/agents/tests/test_claude.py +379 -210
  17. hud/agents/tests/test_client.py +9 -10
  18. hud/agents/tests/test_gemini.py +369 -0
  19. hud/agents/tests/test_grounded_openai_agent.py +65 -50
  20. hud/agents/tests/test_openai.py +376 -140
  21. hud/agents/tests/test_operator.py +362 -0
  22. hud/agents/tests/test_run_eval.py +179 -0
  23. hud/cli/__init__.py +461 -545
  24. hud/cli/analyze.py +43 -5
  25. hud/cli/build.py +664 -110
  26. hud/cli/debug.py +8 -5
  27. hud/cli/dev.py +882 -734
  28. hud/cli/eval.py +782 -668
  29. hud/cli/flows/dev.py +167 -0
  30. hud/cli/flows/init.py +191 -0
  31. hud/cli/flows/tasks.py +153 -56
  32. hud/cli/flows/templates.py +151 -0
  33. hud/cli/flows/tests/__init__.py +1 -0
  34. hud/cli/flows/tests/test_dev.py +126 -0
  35. hud/cli/init.py +60 -58
  36. hud/cli/push.py +29 -11
  37. hud/cli/rft.py +311 -0
  38. hud/cli/rft_status.py +145 -0
  39. hud/cli/tests/test_analyze.py +5 -5
  40. hud/cli/tests/test_analyze_metadata.py +3 -2
  41. hud/cli/tests/test_analyze_module.py +120 -0
  42. hud/cli/tests/test_build.py +108 -6
  43. hud/cli/tests/test_build_failure.py +41 -0
  44. hud/cli/tests/test_build_module.py +50 -0
  45. hud/cli/tests/test_cli_init.py +6 -1
  46. hud/cli/tests/test_cli_more_wrappers.py +30 -0
  47. hud/cli/tests/test_cli_root.py +140 -0
  48. hud/cli/tests/test_convert.py +361 -0
  49. hud/cli/tests/test_debug.py +12 -10
  50. hud/cli/tests/test_dev.py +197 -0
  51. hud/cli/tests/test_eval.py +251 -0
  52. hud/cli/tests/test_eval_bedrock.py +51 -0
  53. hud/cli/tests/test_init.py +124 -0
  54. hud/cli/tests/test_main_module.py +11 -5
  55. hud/cli/tests/test_mcp_server.py +12 -100
  56. hud/cli/tests/test_push_happy.py +74 -0
  57. hud/cli/tests/test_push_wrapper.py +23 -0
  58. hud/cli/tests/test_registry.py +1 -1
  59. hud/cli/tests/test_utils.py +1 -1
  60. hud/cli/{rl → utils}/celebrate.py +14 -12
  61. hud/cli/utils/config.py +18 -1
  62. hud/cli/utils/docker.py +130 -4
  63. hud/cli/utils/env_check.py +9 -9
  64. hud/cli/utils/git.py +136 -0
  65. hud/cli/utils/interactive.py +39 -5
  66. hud/cli/utils/metadata.py +69 -0
  67. hud/cli/utils/runner.py +1 -1
  68. hud/cli/utils/server.py +2 -2
  69. hud/cli/utils/source_hash.py +3 -3
  70. hud/cli/utils/tasks.py +4 -1
  71. hud/cli/utils/tests/__init__.py +0 -0
  72. hud/cli/utils/tests/test_config.py +58 -0
  73. hud/cli/utils/tests/test_docker.py +93 -0
  74. hud/cli/utils/tests/test_docker_hints.py +71 -0
  75. hud/cli/utils/tests/test_env_check.py +74 -0
  76. hud/cli/utils/tests/test_environment.py +42 -0
  77. hud/cli/utils/tests/test_git.py +142 -0
  78. hud/cli/utils/tests/test_interactive_module.py +60 -0
  79. hud/cli/utils/tests/test_local_runner.py +50 -0
  80. hud/cli/utils/tests/test_logging_utils.py +23 -0
  81. hud/cli/utils/tests/test_metadata.py +49 -0
  82. hud/cli/utils/tests/test_package_runner.py +35 -0
  83. hud/cli/utils/tests/test_registry_utils.py +49 -0
  84. hud/cli/utils/tests/test_remote_runner.py +25 -0
  85. hud/cli/utils/tests/test_runner_modules.py +52 -0
  86. hud/cli/utils/tests/test_source_hash.py +36 -0
  87. hud/cli/utils/tests/test_tasks.py +80 -0
  88. hud/cli/utils/version_check.py +258 -0
  89. hud/cli/{rl → utils}/viewer.py +2 -2
  90. hud/clients/README.md +12 -11
  91. hud/clients/__init__.py +4 -3
  92. hud/clients/base.py +166 -26
  93. hud/clients/environment.py +51 -0
  94. hud/clients/fastmcp.py +13 -6
  95. hud/clients/mcp_use.py +40 -15
  96. hud/clients/tests/test_analyze_scenarios.py +206 -0
  97. hud/clients/tests/test_protocol.py +9 -3
  98. hud/datasets/__init__.py +23 -20
  99. hud/datasets/loader.py +327 -0
  100. hud/datasets/runner.py +192 -105
  101. hud/datasets/tests/__init__.py +0 -0
  102. hud/datasets/tests/test_loader.py +221 -0
  103. hud/datasets/tests/test_utils.py +315 -0
  104. hud/datasets/utils.py +270 -90
  105. hud/environment/__init__.py +50 -0
  106. hud/environment/connection.py +206 -0
  107. hud/environment/connectors/__init__.py +33 -0
  108. hud/environment/connectors/base.py +68 -0
  109. hud/environment/connectors/local.py +177 -0
  110. hud/environment/connectors/mcp_config.py +109 -0
  111. hud/environment/connectors/openai.py +101 -0
  112. hud/environment/connectors/remote.py +172 -0
  113. hud/environment/environment.py +694 -0
  114. hud/environment/integrations/__init__.py +45 -0
  115. hud/environment/integrations/adk.py +67 -0
  116. hud/environment/integrations/anthropic.py +196 -0
  117. hud/environment/integrations/gemini.py +92 -0
  118. hud/environment/integrations/langchain.py +82 -0
  119. hud/environment/integrations/llamaindex.py +68 -0
  120. hud/environment/integrations/openai.py +238 -0
  121. hud/environment/mock.py +306 -0
  122. hud/environment/router.py +112 -0
  123. hud/environment/scenarios.py +493 -0
  124. hud/environment/tests/__init__.py +1 -0
  125. hud/environment/tests/test_connection.py +317 -0
  126. hud/environment/tests/test_connectors.py +218 -0
  127. hud/environment/tests/test_environment.py +161 -0
  128. hud/environment/tests/test_integrations.py +257 -0
  129. hud/environment/tests/test_local_connectors.py +201 -0
  130. hud/environment/tests/test_scenarios.py +280 -0
  131. hud/environment/tests/test_tools.py +208 -0
  132. hud/environment/types.py +23 -0
  133. hud/environment/utils/__init__.py +35 -0
  134. hud/environment/utils/formats.py +215 -0
  135. hud/environment/utils/schema.py +171 -0
  136. hud/environment/utils/tool_wrappers.py +113 -0
  137. hud/eval/__init__.py +67 -0
  138. hud/eval/context.py +674 -0
  139. hud/eval/display.py +299 -0
  140. hud/eval/instrument.py +185 -0
  141. hud/eval/manager.py +466 -0
  142. hud/eval/parallel.py +268 -0
  143. hud/eval/task.py +340 -0
  144. hud/eval/tests/__init__.py +1 -0
  145. hud/eval/tests/test_context.py +178 -0
  146. hud/eval/tests/test_eval.py +210 -0
  147. hud/eval/tests/test_manager.py +152 -0
  148. hud/eval/tests/test_parallel.py +168 -0
  149. hud/eval/tests/test_task.py +145 -0
  150. hud/eval/types.py +63 -0
  151. hud/eval/utils.py +183 -0
  152. hud/patches/__init__.py +19 -0
  153. hud/patches/mcp_patches.py +151 -0
  154. hud/patches/warnings.py +54 -0
  155. hud/samples/browser.py +4 -4
  156. hud/server/__init__.py +2 -1
  157. hud/server/low_level.py +2 -1
  158. hud/server/router.py +164 -0
  159. hud/server/server.py +567 -80
  160. hud/server/tests/test_mcp_server_integration.py +11 -11
  161. hud/server/tests/test_mcp_server_more.py +1 -1
  162. hud/server/tests/test_server_extra.py +2 -0
  163. hud/settings.py +45 -3
  164. hud/shared/exceptions.py +36 -10
  165. hud/shared/hints.py +26 -1
  166. hud/shared/requests.py +15 -3
  167. hud/shared/tests/test_exceptions.py +40 -31
  168. hud/shared/tests/test_hints.py +167 -0
  169. hud/telemetry/__init__.py +20 -19
  170. hud/telemetry/exporter.py +201 -0
  171. hud/telemetry/instrument.py +158 -253
  172. hud/telemetry/tests/test_eval_telemetry.py +356 -0
  173. hud/telemetry/tests/test_exporter.py +258 -0
  174. hud/telemetry/tests/test_instrument.py +401 -0
  175. hud/tools/__init__.py +16 -2
  176. hud/tools/apply_patch.py +639 -0
  177. hud/tools/base.py +54 -4
  178. hud/tools/bash.py +2 -2
  179. hud/tools/computer/__init__.py +4 -0
  180. hud/tools/computer/anthropic.py +2 -2
  181. hud/tools/computer/gemini.py +385 -0
  182. hud/tools/computer/hud.py +23 -6
  183. hud/tools/computer/openai.py +20 -21
  184. hud/tools/computer/qwen.py +434 -0
  185. hud/tools/computer/settings.py +37 -0
  186. hud/tools/edit.py +3 -7
  187. hud/tools/executors/base.py +4 -2
  188. hud/tools/executors/pyautogui.py +1 -1
  189. hud/tools/grounding/grounded_tool.py +13 -18
  190. hud/tools/grounding/grounder.py +10 -31
  191. hud/tools/grounding/tests/test_grounded_tool.py +26 -44
  192. hud/tools/jupyter.py +330 -0
  193. hud/tools/playwright.py +18 -3
  194. hud/tools/shell.py +308 -0
  195. hud/tools/tests/test_apply_patch.py +718 -0
  196. hud/tools/tests/test_computer.py +4 -9
  197. hud/tools/tests/test_computer_actions.py +24 -2
  198. hud/tools/tests/test_jupyter_tool.py +181 -0
  199. hud/tools/tests/test_shell.py +596 -0
  200. hud/tools/tests/test_submit.py +85 -0
  201. hud/tools/tests/test_types.py +193 -0
  202. hud/tools/types.py +21 -1
  203. hud/types.py +167 -57
  204. hud/utils/__init__.py +2 -0
  205. hud/utils/env.py +67 -0
  206. hud/utils/hud_console.py +61 -3
  207. hud/utils/mcp.py +15 -58
  208. hud/utils/strict_schema.py +162 -0
  209. hud/utils/tests/test_init.py +1 -2
  210. hud/utils/tests/test_mcp.py +1 -28
  211. hud/utils/tests/test_pretty_errors.py +186 -0
  212. hud/utils/tests/test_tool_shorthand.py +154 -0
  213. hud/utils/tests/test_version.py +1 -1
  214. hud/utils/types.py +20 -0
  215. hud/version.py +1 -1
  216. hud_python-0.5.1.dist-info/METADATA +264 -0
  217. hud_python-0.5.1.dist-info/RECORD +299 -0
  218. {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/WHEEL +1 -1
  219. hud/agents/langchain.py +0 -261
  220. hud/agents/lite_llm.py +0 -72
  221. hud/cli/rl/__init__.py +0 -180
  222. hud/cli/rl/config.py +0 -101
  223. hud/cli/rl/display.py +0 -133
  224. hud/cli/rl/gpu.py +0 -63
  225. hud/cli/rl/gpu_utils.py +0 -321
  226. hud/cli/rl/local_runner.py +0 -595
  227. hud/cli/rl/presets.py +0 -96
  228. hud/cli/rl/remote_runner.py +0 -463
  229. hud/cli/rl/rl_api.py +0 -150
  230. hud/cli/rl/vllm.py +0 -177
  231. hud/cli/rl/wait_utils.py +0 -89
  232. hud/datasets/parallel.py +0 -687
  233. hud/misc/__init__.py +0 -1
  234. hud/misc/claude_plays_pokemon.py +0 -292
  235. hud/otel/__init__.py +0 -35
  236. hud/otel/collector.py +0 -142
  237. hud/otel/config.py +0 -181
  238. hud/otel/context.py +0 -570
  239. hud/otel/exporters.py +0 -369
  240. hud/otel/instrumentation.py +0 -135
  241. hud/otel/processors.py +0 -121
  242. hud/otel/tests/__init__.py +0 -1
  243. hud/otel/tests/test_processors.py +0 -197
  244. hud/rl/README.md +0 -30
  245. hud/rl/__init__.py +0 -1
  246. hud/rl/actor.py +0 -176
  247. hud/rl/buffer.py +0 -405
  248. hud/rl/chat_template.jinja +0 -101
  249. hud/rl/config.py +0 -192
  250. hud/rl/distributed.py +0 -132
  251. hud/rl/learner.py +0 -637
  252. hud/rl/tests/__init__.py +0 -1
  253. hud/rl/tests/test_learner.py +0 -186
  254. hud/rl/train.py +0 -382
  255. hud/rl/types.py +0 -101
  256. hud/rl/utils/start_vllm_server.sh +0 -30
  257. hud/rl/utils.py +0 -524
  258. hud/rl/vllm_adapter.py +0 -143
  259. hud/telemetry/job.py +0 -352
  260. hud/telemetry/replay.py +0 -74
  261. hud/telemetry/tests/test_replay.py +0 -40
  262. hud/telemetry/tests/test_trace.py +0 -63
  263. hud/telemetry/trace.py +0 -158
  264. hud/utils/agent_factories.py +0 -86
  265. hud/utils/async_utils.py +0 -65
  266. hud/utils/group_eval.py +0 -223
  267. hud/utils/progress.py +0 -149
  268. hud/utils/tasks.py +0 -127
  269. hud/utils/tests/test_async_utils.py +0 -173
  270. hud/utils/tests/test_progress.py +0 -261
  271. hud_python-0.4.45.dist-info/METADATA +0 -552
  272. hud_python-0.4.45.dist-info/RECORD +0 -228
  273. {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/entry_points.txt +0 -0
  274. {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,45 @@
1
+ """Provider integrations - format conversion and framework tools."""
2
+
3
+ from hud.environment.integrations.adk import ADKMixin
4
+ from hud.environment.integrations.anthropic import AnthropicMixin
5
+ from hud.environment.integrations.gemini import GeminiMixin
6
+ from hud.environment.integrations.langchain import LangChainMixin
7
+ from hud.environment.integrations.llamaindex import LlamaIndexMixin
8
+ from hud.environment.integrations.openai import OpenAIMixin
9
+
10
+ __all__ = ["IntegrationsMixin"]
11
+
12
+
13
+ class IntegrationsMixin(
14
+ OpenAIMixin,
15
+ AnthropicMixin,
16
+ GeminiMixin,
17
+ LangChainMixin,
18
+ LlamaIndexMixin,
19
+ ADKMixin,
20
+ ):
21
+ """Combined integration mixin for all providers.
22
+
23
+ OpenAI:
24
+ as_openai_chat_tools() - Chat Completions format
25
+ as_openai_responses_tools() - Responses API format
26
+ as_openai_agent_tools() - Agents SDK (requires openai-agents)
27
+
28
+ Anthropic/Claude:
29
+ as_claude_tools() - Claude API format
30
+ as_claude_programmatic_tools() - Programmatic tool use
31
+ as_anthropic_runner() - Tool runner (requires anthropic)
32
+
33
+ Google/Gemini:
34
+ as_gemini_tools() - Gemini format
35
+ as_gemini_tool_config() - Tool config
36
+
37
+ Google ADK:
38
+ as_adk_tools() - ADK FunctionTool objects (requires google-adk)
39
+
40
+ LangChain:
41
+ as_langchain_tools() - StructuredTools (requires langchain-core)
42
+
43
+ LlamaIndex:
44
+ as_llamaindex_tools() - FunctionTools (requires llama-index-core)
45
+ """
@@ -0,0 +1,67 @@
1
+ """Google ADK integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from hud.environment.utils.tool_wrappers import create_async_tool_fn
8
+
9
+ if TYPE_CHECKING:
10
+ import mcp.types as mcp_types
11
+
12
+ __all__ = ["ADKMixin"]
13
+
14
+
15
+ class ADKMixin:
16
+ """Mixin providing Google ADK (Agent Development Kit) integration.
17
+
18
+ Integration methods (requires google-adk):
19
+ as_adk_tools() - ADK FunctionTool objects
20
+
21
+ Requires: as_tools() -> list[mcp_types.Tool], call_tool(name, args)
22
+ """
23
+
24
+ def as_tools(self) -> list[mcp_types.Tool]:
25
+ raise NotImplementedError
26
+
27
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
28
+ raise NotImplementedError
29
+
30
+ def as_adk_tools(self) -> list[Any]:
31
+ """Convert to Google ADK FunctionTool objects.
32
+
33
+ Requires: pip install google-adk
34
+
35
+ Returns:
36
+ List of FunctionTool objects for Google ADK agents.
37
+
38
+ Example:
39
+ ```python
40
+ from google.adk.agents import Agent
41
+ from google.adk.runners import Runner
42
+
43
+ async with env:
44
+ agent = Agent(
45
+ name="assistant",
46
+ model="gemini-2.0-flash",
47
+ instruction="You are a helpful assistant.",
48
+ tools=env.as_adk_tools(),
49
+ )
50
+ runner = Runner(agent=agent)
51
+ result = await runner.run("Find information about Python")
52
+ ```
53
+ """
54
+ try:
55
+ from google.adk.tools.function_tool import FunctionTool
56
+ except ImportError as e:
57
+ raise ImportError(
58
+ "Google ADK not installed. Install with: pip install google-adk"
59
+ ) from e
60
+
61
+ tools = []
62
+ for t in self.as_tools():
63
+ # ADK only needs async function - it wraps it in FunctionTool
64
+ async_fn = create_async_tool_fn(self, t.name, t.description)
65
+ tool = FunctionTool(async_fn)
66
+ tools.append(tool)
67
+ return tools
@@ -0,0 +1,196 @@
1
+ """Anthropic/Claude integrations - format conversion and tool runner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ if TYPE_CHECKING:
9
+ import mcp.types as mcp_types
10
+
11
+ __all__ = ["AnthropicMixin"]
12
+
13
+
14
+ class AnthropicMixin:
15
+ """Mixin providing Anthropic/Claude format conversion and tool runner.
16
+
17
+ Format methods (no deps):
18
+ as_claude_tools() - Claude API format
19
+ as_claude_programmatic_tools() - Programmatic tool use format
20
+
21
+ Integration methods (requires anthropic):
22
+ as_anthropic_runner() - Tool runner for executing tool_use blocks
23
+
24
+ Requires: as_tools() -> list[mcp_types.Tool], call_tool(name, args)
25
+ """
26
+
27
+ def as_tools(self) -> list[mcp_types.Tool]:
28
+ raise NotImplementedError
29
+
30
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
31
+ raise NotImplementedError
32
+
33
+ # =========================================================================
34
+ # Format Conversion (no external deps)
35
+ # =========================================================================
36
+
37
+ def as_claude_tools(self, *, cache_control: bool = False) -> list[dict[str, Any]]:
38
+ """Convert to Claude/Anthropic tool format.
39
+
40
+ Args:
41
+ cache_control: Add cache_control for prompt caching
42
+
43
+ Returns:
44
+ List of tool definitions for Claude API.
45
+
46
+ Example:
47
+ ```python
48
+ from anthropic import Anthropic
49
+
50
+ client = Anthropic()
51
+ async with env:
52
+ response = client.messages.create(
53
+ model="claude-sonnet-4-20250514",
54
+ max_tokens=1024,
55
+ messages=[{"role": "user", "content": "Navigate to google.com"}],
56
+ tools=env.as_claude_tools(),
57
+ )
58
+ # Execute tool calls
59
+ for block in response.content:
60
+ if block.type == "tool_use":
61
+ result = await env.call_tool(block)
62
+ ```
63
+ """
64
+ tools = []
65
+ for t in self.as_tools():
66
+ tool: dict[str, Any] = {
67
+ "name": t.name,
68
+ "description": t.description or "",
69
+ "input_schema": t.inputSchema or {"type": "object", "properties": {}},
70
+ }
71
+ if cache_control:
72
+ tool["cache_control"] = {"type": "ephemeral"}
73
+ tools.append(tool)
74
+ return tools
75
+
76
+ def as_claude_programmatic_tools(self, *, cache_control: bool = False) -> list[dict[str, Any]]:
77
+ """Convert to Claude programmatic tool use format.
78
+
79
+ Programmatic tool use allows Claude to execute tools via code execution.
80
+
81
+ Example:
82
+ ```python
83
+ from anthropic import Anthropic
84
+
85
+ client = Anthropic()
86
+ async with env:
87
+ response = client.messages.create(
88
+ model="claude-sonnet-4-20250514",
89
+ max_tokens=1024,
90
+ messages=[{"role": "user", "content": "Analyze the data"}],
91
+ tools=env.as_claude_programmatic_tools(),
92
+ betas=["code-execution-2025-01-24"],
93
+ )
94
+ ```
95
+ """
96
+ tools = []
97
+ for t in self.as_tools():
98
+ tool: dict[str, Any] = {
99
+ "name": t.name,
100
+ "description": t.description or "",
101
+ "input_schema": t.inputSchema or {"type": "object", "properties": {}},
102
+ "allowed_callers": ["code_execution_20250825"],
103
+ }
104
+ if cache_control:
105
+ tool["cache_control"] = {"type": "ephemeral"}
106
+ tools.append(tool)
107
+ return tools
108
+
109
+ # =========================================================================
110
+ # Tool Runner Integration (requires anthropic)
111
+ # =========================================================================
112
+
113
+ def as_anthropic_runner(self) -> EnvToolRunner:
114
+ """Create an Anthropic tool runner for this environment.
115
+
116
+ Requires: pip install anthropic
117
+
118
+ Returns:
119
+ EnvToolRunner that can process tool_use blocks from Claude.
120
+
121
+ Example:
122
+ ```python
123
+ from anthropic import Anthropic
124
+
125
+ client = Anthropic()
126
+ async with env:
127
+ runner = env.as_anthropic_runner()
128
+
129
+ response = client.messages.create(
130
+ model="claude-sonnet-4-20250514",
131
+ max_tokens=1024,
132
+ messages=[{"role": "user", "content": "Navigate to google.com"}],
133
+ tools=env.as_claude_tools(),
134
+ )
135
+
136
+ # Execute all tool_use blocks
137
+ results = []
138
+ for block in response.content:
139
+ if block.type == "tool_use":
140
+ result = await runner.run(block)
141
+ results.append(result)
142
+ ```
143
+ """
144
+ return EnvToolRunner(self)
145
+
146
+
147
+ class EnvToolRunner:
148
+ """Tool runner that executes tools against an Environment."""
149
+
150
+ def __init__(self, env: AnthropicMixin) -> None:
151
+ self.env = env
152
+ self._tool_names: set[str] | None = None
153
+
154
+ @property
155
+ def tool_names(self) -> set[str]:
156
+ """Get available tool names."""
157
+ if self._tool_names is None:
158
+ self._tool_names = {t.name for t in self.env.as_tools()}
159
+ return self._tool_names
160
+
161
+ async def run(self, tool_use_block: Any) -> Any:
162
+ """Execute a tool_use block from Claude.
163
+
164
+ Args:
165
+ tool_use_block: A ToolUseBlock from Claude's response.
166
+
167
+ Returns:
168
+ Tool result dict (or BetaToolResultBlockParam if anthropic installed).
169
+ """
170
+ name = tool_use_block.name
171
+ tool_use_id = tool_use_block.id
172
+ arguments = tool_use_block.input or {}
173
+
174
+ try:
175
+ result = await self.env.call_tool(name, **arguments)
176
+ content = result if isinstance(result, str) else json.dumps(result) if result else ""
177
+ result_dict: dict[str, Any] = {
178
+ "type": "tool_result",
179
+ "tool_use_id": tool_use_id,
180
+ "content": content,
181
+ }
182
+ except Exception as e:
183
+ result_dict = {
184
+ "type": "tool_result",
185
+ "tool_use_id": tool_use_id,
186
+ "content": f"Error: {e}",
187
+ "is_error": True,
188
+ }
189
+
190
+ # Return typed object if anthropic is available
191
+ try:
192
+ from anthropic.types.beta import BetaToolResultBlockParam
193
+
194
+ return BetaToolResultBlockParam(**result_dict)
195
+ except ImportError:
196
+ return result_dict
@@ -0,0 +1,92 @@
1
+ """Google/Gemini integrations - format conversion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ import mcp.types as mcp_types
9
+
10
+ __all__ = ["GeminiMixin"]
11
+
12
+
13
+ class GeminiMixin:
14
+ """Mixin providing Google/Gemini format conversion.
15
+
16
+ Format methods (no deps):
17
+ as_gemini_tools() - Gemini tool format
18
+ as_gemini_tool_config() - Tool execution config
19
+
20
+ Requires: as_tools() -> list[mcp_types.Tool]
21
+ """
22
+
23
+ def as_tools(self) -> list[mcp_types.Tool]:
24
+ raise NotImplementedError
25
+
26
+ def as_gemini_tools(self) -> list[dict[str, Any]]:
27
+ """Convert to Gemini/Google AI tool format.
28
+
29
+ Returns:
30
+ List with function_declarations for Gemini API.
31
+
32
+ Example:
33
+ ```python
34
+ import google.generativeai as genai
35
+
36
+ model = genai.GenerativeModel("gemini-1.5-pro")
37
+ async with env:
38
+ response = model.generate_content(
39
+ "Navigate to google.com",
40
+ tools=env.as_gemini_tools(),
41
+ )
42
+ # Execute tool calls
43
+ for part in response.candidates[0].content.parts:
44
+ if fn := part.function_call:
45
+ result = await env.call_tool(part)
46
+ ```
47
+ """
48
+ return [
49
+ {
50
+ "function_declarations": [
51
+ {
52
+ "name": t.name,
53
+ "description": t.description or "",
54
+ "parameters": t.inputSchema or {"type": "object", "properties": {}},
55
+ }
56
+ for t in self.as_tools()
57
+ ]
58
+ }
59
+ ]
60
+
61
+ def as_gemini_tool_config(
62
+ self,
63
+ mode: str = "AUTO",
64
+ allowed_tools: list[str] | None = None,
65
+ ) -> dict[str, Any]:
66
+ """Get Gemini tool_config for controlling tool execution.
67
+
68
+ Args:
69
+ mode: "AUTO", "ANY", or "NONE"
70
+ allowed_tools: If mode is "ANY", list of allowed tool names
71
+
72
+ Returns:
73
+ Tool config dict for Gemini API.
74
+
75
+ Example:
76
+ ```python
77
+ import google.generativeai as genai
78
+
79
+ model = genai.GenerativeModel("gemini-1.5-pro")
80
+ async with env:
81
+ # Force specific tool usage
82
+ response = model.generate_content(
83
+ "Search for cats",
84
+ tools=env.as_gemini_tools(),
85
+ tool_config=env.as_gemini_tool_config(mode="ANY", allowed_tools=["search"]),
86
+ )
87
+ ```
88
+ """
89
+ config: dict[str, Any] = {"function_calling_config": {"mode": mode}}
90
+ if mode == "ANY" and allowed_tools:
91
+ config["function_calling_config"]["allowed_function_names"] = allowed_tools
92
+ return config
@@ -0,0 +1,82 @@
1
+ """LangChain integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from hud.environment.utils.schema import schema_to_pydantic
8
+ from hud.environment.utils.tool_wrappers import create_tool_fns
9
+
10
+ if TYPE_CHECKING:
11
+ import mcp.types as mcp_types
12
+
13
+ __all__ = ["LangChainMixin"]
14
+
15
+
16
+ class LangChainMixin:
17
+ """Mixin providing LangChain integration.
18
+
19
+ Integration methods (requires langchain-core):
20
+ as_langchain_tools() - LangChain StructuredTool objects
21
+
22
+ Requires: as_tools() -> list[mcp_types.Tool], call_tool(name, args)
23
+ """
24
+
25
+ def as_tools(self) -> list[mcp_types.Tool]:
26
+ raise NotImplementedError
27
+
28
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
29
+ raise NotImplementedError
30
+
31
+ def as_langchain_tools(self) -> list[Any]:
32
+ """Convert to LangChain StructuredTool objects.
33
+
34
+ Requires: pip install langchain-core
35
+
36
+ Returns:
37
+ List of StructuredTool objects for LangChain agents.
38
+
39
+ Example:
40
+ ```python
41
+ from langchain_openai import ChatOpenAI
42
+ from langchain.agents import create_tool_calling_agent, AgentExecutor
43
+ from langchain_core.prompts import ChatPromptTemplate
44
+
45
+ llm = ChatOpenAI(model="gpt-4o")
46
+ async with env:
47
+ tools = env.as_langchain_tools()
48
+
49
+ prompt = ChatPromptTemplate.from_messages(
50
+ [
51
+ ("system", "You are a helpful assistant."),
52
+ ("human", "{input}"),
53
+ ("placeholder", "{agent_scratchpad}"),
54
+ ]
55
+ )
56
+
57
+ agent = create_tool_calling_agent(llm, tools, prompt)
58
+ executor = AgentExecutor(agent=agent, tools=tools)
59
+ result = await executor.ainvoke({"input": "Navigate to google.com"})
60
+ ```
61
+ """
62
+ try:
63
+ from langchain_core.tools import StructuredTool
64
+ except ImportError as e:
65
+ raise ImportError(
66
+ "LangChain not installed. Install with: pip install langchain-core"
67
+ ) from e
68
+
69
+ tools = []
70
+ for t in self.as_tools():
71
+ schema = t.inputSchema or {"type": "object", "properties": {}}
72
+ sync_fn, async_fn = create_tool_fns(self, t)
73
+
74
+ tool = StructuredTool(
75
+ name=t.name,
76
+ description=t.description or "",
77
+ func=sync_fn,
78
+ coroutine=async_fn,
79
+ args_schema=schema_to_pydantic(t.name, schema),
80
+ )
81
+ tools.append(tool)
82
+ return tools
@@ -0,0 +1,68 @@
1
+ """LlamaIndex integration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from hud.environment.utils.tool_wrappers import create_tool_fns
8
+
9
+ if TYPE_CHECKING:
10
+ import mcp.types as mcp_types
11
+
12
+ __all__ = ["LlamaIndexMixin"]
13
+
14
+
15
+ class LlamaIndexMixin:
16
+ """Mixin providing LlamaIndex integration.
17
+
18
+ Integration methods (requires llama-index-core):
19
+ as_llamaindex_tools() - LlamaIndex FunctionTool objects
20
+
21
+ Requires: as_tools() -> list[mcp_types.Tool], call_tool(name, args)
22
+ """
23
+
24
+ def as_tools(self) -> list[mcp_types.Tool]:
25
+ raise NotImplementedError
26
+
27
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
28
+ raise NotImplementedError
29
+
30
+ def as_llamaindex_tools(self) -> list[Any]:
31
+ """Convert to LlamaIndex FunctionTool objects.
32
+
33
+ Requires: pip install llama-index-core
34
+
35
+ Returns:
36
+ List of FunctionTool objects for LlamaIndex agents.
37
+
38
+ Example:
39
+ ```python
40
+ from llama_index.llms.openai import OpenAI
41
+ from llama_index.core.agent import ReActAgent
42
+
43
+ llm = OpenAI(model="gpt-4o")
44
+ async with env:
45
+ tools = env.as_llamaindex_tools()
46
+ agent = ReActAgent.from_tools(tools, llm=llm, verbose=True)
47
+ response = await agent.achat("Find information about Python")
48
+ ```
49
+ """
50
+ try:
51
+ from llama_index.core.tools import FunctionTool
52
+ except ImportError as e:
53
+ raise ImportError(
54
+ "LlamaIndex not installed. Install with: pip install llama-index-core"
55
+ ) from e
56
+
57
+ tools = []
58
+ for t in self.as_tools():
59
+ sync_fn, async_fn = create_tool_fns(self, t)
60
+
61
+ tool = FunctionTool.from_defaults(
62
+ fn=sync_fn,
63
+ async_fn=async_fn,
64
+ name=t.name,
65
+ description=t.description or "",
66
+ )
67
+ tools.append(tool)
68
+ return tools