We gebruiken cookies. Cookies en browseropslag houden je ingelogd en onthouden je voorkeuren. Met je toestemming meten we ook hoe de site wordt gebruikt. Je kunt je wanneer je wilt. Zie ons cookiebeleid.

Gids: agents met tool calling

Aan de slag

Gids: agents met tool calling

Bouw een agent-loop met parallelle tool calls, een staplimiet, gestructureerde uitvoer en kostenbeheersing met het OpenAI-formaat.

Een agent is een loop: het model leest het gesprek, besluit een of meer van jouw tools aan te roepen, jij voert ze uit en stuurt de resultaten terug, en het model gaat door totdat het kan antwoorden. api14 ondersteunt dit met het OpenAI-formaat tools op elk model dat tools aankan, zodat dezelfde code met OpenAI, Anthropic, Google en andere providers werkt door het model-id te wijzigen.

Deze gids bouwt een kleine agent met een staplimiet, parallelle uitvoering van tools, een gestructureerd eindantwoord en kostenbeheersing. De code gebruikt het officiële pakket openai met base URL https://api14.com/api/v1.

Tools definiëren

Elke tool bestaat uit een naam, een beschrijving die het model leest en een JSON Schema voor de argumenten. Houd beschrijvingen specifiek; ze zijn de enige documentatie van het model.

import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api14.com/api/v1", api_key=os.environ["API14_API_KEY"])

def search_orders(customer_email: str) -> list:
    return [{"id": "A-1001", "status": "shipped"}]  # je databasequery

def get_shipment(order_id: str) -> dict:
    return {"order_id": order_id, "carrier": "UPS", "eta": "2026-10-09"}  # je API-aanroep

TOOLS = {"search_orders": search_orders, "get_shipment": get_shipment}

tools = [
    {"type": "function", "function": {
        "name": "search_orders",
        "description": "List a customer's orders with their status.",
        "parameters": {"type": "object", "properties": {"customer_email": {"type": "string"}},
                       "required": ["customer_email"]},
    }},
    {"type": "function", "function": {
        "name": "get_shipment",
        "description": "Carrier and estimated delivery date for one order.",
        "parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
                       "required": ["order_id"]},
    }},
]

De loop met een staplimiet

Elke iteratie is een apart gefactureerd request, dus begrens altijd het aantal stappen. Geef toolfouten als inhoud terug aan het model in plaats van te crashen; modellen herstellen zich meestal door de argumenten te corrigeren.

MAX_STEPS = 8

