Function calling

Describe a function you can run, and get back structured arguments instead of a paragraph to parse.

A function tool is yours: you describe its name, what it does, and the shape of its arguments as JSON Schema. When a question needs it, the model doesn't answer — it hands back the name and the arguments, and stops. Nothing on this platform runs your function. You execute it, send the result back, and the model uses it to write the actual answer.

The tools parameter

tools is a list on your chat.completions request. Each entry naming a function has three parts: a name, a description the model reads to decide whether this is the right function, and parameters — a JSON Schema object describing what to fill in.

tools
[
  {
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Current weather for a named city.",
      "parameters": {
        "type": "object",
        "properties": {
          "city": { "type": "string" }
        },
        "required": ["city"]
      }
    }
  }
]
description is the only thing the model has to go on when deciding whether, and when, to call this. Write it the way you'd explain the function to a colleague, not the way you'd name a variable.

The tool_call response

When the model decides to call one of your functions, the HTTP response still comes back normally — status 200, same shape — but choices[0].message carries tool_calls instead of a finished answer, and finish_reason is "tool_calls". Each call has an id and a function.arguments string: a string, JSON-encoded, not an object — decode it yourself.

Response
{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call_8f2a1",
        "type": "function",
        "function": {
          "name": "get_weather",
          "arguments": "{\"city\": \"Tehran\"}"
        }
      }]
    },
    "finish_reason": "tool_calls"
  }]
}

Parallel calls

tool_calls is an array, and one turn can hold more than one entry — asked for the weather in two cities, the model can ask for both at once rather than one round trip each. Run every call, and answer every id before you send the next request; a call left unanswered is not a state this platform's backends are asked to recover from.

Response
{
  "message": {
    "tool_calls": [
      { "id": "call_1", "function": { "name": "get_weather",
          "arguments": "{\"city\": \"Tehran\"}" } },
      { "id": "call_2", "function": { "name": "get_weather",
          "arguments": "{\"city\": \"Madrid\"}" } }
    ]
  },
  "finish_reason": "tool_calls"
}

The round trip

  1. Send messages and tools.
  2. If finish_reason is "tool_calls", run every call yourself, locally.
  3. Append the assistant message exactly as returned, then one {"role": "tool", "tool_call_id": ..., "content": ...} message per call.
  4. Send the same messages (now longer) and the same tools again. Repeat until finish_reason is "stop".
For a function tool, nothing here inspects, logs, or runs your function's body — the whole round trip happens between your code and the model. This platform's part ends at handing back tool_calls.

tool_choice

tool_choice is passed straight through to the model behind your request, unchanged. It defaults to "auto".

ValueEffect
"auto"The model decides whether to call anything. Default.
"none"Forbidden — the model must answer in text this turn.
"required"The model must call something, any of the tools you sent.
{"type": "function", "function": {"name": "…"}}Forces that one function, every time.

Mixing your functions with built-in tools

web_search, file_search, code_interpreter and an mcp server's tools run on this side — the platform executes them and loops back to the model itself, invisibly. Your function tools never do. You can list both kinds in one tools array.

If the model calls one of *your* functions and a built-in one in the same turn, the built-in call runs as usual — but your function's call is answered on your behalf with a synthetic error, and the request keeps looping on its own rather than handing control back to you. It only surfaces your tool_calls cleanly when nothing built-in was called in that turn. Until this is tightened, keep a request's tools to one kind: yours, or the platform's.

Complete example

Two turns, one function, two cities asked about at once. The model is larsa-auto.

# Turn 1 -- ask, and let the model decide what to call.
curl https://api.console.larsa.larsima.com/v1/chat/completions \
  -H "Authorization: Bearer $LARSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "larsa-auto",
    "messages": [{"role": "user",
      "content": "Weather in Tehran and Madrid, one sentence each."}],
    "tools": [{"type": "function", "function": {
      "name": "get_weather",
      "description": "Current weather for a named city.",
      "parameters": {"type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]}
    }}]
  }' > turn1.json

# Read turn1.json: for every entry in
# .choices[0].message.tool_calls, run get_weather(city)
# yourself, and build turn2.json as messages + [the assistant
# message from turn1, unchanged] + [one {"role":"tool",
# "tool_call_id":..., "content":...} message per call], then:

# Turn 2 -- send the results back, get the sentence.
curl https://api.console.larsa.larsima.com/v1/chat/completions \
  -H "Authorization: Bearer $LARSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d @turn2.json
Navigate Open esc Close