Что такое AI-агент и чем он отличается от чат-бота
Содержание курса
Что такое агент
С первого взгляда может показаться, что агент и чат-бот — это одно и то же под разными названиями, однако это не так. Этому отличию и посвящён урок: на этом шаге разберём, что из себя представляет агент, а дальше сравним его с чат-ботом и проведём между ними чёткую границу.
Чат-бот — это система, обрабатывающая пользовательские запросы в формате диалога и формирующая ответ на основе шаблонов, правил или языковой модели. Его цель — дать релевантный ответ в пределах диалога, но не достичь внешней цели и не выполнить действие.
Пример. Допустим, нам необходимо получать информацию из базы данных через простые словесные запросы: есть таблица клиентов, и нужно узнать, сколько среди них человек с балансом выше 1000. Попробуем обойтись одним вызовом модели — то есть, по сути, чат-ботом.
from langchain.chat_models import init_chat_model
# Имя модели меняется от провайдера к провайдеру — берите актуальное из его документации.
MODEL = "openai:gpt-5.6-sol"
llm = init_chat_model(MODEL)
response = llm.invoke("Сколько клиентов с балансом выше 1000?")
print(response.text)
Ответ будет примерно таким:
Числа мы не получили — модель просит прислать данные. И дело не в том, что она недостаточно умна: у неё просто нет доступа к базе. Всё, что она умеет, — превращать текст на входе в текст на выходе. Чтобы получить число, кто-то должен сходить в базу.
Инструмент — это функция, о которой модель знает
Инструмент — обычная функция вашего кода, которую вы описали так, чтобы модель могла её выбрать: имя, человеческое описание и схема аргументов с типами.
import sqlite3
from contextlib import closing
from langchain.tools import tool
@tool
def run_sql(query: str) -> str:
"""Выполнить SELECT в базе клиентов clients(name, balance) и вернуть строки результата."""
with closing(sqlite3.connect("clients.db")) as conn:
rows = conn.execute(query).fetchall()
return str(rows)
Про closing стоит сказать отдельно, потому что на этом спотыкаются регулярно: with sqlite3.connect(...) сам по себе соединение не закрывает — он только фиксирует или откатывает транзакцию. Инструмент агент дёргает в цикле, и без явного закрытия каждый вызов оставляет за собой открытое соединение.
Ключевой момент, который легко пропустить: модель не вызывает функцию. Она не может ничего выполнить — она возвращает текст. Получив описание инструментов, модель отвечает не обычным сообщением, а заявкой на вызов: имя инструмента и аргументы к нему.
llm_with_tools = llm.bind_tools([run_sql])
response = llm_with_tools.invoke("Сколько клиентов с балансом выше 1000?")
print(response.text)
print(response.tool_calls)
Вот что печатает этот код:
Первая строка пустая — обычного ответа модель не дала. Вместо него во второй строке лежит заявка: имя инструмента, аргументы и идентификатор вызова.
Дальше эту заявку исполняет ваш код: находит функцию по имени, передаёт аргументы, забирает результат. Модель предлагает действие, программа его совершает — граница ответственности проходит ровно здесь, и она же определяет, что агенту вообще разрешено делать в мире.
Один вызов задачу не закрывает
Результат run_sql нужно вернуть модели: сама она не знает, что вернула база. Значит, модель придётся вызвать ещё раз — уже с результатом инструмента в диалоге. На втором вызове она может ответить пользователю, а может запросить следующий инструмент: например, увидев пустой результат, попросить список таблиц и переписать запрос.
Заранее неизвестно, сколько вызовов понадобится. Поэтому вызовы модели заворачивают в цикл: модель отвечает — если в ответе есть заявки на вызов, код их исполняет, добавляет результаты в диалог и вызывает модель снова.
То, что накапливается по ходу цикла, — состояние. В простом случае это список сообщений: запрос пользователя, ответы модели, результаты инструментов. Состояние целиком уходит в модель на каждой итерации, поэтому оно и растёт, и стоит денег.
Остановка
Цикл нужно чем-то завершать, и здесь два разных механизма.
Первый — естественная остановка: модель возвращает обычное сообщение без заявок на вызов. Это значит, что она считает задачу решённой.
Второй — жёсткий лимит. Модель может зациклиться: снова и снова дёргать тот же инструмент, получать ту же ошибку и пробовать ещё раз. Без ограничения такой цикл крутится, пока не кончатся деньги на счёте. Поэтому число шагов ограничивают снаружи, и это не перестраховка, а обязательная часть конструкции.
Собираем всё вместе
Итак, агент — это программа, способная автономно воспринимать среду, принимать решения и выполнять действия для достижения цели. В системах на языковых моделях агент — это не просто чат с моделью, а механизм управления рассуждениями, действиями и памятью, который умеет использовать инструменты и подстраивать своё поведение под задачу. Агент не просто реагирует на команды пользователя (как чат-бот), а сам выстраивает последовательность действий, чтобы достичь результата.
Четыре части, которые мы разобрали, — инструменты, цикл, состояние и условие остановки — переводят это определение в рабочее: агент — это языковая модель в цикле, у которой есть инструменты, состояние между шагами и условие остановки. Если хотя бы одной части нет, перед вами что-то другое.
Писать этот цикл руками не нужно, его даёт фреймворк:
from langchain.agents import create_agent
agent = create_agent(
model=MODEL,
tools=[run_sql],
system_prompt=(
"Ты аналитик. Отвечай на вопросы о клиентах, "
"получая данные через инструмент run_sql."
),
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "Сколько клиентов с балансом выше 1000?"}]},
{"recursion_limit": 12},
)
print(result["messages"][-1].text)
create_agent принимает модель, список инструментов и системный промпт, а возвращает готовый цикл. recursion_limit — тот самый жёсткий лимит: сколько внутренних шагов разрешено сделать, прежде чем выполнение прервётся с GraphRecursionError.
Считаются здесь шаги графа, а не обращения к инструментам: вызов модели — один шаг, исполнение её заявок — второй. То есть один полный ход «модель решила — код сходил» стоит примерно две единицы, и 12 в примере означает около шести ходов.
Значение по умолчанию — 10007, то есть защиты по факту нет: агент успеет сжечь бюджет задолго до срабатывания. Поэтому лимит выставляют вручную и под конкретную задачу — как здесь.
Схема получившейся конструкции:

