Usamos cookies. Cookies e o armazenamento do navegador mantêm você conectado e lembram suas preferências. Com sua permissão, também medimos como o site é usado. Você pode a qualquer momento. Veja nossa política de cookies.

Guia: agentes com tool calling

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 tools em supported_parameters. Verifique com GET /models, veja Modelos. Para o passo final, procure structured_outputs ou response_format.
  • Roteamento: adicione provider: { "require_parameters": true } (em Python, via extra_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-mini lidam 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.cost total.
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) e max_tokens por requisição, como acima.
  • Orçamento por tarefa: some o usage.cost de 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_reached até 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.