Перейти к основному содержимому

Вызов функций

Отдать LLM 299 эндпоинтов в виде 299 инструментов — очевидный путь, но неверный. Вот схема, которая работает, и четыре ошибки, от которых стоит защититься заранее.

Не создавайте 299 инструментов

У всех крупных провайдеров качество падает по мере роста списка инструментов: схемы съедают контекстное окно ещё до начала разговора, а точность выбора снижается, как только у нескольких инструментов пересекаются описания. При 299 эндпоинтах, большинство из которых принимают одни и те же четыре поля, вы тратили бы тысячи токенов на каждый ход только для того, чтобы модель хуже выбирала.

Создайте один инструмент или два. Модель передаёт маршрут как аргумент, а не выбирает из сотен схем.

Схема с одним инструментом

определение инструмента
{
  "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"]
  }
}

Описание решает две задачи. Оно говорит, когда браться за инструмент, и говорит не считать самой — а это важнее, чем кажется. Модель, у которой спросили накшатру, охотно выдаст правдоподобную из обучающих данных, если ничто не подскажет ей, что для положений планет нужны эфемериды.

Обработчик

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;
}

Три вещи здесь стоит сохранить, когда будете его адаптировать. Ключ читается на сервере — ключ в браузерном коде уже тратит кто-то другой. 402 не повторяется — это значит, что баланс пуст, и цикл с задержками только потратит время, ничего не исправив. Сообщение об ошибке передаётся модели как есть — API указывает, какое поле неверно, и модель, получив это, исправит свой запрос на следующем ходу.

Схема с двумя инструментами — когда модели нужно исследовать

Если пользователи задают открытые вопросы, модель не будет знать, какой маршрут ей нужен. Дайте ей ещё и инструмент поиска: поиск не стоит кредитов, так что исследование бесплатно и платить нужно только за ответ.

определения инструментов
[
  {
    "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"]
    }
  }
]

Направьте occult_search на /llms.txt или на более полный /llms-full.txt — оба файла существуют ровно для этого. Есть и спецификация OpenAPI 3.1, если ваш фреймворк генерирует инструменты по ней.

Четыре ошибки, от которых стоит защититься

  • Один вызов на каждый расчёт. Большинство эндпоинтов принимают массив keys и считают несколько вещей в одном запросе за один кредит. Модель, предоставленная сама себе, сделает четыре вызова и потратит четыре кредита. Напишите об этом в описании инструмента.
  • Повтор после 402. Это проблема баланса, а не сети. Пометьте этот код в обработчике как неповторяемый: по одному коду статуса модель разницы не поймёт.
  • Ключ попадает на клиент. Инструменты должны выполняться на вашем сервере. Модели никогда не нужно видеть ключ, а в любой схеме, где она может его увидеть, до утечки остаётся одна промпт-инъекция.
  • Выдуманные имена полей. Модели угадывают date или lat. Настоящие поля — date_time, latitude, longitude и timezone_as_float. Укажите их в описании, а не ждите, пока модель узнает о них из ошибки 400.

Как на самом деле выглядит вызов

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

Четыре расчёта — один кредит. В ответе есть X-Credits-Charged и X-Credits-Remaining: это самый дешёвый способ показывать текущие расходы в вашем интерфейсе.

Дальше