Tool use
Give Kalle functions it can call: look up an order, check a calendar, query your database. Kalle decides when a tool helps, asks you to run it, and uses the result in its answer.
Your code always runs the tool. Kalle never calls your systems directly.
1. Describe your tools
Each tool has a name, a description that tells Kalle when to use it, and an input_schema in JSON Schema that describes its arguments.
curl https://api.kalle.build/v1/messages \
-H "Authorization: Bearer $KALLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kalle-turbo-max",
"max_tokens": 1024,
"tools": [
{
"name": "get_order_status",
"description": "Look up the delivery status of an order by its order number.",
"input_schema": {
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number, for example A-1042."
}
},
"required": ["order_number"]
}
}
],
"messages": [{ "role": "user", "content": "Where is my order A-1042?" }]
}'const response = await fetch("https://api.kalle.build/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KALLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "kalle-turbo-max",
max_tokens: 1024,
tools: [
{
name: "get_order_status",
description: "Look up the delivery status of an order by its order number.",
input_schema: {
type: "object",
properties: {
order_number: {
type: "string",
description: "The order number, for example A-1042."
}
},
required: ["order_number"]
}
}
],
messages: [{ role: "user", content: "Where is my order A-1042?" }]
}),
});
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.kalle.build/v1/messages",
headers={"Authorization": f"Bearer {os.environ['KALLE_API_KEY']}"},
json={
"model": "kalle-turbo-max",
"max_tokens": 1024,
"tools": [
{
"name": "get_order_status",
"description": "Look up the delivery status of an order by its order number.",
"input_schema": {
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number, for example A-1042."
}
},
"required": ["order_number"]
}
}
],
"messages": [{"role": "user", "content": "Where is my order A-1042?"}]
},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{
"model": "kalle-turbo-max",
"max_tokens": 1024,
"tools": [
{
"name": "get_order_status",
"description": "Look up the delivery status of an order by its order number.",
"input_schema": {
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number, for example A-1042."
}
},
"required": ["order_number"]
}
}
],
"messages": [{ "role": "user", "content": "Where is my order A-1042?" }]
}`)
req, _ := http.NewRequest("POST", "https://api.kalle.build/v1/messages", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KALLE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(string(out))
}require "json"
require "net/http"
uri = URI("https://api.kalle.build/v1/messages")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("KALLE_API_KEY")}"
request["Content-Type"] = "application/json"
request.body = <<~JSON
{
"model": "kalle-turbo-max",
"max_tokens": 1024,
"tools": [
{
"name": "get_order_status",
"description": "Look up the delivery status of an order by its order number.",
"input_schema": {
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number, for example A-1042."
}
},
"required": ["order_number"]
}
}
],
"messages": [{ "role": "user", "content": "Where is my order A-1042?" }]
}
JSON
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
puts JSON.pretty_generate(JSON.parse(response.body))<?php
$body = <<<'JSON'
{
"model": "kalle-turbo-max",
"max_tokens": 1024,
"tools": [
{
"name": "get_order_status",
"description": "Look up the delivery status of an order by its order number.",
"input_schema": {
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number, for example A-1042."
}
},
"required": ["order_number"]
}
}
],
"messages": [{ "role": "user", "content": "Where is my order A-1042?" }]
}
JSON;
$ch = curl_init("https://api.kalle.build/v1/messages");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("KALLE_API_KEY"),
"Content-Type: application/json",
],
]);
echo curl_exec($ch);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class KalleExample {
public static void main(String[] args) throws Exception {
String body = """
{
"model": "kalle-turbo-max",
"max_tokens": 1024,
"tools": [
{
"name": "get_order_status",
"description": "Look up the delivery status of an order by its order number.",
"input_schema": {
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The order number, for example A-1042."
}
},
"required": ["order_number"]
}
}
],
"messages": [{ "role": "user", "content": "Where is my order A-1042?" }]
}
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.kalle.build/v1/messages"))
.header("Authorization", "Bearer " + System.getenv("KALLE_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}2. Kalle asks for a tool call
When Kalle wants to use a tool, the response stops with stop_reason set to tool_use and contains a tool_use block:
{
"id": "msg_01J8Z7",
"type": "message",
"role": "assistant",
"model": "kalle-turbo-max",
"content": [
{ "type": "text", "text": "Let me check that order for you." },
{
"type": "tool_use",
"id": "call_7Ks2",
"name": "get_order_status",
"input": { "order_number": "A-1042" }
}
],
"stop_reason": "tool_use",
"usage": { "input_tokens": 212, "output_tokens": 38 }
}3. Send the result back
Run the function with input, then send the conversation again with two more messages: Kalle's message as it was, and a user message with a tool_result block that points to the call by its id.
{
"model": "kalle-turbo-max",
"max_tokens": 1024,
"tools": ["...the same tools as before..."],
"messages": [
{ "role": "user", "content": "Where is my order A-1042?" },
{
"role": "assistant",
"content": [
{ "type": "text", "text": "Let me check that order for you." },
{
"type": "tool_use",
"id": "call_7Ks2",
"name": "get_order_status",
"input": { "order_number": "A-1042" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "call_7Ks2",
"content": "Shipped on 3 November with PostNord. Expected delivery 6 November."
}
]
}
]
}Kalle then answers in plain words, or calls another tool. Keep looping until stop_reason is no longer tool_use.
When a tool fails
Send the failure back instead of stopping. Set is_error to true and describe what went wrong, and Kalle will explain it or try another way:
{
"type": "tool_result",
"tool_use_id": "call_7Ks2",
"is_error": true,
"content": "No order found with the number A-1042."
}Controlling tool calls
tool_choice | What Kalle does |
|---|---|
auto | The default. Kalle calls a tool when it helps. |
any | Kalle must call one of the tools. |
none | Kalle answers without calling tools. |
{ "type": "tool", "name": "get_order_status" } | Kalle must call this exact tool. |
Kalle can ask for several tool calls in one message. Run them, and send all the results back in one user message.
Writing good tools
- Write the
descriptionfor a new colleague: what the tool does, when to use it and what it returns. - Keep arguments few and specific. An
order_numberbeats a free-textquery. - Check every argument before you act on it, just like input from a form.
- Ask your user before a tool does something that cannot be undone, such as a refund or a deleted record.