Function calling (also known as tool use) allows the model to invoke external functions you define. Instead of generating a text-only response, the model can output a structured function call with arguments, which your code executes and returns the result for the model to incorporate into its final answer.
This enables the model to interact with APIs, databases, calculators, and any external system.
- You define one or more tools (functions) in the request, each with a name, description, and JSON Schema for parameters.
- The model decides whether to call a tool based on the user's message.
- If the model calls a tool, it returns a response with
finish_reason: "tool_calls"containing the function name and arguments. - Your code executes the function and sends the result back.
- The model generates a final response using the function result.
User message --> Model --> tool_calls response
|
Your code executes function
|
Tool result sent back --> Model --> Final response
Tools are defined in the tools array of the request. Each tool has a type of "function" and a function object containing the name, description, and parameter schema.
{
"model": "dos-ai",
"messages": [
{ "role": "user", "content": "What is the weather in Hanoi?" }
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a given city.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. 'Hanoi' or 'Ho Chi Minh City'."
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit. Defaults to celsius."
}
},
"required": ["city"]
}
}
}
]
}- Write clear descriptions. The model uses the description to decide when to call the function. Be specific about what the function does and when it should be used.
- Use JSON Schema constraints. Use
enum,required,minimum,maximum, andpatternto constrain parameters. This helps the model generate valid arguments. - Keep parameter names intuitive. Use descriptive names like
cityrather thancorparam1.
When the model decides to call a tool, the response looks like this:
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Hanoi\", \"unit\": \"celsius\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}Key points:
contentmay benullwhen the model makes a tool call.argumentsis a JSON string that you need to parse.idon each tool call is required when sending the result back.finish_reasonis"tool_calls"instead of"stop".
After executing the function, send the result back by appending both the assistant's tool call message and a tool role message to the conversation:
{
"model": "dos-ai",
"messages": [
{ "role": "user", "content": "What is the weather in Hanoi?" },
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Hanoi\", \"unit\": \"celsius\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"temperature\": 32, \"unit\": \"celsius\", \"condition\": \"Partly cloudy\", \"humidity\": 75}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a given city.",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "The city name." },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
}
]
}The model then generates a natural language response incorporating the tool result:
{
"choices": [
{
"message": {
"role": "assistant",
"content": "The current weather in Hanoi is 32C and partly cloudy with 75% humidity."
},
"finish_reason": "stop"
}
]
}The model can call multiple tools in a single response. This is known as parallel tool calling.
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_001",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Hanoi\"}"
}
},
{
"id": "call_002",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Ho Chi Minh City\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}When responding, include a separate tool message for each call, matched by tool_call_id:
{
"messages": [
{ "role": "user", "content": "Compare the weather in Hanoi and Ho Chi Minh City." },
{
"role": "assistant",
"content": null,
"tool_calls": [
{ "id": "call_001", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"Hanoi\"}" } },
{ "id": "call_002", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"Ho Chi Minh City\"}" } }
]
},
{
"role": "tool",
"tool_call_id": "call_001",
"content": "{\"temperature\": 32, \"condition\": \"Partly cloudy\"}"
},
{
"role": "tool",
"tool_call_id": "call_002",
"content": "{\"temperature\": 35, \"condition\": \"Sunny\"}"
}
]
}You can control whether and how the model uses tools with the tool_choice parameter:
| Value | Behavior |
|---|---|
"auto" |
The model decides whether to call a tool (default). |
"none" |
The model will not call any tools, even if they are defined. |
"required" |
The model must call at least one tool. |
{"type": "function", "function": {"name": "get_weather"}} |
Force the model to call a specific function. |
{
"model": "dos-ai",
"messages": [{ "role": "user", "content": "What is the weather?" }],
"tools": ["..."],
"tool_choice": "required"
}import json
from openai import OpenAI
client = OpenAI(
api_key="dos_sk_your_api_key_here",
base_url="https://api.dos.ai/v1",
)
# Define available tools
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name.",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit.",
},
},
"required": ["city"],
},
},
},
]
# Simulate the actual function
def get_weather(city: str, unit: str = "celsius") -> dict:
# In production, call a real weather API
return {
"city": city,
"temperature": 32,
"unit": unit,
"condition": "Partly cloudy",
}
# Step 1: Send the user message with tools
messages = [{"role": "user", "content": "What is the weather like in Hanoi?"}]
response = client.chat.completions.create(
model="dos-ai",
messages=messages,
tools=tools,
)
assistant_message = response.choices[0].message
# Step 2: Check if the model wants to call a tool
if assistant_message.tool_calls:
# Append the assistant message (with tool calls) to history
messages.append(assistant_message)
# Execute each tool call
for tool_call in assistant_message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# Dispatch to the actual function
if function_name == "get_weather":
result = get_weather(**arguments)
else:
result = {"error": f"Unknown function: {function_name}"}
# Append the tool result
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
}
)
# Step 3: Get the final response with tool results
final_response = client.chat.completions.create(
model="dos-ai",
messages=messages,
tools=tools,
)
print(final_response.choices[0].message.content)
else:
# No tool call -- direct response
print(assistant_message.content)import OpenAI from "openai";
const client = new OpenAI({
apiKey: "dos_sk_your_api_key_here",
baseURL: "https://api.dos.ai/v1",
});
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a city.",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "The city name." },
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit.",
},
},
required: ["city"],
},
},
},
];
// Simulate the actual function
function getWeather(city, unit = "celsius") {
return { city, temperature: 32, unit, condition: "Partly cloudy" };
}
// Step 1: Send user message with tools
const messages = [
{ role: "user", content: "What is the weather like in Hanoi?" },
];
let response = await client.chat.completions.create({
model: "dos-ai",
messages,
tools,
});
const assistantMessage = response.choices[0].message;
// Step 2: Check if the model wants to call a tool
if (assistantMessage.tool_calls) {
messages.push(assistantMessage);
for (const toolCall of assistantMessage.tool_calls) {
const args = JSON.parse(toolCall.function.arguments);
let result;
if (toolCall.function.name === "get_weather") {
result = getWeather(args.city, args.unit);
} else {
result = { error: `Unknown function: ${toolCall.function.name}` };
}
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
}
// Step 3: Get the final response
response = await client.chat.completions.create({
model: "dos-ai",
messages,
tools,
});
console.log(response.choices[0].message.content);
} else {
console.log(assistantMessage.content);
}# Step 1: Send user message with tools
curl https://api.dos.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dos_sk_your_api_key_here" \
-d '{
"model": "dos-ai",
"messages": [
{"role": "user", "content": "What is the weather in Hanoi?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city name."},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
}'
# Step 2: After parsing the tool call response, send back the result
curl https://api.dos.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dos_sk_your_api_key_here" \
-d '{
"model": "dos-ai",
"messages": [
{"role": "user", "content": "What is the weather in Hanoi?"},
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Hanoi\", \"unit\": \"celsius\"}"
}
}]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"temperature\": 32, \"unit\": \"celsius\", \"condition\": \"Partly cloudy\"}"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city name."},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
}'Common issues with function calling:
| Issue | Cause | Solution |
|---|---|---|
| Model ignores tools | Description is too vague | Write a clearer, more specific function description. |
| Invalid arguments | Schema is too loose | Add required, enum, and constraints to the schema. |
| Model hallucinates functions | Too many tools defined | Reduce the number of tools, or use tool_choice to constrain. |
JSON parse error on arguments |
Model output was malformed | Wrap JSON.parse in a try/catch and retry the request. |
- Chat Completions -- learn the basics of the API.
- Streaming -- stream tool call responses in real time.
- Structured Outputs -- get reliable JSON from the model.