Ir para o conteúdo

Function calling

Dar a um LLM 299 endpoints como 299 ferramentas é o caminho óbvio — e o errado. Veja o formato que funciona e as quatro falhas que vale a pena evitar desde o projeto.

Não defina 299 ferramentas

Em todos os grandes provedores, o desempenho cai conforme a lista de ferramentas cresce: os schemas ocupam a janela de contexto antes mesmo de a conversa começar, e a precisão da escolha despenca quando várias ferramentas têm descrições parecidas. Com 299 endpoints, quase todos recebendo os mesmos quatro campos, você gastaria milhares de tokens por turno só para o modelo escolher pior.

Defina uma ferramenta, ou duas. O modelo passa a rota como argumento, em vez de escolher entre centenas de schemas.

O formato com uma ferramenta

definição da ferramenta
{
  "name": "occult_api",
  "description":
    "Vedic astrology calculations from the Occult API. Call this for anything involving a birth chart, panchanga, dasha, muhurta, matching or numerology. Do not compute astrology yourself - planetary positions require an ephemeris and cannot be derived from a date.",
  "input_schema": {
    "type": "object",
    "properties": {
      "route": {
        "type": "string",
        "description": "Endpoint path, e.g. /api/astro/panchanga/. Use the catalogue at https://occultapi.com/llms.txt to choose one."
      },
      "body": {
        "type": "object",
        "description": "Request body. Always include date_time (ISO 8601 with offset), latitude, longitude, timezone_as_float. Batch calculations with a keys array where the endpoint supports it."
      }
    },
    "required": ["route", "body"]
  }
}

A descrição tem duas funções. Diz quando usar a ferramenta e diz ao modelo para não fazer as contas sozinho — o que importa mais do que parece. Peça um nakshatra a um modelo e ele inventa, sem pestanejar, um valor plausível a partir dos dados de treino, se nada avisar que posições planetárias exigem efemérides.

O handler

node
async function occultApi({ route, body }) {
  const response = await fetch(`${process.env.OCCULT_API_BASE}${route}`, {
    method: "POST",
    headers: {
      "X-API-Key": process.env.OCCULT_API_KEY,   // server-side only
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });

  const payload = await response.json();

  // 402 means out of credits. It is NOT transient - retrying spends nothing
  // and fixes nothing, so surface it rather than backing off.
  if (response.status === 402) {
    return { error: "insufficient_credits", retryable: false };
  }

  // The envelope is the same on every endpoint: branch on is_error, and hand
  // the model the message rather than a stack trace it cannot act on.
  if (payload.is_error) {
    return { error: payload.message, retryable: false };
  }

  return payload.data;
}

Ao adaptar o código, mantenha três coisas. A chave é lida no servidor — chave no código do navegador é chave que outra pessoa já está gastando. O 402 não é repetido — ele significa saldo zerado, então um loop de backoff só gasta tempo e não resolve nada. A mensagem de erro é repassada — a API diz qual campo está errado, e o modelo que recebe isso corrige a própria requisição no turno seguinte.

O formato com duas ferramentas, para quando o modelo precisa explorar

Se os seus usuários fazem perguntas abertas, o modelo não vai saber de qual rota precisa. Dê a ele também uma ferramenta de busca: buscar não gasta créditos, então a exploração sai de graça e só a resposta é cobrada.

definições das ferramentas
[
  {
    "name": "occult_search",
    "description": "Find the right Occult API endpoint by name or by what it calculates, e.g. 'manglik' or 'sunrise'. Returns routes and their request fields. Costs nothing.",
    "input_schema": {
      "type": "object",
      "properties": { "query": { "type": "string" } },
      "required": ["query"]
    }
  },
  {
    "name": "occult_call",
    "description": "Call an Occult API endpoint found with occult_search. One credit per successful call (the PDF reports and chat cost 25); failures are free.",
    "input_schema": {
      "type": "object",
      "properties": {
        "route": { "type": "string" },
        "body": { "type": "object" }
      },
      "required": ["route", "body"]
    }
  }
]

Aponte occult_search para /llms.txt ou para o mais completo /llms-full.txt — os dois existem exatamente para isso. Se o seu framework gera ferramentas a partir de uma especificação, também há uma especificação OpenAPI 3.1.

Quatro falhas para evitar desde o projeto

  • Uma chamada por cálculo. A maioria dos endpoints recebe um array keys e calcula várias coisas numa só requisição, por um crédito. Deixado por conta própria, o modelo chama quatro vezes e gasta quatro. Diga isso na descrição da ferramenta.
  • Repetir um 402. É problema de saldo, não de rede. Marque-o como não repetível no handler, porque só pelo código de status o modelo não percebe a diferença.
  • A chave chegar ao cliente. A execução das ferramentas fica no seu servidor. O modelo nunca precisa ver a chave, e qualquer arquitetura em que ele possa vê-la está a uma injeção de prompt de um vazamento.
  • Nomes de campo inventados. Os modelos chutam date ou lat. Os campos reais são date_time, latitude, longitude e timezone_as_float. Coloque-os na descrição, em vez de deixar o modelo descobri-los na base do erro 400.

Como é uma chamada de verdade

curl
curl -X POST https://api.occultapi.com/api/astro/panchanga/ \
  -H 'X-API-Key: yt_live_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "date_time": "2026-09-17T06:00:00+05:30",
    "latitude": 26.9124,
    "longitude": 75.7873,
    "timezone_as_float": 5.5,
    "keys": ["tithi", "nakshatra_segments", "yogam", "karana"]
  }'

Quatro cálculos, um crédito. A resposta traz X-Credits-Charged e X-Credits-Remaining — o jeito mais barato de mostrar o custo acumulado na sua própria interface.

Próximos passos