Tools & Function Calling

Use tools and function calling with the Sansa API

Overview

Tools let the model call functions you define. The model decides when to call a tool and generates the arguments — your code executes it and sends the result back.

Tool Definition Format

{
  "tools": [{
    "type": "function",
    "function": {
      // Descriptive name, snake_case recommended.
      "name": "get_weather",
      
      // Detailed description helps the model decide when to use it.
      "description": "Get current weather for a location",
      
      // JSON Schema object.
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "City name or coordinates"
          },
          "units": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "Temperature unit"
          }
        },
        "required": ["location"]
      }
    }
  }]
}

Three-Step Flow

Step 1: Send request with tools

Include tools in your request. The model may respond with text or with tool_calls.

Step 2: Model responds with tool_calls

When the model wants to call a tool, content is null and tool_calls contains the calls. The model generates a unique id for each tool call — your code will use this ID in Step 3:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call12345",  // Model generates this ID
        "type": "function",
        "function": {
          "name": "get_weather",
          "arguments": "{\"location\": \"Tokyo\", \"units\": \"celsius\"}"
        }
      }]
    },
    "finish_reason": "tool_calls"
  }]
}

Step 3: Send tool result back

Append the assistant message (from Step 2) and a tool message with the result. The tool_call_id must match the id from the assistant's tool call. Include tools in every request — not just the first one.

{
  "messages": [
    {"role": "user", "content": "What's the weather in Tokyo?"},
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call12345",  // ID generated by the model
        "type": "function",
        "function": {
          "name": "get_weather",
          "arguments": "{\"location\": \"Tokyo\", \"units\": \"celsius\"}"
        }
      }]
    },
    {
      "role": "tool",
      "tool_call_id": "call12345",  // Use the same ID from above
      "content": "{\"temperature\": 22, \"conditions\": \"sunny\"}"
    }
  ],
  "tools": [/* same tool definitions */]
}

Tool Call IDs

The model generates tool call IDs automatically. When the model decides to call a tool, it returns a response with tool_calls containing unique IDs. Your code should:

  1. Extract the id from each tool call
  2. Execute the function
  3. Send the result back using that exact same ID in tool_call_id

You do not create or format these IDs — simply use whatever the model returns.

// 1. Model returns tool calls with IDs
const toolCall = response.choices[0].message.tool_calls[0];
const toolCallId = toolCall.id;  // e.g., "call12345"

// 2. Execute your function
const result = await myFunction(toolCall.function.arguments);

// 3. Send result back with the SAME ID
messages.push({
  role: "tool",
  tool_call_id: toolCallId,  // Use the exact ID from step 1
  content: JSON.stringify(result)
});

Note: Sansa normalizes tool call IDs internally for cross-provider compatibility. Just pass back the exact ID from the model's response — you do not need to worry about ID format.

tool_choice

Controls which tool (if any) the model calls.

// Default. Model decides whether to call a tool or generate text.
"auto"

// Model will not call any tool.
"none"

// Model must call at least one tool.
"required"

// Forces the model to call the named function.
{ type: "function", function: { name: "my_function" } }

parallel_tool_calls

Default: true.

  • When true, the model may request multiple tool calls in a single response.
  • When false, the model makes one tool call at a time.
  • Each tool call has a unique id — match results by tool_call_id.

Streaming with Tool Calls

  • delta.tool_calls[].id and delta.tool_calls[].function.name arrive first.
  • delta.tool_calls[].function.arguments streams as partial JSON fragments.
  • Client must accumulate argument fragments, then JSON.parse the complete string.
  • finish_reason: "tool_calls" signals all tool calls are complete.

Best Practices

  • Use descriptive function names and detailed descriptions.
  • Include property descriptions in the JSON Schema.
  • Design tools that compose well (search → get details → take action).
  • Set a max iteration limit in agentic loops.
  • Handle errors in tool execution gracefully (return error info as tool content).