HOW-TO · RAG
How to Register and Call Custom Tools in Agents
Target environment
Ubuntu 24.04 · Ollama 0.4.x
PREREQUISITES
Custom tool functions defined, agent framework installed, Python 3.10+
What this does
Tool registration creates a discoverable catalog that the agent's LLM can reference when deciding which tool to call. This separates tool definition from tool invocation.
Steps
- Create a tool registry. A central registry maps tool names to functions and schemas.
class ToolRegistry:
def __init__(self):
self._tools = {}
self._schemas = []
def register(self, tool):
self._tools[tool.name] = tool
self._schemas.append({
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.args_schema.schema() if tool.args_schema else {
"type": "object",
"properties": {
k: {"type": v.__name__} for k, v in tool.args.items()
}
}
}
})
def get_schemas(self) -> list[dict]:
return self._schemas
def call(self, name: str, **kwargs):
if name not in self._tools:
raise KeyError(f"Tool '{name}' not registered")
return self._tools[name].invoke(kwargs)
- Register tools with the agent. Pass the registry when creating the agent.
from langchain.tools import tool
@tool
def search_web(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"
@tool
def calculate(expression: str) -> str:
"""Evaluate a math expression."""
return str(eval(expression))
registry = ToolRegistry()
registry.register(search_web)
registry.register(calculate)
# Pass schemas to the LLM
llm_with_tools = llm.bind_tools(registry.get_schemas())
- Dispatch tool calls. On receiving a tool call from the LLM, look up and execute.
def handle_tool_calls(tool_calls: list, registry: ToolRegistry) -> list[dict]:
results = []
for tc in tool_calls:
try:
result = registry.call(tc.function.name, **json.loads(tc.function.arguments))
results.append({
"role": "tool",
"tool_call_id": tc.id,
"content": str(result)
})
except Exception as e:
results.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps({"error": str(e)})
})
return results
- Validate tool existence before calling. Provide a friendly error if the tool is missing.
def safe_call(registry: ToolRegistry, name: str, args: dict) -> str:
if name not in registry._tools:
return json.dumps({"error": f"Unknown tool '{name}'. Available: {list(registry._tools.keys())}"})
return registry.call(name, **args)
- List registered tools for inspection.
def list_tools(registry: ToolRegistry) -> str:
lines = ["Available tools:"]
for name, tool in registry._tools.items():
lines.append(f" - {name}: {tool.description}")
return "\n".join(lines)
Verification
python -c "
from langchain.tools import tool
@tool
def echo(msg: str) -> str:
return msg
r = {'echo': echo}
result = r['echo'].invoke({'msg': 'hello'})
print(result)
# Expected: hello
"
Common failures
- Tool name collision. Two tools with the same name silently overwrite. Use a namespace prefix (e.g.,
db_query,web_search). - Args schema mismatch. The JSON schema generated by LangChain may differ from what the LLM expects. Validate by printing
tool.args_schema.schema(). - Forgetting to register. A tool defined but not registered returns "Unknown tool" errors. Always check the registry after setup.
- Version mismatch - The installed package or runtime differs from the command shown; check the version first and rerun the smallest verification command.
- Local environment drift - Another service, virtual environment, model, or path is being used; print the active binary path and configuration before changing the guide steps.
Related guides
- How to Build Custom Tools for Agents
- How to Use OpenAI Function Calling with Tools