Техника Few-Shot: Как показать, а не рассказать

09.06.2026 17:00

Вы смотрите на системный промт длиной в двести строк. В нем с маниакальной педантичностью учтены все возможные граничные случаи. "Если пользователь спрашивает про возврат, отвечай вежливо. Если он ругается, переводи на оператора. Возвращай строго валидный JSON. Не добавляй лишнего текста, никаких 'Конечно, вот ваш ответ'."

Вы запускаете тест. Модель радостно отвечает: Конечно, вот ваш JSON: { "status": "transfer" }. Надеюсь, я смог вам помочь!

Логика на бекенде ломается, парсер выдает фатальную ошибку, вы бьетесь головой об стол. Возникает стойкое ощущение: "Она меня просто не понимает. Я же четко написал — никакого лишнего текста!".

Мы привыкли писать код, где инструкции выполняются буквально. Компилятору не нужны примеры, ему нужен корректный синтаксис. Но большие языковые модели работают иначе. Они не "читают мануалы" в человеческом смысле. Они продолжают паттерны, опираясь на распределение вероятностей. Когда вы пытаетесь описать сложный формат ответа или тонкую логику словами, вы заставляете модель переводить ваши абстрактные текстовые правила в конкретные токены. Это тяжелая когнитивная нагрузка. Инструкции конфликтуют друг с другом, вес внимания (attention weights) размазывается по сотням слов системного промта.

Решение проблемы лежит на поверхности, но многие разработчики к нему приходят только после месяцев борьбы с промтами. Гораздо эффективнее задать сам паттерн. Показать, а не рассказать.

Что такое Few-Shot и почему он работает

Few-Shot Prompting — это техника, при которой вы даете модели несколько примеров желаемого входа и выхода до того, как задать реальный вопрос. Вы не объясняете, как решать задачу. Вы показываете уже решенные задачи.

Представьте, что вам нужно вытащить имена и должности из сырого неструктурированного текста и сложить их в массив.

