Cookbook
Agents & skills · for AI agents

Создание навыка по требованию

Упакуйте возможность, которую модель запрашивает только при необходимости — создайте её и подключите к агенту.

MCP tools:list_skillscreate_skillupdate_agenttest_skill_trigger

Принцип — держите количество навыков небольшим. Подключайте только то, что необходимо для работы этого агента (≈5 или меньше). Модель выбирает навыки по их однострочному description; чем их больше, тем чаще она загружает не тот или вообще никакой. Если два навыка перекрываются по времени использования, объедините их. Подробнее в Принципах проектирования.

Навык — это пакет возможностей, загружаемый по требованию: в системном промпте присутствует только его description («когда использовать»); модель извлекает полные instructions только тогда, когда считает навык релевантным. Поэтому description должен быть кратким и указывать когда, иначе модель не поймёт, что нужно его вызвать.

list_skills()                                # what already exists
 
create_skill(
  name="refund-policy",
  description="Use when the user asks about refunds, cancellations or chargebacks.",
  instructions="The full procedure: eligibility windows, how to word the outcome, when to escalate …",
)
 
update_agent(name="Support", add_skills=["refund-policy"])   # attach it (incremental — keeps existing skills)

Храните процедуры в instructions (загружаются при активации), а не в soul агента (который присутствует в промпте при каждом ходе). Однострочный description, указывающий когда, делает навык обнаруживаемым — расплывчатый означает, что модель никогда его не вызовет.

Инструменты могут быть привязаны к навыку

Навык может нести собственные привязки инструментов — create_skill(tools=[…]) или update_skill(name, add_tools=[…]), так что навык поставляется как самодостаточная возможность: процедура плюс необходимые инструменты, подключаемые одним действием.

Собственные инструменты навыка появляются только после загрузки навыка. Их нет в наборе инструментов в начале хода; загрузка навыка добавляет их туда. Порядок фиксирован: сначала прочитать процедуру, затем получить инструменты — модель не может пропустить инструменты и импровизировать.

Этот порядок enforced в коде, а не запрашивается в промпте, потому что запросы не работали. Измерено 2026-08-22, на запросе "исследуйте конкурентов компании X" с процедурой и инструментами, переданными вместе: модель вызвала веб-поиск три раза и ни разу не загрузила навык. Она придумала имена конкурентов из памяти, поискала по этим выдуманным именам, ничего не нашла (их не существует) и записала выдуманные имена как ответ — в то время как первый шаг процедуры, который она не читала, гласил "сначала установите, чем на самом деле занимается эта компания". Процедура, которая является необязательной, не читается.

Два момента, которые нужно знать перед тем, как полагаться на это:

  • Инструменты навыка не появляются в собственном списке tools агента. get_agent показывает только напрямую подключенные к агенту инструменты; во вкладке Tools консоли привязанные к навыкам отображаются отдельной строкой "via skills" (через навыки), доступной только для чтения. При проверке "включён ли инструмент X" проверяйте оба места — или просто вызовите инструмент в тестовом чате.
  • Инструменты, которые уже есть у агента, не затрагиваются. Если у самого агента есть web_search, он остаётся доступным весь ход; только инструменты, которые появляются благодаря навыку, ждут загрузки этого навыка.
  • Предпочитайте привязывать инструмент к навыку, когда это имеет смысл только внутри этой процедуры (поиск возврата внутри навыка возврата); привязывайте к агенту, когда он обычно полезен в разных ходах (save_contact, web_search).

Привязка инструмента — это доступность, а не политика — инструкции должны говорить, когда его вызывать

Проверка инструмента на навыке (или агенте) только делает его вызываемым. Модель видит схему инструмента и его однострочное описание при каждом ходе, поэтому может использовать его opportunistically — посетитель добровольно сообщает email, и save_contact срабатывает. Но любое поведение, которое должно происходить надёжно, должно быть записано, и у каждой его части есть единственное правильное место:

Что вы указываетеКуда это помещается
Когда загружать этот навыкdescription навыка (в промпте при каждом ходе)
Процедура: на каком шаге вызвать какой инструмент, с какими аргументамиinstructions навыка — явно назовите инструмент ("после подтверждения заинтересованности звонящего, вызовите save_contact с email и телефоном; затем вызовите schedule_followup на 1 рабочий день позже")
Поведение, которое должно управлять всем разговором (всегда захватывать лиды, никогда не цитировать ставки)soul / task агента

Режим отказа, когда инструкции не называют инструмент: всё выглядит настроенным — инструмент проверен, навык загружен — и агент всё равно его не вызывает, или вызывает с угаданными аргументами. Ничего не выдаёт ошибку. Пишите процедуру так, как будто инструктируете нового сотрудника: шаг, имя инструмента, аргументы и то, как выглядит "готово".

Обязательные вызовы инструментов: триггер должен жить в description, а не только в instructions

