Guides
Tool calling
Let deepseek-flash call your functions, using the OpenAI tools format, with a complete multi-turn example.
Tool calling (function calling) lets the model ask your code to run a function and then use the result in its answer. It uses the OpenAI tools format unchanged: you describe functions with JSON Schema, the model replies with tool_calls, you run them and send the results back as tool messages.
The loop
- Send the conversation with a
toolslist. - If the model wants a tool, the response has
finish_reason: "tool_calls"andmessage.tool_calls. - Run each call in your code.
- Append the assistant message (with its
tool_calls) and onetoolmessage per call, each with the matchingtool_call_id. - Send the conversation again. Repeat until
finish_reasonis"stop".
Each round trip is a separate request and is billed separately.
Complete example
This example gives the model one function, get_order_status, and runs the loop until the model answers in plain text.
import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.larasrouter.com/v1",
api_key=os.environ["LARAS_API_KEY"],
)
tools = [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up the shipping status of an order by its ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "Order ID, e.g. INV-1042"}
},
"required": ["order_id"],
},
},
}
]
def get_order_status(order_id: str) -> dict:
# Your real lookup goes here.
return {"order_id": order_id, "status": "shipped", "courier": "JNE", "eta_days": 2}
messages = [
{"role": "system", "content": "You are a helpful support agent for an online shop."},
{"role": "user", "content": "Where is my order INV-1042?"},
]
while True:
response = client.chat.completions.create(
model="deepseek-flash",
messages=messages,
tools=tools,
)
message = response.choices[0].message
if not message.tool_calls:
print(message.content)
break
# Keep the assistant turn that asked for the tools, then answer each call.
messages.append(
{
"role": "assistant",
"content": message.content,
"tool_calls": [call.model_dump() for call in message.tool_calls],
}
)
for call in message.tool_calls:
args = json.loads(call.function.arguments)
result = get_order_status(**args)
messages.append(
{"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)}
)
import OpenAI from "openai";
import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";
const client = new OpenAI({
baseURL: "https://api.larasrouter.com/v1",
apiKey: process.env.LARAS_API_KEY,
});
const tools: ChatCompletionTool[] = [
{
type: "function",
function: {
name: "get_order_status",
description: "Look up the shipping status of an order by its ID.",
parameters: {
type: "object",
properties: {
order_id: { type: "string", description: "Order ID, e.g. INV-1042" },
},
required: ["order_id"],
},
},
},
];
function getOrderStatus(orderId: string) {
// Your real lookup goes here.
return { order_id: orderId, status: "shipped", courier: "JNE", eta_days: 2 };
}
const messages: ChatCompletionMessageParam[] = [
{ role: "system", content: "You are a helpful support agent for an online shop." },
{ role: "user", content: "Where is my order INV-1042?" },
];
while (true) {
const response = await client.chat.completions.create({
model: "deepseek-flash",
messages,
tools,
});
const message = response.choices[0].message;
if (!message.tool_calls?.length) {
console.log(message.content);
break;
}
// Keep the assistant turn that asked for the tools, then answer each call.
messages.push({ role: "assistant", content: message.content, tool_calls: message.tool_calls });
for (const call of message.tool_calls) {
if (call.type !== "function") continue;
const args = JSON.parse(call.function.arguments);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(getOrderStatus(args.order_id)),
});
}
}
What goes over the wire
The first response asks for the tool:
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0",
"type": "function",
"function": { "name": "get_order_status", "arguments": "{\"order_id\": \"INV-1042\"}" }
}
]
},
"finish_reason": "tool_calls"
}
]
}
The second request sends the whole conversation back, including the tool result:
[
{ "role": "system", "content": "You are a helpful support agent for an online shop." },
{ "role": "user", "content": "Where is my order INV-1042?" },
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0",
"type": "function",
"function": { "name": "get_order_status", "arguments": "{\"order_id\": \"INV-1042\"}" }
}
]
},
{
"role": "tool",
"tool_call_id": "call_0",
"content": "{\"order_id\": \"INV-1042\", \"status\": \"shipped\", \"courier\": \"JNE\", \"eta_days\": 2}"
}
]
And the model answers: "Your order INV-1042 has shipped with JNE and should arrive in about 2 days."
Controlling tool use
tool_choice | Behaviour |
|---|---|
"auto" (default when tools are present) | The model decides whether to call a tool. |
"none" | The model answers in text and calls no tool. |
"required" | The model must call at least one tool. |
{"type": "function", "function": {"name": "get_order_status"}} | The model must call that function. |
Tips
- Validate arguments.
function.argumentsis a JSON string written by the model. Parse it defensively and check it against your schema before acting on it. - Several calls at once. A response can contain more than one entry in
tool_calls. Answer every one, each with its owntool_call_id, before the next request. - Streaming. With
"stream": true, tool calls arrive asdelta.tool_callsfragments; concatenatefunction.argumentsbyindexuntilfinish_reasonis"tool_calls". - Token cost. Tool definitions are part of the prompt and count as input tokens on every request. Long, stable prefixes — system prompt, tools — are often served from cache at the lower price.