Tool registry
The tools layer is where most agent code goes off the rails. People wire tools with long if-else chains in the handler, then every new tool means editing three places. The registry pattern fixes that: every tool registers its name and handler in one place, and the dispatch code never grows.
def get_time() -> str:
return datetime.now(timezone.utc).isoformat()
def calculator(expression: str) -> str:
expr = expression.replace("x", "*").replace("X", "*")
expr = expr.strip()
if not _SAFE_EXPR.match(expr):
raise ValueError("unsupported characters in expression")
value = eval(expr, {"__builtins__": {}}, {})
return str(value)
class ToolsLayer:
def __init__(self) -> None:
self._registry: Dict[str, Callable] = {
"get_time": lambda **_: get_time(),
"calculator": lambda expression, **_: calculator(expression),
}
def list_tools(self) -> list[str]:
return list(self._registry.keys())
def run(self, tool_name: str, message: str) -> str:
fn = self._registry.get(tool_name)
if not fn:
raise ValueError(f"unknown tool: {tool_name}")
if tool_name == "calculator":
match = re.search(r"[\d\s\+\-\*/xX\.\(\)]+", message)
expression = match.group(0) if match else message
return fn(expression=expression)
return fn()Two tools, one registry, one dispatch function. Add a new tool by adding one line to _registry. The orchestrator and the router never change.
Registry dispatch vs if-else chains
Registry keeps the dispatch site size constant as tools grow.
Quiz: Quiz
Loading practice…