instructions видны модели только после того, как она вызывает load_skill — и модели часто отвечают напрямую, не загружая навык. Поэтому правило вроде "когда пользователь предоставляет номер телефона, вы должны вызвать save_contact", записанное только в instructions, невидимо именно тогда, когда это важно: инструмент молча не вызывается, и модель может даже утверждать, что контакт сохранён. Это произошло в продакшене — агент ответил на несколько сообщений с контактами подряд, "подтвердил", что детали записаны, и записи не существовало.

Исправление — одно предложение в description (которое есть в системном промпте при каждом ходе):

update_skill(
  name="consultative-sales",
  description="Consultative sales: understand the case, recommend products, arrange expert callbacks. "
              "When the user provides a phone number or email, call save_contact BEFORE answering anything else.",
)

Храните детальную процедуру в instructions; помещайте триггер любого обязательного вызова в description.

Два связанных правила:

  • Никогда не прописывайте возвращаемые значения, которые инструмент не производит — передавайте реальные дословно. Встроенные save_contact и schedule_followup возвращают реальный ссылочный код при успехе (формат AB2C-D3EF, хранится на сервере и виден арендатору на странице деталей конечного пользователя). Инструктируйте модель передавать код из результата инструмента дословно — никогда не придумывать его или не переформатировать как "#12345". Для любого другого инструмента убедитесь, что он действительно возвращает ID, прежде чем прописывать один: обещанный, но отсутствующий ID будет сфабрикован.
  • create_skill / update_skill / get_skill теперь возвращают список warnings, который отмечает именно эти два паттерна (trigger_hidden_in_instructions, promised_tool_return_id). Предупреждения носят рекомендательный характер — сохранение успешно — но относитесь к ним как к линтеру: исправляйте, а не игнорируйте.

Примеры в instructions измеримо повышают соблюдение правил вызова инструментов

Мы провели бенчмаркинг на продакшен-бэкендах (6 сообщений о захвате лидов возрастающей сложности — номера, скрытые в длинных вопросах, группы цифр с пробелами, исправления — выборка по условиям). При загруженном навыке простое mandate "вы должны вызвать X" давало 3–4/6; добавление короткого блока примеров привело обе протестированные модели к 6/6. Перемещение раздела mandate не дало эффекта — позиция не имеет значения, примеры имеют.

Эффективный блок примеров имеет четыре вида записей, каждая по одной строке:

### Worked examples (follow exactly)
 
1. User: "I'd like to know about X, my phone is 13800138000"
   → call save_contact(phone="13800138000") first, then answer about X.
2. User: "email me the offer: li@example.com"
   → call save_contact(email="li@example.com").
3. COUNTER-EXAMPLE (forbidden): user gives a phone number and you reply
   "I've noted it down" WITHOUT calling the tool — claiming success without
   the call is the worst failure.
4. User: "sorry, wrong number — it's 13633334444"
   → call save_contact again with the corrected value.
5. Numbers may contain spaces ("138 0013 9000") — still a phone number;
   strip the spaces and call save_contact(phone="13800139000").

Контрпример (3) и краевой случай формата (5) закрывают большую часть оставшихся промахов — модели ошибаются в распознавании ("это номер телефона?") и в честности под давлением (сначала отвечают на сложный доменный вопрос и утверждают, что сохранение произошло) больше, чем в готовности. Держите блок ~5 записей; используйте точный синтаксис вызова с реалистичными аргументами.

Тестируйте триггер перед выпуском — не считайте трупов в продакшене

Возможность того, что навык действительно срабатывает, измерима, поэтому измеряйте её. test_skill_trigger прогоняет ваши сообщения против сборки продакшен промпта, схем инструментов и маршрутизации модели, и отчитывается о решении модели — инструменты никогда не выполняются, ничего не сохраняется, токены учитываются в вашей квоте (лимиты: 5 сообщений × 5 образцов).

test_skill_trigger(
  agent="advisor",
  messages=[
    "My phone is 555 0123, call me back",          # easy
    "long question about the product … oh and my number is 555 0123",  # buried
    "555 0123 — that's me",                        # implicit
    "sorry, wrong number, it's 555 9999",          # correction
  ],
  expect_tool="save_contact",
  samples=3,
  loaded=true,        # simulate post-load_skill → tests instructions quality
)                     # loaded=false (default) → first turn, tests the description trigger

Читайте результат так:

  • hit_rate ниже ~90% на реалистичных сообщениях → усильте триггер (description) или добавьте примеры (instructions), затем повторите тест.
  • claimed_without_call > 0 — это худший отказ — модель сказала пользователю "принято!", не вызвав инструмент. Добавьте контрпример из блока выше.
  • Тестируйте оба режима: loaded=false доказывает, что описание само по себе срабатывает на первом ходу; loaded=true доказывает, что загруженные инструкции не размывают его (длинные инструкции измеримо размывают — именно это компенсируют примеры).

Результат также содержит список advice: когда образцы ошибаются или лгут, он точно указывает, какое исправление применить (триггер в описание, добавить блок примеров, добавить контрпример или краевой случай формата) с числами бенчмарка за каждой рекомендацией — примените его и повторите тест.

Полный цикл: create_skill → исправьте любые warnings (статический линт) → test_skill_trigger (динамическая проверка реальностью) → примените его advice → повторите тест, пока hit rate не стабилизируется.