Capabilities
Tools
Tools turn the model into a function caller. You declare what you want it to be able to do, the model decides when to call, you execute, and the result re-enters the conversation.
The tool-use loop
- Send the request with
toolsdeclared. - The model either replies normally (
stop_reason: end_turn) or asks to call a tool (stop_reason: tool_use). - On
tool_use, find thetool_useblock incontent, run your code with the suppliedinput, and append ausermessage containing atool_resultblock referencing the sametool_use_id. - Send the updated message list back. The model continues with the result in context. Repeat until
stop_reasonis no longertool_use.
Declaring a tool
A tool is a name, a description, and a JSON schema for the input. Spend time on the description and on each parameter description — that text is what the model reads to decide whether to call your tool.
{
"name": "get_weather",
"description": "Get the current weather for a city. Use this when the user asks about temperature, rain, or conditions outside.",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name in plain English." },
"units": { "type": "string", "enum": ["celsius","fahrenheit"], "description": "Default celsius." }
},
"required": ["city"]
}
}Full loop, end to end
The example below sends one prompt, handles the resulting tool call, and prints the model's final answer. In production you would also handle the case where the model asks for a tool you do not recognize (return an error in the tool_result content) and the case where the loop runs more than a sensible number of iterations (cap at 8 or 10).
curl https://caicaini.com/v1/messages \
-H "Authorization: Bearer cai_api_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "caicaini/sonnet",
"max_tokens": 1024,
"tools": [{
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
"units": {"type": "string", "enum": ["celsius","fahrenheit"]}
},
"required": ["city"]
}
}],
"messages": [
{"role": "user", "content": "What is the weather in Lisbon right now?"}
]
}'What a tool_use response looks like
{
"id": "msg_01H...",
"type": "message",
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01XyZ...",
"name": "get_weather",
"input": { "city": "Lisbon", "units": "celsius" }
}
],
"model": "caicaini/sonnet",
"stop_reason": "tool_use",
"usage": { "input_tokens": 122, "output_tokens": 38, "credits_consumed": 76 }
}Replying with a tool_result
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01XyZ...",
"content": "{\"city\":\"Lisbon\",\"temperature\":21,\"units\":\"celsius\",\"conditions\":\"Sunny\"}"
}
]
}Forcing a tool call
By default the model decides whether to call a tool. Override that with tool_choice:
- developers.docs.tools.toolChoiceItem1
- developers.docs.tools.toolChoiceItem2
- developers.docs.tools.toolChoiceItem3
Parallel tool calls
A single assistant turn can include multiple tool_use blocks. Run them in parallel client-side and reply with one user message containing the same number of tool_result blocks, in the same order.
Reporting errors back
If your tool fails, return a tool_result with a JSON-encoded error body and set is_error to true. The model will see the failure and either retry with different inputs or apologize to the user.
{
"type": "tool_result",
"tool_use_id": "toolu_01XyZ...",
"is_error": true,
"content": "{\"error\":\"city_not_found\",\"city\":\"Atlantis\"}"
}Which models support tools
All five virtual ids accept tools. Quality and reliability are highest on caicaini/opus and caicaini/sonnet; the smaller models are excellent for narrow, low-ambiguity tools. For complex agent loops with many tools and long-running reasoning, pair tools with extended thinking on caicaini/opus.