Article
tool-callingfunction-callingpythonai-agentsopenaipydanticautomationscript
Build a Robust Tool-Calling Agent with Python
Use the Tool Calling Setup script to build a modular AI agent. This pack shows you how to define custom tools with typed arguments using Pydantic, register them, and run an agent that can use your new tools securely.
intermediate30 min4 steps
The play
- Understand the Core StructureDownload or clone the 'Tool Calling Setup' script. Familiarize yourself with its modular design: a central agent loop, a pluggable tool registry, and individual tool definitions. This separation makes it easy to add or remove capabilities without rewriting the core logic.
- Define a Custom Tool with PydanticCreate a new tool by defining a Python function and a Pydantic model for its arguments. This enables automatic validation and provides a clear schema for the LLM. For example, let's create a simple weather tool.
- Register Your New ToolThe 'Tool Calling Setup' uses a central registry to know which tools are available. Import your new tool and add its function and argument schema to the registry. This makes it discoverable by the agent.
- Run the Agent and Test the ToolExecute the main agent script. Give it a prompt that requires your new tool, like 'What's the weather in Boston?'. The agent will identify the correct tool, validate the 'location' argument, execute the function, and use its output to answer your question.
Starter code
import os
import json
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Dict, Any, Callable
# --- 1. Tool Definition (e.g., tools/weather.py) ---
class GetWeatherArgs(BaseModel):
location: str = Field(..., description="The city, e.g., San Francisco")
def get_weather(args: GetWeatherArgs) -> str:
"""Gets the current weather for a specified location."""
print(f"[Tool Executed] Getting weather for {args.location}")
# In a real app, you'd call a weather API here.
if "boston" in args.location.lower():
return json.dumps({"temperature": "52°F", "conditions": "cloudy"})
return json.dumps({"temperature": "75°F", "conditions": "sunny"})
# --- 2. Tool Registry (e.g., tool_registry.py) ---
# The registry holds the function reference and its Pydantic schema
TOOL_REGISTRY: Dict[str, Dict[str, Any]] = {
"get_weather": {
"function": get_weather,
"pydantic_model": GetWeatherArgs
}
}
# --- 3. Agent Logic (e.g., agent.py) ---
def run_conversation(user_prompt: str, client: OpenAI):
print(f"\nUser: {user_prompt}")
messages = [{"role": "user", "content": user_prompt}]
# Convert Pydantic models to OpenAI tool format
tools_for_api = [
{
"type": "function",
"function": {
"name": name,
"description": details["function"].__doc__,
"parameters": details["pydantic_model"].model_json_schema()
}
}
for name, details in TOOL_REGISTRY.items()
]
# First API call to see if a tool is needed
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools_for_api,
tool_choice="auto",
)
response_message = response.choices[0].message
messages.append(response_message)
# Check for tool calls
if response_message.tool_calls:
for tool_call in response_message.tool_calls:
tool_name = tool_call.function.name
print(f"[LLM decided to call tool: {tool_name}]")
if tool_name in TOOL_REGISTRY:
tool_info = TOOL_REGISTRY[tool_name]
function_to_call = tool_info["function"]
pydantic_model = tool_info["pydantic_model"]
try:
# Validate arguments with Pydantic
tool_args = pydantic_model.model_validate_json(tool_call.function.arguments)
function_response = function_to_call(tool_args)
except Exception as e:
print(f"[Error] Invalid arguments: {e}")
function_response = f"Error: {e}"
messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": tool_name,
"content": function_response,
}
)
else:
print(f"[Error] Tool '{tool_name}' not found in registry.")
# Second API call to get a final response based on tool output
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
print(f"Agent: {final_response.choices[0].message.content}")
else:
print(f"Agent: {response_message.content}")
if __name__ == "__main__":
# Make sure to set your OPENAI_API_KEY environment variable
try:
client = OpenAI()
except Exception as e:
print("Error: OpenAI API key not configured.")
print("Please set the OPENAI_API_KEY environment variable.")
exit(1)
run_conversation("What is the weather like in Boston?", client)