Primeiros passos
Guia: agentes com tool calling
Construa um agente com loop de tool calling, chamadas paralelas, limite de passos, saída estruturada e controle de custos usando o formato tools da OpenAI.
Um agente é um loop: o modelo lê a conversa, decide chamar uma ou mais das suas ferramentas, você as executa e devolve os resultados, e o modelo continua até conseguir responder. O api14 oferece suporte a isso com o formato tools da OpenAI em todos os modelos que aceitam ferramentas, então o mesmo código funciona com OpenAI, Anthropic, Google e outros provedores trocando apenas o id do modelo.
Este guia constrói um agente pequeno com limite de passos, execução paralela de ferramentas, resposta final estruturada e controles de custo. O código usa o pacote oficial openai com a base URL https://api14.com/api/v1.
Defina as ferramentas
Cada ferramenta tem um nome, uma descrição que o modelo lê e um JSON Schema para seus argumentos. Mantenha as descrições específicas: elas são a única documentação do modelo.
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"}] # sua consulta ao banco de dados
def get_shipment(order_id: str) -> dict:
return {"order_id": order_id, "carrier": "UPS", "eta": "2026-10-09"} # sua chamada de 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"]},
}},
]
O loop com limite de passos
Cada iteração é uma requisição cobrada separadamente, então sempre limite o número de passos. Devolva os erros das ferramentas ao modelo como conteúdo em vez de deixar o programa quebrar: os modelos geralmente se recuperam corrigindo os argumentos.
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 inválido, ferramenta desconhecida ou falha na sua função
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"))
Envie tools em toda requisição do loop. Muitos provedores rejeitam uma mensagem tool quando a requisição não tem definições de ferramentas.
Chamadas de ferramentas em paralelo
Com parallel_tool_calls (ativado por padrão onde há suporte), um modelo pode pedir várias chamadas em um único turno, por exemplo get_shipment para três pedidos. Execute-as de forma concorrente e adicione uma mensagem tool por chamada, em qualquer ordem: o tool_call_id liga cada resultado à sua chamada. Em 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 suas ferramentas têm efeitos colaterais que precisam acontecer em ordem (criar e depois atualizar), defina parallel_tool_calls: false para que o modelo peça uma chamada por turno.
Saída final estruturada
Quando a resposta do agente alimenta outro código, peça um JSON que siga um schema. Um padrão simples e portátil é uma chamada extra depois que o loop reuniu os fatos (aqui messages é a conversa do loop; devolva-a de agent() junto com a resposta):
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" mantém tools na requisição (necessário porque o histórico contém mensagens de ferramenta) e impede novas chamadas. Sempre valide o JSON interpretado. Veja Saídas estruturadas.
Como escolher um modelo
- Capacidade: o modelo precisa listar
toolsemsupported_parameters. Verifique comGET /models, veja Modelos. Para o passo final, procurestructured_outputsouresponse_format. - Roteamento: adicione
provider: { "require_parameters": true }(em Python, viaextra_body) para que as requisições só vão a provedores que aceitam todos os parâmetros enviados. Veja Roteamento de provedores. - Tamanho: modelos pequenos como
openai/gpt-5-minilidam com poucas ferramentas bem descritas de forma confiável e barata. Passe para um modelo maior quando o agente tiver muitas ferramentas, planos longos de vários passos ou continuar escolhendo a ferramenta errada. - Teste com as suas ferramentas: rode as mesmas 20 a 50 tarefas reais em dois ou três modelos candidatos e compare a taxa de sucesso, os passos por tarefa e o
usage.costtotal.
curl -s "https://api14.com/api/v1/models?category=text" \
| jq -r '.data[] | select(.supported_parameters | index("tools")) | "\(.id) \(.pricing.prompt)"'
Controle de custos
Um agente pode fazer muitas requisições para uma única tarefa, e a conversa inteira é reenviada a cada passo. Limite o gasto em vários níveis:
- Limite de passos (
MAX_STEPS) emax_tokenspor requisição, como acima. - Orçamento por tarefa: some o
usage.costde todos os passos e pare quando passar do seu limite. - Resultados curtos das ferramentas: devolva apenas os campos de que o modelo precisa. Saídas grandes de ferramentas são pagas como tokens de entrada em todos os passos seguintes.
- Limite diário em USD por chave: crie uma chave dedicada ao agente em Chaves de API e defina um limite diário. Quando ele é atingido, as requisições retornam
429 daily_limit_reachedaté o próximo dia UTC. Trate esse código parando o agente, não tentando de novo. - Lista de modelos permitidos: restrinja a chave aos modelos que você testou; qualquer outro retorna
403 model_not_allowed. - Prompt caching: mantenha o prompt de sistema e as definições de ferramentas idênticos e no início de toda requisição para que os descontos de prefixo em cache sejam aplicados. Veja Prompt caching.
Veja Tool calling, Limites de taxa e Erros.