Tool Use / Function Calling
Have the model output structured call arguments based on tool (function) signatures you define. You execute the actual function on the backend and feed the result back to the model to close the conversation loop. This is the standard pattern for Agent workflows, automation, and external system integration.
When to Use
- The model needs to call your business systems (look up orders, place orders, send messages, change configuration)
- The model needs to access real-time data (databases, search, internal APIs)
- You require strict schema on the output (better suited for dynamic dispatch than plain
response_formatJSON) - Building Agent / multi-step task loops
Don't need tool calling and just want output that strictly conforms to a schema? Use Structured Output instead—it's lighter weight.
Basic Usage (OpenAI Protocol / Chat Completions)
Three-step loop: define → model decides to call → you execute and return the result.
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://live-turing.cn.llm.tcljd.com/api/v1",
)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a specified city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}]
# Turn 1: model decides to call the tool
r1 = client.chat.completions.create(
model="turing/gpt-5.4-mini",
messages=[{"role": "user", "content": "What's the temperature in Shanghai today?"}],
tools=tools,
)
tool_call = r1.choices[0].message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
# args == {"city": "Shanghai", "unit": "celsius"}
# Turn 2: execute the real function and return the result
r2 = client.chat.completions.create(
model="turing/gpt-5.4-mini",
messages=[
{"role": "user", "content": "What's the temperature in Shanghai today?"},
r1.choices[0].message, # contains tool_calls
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": '{"temp_c": 22, "condition": "Sunny"}',
},
],
tools=tools,
)
print(r2.choices[0].message.content)
# -> "It's 22°C and sunny in Shanghai today."
curl $TURING_BASE_URL/chat/completions \
-H "Authorization: Bearer $TURING_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "turing/gpt-5.4-mini",
"messages": [{"role": "user", "content": "What'\''s the temperature in Shanghai today?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a specified city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}]
}'
Controlling Tool Selection (tool_choice)
| Value | Behavior |
|---|---|
"auto" (default) | The model decides whether to call a tool |
"none" | Disables tool calling; forces a normal conversational reply |
"required" | The model must call at least one tool |
{"type": "function", "function": {"name": "X"}} | Forces a call to the specified tool X |
Parallel Tool Calls
Most modern models (GPT-4.1+, Claude 3.5+, Gemini 2.0+, Qwen-Plus) support issuing multiple parallel tool calls in a single response by default. To disable: parallel_tool_calls: false.
Anthropic Messages Protocol (Claude Native)
The Claude native protocol uses different field names for tool definitions—input_schema instead of parameters—and tool results are returned via a tool_result content block.
from anthropic import Anthropic
client = Anthropic(
base_url="https://live-turing.cn.llm.tcljd.com/api/v1",
auth_token="your-api-key",
)
tools = [{
"name": "get_weather",
"description": "Get the current weather for a specified city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}]
# Turn 1
r1 = client.messages.create(
model="turing/claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "What's the temperature in Shanghai today?"}],
tools=tools,
)
# Find the tool_use block
tool_use = next(b for b in r1.content if b.type == "tool_use")
# Turn 2: return the result
r2 = client.messages.create(
model="turing/claude-sonnet-5",
max_tokens=1024,
messages=[
{"role": "user", "content": "What's the temperature in Shanghai today?"},
{"role": "assistant", "content": r1.content},
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": '{"temp_c": 22, "condition": "Sunny"}'
}],
},
],
tools=tools,
)
Claude Native tool_choice
{"type": "auto"} // default
{"type": "any"} // must call a tool
{"type": "tool", "name": "X"} // force a specific tool
{"type": "none"} // disable tool calling
Provider Differences at a Glance
| Provider | Schema Field | Tool Result Return | Force Call | Parallel | Notes |
|---|---|---|---|---|---|
| OpenAI | parameters | "role": "tool" + tool_call_id + content | tool_choice: "required" or explicit function object | ✅ | Full JSON Schema support |
| Anthropic | input_schema | tool_result content block | tool_choice: "any" or explicit tool object | ✅ | Supports cache_control for caching tool definitions |
| Gemini | parameters | tool role message | tool_choice: "any" | ✅ | Gemini 3: multi-turn requires thought_signature (see Gemini Thought Signatures) |
| Qwen | parameters | "role": "tool" | explicit tool_choice object | ✅ | Recommended: qwen-plus / qwen-max series; some smaller models have poor JSON stability |
| DeepSeek | parameters | "role": "tool" | tool_choice | ✅ | Enabling reasoning mode in DeepSeek R1 may reduce tool call stability |
Cross-provider best practice: When using the Chat Completions protocol, Turing automatically maps OpenAI-style tools to each provider's native format—just write to the OpenAI spec and you won't need to worry about provider differences in most cases. For Claude, you can also use the Messages protocol for finer-grained control.
Parameter Reference
For full request/response schemas, see:
- Chat Completions:
/api/create-chat-completion(tools,tool_choice,parallel_tool_calls) - Messages:
/api/create-message(tools,tool_choice) - Responses:
/api/create-response(toolsflat structure, no outerfunctionwrapper)
Billing Impact
Tool definitions themselves consume input tokens (description, parameters, and input_schema are all encoded). The more tools you define—and the more verbose they are—the higher your input costs. Recommendations:
- Once tool definitions stabilize, use Prompt Caching to cache the
toolsblock (Claude supportscache_control) - Keep
descriptionconcise to avoid repeating long text on every request - Keep the number of tools per request under 20; more than that degrades model accuracy
FAQ
- The model never calls the tool → Change
tool_choicefromautotorequired, or emphasize "use tools when necessary" in the system prompt - Malformed argument JSON / hallucinated parameters → More likely with lightweight or near-deprecated models; upgrade to a more capable recent model, or add stronger constraints in
description - Gemini 3 multi-turn returns 400 → Missing
thought_signature; see Gemini Thought Signatures - Not receiving all results from parallel calls → Ensure Turn 2 messages include a corresponding tool message for every
tool_call_id
See also
- Structured Output — Get schema-conformant JSON in a single call (no tool execution loop required)
- Chat Completions — Core protocol concepts
- Messages (Anthropic) — Tool use under the Claude native protocol
- Gemini Thought Signatures — Required for Gemini 3 multi-turn
- Prompt Caching — Cache
toolsdefinitions to reduce repeated costs