Зачем вашему чат-боту цель? (Он же не человек)
Большинство диалоговых систем на LLM в 2026 году все еще страдают от одной болезни: они прекрасно болтают, но совершенно бесполезны в достижении конкретного результата. Пользователь спрашивает про отель в Париже, а ему в ответ — трехстраничное эссе об истории французской архитектуры. Звучит знакомо? Это проблема бесцельности, и она съедает всю практическую ценность агента.
Целеустремленный (goal-oriented) диалоговый агент — это не просто чат-интерфейс к API авиакомпаний. Это система, которая ведет переговоры, заполняет пробелы в информации, строит многораундовый план и, в конечном счете, выполняет задачу: бронирует поездку, подбирает маршрут, находит компромисс между бюджетом и комфортом. Здесь мы разберем, как построить такого агента для планирования путешествий, используя последние LLM (на 2026 год это могут быть модели вроде GPT-4.5 Turbo, Claude 4 Opus или Gemini 2.5 Pro) и архитектурные паттерны, которые не дадут ему свернуть в философские дебри.
Ключевая ошибка: многие думают, что LLM сами по себе понимают цель. Как показывает статья \"LLM понимают цель, но игнорируют её\", модель может отлично осознавать, что от нее хотят, но ее дизайн поощряет генерацию правдоподобного текста, а не движение к результату. Это нужно исправлять архитектурой.
Из чего состоит агент, который не заблудится
Однослойная архитектура (пользователь -> LLM -> ответ) здесь не работает. Нужен координационный слой, который разбивает высокоуровневую цель (\"спланируй путешествие\") на последовательность действий и диалоговых ходов. Вспомните про System 2 архитектуру. Для планирования путешествий я использую четыре ключевых модуля:
- Диалоговый менеджер (Dialog Manager): Отвечает за состояние разговора. Отслеживает, какую информацию мы уже собрали (бюджет, даты, предпочтения), а какую еще нужно уточнить.
- Планировщик (Planner): Принимает текущее состояние диалога и определяет следующее действие. Запрос данных у пользователя? Вызов внешнего API для поиска рейсов? Предложение компромисса?
- Модуль знаний (Knowledge Module): Хранит и извлекает контекстно-зависимую информацию. Это не просто RAG с брошюрами отелей. Это упакованные skills, например, \"как сравнивать цены на авиабилеты с учетом багажа\" или \"какие визовые требования актуальны для граждан РФ в 2026\".
- Исполнитель (Executor): Выполняет действия, определенные планировщиком: формирует запросы к внешним API (Amadeus, Booking.com), вычисляет бюджет, генерирует финальный маршрут в PDF.
1Определите цель так, чтобы ее нельзя было проигнорировать
\"Планирование путешествия\" — это абстракция. Агент будет тонуть. Разбейте цель на конкретные, измеримые подцели (success criteria).
- Сбор ограничений: Получить от пользователя бюджет, даты, пункт назначения, количество путешественников, предпочтения (тип жилья, активность).
- Поиск и оценка вариантов: Найти минимум 3 варианта перелета и 3 варианта проживания, соответствующих ограничениям.
- Переговоры и уточнения: Обсудить компромиссы (\"Отель у моря дороже на 20%, но есть бесплатный трансфер\").
- Формирование итога: Предоставить сводный маршрут с ценами, ссылками и следующем шагом (например, переход к бронированию).
Эти подцели становятся состояниями в вашем диалоговом менеджере. Агент не может перейти к поиску отелей, пока не подтвердит даты. Это жесткая логика, а не рекомендация.
2Спроектируйте диалоговый поток как конечный автомат
Не надейтесь, что LLM сама будет вести логичную беседу. Задайте сценарии. Для каждого состояния (\"сбор дат\", \"выбор отеля\") определите:
- Интенты пользователя: Что он может сказать в этом состоянии? (\"Я передумал на эти даты\", \"Покажите варианты подешевле\").
- Действия агента: Как он должен реагировать? (Обновить состояние, вернуться на шаг назад, вызвать API).
- Валидацию ввода: Даты должны быть в будущем, бюджет — числом. Делегируйте валидацию отдельному компоненту, а не LLM.
# Упрощенный пример состояния в диалоговом менеджере (Python, 2026)
class DialogState:
def __init__(self):
self.stage = \"COLLECTING_DATES\" # Текущий этап
self.slots = { # Собранные данные
\"destination\": None,
\"budget\": None,
\"travel_dates\": None,
\"travelers\": None
}
def transition(self, user_input: str, llm_planner):
# Планировщик на LLM анализирует input и текущее состояние
# и возвращает следующее действие и обновленные слоты
action = llm_planner.decide_next_action(self, user_input)
if action == \"UPDATE_SLOT\":
self.slots[action.slot_name] = action.value
# Проверяем, все ли обязательные слоты заполнены
if self.all_required_slots_filled():
self.stage = \"SEARCHING_OPTIONS\"
elif action == \"REQUEST_CLARIFICATION\":
# Вернуться в то же состояние, но с запросом уточнений
pass3Промпты — это код. Пишите их соответственно
Системный промпт — это ДНК вашего агента. Он должен быть конкретным, императивным и включать четкие правила игры. Вот пример для модуля-планировщика, актуальный для моделей 2026 года (учтены последние возможности, like reasoning tokens):
Ты — планировщик диалога для агента по планированию путешествий. Твоя единственная цель — определить следующее действие системы на основе текущего состояния и реплики пользователя.
ТЕКУЩЕЕ СОСТОЯНИЕ ДИАЛОГА:
{диалоговое состояние в JSON}
ИСТОРИЯ ДИАЛОГА (последние 3 реплики):
{история}
ПОСЛЕДНЯЯ РЕПЛИКА ПОЛЬЗОВАТЕЛЯ: \"{user_input}\"
ДОСТУПНЫЕ ДЕЙСТВИЯ:
1. REQUEST_SLOT: Запросить у пользователя значение для определенного поля (например, budget, dates). Используй, если поле пустое или значение некорректно.
2. CONFIRM_SLOT: Подтвердить у пользователя значение важного поля (например, \"Вы подтверждаете даты с 15 по 22 апреля?\").
3. SEARCH_EXTERNAL: Запустить поиск вариантов (рейсов, отелей) на основе заполненных слотов. Только если все обязательные слоты заполнены.
4. PRESENT_OPTIONS: Представить пользователю найденные варианты с ключевыми параметрами (цена, рейтинг, условия).
5. NEGOTIATE: Предложить компромисс или альтернативу, если пользователь недоволен вариантами.
6. FINALIZE: Сформировать итоговый маршрут и завершить диалог.
ПРАВИЛА:
- Никогда не переходи к поиску (SEARCH_EXTERNAL), пока не заполнены обязательные слоты: destination, budget, travel_dates, travelers.
- Если пользователь меняет уже заполненный слот (\"Я передумал, бюджет 100 тысяч\"), немедленно обнови значение и, если нужно, вернись на этап поиска.
- Если пользователь спрашивает о чем-то за пределами планирования путешествий (\"Расскажи анекдот\"), вежливо отклони и верни фокус к цели.
ТВОЙ ВЫВОД ДОЛЖЕН БЫТЬ ТОЛЬКО В ФОРМАТЕ JSON:
{
\"next_action\": \"название_действия\",
\"action_parameters\": {}, // например, {\"slot_name\": \"budget\"}
\"reasoning\": \"Краткое объяснение выбора (1 предложение)\"
}Не засовывайте все инструкции в один промпт. Разделяйте ответственность. Отдельный промпт для вежливого отклонения оффтопика, отдельный — для генерации убедительных предложений по отелям. Это принцип Agent Skills.
4Интегрируйте внешний мир: API и не только
Агент, который только говорит — это справочник. Исполнитель должен делать. Для поиска рейсов и отелей в 2026 году все еще актуальны API Amadeus, Skyscanner (RapidAPI) и прямые интеграции с агрегаторами. Важный нюанс: LLM не должна формировать финальный HTTP-запрос. Она должна выдавать структурированный запрос на поиск, который ваш код преобразует в вызов API с правильными заголовками и аутентификацией.
# Пример: Исполнитель получает от планировщика задачу SEARCH_FLIGHTS
# с параметрами и делает вызов
class FlightSearchExecutor:
def execute(self, search_params: dict):
# Преобразуем параметры от LLM в формат внешнего API
# LLM могла написать \"завтра\", нам нужно преобразовать в дату
formatted_params = self._format_params(search_params)
# Вызов реального API (пример с aiohttp)
async with aiohttp.ClientSession() as session:
async with session.get('https://api.skyscanner.com/v3/flights',
params=formatted_params,
headers={'API_KEY': os.environ["API_KEY"]}) as resp:
search_results = await resp.json()
# Фильтрация и ранжирование результатов (можно с помощью другого вызова LLM)
ranked_results = self._rank_flights(search_results, search_params['budget'])
return ranked_results[:3] # Возвращаем топ-35Оценивайте не слова, а результат
Традиционные метрики типа BLEU или ROUGE бесполезны. Вам нужны метрики, измеряющие успех цели.
- Коэффициент завершенности задачи (Task Completion Rate): Какой процент диалогов заканчивается предоставлением валидного, полного маршрута?
- Эффективность сбора слотов (Slot Filling Efficiency): Сколько реплик потребовалось, чтобы собрать все обязательные данные? Меньше — лучше.
- Коэффициент успешных API-вызовов: Сколько поисковых запросов вернули релевантные, непустые результаты?
- Удовлетворенность пользователя (User Satisfaction Score): Опрос после диалога или косвенная метрика — согласился ли пользователь с предложенным вариантом.
Создайте тестовую панель с этими метриками. Запускайте диалоги с заранее подготовленными сценариями (персонажами с разными предпочтениями) и смотрите, где агент ломается. Часто ломается он именно в точках переговоров, где нужно проявить гибкость — тут может помочь введение суб-агента-переговорщика.
Где все пойдет не так: подводные камни 2026 года
Даже с самой продуманной архитектурой вы наткнетесь на эти проблемы. Лучше знать заранее.
| Проблема | Причина | Решение |
|---|---|---|
| Агент зацикливается на уточнениях | Планировщик слишком робок, боится перейти к поиску без 100% уверенности. | Введите правило: после 3 уточнений по одному слоту — делайте предположение (на основе истории) и просите подтвердить. |
| Пользователь сбивает агента с цели | LLM по своей природе хочет угодить и ответить на любой вопрос. | Жесткое правило в системном промпте + отдельный классификатор интентов на оффтопик, который триггерит стандартный ответ-отклонение. |
| API возвращает ошибку или пустой результат | Агент впадает в ступор или начинает выдумывать варианты (галлюцинировать). | Обучите планировщик действию \"HANDLE_API_ERROR\" с заготовленными стратегиями: расширить параметры поиска, предложить альтернативные даты. |
| Эпистемическая асимметрия | Агент \"знает\" (из промпта), что нужно собрать даты, но не может объяснить пользователю, зачем это нужно, если тот сопротивляется. | Используйте технику из статьи про \"Молчаливого ученого\": дайте агенту в промпт не только цель, но и разрешение на простые объяснения (\"Даты нужны, чтобы проверить наличие билетов и сезонные цены\"). |
Что дальше? Агенты начинают договариваться
Следующий эволюционный шаг для таких систем — мультиагентность. Ваш агент по планированию путешествий не будет одинок. Он станет координироваться с агентом-финансистом (который следит за бюджетом и курсами валют) и агентом-юристом (который проверяет визовые правила). Они будут обмениваться сообщениями, как в эксперименте, описанном в статье про AI-картель, но, надеюсь, с более полезными целями. Архитектурно это означает переход к полностью децентрализованной системе с шиной сообщений, где каждый агент — это независимый сервис со своими промптами и skills. Инструменты вроде Amazon Bedrock AgentCore или открытые фреймворки для мультиагентного взаимодействия (например, Camel) уже в 2026 году делают это доступным.
Главный неочевидный совет? Не пытайтесь сделать агента универсально вежливым и дружелюбным. Его цель — выполнить задачу, а не понравиться. Иногда лучший диалог — это тот, который закончился за 5 реплик, а не за 50, даже если последняя реплика агента была сухой констатацией факта: \"Ваш маршрут составлен. Вот ссылка на оплату. Хорошей поездки.\" В мире, заваленном болтливыми AI, именно целеустремленность станет вашим главным конкурентным преимуществом.