В подходе Zero-Shot (без примеров) вы пишете: "Извлеки имена и должности. Верни JSON в формате [{'name': '..', 'title': '..'}]. Никакого Markdown. Ключи должны быть в двойных кавычках." Модель может ошибиться в регистрах ключей, добавить маркдаун-блоки `json или забыть закрыть скобку. Почему? Потому что в ее тренировочных данных тексты часто сопровождались вежливыми ответами и форматированием.

В подходе Few-Shot вы даете 2-3 примера текста и эталонного JSON. Модель мгновенно подхватывает структуру. Ей больше не нужно "понимать" инструкцию про кавычки — она просто видит, что в предыдущих ответах кавычки стоят именно так, и продолжает этот вектор. Вероятность галлюцинаций падает в десятки раз.

Искусство подбора примеров

Нельзя брать примеры наугад. Три одинаковых примера хуже, чем один. Плохой few-shot может навредить сильнее, чем его отсутствие, закрепив в модели неверные паттерны.

  1. Разнообразие (Coverage). Примеры должны покрывать разные сценарии. Один стандартный случай (happy path), один пограничный (edge case) и один случай-отказ (когда в тексте нет нужных данных, и модель должна вернуть пустой массив или ошибку). Если вы не покажете, как выглядят данные без имен, модель начнет их выдумывать.
  2. Абсолютная чистота формата. Примеры должны строго соответствовать тому формату, который вы ожидаете получить. Если нужен чистый JSON, в примерах ответов ассистента не должно быть ни единого пробела вне спецификации.
  3. Порядок (Recency Bias). Трансформеры склонны обращать больше внимания на последние токены в контексте. Ставьте самый сложный или самый критичный пример ближе к концу обучающей выборки, прямо перед реальным запросом пользователя.

Интеграция Few-Shot в RouterAPI

Как технически передать эти примеры в API? Частая архитектурная ошибка — запихнуть весь текст примеров в один длинный системный промт.

System: Ты ассистент. Вот примеры того, как надо отвечать:
User: привет -> Assistant: Здравствуйте!
User: пока -> Assistant: До свидания!

Это работает хуже, чем могло бы. Модель воспринимает этот блок как единую текстовую стену. Правильный путь — использовать нативную структуру истории сообщений (history array). В RouterAPI, как и в OpenAI-совместимых эндпоинтах, вы передаете массив messages. Few-Shot примеры идеально ложатся в структуру сообщений с ролями user и assistant. Вы буквально симулируете прошлый успешный диалог.

Давайте посмотрим, как это выглядит в коде при интеграции с RouterAPI:

{
 "model": "claude-3-5-sonnet",
 "messages": [
 {
 "role": "system",
 "content": "Ты анализатор тональности. Определи тональность отзыва. Отвечай только одним словом: POSITIVE, NEGATIVE или NEUTRAL."
 },
 {
 "role": "user",
 "content": "Ужасный сервис, доставка опоздала на три дня, поддержка молчит."
 },
 {
 "role": "assistant",
 "content": "NEGATIVE"
 },
 {
 "role": "user",
 "content": "Коробка была немного помята, но внутри все целое. Работать можно."
 },
 {
 "role": "assistant",
 "content": "NEUTRAL"
 },
 {
 "role": "user",
 "content": "Ребята просто супер, спасли мой праздник! Привезли за час."
 },
 {
 "role": "assistant",
 "content": "POSITIVE"
 },
 {
 "role": "user",
 "content": "Курьер приехал, отдал коробку и молча ушел."
 }
 ],
 "temperature": 0.1
}

В этом JSON-payload мы заложили три few-shot примера прямо в историю переписки. Модель "видит" контекст: пользователь кидает текст, ассистент отвечает одним словом в верхнем регистре, без точек и лишних слов. Когда очередь доходит до генерации ответа на последнее сообщение ("Курьер приехал.."), модели даже не нужно вчитываться в системный промт. Паттерн диалога настолько сильный, что следующим токеном неизбежно будет NEUTRAL. Никаких "Конечно, тональность этого отзыва..".

Динамический Few-Shot: Архитектура для сложных систем

Хардкодить примеры в массив messages хорошо для простых задач вроде классификации или экстракции базовых сущностей. Но что если вы делаете генератор SQL-запросов (Text-to-SQL) или бота поддержки, отвечающего по огромной базе знаний? База огромная, и три статических примера не покроют все схемы таблиц или сценарии поддержки.

Здесь на сцену выходит динамический few-shot. Эта архитектура требует чуть больше инженерных усилий, но дает радикальный прирост точности.

Вместо того чтобы вшивать примеры в код, вы собираете репозиторий успешных пар "вопрос-ответ". Каждая пара превращается в вектор (эмбеддинг) и сохраняется в векторную базу данных (например, Qdrant, Pinecone или pgvector).

Workflow при новом запросе выглядит так:

  1. Пользователь задает вопрос: "Покажи всех клиентов из Москвы, сделавших больше 5 заказов".
  2. Ваш бекенд переводит этот вопрос в эмбеддинг.
  3. Происходит поиск по векторной базе: система находит 3-5 самых семантически похожих прошлых вопросов (например, запросы про клиентов из других городов и фильтрацию по количеству заказов).
  4. Бекенд извлекает эти вопросы и эталонные SQL-ответы к ним.
  5. Вы динамически собираете массив messages для RouterAPI, встраивая найденные пары user/assistant перед текущим запросом.
// Пример реализации динамического few-shot на PHP/Laravel
$currentQuery = $request->input('query');

// Ищем похожие успешные примеры в векторной БД
$similarExamples = $vectorDb->similaritySearch($currentQuery, limit: 3);

$messages = [
 ['role' => 'system', 'content' => 'Ты эксперт по PostgreSQL. Пиши только SQL код.']
];

// Встраиваем few-shot примеры из истории
foreach ($similarExamples as $example) {
 $messages[] = ['role' => 'user', 'content' => $example->question];
 $messages[] = ['role' => 'assistant', 'content' => $example->sql_query];
}

// Добавляем текущий запрос пользователя
$messages[] = ['role' => 'user', 'content' => $currentQuery];

$response = $routerApi->chat->create([
 'model' => 'gpt-4o',
 'messages' => $messages,
 'temperature' => 0.0
]);

Таким образом, модель всегда получает контекстно-релевантные примеры. Если пользователь спрашивает про таблицу "Заказы", модель увидит few-shot примеры, работающие именно с джоинами таблицы заказов. Вы обходите ограничения длины контекста, не перегружая промт лишней информацией, и показываете модели ровно то, что ей нужно для решения текущей задачи.

Инсайт

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

Длинные многостраничные инструкции вызывают эффект усталости контекста (context fatigue). Внимание модели рассеивается. Примеры же работают как направляющие рельсы.

Встраивание few-shot примеров через историю сообщений — это самый чистый и архитектурно правильный способ задать эти рельсы при работе с RouterAPI. Вы не перегружаете системный промт абстрактными запретами. Вы просто показываете модели мир, в котором она уже ведет себя идеально. И ей остается только продолжить играть эту роль по вашим правилам.

***

Теги

Ещё по теме