Function Calling
Intent
Enable LLMs to invoke external functions with structured parameters, bridging the gap between language and action.
Problem
LLMs can reason and generate text but can't directly interact with the world. They can't query databases, call APIs, or perform calculations. You need a reliable way for the model to specify which action to take with what parameters, in a format that can be programmatically executed.
Solution
Define a set of functions with typed parameters and descriptions. The LLM generates structured function calls (typically JSON) specifying which function to invoke and with what arguments. Your system executes the function and returns the result to the LLM for further reasoning. The key is treating tool definitions like API documentation for the model — the better your descriptions and parameter schemas, the more accurately the model will use them.
Diagram
User: "What's the weather in Tokyo?"
↓
LLM → { function: "get_weather", args: { city: "Tokyo" } }
↓
[System executes get_weather("Tokyo")]
↓
Result: { temp: 22, condition: "sunny" }
↓
LLM → "It's 22°C and sunny in Tokyo!"When to Use
- Any task requiring interaction with external systems
- Database queries, API calls, calculations
- When you need structured, reliable tool invocation
- Building the 'hands' of an agent that can act in the world
When NOT to Use
- Pure text generation tasks with no need for external data
- When the model's training data is sufficient
Pros & Cons
Pros
- Reliable, structured interaction with external systems
- Model chooses the right tool and parameters based on context
- Easy to add new tools by defining new function schemas
- Works across all major LLM providers
Cons
- Model may call wrong functions or pass wrong parameters
- Tool descriptions need careful engineering
- Each tool call adds latency
- Security: model-directed actions need guardrails
Implementation Steps
- 1Define functions with clear names, descriptions, and typed parameter schemas
- 2Write descriptions as if documenting APIs for a junior developer
- 3Implement function execution and error handling
- 4Return results in a format the LLM can easily interpret
- 5Test with varied inputs to catch common misuse patterns
- 6Add parameter validation before executing any function
Real-World Example
E-commerce Agent
Agent has tools: search_products(query, filters), get_product_details(id), add_to_cart(product_id, quantity), check_inventory(product_id). User says 'Find me a blue jacket under $100.' Agent calls search_products with appropriate filters, presents results, and adds the user's choice to cart.
from openai import OpenAI
import json
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
},
}]
def get_weather(city: str, units: str = "celsius") -> dict:
return {"city": city, "temp": 22, "condition": "sunny", "units": units}
TOOL_MAP = {"get_weather": get_weather}
def chat(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
response = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
msg = response.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
fn = TOOL_MAP[call.function.name]
result = fn(**json.loads(call.function.arguments))
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
response = client.chat.completions.create(model="gpt-4o", messages=messages)
return response.choices[0].message.content