5. Предварительный обзор агента — момент «Ага!»
До этого момента вы создавали системы, где вы контролируете поток выполнения. Вы пишете промпт, вызываете LLM и обрабатываете ответ. LLM мощная модель, но она всё ещё просто следует вашим инструкциям.
Эта глава представляет фундаментальный сдвиг: что если LLM будет решать, что произойдёт дальше?
Зачем существует эта глава
Проблема: Если вы продолжите создавать чат-ботов до главы 11 без этого предварительного обзора, вы, вероятно, подумаете, что «агенты — это просто улучшенные чат-боты».
Правда: Агенты фундаментально отличаются. Они принимают решения, которые управляют вашей программой, а не просто генерируют текст.
Решение: Эта глава показывает вам, куда мы движемся, поэтому когда вы будете создавать конвейеры и управление состоянием в следующих главах, вы поймёте зачем важна каждая часть.
Что вы узнаете (и что не узнаете)
Эта глава (Глава 5):
- Минимальный 15-минутный обзор агентного мышления
- Как вывод LLM может запускать разные пути выполнения кода
- Концептуальная разница между маршрутизацией и циклами агента
Последующие главы (начиная с главы 12):
- Реальные агентные системы с инструментами, циклами и самокоррекцией
- Готовые к продакшену реализации
- Продвинутые паттерны, такие как координация мультиагентов
Почему разрыв? Перед созданием агентов вам нужны основы: промпт-инжиниринг (Гл. 4), конвейеры выполнения (Гл. 6), структурированный вывод (Гл. 7), управление состоянием (Гл. 8) и интеграция знаний (Гл. 9-11).
Заметка о подходе к обучению: Эта глава фокусируется на концепциях, а не на деталях реализации. Фактические техники проектирования и разработки появятся в главе 12 и далее. Не беспокойтесь, если вы не знаете, как реализовать всё, что видите здесь—это намеренно. Понимания что такое агент концептуально достаточно на данный момент.
Давайте начнём.
5.1) От чат-бота к принимающему решения
Концепция: Вместо ответа на вопрос, LLM решает, какой инструмент использовать
В главах 3 и 4 вы создавали чат-системы, где LLM генерирует текстовые ответы. Поток был простым:
- Пользователь задаёт вопрос
- LLM генерирует ответ
- Вы отображаете ответ
Это статическая цепочка(chain): поток предопределён. Единственная задача LLM — производить текст.
Теперь давайте изменим тип запроса. Пользователь спрашивает: «Сколько будет 847 × 923?»
Ваш чат-бот из главы 3 попытается ответить на это, но LLM на самом деле не вычисляют—они предсказывают правдоподобно выглядящие токены. Для «2 + 2» ответ «4» появляется так часто в обучающих данных, что LLM даёт правильный ответ. Но для «847 × 923»—вычисления, которое LLM никогда не видела—она сгенерирует что-то, что выглядит как число, но, вероятно, будет неправильным.
Что вам действительно нужно:
- LLM распознаёт: «Это математическая задача»
- LLM решает: «Использовать инструмент калькулятора, а не мою предсказательную модель токенов»
- Python выполняет: 847 × 923 = 781,781
- Система возвращает: Правильный ответ
Задача LLM не в том, чтобы вычислять—а в том, чтобы направить к инструменту, который может.
Вот ключевая идея: LLM не нужно решать математику—ей нужно решить использовать калькулятор.
Это сдвиг от чат-бота к принимающему решения. LLM анализирует запрос и направляет его к соответствующему инструменту. Она принимает решение о потоке выполнения программы, а не просто генерирует текст.
Давайте визуализируем эту разницу:
В статической цепочке LLM пытается ответить на всё напрямую. В системе динамической маршрутизации LLM направляет запрос к правильному инструменту.
Визуализация: Сравнение «Линейной цепочки» (Статическая) и «Роутера» (Динамическая)
Вот как статическая цепочка обрабатывает любой запрос пользователя:
LLM пытается ответить на всё напрямую. Независимо от того, спрашиваете ли вы «Какая столица Франции?» или «Сколько будет 15% от 240?», LLM генерирует текстовый ответ. Она может правильно назвать столицу (она видела «Париж» много раз в обучающих данных), но, вероятно, неправильно вычислит процент.
Проблема: LLM использует один и тот же подход для всех вопросов—предсказание токенов—даже когда существуют лучшие инструменты.
Теперь давайте представим систему, которая может обрабатывать разные типы запросов по-разному:
- Фактические вопросы: «Какая столица Франции?» → Использовать инструмент поиска
- Математические задачи: «Сколько будет 15% от 240?» → Использовать калькулятор
- Разговорные: «Как дела?» → LLM отвечает напрямую
Вот динамический роутер, который делает это возможным:
LLM анализирует ввод и выбирает путь в зависимости от типа запроса. Это логика маршрутизации, и LLM действует как роутер.
Ключевое отличие: Вместо того чтобы всегда генерировать текст, LLM теперь решает как обработать каждый запрос—направляя его к соответствующему инструменту.
Ключевой вывод: От генерации ответов к выбору действий
Это фундаментальный сдвиг в агентных системах:
Традиционное использование LLM: Вы задаёте вопрос, LLM пишет ответ.
Агентное использование LLM: Вы задаёте вопрос, LLM решает, что делать, и ваш код выполняет это решение.
Вывод LLM больше не просто текст для пользователя—это инструкции для вашей программы.
Думайте об этом так: в традиционном чат-боте LLM — это сотрудник, который отвечает на вопросы клиентов. В агентной системе LLM — это менеджер, который решает, какой отдел должен обработать каждый запрос.
Эта способность принимать решения — суть агентных систем. LLM не просто отвечает—она контролирует, что ваша программа делает дальше. На основе ввода она может запустить калькулятор, вызвать API поиска или ответить напрямую. Поведение программы меняется в зависимости от решения LLM.
Но как мы на самом деле реализуем это? Давайте посмотрим на минимальный код, который делает это возможным.
5.2) Минимальное выполнение
Триггер: Системный промпт, который заставляет выводить структурированные данные
Чтобы заставить LLM действовать как роутер, нам нужно ограничить её вывод. Вместо генерации полного ответа мы хотим, чтобы она выводила структурированное решение—JSON-объект, который сообщает нашему коду и что делать, и какие данные использовать.
Вот системный промпт, который это делает:
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
import json
llm = ChatOpenAI(model="gpt-4o-mini")
system_prompt = """Вы помощник по маршрутизации. Проанализируйте запрос пользователя и ответьте в формате JSON.
Формат вывода:
{
"action": "CALC" | "SEARCH" | "CHAT",
"input": "очищенный ввод для инструмента"
}
Примеры:
Пользователь: "Вычисли 15 умножить на 7"
Вывод: {"action": "CALC", "input": "15 * 7"}
Пользователь: "Какая столица Японии?"
Вывод: {"action": "SEARCH", "input": "столица Японии"}
Пользователь: "Привет!"
Вывод: {"action": "CHAT", "input": "Привет!"}
Извлеките существенную информацию и отформатируйте её для соответствующего инструмента.
Выводите только валидный JSON, ничего больше."""
def get_routing_decision(user_input: str) -> dict:
messages = [
SystemMessage(content=system_prompt),
HumanMessage(content=user_input)
]
response = llm.invoke(messages)
# Парсим JSON-ответ
try:
decision = json.loads(response.content.strip())
return decision
except json.JSONDecodeError:
# Запасной вариант, если LLM не выводит валидный JSON
return {"action": "CHAT", "input": user_input}
# Тестируем с ДРУГИМИ вводами, отличными от примеров
print(get_routing_decision("Сколько будет 25 * 48?"))
# Вывод: {'action': 'CALC', 'input': '25 * 48'}
print(get_routing_decision("Кто написал Гамлета?"))
# Вывод: {'action': 'SEARCH', 'input': 'автор Гамлета'}
print(get_routing_decision("Как у тебя дела сегодня?"))
# Вывод: {'action': 'CHAT', 'input': 'Как у тебя дела сегодня?'}Этот промпт намеренно ограничивающий. Мы не просим LLM быть креативной—мы просим её классифицировать ввод и извлечь релевантную информацию в структурированном формате.
Обратите внимание, что происходит здесь:
- LLM читает вопрос пользователя
- Она определяет намерение (вычисление, фактический поиск, разговор)
- Она извлекает и очищает существенную информацию
- Она выводит JSON-объект с действием и очищенным вводом
- Наш код получает эти структурированные данные и направляет соответственно
LLM не отвечает на вопрос—она решает, что должно ответить на вопрос.
Мост: Соединение решений LLM с выполнением инструментов
Теперь нам нужно соединить решение LLM с фактическим выполнением инструмента. Вот минимальный мост:
def safe_calculator(expression: str) -> float:
try:
# Используем eval с ограниченным пространством имён для безопасности
# Примечание: eval() имеет риски безопасности.
# В продакшене используйте библиотеку парсинга математики, такую как sympy или оценку на основе ast.
result = eval(expression, {"__builtins__": {}}, {})
return float(result)
except:
raise ValueError(f"Не удалось вычислить: {expression}")
def search(query: str) -> str:
"""Заглушка функции поиска"""
# В реальности это вызовет API поиска
# Теперь получает очищенный запрос типа "автор Гамлета"
return f"Результаты поиска для: {query}"
def route_and_execute(user_input: str) -> str:
"""Получаем решение LLM и выполняем соответствующий инструмент"""
decision = get_routing_decision(user_input)
action = decision["action"]
tool_input = decision["input"] # Очищенный LLM ввод
if action == "CALC":
try:
result = safe_calculator(tool_input)
return f"Результат вычисления: {result}"
except ValueError as e:
return f"Не удалось выполнить вычисление: {e}"
elif action == "SEARCH":
result = search(tool_input)
return result
else: # CHAT
# Для разговорных запросов позволяем LLM ответить напрямую
response = llm.invoke([HumanMessage(content=tool_input)])
return response.content
# Тестируем полный поток
print(route_and_execute("Сколько будет 25 * 48?"))
# LLM извлекает "25 * 48" → калькулятор получает чистый ввод
# Вывод: Результат вычисления: 1200.0
print(route_and_execute("Кто написал Гамлета?"))
# LLM переформатирует в "автор Гамлета" → лучший поисковый запрос
# Вывод: Результаты поиска для: автор Гамлета
print(route_and_execute("Как у тебя дела сегодня?"))
# Вывод: У меня всё хорошо, спасибо, что спросили!Это минимальный паттерн маршрутизации в его простейшей форме:
- Решение LLM: Получаем решение о маршрутизации от LLM
- Выполнение кода: Python-код проверяет решение
- Вызов инструмента: Вызывается соответствующая функция
- Возврат результата: Вывод форматируется и возвращается
Критическая идея: Вывод LLM больше не текст для пользователя—это структурированные данные, которые контролируют поведение вашей программы. Поле action сообщает вашему коду, какой путь выбрать, а поле input предоставляет очищенные данные для этого инструмента. Этот JSON-ответ становится исполняемой логикой.
Это фундаментально отличается от чат-бота. В чат-боте вывод LLM идёт напрямую пользователю. Здесь вывод LLM идёт вашему коду, который затем решает, что выполнить.
То, что мы построили в этом разделе, — это роутер—предшественник полных агентов. Он демонстрирует фундаментальный принцип (LLM контролирует поток), но не имеет итерации и самокоррекции, которые определяют настоящие циклы агентов.
Эта простая система маршрутизации имеет серьёзное ограничение: она не может исправлять свои собственные ошибки. Давайте исследуем, почему это важно и что будет дальше.
5.3) Понимание терминологии: Роутер против агента
То, что мы только что построили, — это роутер. Но как он соотносится с полными агентами? Давайте установим чёткие определения:
Роутер (что мы только что построили)
- Принимает одно решение о классификации на запрос
- Выбирает, какой инструмент/путь выполнить
- Выполняется один раз и возвращает результат
- Нет итерации, нет состояния, нет самокоррекции
- Пример: Классификатор email, детектор намерений, селектор инструментов
Помощник с вызовом инструментов (Глава 13)
- LLM может вызывать инструменты напрямую через API вызова функций
- Всё ещё обычно однооборотный (один запрос → один ответ)
- Более сложный, чем маршрутизация, но не обязательно итеративный
- Пример: «Найди погоду и обобщи» в одном вызове
Агент (полный цикл) (Главы 14-17)
- Итерирует через циклы думать → действовать → наблюдать
- Сохраняет состояние между итерациями
- Может пересматривать решения на основе результатов
- Реализует самокоррекцию
- Пример: Помощник по отладке, который пробует исправления, пока код не заработает
Агентная система (общий термин)
- Любая система, где вывод LLM влияет на поток управления
- Включает роутеры, помощников с вызовом инструментов и полные агенты
- «Агентный» описывает свойство; «агент» описывает конкретную архитектуру
- Пример: Всё вышеперечисленное демонстрирует агентное поведение
То, что мы построили в 5.2, — это роутер—он принимает одно решение и выполняет его. Это хорошо работает для простых задач классификации, но не может обрабатывать ситуации, требующие нескольких шагов, верификации или корректировки курса. Если начальное решение неоптимально или если задача оказывается более сложной, чем ожидалось, роутер не имеет механизма для адаптации.
Агенты решают это ограничение, вводя итерацию. Они могут наблюдать результат действия, пересматривать свой подход и пробовать снова. Это делает их подходящими для сложных многошаговых задач, где путь к решению не ясен с самого начала.
В главах 14-17 вы узнаете, как создавать эти итеративные циклы агентов.
Примечание: Недавно модели рассуждения, такие как o1 и o3, могут обрабатывать некоторые многошаговые задачи внутренне, уменьшая необходимость в явных циклах в определённых сценариях. Мы исследуем, когда использовать циклы против моделей рассуждения, когда вы будете создавать реальных агентов в части IV.
Что вы должны понимать сейчас:
- Агентные системы начинаются с потока управления: Вывод LLM определяет, что ваш код делает дальше
- Маршрутизация — это простейшая форма: Одно решение, один инструмент, один результат—то, что мы построили в 5.2
- Реальным агентам нужны циклы: Для обработки многошаговых задач, верификации и восстановления после ошибок (Главы 14-17)
- Самокоррекция — ключевое отличие: Агенты могут наблюдать результаты и корректировать свой подход
Что мы не рассмотрели (и не будем до последующих глав):
- Как компоновать конвейеры выполнения (Глава 6)
- Как обрабатывать структурированный вывод (Глава 7)
- Как управлять памятью разговора (Глава 8)
- Как правильно определять инструменты (Глава 12)
- Как реализовывать циклы агентов с самокоррекцией (Главы 14-17)
- Как делать агентов готовыми к продакшену (Главы 18-26)
Эта глава была о концептуальном сдвиге—понимании того, что делает систему демонстрирующей агентное поведение. Детали реализации придут позже.
В следующей главе мы продолжим строить практические основы: компоновку конвейеров выполнения с LangChain Expression Language (LCEL). Это строительные блоки, которые вы будете использовать, когда мы реализуем реальных агентов в части IV.
Момент «ага!» завершён. Теперь вы понимаете, что агентные системы — это те, где LLM решает, а ваш код выполняет. Всё остальное—инструменты, циклы, управление состоянием—это о том, чтобы сделать этот цикл принятия решений и выполнения более надёжным и способным.
Давайте продолжим строить фундамент.