sasukeUchiha123 commited on
Commit
f1a4113
·
verified ·
1 Parent(s): 8986207

Upload agent/tools/__init__.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. agent/tools/__init__.py +133 -0
agent/tools/__init__.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tool registry for the GPU Goblin agent loop.
2
+
3
+ Each tool file exports a `Tool` instance. The registry collects them so
4
+ `agent/loop.py` can pass `tool_schemas()` to the Anthropic API and
5
+ dispatch tool calls by name without hardcoded imports.
6
+
7
+ Phase 2 agents replacing tool implementations should NOT touch this file —
8
+ they edit the `fn` and the implementation module only. Adding a new tool
9
+ requires one edit here (the import + ALL_TOOLS append).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import inspect
15
+ from collections.abc import Callable
16
+ from dataclasses import dataclass
17
+ from typing import Any
18
+
19
+ from agent.schemas import ToolResult
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class Tool:
24
+ name: str
25
+ description: str
26
+ input_schema: dict[str, Any]
27
+ """JSON schema for the tool's input — passed to Claude's tool-use API."""
28
+ fn: Callable[..., ToolResult]
29
+ """Callable. Must accept the JSON-decoded input dict and return ToolResult."""
30
+
31
+
32
+ from agent.tools.benchmark import BENCHMARK # noqa: E402
33
+ from agent.tools.compare_runs import COMPARE_RUNS # noqa: E402
34
+ from agent.tools.parse_config import PARSE_CONFIG # noqa: E402
35
+ from agent.tools.profile_run import PROFILE_RUN # noqa: E402
36
+ from agent.tools.propose_patch import PROPOSE_PATCH # noqa: E402
37
+ from agent.tools.query_rocm_kb import QUERY_ROCM_KB # noqa: E402
38
+
39
+ ALL_TOOLS: list[Tool] = [
40
+ PARSE_CONFIG,
41
+ PROFILE_RUN,
42
+ QUERY_ROCM_KB,
43
+ PROPOSE_PATCH,
44
+ BENCHMARK,
45
+ COMPARE_RUNS,
46
+ ]
47
+
48
+ TOOL_BY_NAME: dict[str, Tool] = {t.name: t for t in ALL_TOOLS}
49
+
50
+
51
+ def tool_schemas() -> list[dict[str, Any]]:
52
+ """Schemas in the shape Claude's tool-use API expects."""
53
+ return [
54
+ {"name": t.name, "description": t.description, "input_schema": t.input_schema}
55
+ for t in ALL_TOOLS
56
+ ]
57
+
58
+
59
+ def call(name: str, **kwargs: Any) -> ToolResult:
60
+ """Dispatch a tool call by name with keyword args from the JSON-decoded input.
61
+
62
+ Hardening notes (live-AMD-GPU lessons):
63
+
64
+ 1. **Hallucinated kwargs get silently dropped.** Models routinely invent
65
+ plausible-sounding argument names (e.g. ``cache=True`` instead of the
66
+ declared ``force_rerun``). We filter ``kwargs`` against the function's
67
+ inspected signature so a single fabricated kwarg can't tank the call.
68
+
69
+ 2. **Missing required args become structured errors.** If the model
70
+ forgets a required arg (e.g. ``profile_run`` with empty input), we
71
+ return ``ToolResult(ok=False)`` with a message that names the missing
72
+ fields and lists what the tool actually accepts. Letting the
73
+ ``TypeError`` leak just confuses the model on the next turn.
74
+
75
+ 3. **Any other exception is wrapped, never raised.** Pydantic
76
+ ``ValidationError``, runtime errors inside the tool, anything — they
77
+ all surface as ``ToolResult(ok=False, error=...)`` so the agent loop
78
+ can adapt and the SSE stream stays well-formed.
79
+
80
+ Tools that declare ``**kwargs`` themselves are handled transparently —
81
+ we pass everything through.
82
+ """
83
+ tool = TOOL_BY_NAME.get(name)
84
+ if tool is None:
85
+ return ToolResult(
86
+ ok=False,
87
+ error=f"Unknown tool: {name!r}. Available: {sorted(TOOL_BY_NAME)}",
88
+ )
89
+
90
+ sig = inspect.signature(tool.fn)
91
+ accepts_var_kwargs = any(
92
+ p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
93
+ )
94
+ declared = {
95
+ p.name
96
+ for p in sig.parameters.values()
97
+ if p.kind
98
+ in (
99
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
100
+ inspect.Parameter.KEYWORD_ONLY,
101
+ )
102
+ }
103
+
104
+ if accepts_var_kwargs:
105
+ filtered = dict(kwargs)
106
+ else:
107
+ filtered = {k: v for k, v in kwargs.items() if k in declared}
108
+
109
+ missing = [
110
+ p.name
111
+ for p in sig.parameters.values()
112
+ if p.kind
113
+ in (
114
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
115
+ inspect.Parameter.KEYWORD_ONLY,
116
+ )
117
+ and p.default is inspect.Parameter.empty
118
+ and p.name not in filtered
119
+ ]
120
+ if missing:
121
+ return ToolResult(
122
+ ok=False,
123
+ error=(
124
+ f"Tool {name!r} is missing required argument(s): "
125
+ f"{', '.join(missing)}. Accepted arguments: "
126
+ f"{sorted(declared)}."
127
+ ),
128
+ )
129
+
130
+ try:
131
+ return tool.fn(**filtered)
132
+ except Exception as exc:
133
+ return ToolResult(ok=False, error=f"{type(exc).__name__}: {exc}")