Lab: Wire the tools to the layer
Trace and verify the tool dispatch that connects Claude's tool calls to your semantic layer, and test it without any API calls.
A tested tools.py that turns a tool name + input into a real semantic-layer result — the bridge the agent will drive. No API key needed for this lab.
Step 1 — Read and trace the dispatcher
The dispatcher ships complete in agent/tools.py — your job here is to read it, trace what happens to a tool call, and then prove to yourself it behaves. (If you learn best by typing, delete the body of run_tool and rebuild it from memory — optional, but effective.) It takes a tool name and the model's input dict, runs the right semantic-layer call, and returns a JSON string (what a tool result must be). Crucially, it catches SemanticError and returns it as data so the model can read the error and fix its request — and it answers an unknown tool name with error JSON too, never a crash.
def run_tool(name: str, tool_input: dict) -> str:
try:
if name == "list_metrics":
return json.dumps(_layer.catalog())
if name == "query_metrics":
result = _layer.query(
metrics=tool_input.get("metrics", []),
dimensions=tool_input.get("dimensions", []),
filters=tool_input.get("filters", []),
time_grain=tool_input.get("time_grain"),
order_by=tool_input.get("order_by"),
descending=tool_input.get("descending", True),
limit=tool_input.get("limit"),
)
return json.dumps(result)
return json.dumps({"error": f"Unknown tool '{name}'"})
except SemanticError as e:
return json.dumps({"error": str(e)}) # model reads this and retriesStep 2 — Test the bridge (no LLM required)
Run this from inside the agent/ folder (cd agent, venv active, then python3) — from tools import run_tool only resolves there:
from tools import run_tool
import json
print(json.loads(run_tool("list_metrics", {}))["metrics"].keys())
print(run_tool("query_metrics", {"metrics": ["revenue", "aov"], "dimensions": ["channel"]}))
print(run_tool("query_metrics", {"metrics": ["not_a_metric"]})) # → clean error JSON- 1Confirm list_metrics returns the full catalog.
- 2Confirm query_metrics returns rows for a valid request.
- 3Confirm an invalid metric name returns {"error": "..."} — not a crash. This graceful-error behavior is what lets the model self-correct next module.
When the model picks a wrong name, it receives the error text as the tool result, reads "Unknown metric 'profit'; known: [...]", and retries with a valid name — often in the same turn. Returning errors as readable data (not exceptions) turns the model into a self-correcting user of your layer.