Usiamo i cookie. Cookie e memoria del browser ti mantengono connesso e ricordano le tue preferenze. Con il tuo consenso misuriamo anche come viene usato il sito. Puoi in qualsiasi momento. Consulta la nostra cookie policy.

Guida: agenti con tool calling

Per iniziare

Guida: agenti con tool calling

Costruisci un ciclo di agente con chiamate di strumento in parallelo, limite di passi, output strutturato finale e controlli di costo sul tuo budget.

Un agente è un ciclo: il modello legge la conversazione, decide di chiamare uno o più dei tuoi strumenti, tu li esegui e rimandi i risultati, e il modello continua finché non può rispondere. api14 lo supporta con il formato tools di OpenAI su ogni modello compatibile con gli strumenti, quindi lo stesso codice funziona con OpenAI, Anthropic, Google e altri provider cambiando l'id del modello.

Questa guida costruisce un piccolo agente con limite di passi, esecuzione parallela degli strumenti, risposta finale strutturata e controlli di costo. Il codice usa il pacchetto ufficiale openai con base URL https://api14.com/api/v1.

Definire gli strumenti

Ogni strumento è un nome, una descrizione che il modello legge e uno JSON Schema per i suoi argomenti. Scrivi descrizioni specifiche: sono l'unica documentazione del modello.

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"}]  # la tua query al database

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

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"]},
    }},
]

Il ciclo con un limite di passi

Ogni iterazione è una richiesta fatturata separatamente, quindi limita sempre il numero di passi. Restituisci gli errori degli strumenti al modello come contenuto invece di interrompere il programma; di solito i modelli si riprendono correggendo gli argomenti.

MAX_STEPS = 8

def run_tool(call):
    try:
        args = json.loads(call.function.arguments)
        result = TOOLS[call.function.name](**args)
    except Exception as e:  # JSON errato, strumento sconosciuto o funzione fallita
        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"))

Invia tools a ogni richiesta del ciclo. Molti provider rifiutano un messaggio tool quando la richiesta non contiene le definizioni degli strumenti.

Chiamate di strumento in parallelo

Con parallel_tool_calls (attivo per impostazione predefinita dove supportato), un modello può richiedere più chiamate in un solo turno, ad esempio get_shipment per tre ordini. Eseguile in modo concorrente e aggiungi un messaggio tool per ogni chiamata, in qualsiasi ordine: tool_call_id collega ogni risultato alla sua chiamata. 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`);
}

Se i tuoi strumenti hanno effetti collaterali che devono avvenire in ordine (crea, poi aggiorna), imposta parallel_tool_calls: false in modo che il modello chieda una sola chiamata per turno.

Output finale strutturato

Quando la risposta dell'agente alimenta altro codice, chiedi un JSON conforme a uno schema. Un modello semplice e portabile è una chiamata in più dopo che il ciclo ha raccolto i fatti (qui messages è la conversazione del ciclo; restituiscila da agent() insieme alla risposta):

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" mantiene tools nella richiesta (necessario perché la cronologia contiene messaggi di strumento) impedendo però altre chiamate. Valida sempre il JSON analizzato. Vedi Output strutturati.

Scegliere un modello

  • Capacità: il modello deve elencare tools in supported_parameters. Verifica con GET /models, vedi Modelli. Per il passo finale, cerca structured_outputs o response_format.
  • Instradamento: aggiungi provider: { "require_parameters": true } (in Python, tramite extra_body) così le richieste vanno solo a provider che supportano ogni parametro che invii. Vedi Instradamento dei provider.
  • Dimensione: i modelli piccoli come openai/gpt-5-mini gestiscono in modo affidabile ed economico pochi strumenti ben descritti. Passa a un modello più grande quando l'agente ha molti strumenti, piani lunghi in più passi, o continua a scegliere lo strumento sbagliato.
  • Prova con i tuoi strumenti: esegui gli stessi 20-50 compiti reali su due o tre modelli candidati e confronta tasso di successo, passi per compito e usage.cost totale.
curl -s "https://api14.com/api/v1/models?category=text" \
  | jq -r '.data[] | select(.supported_parameters | index("tools")) | "\(.id)  \(.pricing.prompt)"'

Controllare i costi

Un agente può fare molte richieste per un solo compito, e l'intera conversazione viene reinviata a ogni passo. Limita la spesa su più livelli:

  • Limite di passi (MAX_STEPS) e max_tokens per richiesta, come sopra.
  • Budget per compito: somma usage.cost su tutti i passi e fermati quando supera la tua soglia.
  • Risultati brevi degli strumenti: restituisci solo i campi che servono al modello. Gli output grandi degli strumenti vengono pagati come token di input a ogni passo successivo.
  • Limite giornaliero in USD per chiave: crea una chiave dedicata all'agente in Chiavi API e imposta un limite giornaliero. Quando viene raggiunto, le richieste restituiscono 429 daily_limit_reached fino al giorno UTC successivo: gestisci quel codice fermando l'agente, non riprovando.
  • Lista di modelli consentiti: limita la chiave ai modelli che hai testato; qualsiasi altro restituisce 403 model_not_allowed.
  • Prompt caching: mantieni identici e in testa a ogni richiesta il prompt di sistema e le definizioni degli strumenti, in modo che si applichino gli sconti sul prefisso in cache. Vedi Prompt caching.

Vedi Tool calling, Limiti di frequenza ed Errori.