Tool Calling
Let models call functions in your application with Oru-el's tool calling support.
Tool Calling#
Tool calling (also known as function calling) lets a model invoke functions defined in your application. Instead of generating a text response, the model outputs a structured function call with arguments. Your application executes the function and returns the result, and the model incorporates that result into its final response.
This is useful for:
- Fetching real-time data (weather, stock prices, database queries)
- Performing calculations
- Interacting with external APIs
- Taking actions in your application
How tool calling works#
- You define tools — describe available functions using JSON Schema
- The model decides — based on the user's message, the model chooses whether to call a tool
- You execute — your code runs the function with the model's arguments
- You return the result — send the function output back to the model
- The model responds — the model uses the function output to generate a final answer
Defining tools#
Tools are defined as an array of objects with type: "function" and a function descriptor:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. 'San Francisco, CA'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
}
Tool definition fields#
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Always "function" |
function.name | string | Yes | Function name (must be unique across tools) |
function.description | string | Yes | What the function does (helps the model decide when to use it) |
function.parameters | object | Yes | JSON Schema defining the function's parameters |
Write clear, specific descriptions — the model uses them to decide when and how to call each tool. A vague description leads to incorrect tool selection.
Complete example: weather lookup#
Python#
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.oru-el.com/v1/inference",
api_key="oruel_your_api_key_here",
)
# Step 1: Define your tools
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. 'San Francisco, CA'",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
},
}
]
# Step 2: Your actual function implementation
def get_weather(location: str, unit: str = "celsius") -> dict:
# In production, call a real weather API here
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "sunny",
}
# Step 3: Send the request with tools
messages = [{"role": "user", "content": "What's the weather in Tokyo?"}]
response = client.chat.completions.create(
model="llama-4-maverick",
messages=messages,
tools=tools,
tool_choice="auto",
)
# Step 4: Check if the model wants to call a tool
message = response.choices[0].message
if message.tool_calls:
# Add the assistant's message (with tool calls) to history
messages.append(message)
# Execute each tool call
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# Call the actual function
if function_name == "get_weather":
result = get_weather(**arguments)
else:
result = {"error": f"Unknown function: {function_name}"}
# Add the tool result to messages
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
})
# Step 5: Get the final response with tool results
final_response = client.chat.completions.create(
model="llama-4-maverick",
messages=messages,
tools=tools,
)
print(final_response.choices[0].message.content)
else:
# No tool call — model responded directly
print(message.content)
JavaScript#
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.oru-el.com/v1/inference",
apiKey: "oruel_your_api_key_here",
});
// Define tools
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City name, e.g. 'San Francisco, CA'",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit",
},
},
required: ["location"],
},
},
},
];
// Your function implementation
function getWeather(location, unit = "celsius") {
return {
location,
temperature: unit === "celsius" ? 22 : 72,
unit,
condition: "sunny",
};
}
// Function dispatch
const functionMap = {
get_weather: ({ location, unit }) => getWeather(location, unit),
};
const messages = [{ role: "user", content: "What's the weather in Tokyo?" }];
// First request
let response = await client.chat.completions.create({
model: "llama-4-maverick",
messages,
tools,
tool_choice: "auto",
});
const message = response.choices[0].message;
if (message.tool_calls) {
messages.push(message);
for (const toolCall of message.tool_calls) {
const fn = functionMap[toolCall.function.name];
const args = JSON.parse(toolCall.function.arguments);
const result = fn ? fn(args) : { error: "Unknown function" };
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
}
// Second request with tool results
response = await client.chat.completions.create({
model: "llama-4-maverick",
messages,
tools,
});
}
console.log(response.choices[0].message.content);
Tool call response format#
When the model decides to call a tool, the response looks like this:
{
"id": "chatcmpl-abc123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"Tokyo\", \"unit\": \"celsius\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}
Key details:
contentisnullwhen tool calls are presentfinish_reasonis"tool_calls"tool_calls[].function.argumentsis a JSON string (not a parsed object) — you must callJSON.parse()orjson.loads()on it- Each tool call has a unique
idthat you must reference in the tool response message
tool_choice#
Controls whether and which tools the model uses:
| Value | Behavior |
|---|---|
"auto" | Model decides whether to call a tool or respond with text (default when tools are provided) |
"none" | Model will not call any tools, even if they are defined |
{"type": "function", "function": {"name": "get_weather"}} | Force the model to call a specific tool |
Forcing a specific tool#
response = client.chat.completions.create(
model="llama-4-maverick",
messages=[{"role": "user", "content": "Tokyo weather please"}],
tools=tools,
tool_choice={"type": "function", "function": {"name": "get_weather"}},
)
Multiple tools#
You can define up to 128 tools. The model will choose which one(s) to call based on the user's request.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"}
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "search_restaurants",
"description": "Search for restaurants near a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
"cuisine": {"type": "string", "description": "Type of cuisine"},
"price_range": {
"type": "string",
"enum": ["$", "$$", "$$$", "$$$$"],
"description": "Price range",
},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "book_flight",
"description": "Search for flights between two cities",
"parameters": {
"type": "object",
"properties": {
"origin": {"type": "string", "description": "Departure city"},
"destination": {"type": "string", "description": "Arrival city"},
"date": {"type": "string", "description": "Travel date (YYYY-MM-DD)"},
},
"required": ["origin", "destination", "date"],
},
},
},
]
The model may also call multiple tools in a single response (parallel tool calls). Handle each one and return all results before making the follow-up request.
Parallel tool calls#
Some models can call multiple tools simultaneously:
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"Tokyo\"}"
}
},
{
"id": "call_2",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"London\"}"
}
}
]
}
}
Execute all tool calls and include all results in the follow-up request:
for tool_call in message.tool_calls:
result = execute_function(tool_call.function.name, tool_call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
})
Best practices#
- Write descriptive tool names and descriptions — the model selects tools based on these. "get_current_weather_forecast" is better than "weather".
- Use JSON Schema constraints —
enum,minimum,maximum,pattern, andrequiredhelp the model generate valid arguments. - Validate arguments — always validate the arguments the model returns before executing your function. Models can hallucinate parameter values.
- Handle errors gracefully — if a function fails, return a structured error message as the tool result so the model can respond appropriately.
- Limit the number of tools — while up to 128 tools are supported, model performance degrades with too many. Keep it under 20 for best results.
- Use
tool_choice: "none"to disable — if you want to pass tools for context but prevent the model from calling them in a specific turn, settool_choice: "none".