Tool calling

Describe functions your code can run; the model decides when to call them and with what arguments. You run the function and send the result back. The format is OpenAI's, on every model whose catalog entry has tools set to true.

Define a tool#

Each tool is a JSON Schema description of a function. Good descriptions matter more than clever prompting: the model reads them to decide when a call is appropriate.

curl https://api.ahurasense.com/v1/chat/completions \
  -H "Authorization: Bearer $AHURA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5",
    "messages": [{"role": "user", "content": "What is the weather in Ahmedabad right now?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Current weather for a city.",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {"type": "string", "description": "City name"},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
          },
          "required": ["city"]
        }
      }
    }],
    "tool_choice": "auto"
  }'

Read the call#

When the model wants a tool, the assistant message has tool_calls instead of content, and finish_reason is tool_calls. Arguments arrive as a JSON string you parse.

response
{
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call_7Qx2",
        "type": "function",
        "function": {
          "name": "get_weather",
          "arguments": "{\"city\":\"Ahmedabad\",\"unit\":\"celsius\"}"
        }
      }]
    },
    "finish_reason": "tool_calls"
  }]
}

Return the result#

Append the assistant message as-is, then one tool message per call with the matching tool_call_id, and send the conversation again. The model writes the final answer from the result.

second turn
import json

result = get_weather(**json.loads(call.function.arguments))

second = client.chat.completions.create(
    model="anthropic/claude-sonnet-5",
    messages=[
        {"role": "user", "content": "What is the weather in Ahmedabad right now?"},
        first.choices[0].message,
        {"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)},
    ],
    tools=tools,
)
print(second.choices[0].message.content)

Controlling when tools are used#

  • "tool_choice": "auto": the model decides. The default when tools are present.
  • "tool_choice": "none": never call a tool on this turn, even though tools are defined.
  • "tool_choice": "required": the model must call at least one tool.
  • {"type":"function","function":{"name":"get_weather"}}: force that tool.

A model may return several calls in one turn. Run them all, return every result, then continue. Keep tool results compact: they are input tokens on the next turn.

Streaming tool calls#

On a stream, tool calls arrive as fragments in delta.tool_calls: the first chunk for a call carries its id and function.name, later chunks carry pieces of function.arguments. Group by index and concatenate. The OpenAI SDKs expose this as a finished list once the stream ends; if you parse events yourself, do not try to parse the arguments until finish_reason arrives.

Practical notes#

  1. Check the tools capability in the catalog before sending tools to a model; a model without it ignores them or errors.
  2. Requests with tools are cacheable; the tool definitions are part of the cache key, so a changed schema is a cache miss.
  3. The Anthropic-compatible route passes Anthropic tool definitions through but does not stream tool use as structured blocks. Use this endpoint for streamed tool use.

Something missing or wrong on this page? Tell us, and quote the page title.