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.
[
{
"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.
{
"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.
{
"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
- Send
messagesandtools. - If
finish_reasonis"tool_calls", run every call yourself, locally. - Append the assistant message exactly as returned, then one
{"role": "tool", "tool_call_id": ..., "content": ...}message per call. - Send the same
messages(now longer) and the sametoolsagain. Repeat untilfinish_reasonis"stop".
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".
| Value | Effect |
|---|---|
"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.
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.jsonimport json
import os
import requests
API = "https://api.console.larsa.larsima.com/v1"
KEY = os.environ["LARSA_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json"}
def get_weather(city):
# Your own function. Larsa never sees its body -- only the
# name, the description and the arguments the model chose.
fake = {"Tehran": {"c": 34, "sky": "clear"},
"Madrid": {"c": 29, "sky": "sunny"}}
return fake.get(city, {"c": None, "sky": "unknown"})
TOOLS = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a named city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user",
"content": "Weather in Tehran and Madrid, one sentence each."}]
# Turn 1 -- ask, and let the model decide what to call.
r1 = requests.post(f"{API}/chat/completions", headers=HEADERS,
json={"model": "larsa-auto",
"messages": messages, "tools": TOOLS})
msg = r1.json()["choices"][0]["message"]
messages.append(msg)
for call in msg.get("tool_calls", []):
args = json.loads(call["function"]["arguments"])
result = get_weather(args["city"])
messages.append({"role": "tool", "tool_call_id": call["id"],
"content": json.dumps(result)})
# Turn 2 -- send the results back, get the sentence.
r2 = requests.post(f"{API}/chat/completions", headers=HEADERS,
json={"model": "larsa-auto",
"messages": messages, "tools": TOOLS})
print(r2.json()["choices"][0]["message"]["content"])const API = "https://api.console.larsa.larsima.com/v1";
const HEADERS = {
Authorization: `Bearer ${process.env.LARSA_API_KEY}`,
"Content-Type": "application/json",
};
function getWeather(city) {
const fake = { Tehran: { c: 34, sky: "clear" },
Madrid: { c: 29, sky: "sunny" } };
return fake[city] ?? { c: null, sky: "unknown" };
}
const tools = [{
type: "function",
function: {
name: "get_weather",
description: "Current weather for a named city.",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
}];
const messages = [{ role: "user",
content: "Weather in Tehran and Madrid, one sentence each." }];
async function post(body) {
const res = await fetch(`${API}/chat/completions`, {
method: "POST", headers: HEADERS, body: JSON.stringify(body),
});
return res.json();
}
// Turn 1 -- ask, and let the model decide what to call.
let out = await post({ model: "larsa-auto", messages, tools });
const msg = out.choices[0].message;
messages.push(msg);
for (const call of msg.tool_calls ?? []) {
const args = JSON.parse(call.function.arguments);
const result = getWeather(args.city);
messages.push({ role: "tool", tool_call_id: call.id,
content: JSON.stringify(result) });
}
// Turn 2 -- send the results back, get the sentence.
out = await post({ model: "larsa-auto", messages, tools });
console.log(out.choices[0].message.content);type ToolCall = { id: string;
function: { name: string; arguments: string } };
const API = "https://api.console.larsa.larsima.com/v1";
const HEADERS = {
Authorization: `Bearer ${process.env.LARSA_API_KEY}`,
"Content-Type": "application/json",
};
function getWeather(city: string): { c: number | null; sky: string } {
const fake: Record<string, { c: number; sky: string }> = {
Tehran: { c: 34, sky: "clear" },
Madrid: { c: 29, sky: "sunny" },
};
return fake[city] ?? { c: null, sky: "unknown" };
}
const tools = [{
type: "function",
function: {
name: "get_weather",
description: "Current weather for a named city.",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
}];
const messages: Record<string, unknown>[] = [{ role: "user",
content: "Weather in Tehran and Madrid, one sentence each." }];
async function post(body: unknown) {
const res = await fetch(`${API}/chat/completions`, {
method: "POST", headers: HEADERS, body: JSON.stringify(body),
});
return res.json();
}
// Turn 1 -- ask, and let the model decide what to call.
let out = await post({ model: "larsa-auto", messages, tools });
const msg = out.choices[0].message;
messages.push(msg);
for (const call of (msg.tool_calls ?? []) as ToolCall[]) {
const args = JSON.parse(call.function.arguments);
const result = getWeather(args.city);
messages.push({ role: "tool", tool_call_id: call.id,
content: JSON.stringify(result) });
}
// Turn 2 -- send the results back, get the sentence.
out = await post({ model: "larsa-auto", messages, tools });
console.log(out.choices[0].message.content);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const api = "https://api.console.larsa.larsima.com/v1"
func getWeather(city string) map[string]any {
fake := map[string]map[string]any{
"Tehran": {"c": 34, "sky": "clear"},
"Madrid": {"c": 29, "sky": "sunny"},
}
if w, ok := fake[city]; ok {
return w
}
return map[string]any{"c": nil, "sky": "unknown"}
}
func post(body map[string]any) map[string]any {
buf, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", api+"/chat/completions",
bytes.NewReader(buf))
req.Header.Set("Authorization", "Bearer "+os.Getenv("LARSA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
var out map[string]any
json.Unmarshal(raw, &out)
return out
}
func main() {
tools := []map[string]any{{
"type": "function",
"function": map[string]any{
"name": "get_weather",
"description": "Current weather for a named city.",
"parameters": map[string]any{
"type": "object",
"properties": map[string]any{
"city": map[string]any{"type": "string"}},
"required": []string{"city"},
},
},
}}
messages := []map[string]any{{"role": "user", "content":
"Weather in Tehran and Madrid, one sentence each."}}
// Turn 1 -- ask, and let the model decide what to call.
out := post(map[string]any{"model": "larsa-auto",
"messages": messages, "tools": tools})
choice := out["choices"].([]any)[0].(map[string]any)
msg := choice["message"].(map[string]any)
messages = append(messages, msg)
if calls, ok := msg["tool_calls"].([]any); ok {
for _, c := range calls {
call := c.(map[string]any)
fn := call["function"].(map[string]any)
var args map[string]any
json.Unmarshal([]byte(fn["arguments"].(string)), &args)
result := getWeather(args["city"].(string))
resJSON, _ := json.Marshal(result)
messages = append(messages, map[string]any{
"role": "tool", "tool_call_id": call["id"],
"content": string(resJSON)})
}
}
// Turn 2 -- send the results back, get the sentence.
out = post(map[string]any{"model": "larsa-auto",
"messages": messages, "tools": tools})
final := out["choices"].([]any)[0].(map[string]any)
fmt.Println(final["message"].(map[string]any)["content"])
}// Cargo.toml: reqwest = { version = "0.12", features =
// ["blocking", "json"] }, serde_json = "1"
use serde_json::{json, Value};
use std::collections::HashMap;
use std::env;
const API: &str = "https://api.console.larsa.larsima.com/v1";
fn get_weather(city: &str) -> Value {
let mut fake: HashMap<&str, Value> = HashMap::new();
fake.insert("Tehran", json!({"c": 34, "sky": "clear"}));
fake.insert("Madrid", json!({"c": 29, "sky": "sunny"}));
fake.get(city).cloned()
.unwrap_or(json!({"c": null, "sky": "unknown"}))
}
fn post(client: &reqwest::blocking::Client, key: &str,
body: &Value) -> Value {
client.post(format!("{API}/chat/completions"))
.bearer_auth(key).json(body)
.send().unwrap().json().unwrap()
}
fn main() {
let key = env::var("LARSA_API_KEY").unwrap();
let client = reqwest::blocking::Client::new();
let tools = json!([{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a named city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]);
let mut messages = vec![json!({"role": "user",
"content": "Weather in Tehran and Madrid, one sentence each."})];
// Turn 1 -- ask, and let the model decide what to call.
let out = post(&client, &key, &json!({"model": "larsa-auto",
"messages": messages, "tools": tools}));
let msg = out["choices"][0]["message"].clone();
messages.push(msg.clone());
if let Some(calls) = msg["tool_calls"].as_array() {
for call in calls {
let raw = call["function"]["arguments"]
.as_str().unwrap();
let args: Value = serde_json::from_str(raw).unwrap();
let result = get_weather(args["city"].as_str().unwrap());
messages.push(json!({"role": "tool",
"tool_call_id": call["id"],
"content": result.to_string()}));
}
}
// Turn 2 -- send the results back, get the sentence.
let out = post(&client, &key, &json!({"model": "larsa-auto",
"messages": messages, "tools": tools}));
println!("{}", out["choices"][0]["message"]["content"]);
}// Maven/Gradle: org.json:json:20240303
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
public class FunctionCalling {
static final String API = "https://api.console.larsa.larsima.com/v1";
static final HttpClient CLIENT = HttpClient.newHttpClient();
static JSONObject getWeather(String city) {
Map<String, JSONObject> fake = Map.of(
"Tehran", new JSONObject().put("c", 34).put("sky", "clear"),
"Madrid", new JSONObject().put("c", 29).put("sky", "sunny"));
return fake.getOrDefault(city, new JSONObject()
.put("c", JSONObject.NULL).put("sky", "unknown"));
}
static JSONObject post(JSONObject body) throws Exception {
HttpRequest req = HttpRequest
.newBuilder(URI.create(API + "/chat/completions"))
.header("Authorization", "Bearer " + System.getenv("LARSA_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> res = CLIENT.send(req,
HttpResponse.BodyHandlers.ofString());
return new JSONObject(res.body());
}
public static void main(String[] args) throws Exception {
JSONArray tools = new JSONArray().put(new JSONObject()
.put("type", "function")
.put("function", new JSONObject()
.put("name", "get_weather")
.put("description",
"Current weather for a named city.")
.put("parameters", new JSONObject()
.put("type", "object")
.put("properties", new JSONObject().put(
"city", new JSONObject()
.put("type", "string")))
.put("required", new JSONArray()
.put("city")))));
JSONArray messages = new JSONArray().put(new JSONObject()
.put("role", "user")
.put("content",
"Weather in Tehran and Madrid, one sentence each."));
// Turn 1 -- ask, and let the model decide what to call.
JSONObject out = post(new JSONObject()
.put("model", "larsa-auto")
.put("messages", messages).put("tools", tools));
JSONObject msg = out.getJSONArray("choices")
.getJSONObject(0).getJSONObject("message");
messages.put(msg);
JSONArray calls = msg.has("tool_calls")
? msg.getJSONArray("tool_calls") : new JSONArray();
for (int i = 0; i < calls.length(); i++) {
JSONObject call = calls.getJSONObject(i);
JSONObject fn = call.getJSONObject("function");
JSONObject callArgs = new JSONObject(
fn.getString("arguments"));
JSONObject result = getWeather(
callArgs.getString("city"));
messages.put(new JSONObject()
.put("role", "tool")
.put("tool_call_id", call.getString("id"))
.put("content", result.toString()));
}
// Turn 2 -- send the results back, get the sentence.
out = post(new JSONObject()
.put("model", "larsa-auto")
.put("messages", messages).put("tools", tools));
System.out.println(out.getJSONArray("choices")
.getJSONObject(0).getJSONObject("message")
.getString("content"));
}
}using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
const string Api = "https://api.console.larsa.larsima.com/v1";
var http = new HttpClient();
var key = Environment.GetEnvironmentVariable("LARSA_API_KEY");
JsonObject GetWeather(string city)
{
var fake = new Dictionary<string, JsonObject>
{
["Tehran"] = new JsonObject { ["c"] = 34, ["sky"] = "clear" },
["Madrid"] = new JsonObject { ["c"] = 29, ["sky"] = "sunny" },
};
return fake.TryGetValue(city, out var w) ? w
: new JsonObject { ["c"] = null, ["sky"] = "unknown" };
}
async Task<JsonObject> Post(JsonObject body)
{
using var req = new HttpRequestMessage(HttpMethod.Post,
$"{Api}/chat/completions");
req.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", key);
req.Content = new StringContent(body.ToJsonString(),
Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
return JsonNode.Parse(await res.Content
.ReadAsStringAsync())!.AsObject();
}
var tools = new JsonArray {
new JsonObject {
["type"] = "function",
["function"] = new JsonObject {
["name"] = "get_weather",
["description"] = "Current weather for a named city.",
["parameters"] = new JsonObject {
["type"] = "object",
["properties"] = new JsonObject {
["city"] = new JsonObject { ["type"] = "string" } },
["required"] = new JsonArray { "city" },
},
},
},
};
var messages = new JsonArray {
new JsonObject { ["role"] = "user", ["content"] =
"Weather in Tehran and Madrid, one sentence each." },
};
// Turn 1 -- ask, and let the model decide what to call.
var outp = await Post(new JsonObject { ["model"] = "larsa-auto",
["messages"] = messages, ["tools"] = tools });
var msg = outp["choices"]![0]!["message"]!.AsObject();
messages.Add(JsonNode.Parse(msg.ToJsonString()));
if (msg["tool_calls"] is JsonArray calls)
{
foreach (var c in calls)
{
var call = c!.AsObject();
var args = JsonNode.Parse(call["function"]!["arguments"]!
.GetValue<string>())!.AsObject();
var result = GetWeather(args["city"]!.GetValue<string>());
messages.Add(new JsonObject {
["role"] = "tool",
["tool_call_id"] = call["id"]!.GetValue<string>(),
["content"] = result.ToJsonString(),
});
}
}
// Turn 2 -- send the results back, get the sentence.
outp = await Post(new JsonObject { ["model"] = "larsa-auto",
["messages"] = messages, ["tools"] = tools });
Console.WriteLine(
outp["choices"]![0]!["message"]!["content"]!.GetValue<string>());<?php
$api = "https://api.console.larsa.larsima.com/v1";
$key = getenv("LARSA_API_KEY");
function get_weather(string $city): array {
$fake = [
"Tehran" => ["c" => 34, "sky" => "clear"],
"Madrid" => ["c" => 29, "sky" => "sunny"],
];
return $fake[$city] ?? ["c" => null, "sky" => "unknown"];
}
function post(string $api, string $key, array $body): array {
$ch = curl_init("$api/chat/completions");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key",
"Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode($body),
]);
$raw = curl_exec($ch);
curl_close($ch);
return json_decode($raw, true);
}
$tools = [[
"type" => "function",
"function" => [
"name" => "get_weather",
"description" => "Current weather for a named city.",
"parameters" => [
"type" => "object",
"properties" => ["city" => ["type" => "string"]],
"required" => ["city"],
],
],
]];
$messages = [["role" => "user",
"content" => "Weather in Tehran and Madrid, one sentence each."]];
// Turn 1 -- ask, and let the model decide what to call.
$out = post($api, $key, ["model" => "larsa-auto",
"messages" => $messages, "tools" => $tools]);
$msg = $out["choices"][0]["message"];
$messages[] = $msg;
foreach ($msg["tool_calls"] ?? [] as $call) {
$args = json_decode($call["function"]["arguments"], true);
$result = get_weather($args["city"]);
$messages[] = [
"role" => "tool",
"tool_call_id" => $call["id"],
"content" => json_encode($result),
];
}
// Turn 2 -- send the results back, get the sentence.
$out = post($api, $key, ["model" => "larsa-auto",
"messages" => $messages, "tools" => $tools]);
echo $out["choices"][0]["message"]["content"], "\n";require "json"
require "net/http"
require "uri"
API = "https://api.console.larsa.larsima.com/v1"
KEY = ENV.fetch("LARSA_API_KEY")
def get_weather(city)
fake = {
"Tehran" => { c: 34, sky: "clear" },
"Madrid" => { c: 29, sky: "sunny" },
}
fake.fetch(city, { c: nil, sky: "unknown" })
end
def post(body)
uri = URI("#{API}/chat/completions")
req = Net::HTTP::Post.new(uri,
"Authorization" => "Bearer #{KEY}",
"Content-Type" => "application/json")
req.body = body.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
JSON.parse(res.body)
end
tools = [{
type: "function",
function: {
name: "get_weather",
description: "Current weather for a named city.",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
}]
messages = [{ role: "user",
content: "Weather in Tehran and Madrid, one sentence each." }]
# Turn 1 -- ask, and let the model decide what to call.
out = post({ model: "larsa-auto", messages: messages, tools: tools })
msg = out["choices"][0]["message"]
messages << msg
(msg["tool_calls"] || []).each do |call|
args = JSON.parse(call["function"]["arguments"])
result = get_weather(args["city"])
messages << { role: "tool", tool_call_id: call["id"],
content: result.to_json }
end
# Turn 2 -- send the results back, get the sentence.
out = post({ model: "larsa-auto", messages: messages, tools: tools })
puts out["choices"][0]["message"]["content"]