def run_tool(call):
    try:
        args = json.loads(call.function.arguments)
        result = TOOLS[call.function.name](**args)
    except Exception as e:  # ongeldige JSON, onbekende tool of je functie mislukte
        result = {"error": str(e)}
    return {"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)}

def agent(task, model="openai/gpt-5-mini"):
    messages = [
        {"role": "system", "content": "You are a support agent. Use tools to look things up. Be brief."},
        {"role": "user", "content": task},
    ]
    spent = 0.0
    for step in range(MAX_STEPS):
        r = client.chat.completions.create(model=model, messages=messages, tools=tools, max_tokens=1000)
        spent += r.usage.cost
        msg = r.choices[0].message
        if not msg.tool_calls:
            return msg.content, spent
        messages.append(msg)
        messages.extend(run_tool(call) for call in msg.tool_calls)
    raise RuntimeError(f"No answer after {MAX_STEPS} steps (spent ${spent:.4f})")

print(agent("Where is my order? My email is ada@example.com"))

Stuur tools mee met elk request in de loop. Veel providers weigeren een tool-bericht wanneer het request geen tooldefinities bevat.

Parallelle tool calls

Met parallel_tool_calls (standaard aan waar ondersteund) kan een model in één beurt meerdere aanroepen vragen, bijvoorbeeld get_shipment voor drie bestellingen. Voer ze gelijktijdig uit en voeg per aanroep één tool-bericht toe, in willekeurige volgorde; tool_call_id koppelt elk resultaat aan zijn aanroep. In Node:

import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api14.com/api/v1", apiKey: process.env.API14_API_KEY });
const MAX_STEPS = 8;

async function agent(messages, tools, impl, model = "openai/gpt-5-mini") {
  for (let step = 0; step < MAX_STEPS; step++) {
    const res = await client.chat.completions.create({ model, messages, tools, max_tokens: 1000 });
    const msg = res.choices[0].message;
    if (!msg.tool_calls?.length) return msg.content;

    messages.push(msg);
    const results = await Promise.all(msg.tool_calls.map(async (call) => {
      let content;
      try {
        content = JSON.stringify(await impl[call.function.name](JSON.parse(call.function.arguments)));
      } catch (err) {
        content = JSON.stringify({ error: String(err) });
      }
      return { role: "tool", tool_call_id: call.id, content };
    }));
    messages.push(...results);
  }
  throw new Error(`No answer after ${MAX_STEPS} steps`);
}

Hebben je tools neveneffecten die in volgorde moeten gebeuren (eerst aanmaken, dan bijwerken), zet dan parallel_tool_calls: false zodat het model per beurt om één aanroep vraagt.

Gestructureerde eindoutput

Als het antwoord van de agent door andere code wordt gebruikt, vraag dan om JSON die bij een schema past. Een eenvoudig, draagbaar patroon is één extra aanroep nadat de loop de feiten heeft verzameld (messages is hier het gesprek uit de loop; geef het samen met het antwoord terug uit agent()):

schema = {
    "type": "object",
    "properties": {
        "order_id": {"type": "string"},
        "status": {"type": "string", "enum": ["processing", "shipped", "delivered", "unknown"]},
        "eta": {"type": ["string", "null"]},
        "reply": {"type": "string"},
    },
    "required": ["order_id", "status", "eta", "reply"],
    "additionalProperties": False,
}

final = client.chat.completions.create(
    model="openai/gpt-5-mini",
    messages=messages + [{"role": "user", "content": "Summarize the result for our ticket system."}],
    tools=tools,
    tool_choice="none",
    response_format={"type": "json_schema", "json_schema": {"name": "ticket", "strict": True, "schema": schema}},
)
ticket = json.loads(final.choices[0].message.content)

tool_choice: "none" houdt tools in het request (nodig omdat de geschiedenis toolberichten bevat) en voorkomt verdere aanroepen. Valideer de geparste JSON altijd. Zie Structured outputs.

Een model kiezen

  • Mogelijkheden: het model moet tools vermelden in supported_parameters. Controleer dit met GET /models, zie Modellen. Zoek voor de laatste stap naar structured_outputs of response_format.
  • Routing: voeg provider: { "require_parameters": true } toe (in Python via extra_body) zodat requests alleen naar providers gaan die elke parameter ondersteunen die je meestuurt. Zie Provider routing.
  • Grootte: kleine modellen zoals openai/gpt-5-mini verwerken een paar goed beschreven tools betrouwbaar en goedkoop. Stap over op een groter model wanneer de agent veel tools heeft, lange meerstapsplannen maakt of steeds de verkeerde tool kiest.
  • Test met je eigen tools: voer dezelfde 20 tot 50 echte taken uit op twee of drie kandidaatmodellen en vergelijk slagingspercentage, stappen per taak en totale usage.cost.
curl -s "https://api14.com/api/v1/models?category=text" \
  | jq -r '.data[] | select(.supported_parameters | index("tools")) | "\(.id)  \(.pricing.prompt)"'

Kosten beheersen

Een agent kan voor één taak veel requests doen, en het hele gesprek wordt bij elke stap opnieuw verstuurd. Beperk de uitgaven op meerdere niveaus:

  • Staplimiet (MAX_STEPS) en max_tokens per request, zoals hierboven.
  • Budget per taak: tel usage.cost over de stappen op en stop wanneer je drempel wordt overschreden.
  • Korte toolresultaten: geef alleen de velden terug die het model nodig heeft. Grote tooluitvoer wordt bij elke volgende stap als invoertokens betaald.
  • Dagelijkse USD-limiet per key: maak in API-keys een aparte key voor de agent en stel een dagelijkse limiet in. Als die wordt bereikt, geven requests 429 daily_limit_reached terug tot de volgende UTC-dag; behandel die code door de agent te stoppen, niet door opnieuw te proberen.
  • Model-allowlist: beperk de key tot de modellen die je hebt getest; al het andere geeft 403 model_not_allowed.
  • Prompt caching: houd de systeemprompt en tooldefinities identiek en vooraan in elk request, zodat kortingen voor gecachete prefixen gelden. Zie Prompt caching.

Zie Tool calling, Rate limits en Fouten.