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
toolsinsupported_parameters. Verifica conGET /models, vedi Modelli. Per il passo finale, cercastructured_outputsoresponse_format. - Instradamento: aggiungi
provider: { "require_parameters": true }(in Python, tramiteextra_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-minigestiscono 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.costtotale.
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) emax_tokensper richiesta, come sopra. - Budget per compito: somma
usage.costsu 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_reachedfino 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.