LangChain: что это такое и как построить на нём AI-агента

Собираем агента, который сам решает, когда вызвать Python, а когда достаточно ответа модели

LangChain: что это такое и как построить на нём AI-агента

Вопрос «LangChain — это что вообще?» обычно возникает в проекте не в первый день. Сначала приложение просто отправляет запрос в модель. Потом оказывается, что модели нужно проверить статус заказа, найти ответ во внутренней базе знаний, вспомнить, о чём пользователь спрашивал минуту назад, и вызвать нужную функцию.

В этот момент assistant.py быстро превращается в ящик, куда годами складывали обработчики вызова инструментов, куски промптов, временные JSON-схемы, три разных клиента OpenAI и комментарии TODO: разобраться позже.

Тогда приходится выбирать: продолжать собирать агентный цикл вручную или взять фреймворк. В статье разберём актуальный LangChain 1.x, соберём агента с Python-инструментом, подключим память и добавим небольшой RAG-поиск по документам. Заодно разберёмся, где LangChain действительно экономит время, а где становится ещё одной абстракцией, которую тоже придётся понимать и отлаживать.

ВАМ ПРИШЛО ПРИГЛАШЕНИЕ 💌
Приходите к нам в соцсети поделиться своим мнением и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте — шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!

Что такое LangChain

LangChain — это open-source фреймворк для разработки приложений на языковых моделях. Он упрощает подключение моделей, вызов инструментов, работу с сообщениями и сборку AI-агентов. Его часто называют библиотекой LangChain, хотя сегодня это скорее экосистема связанных Python-пакетов.

Высокоуровневый пакет langchain содержит интерфейсы для моделей и инструментов, а также готовый агентный цикл. LangGraph работает уровнем ниже: он предоставляет рантайм с состоянием, сохранением контрольных точек, паузами и восстановлением выполнения. Интеграции с OpenAI, Anthropic, Google, Ollama и другими провайдерами вынесены в отдельные пакеты.

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

В октябре 2025 года LangChain и LangGraph получили стабильные версии 1.0. На момент релиза экосистема набирала около 90 миллионов загрузок в месяц, а LangGraph использовали в продакшене команды Uber, LinkedIn и Klarna. 

Переход к версии 1.0 важен ещё и потому, что поисковая выдача до сих пор полна примеров с AgentExecutor, initialize_agent и несколькими типами агентов на выбор. В актуальной версии основной точкой входа служит create_agent, а прежние компоненты перенесены в пакет langchain-classic. Если инструкция предлагает выбрать между ZERO_SHOT_REACT_DESCRIPTION и ещё пятью константами, перед вами уже почти цифровая археология.

Зачем нужен фреймворк, если есть API модели

Для одного запроса к модели фреймворк обычно не окупается. Официальный SDK провайдера даёт меньше зависимостей, меньше промежуточных объектов и более прямой доступ к новым возможностям API.

Упрощённый вызов модели через официальный SDK выглядит так:

# Импортируем клиент выбранного провайдера.
from openai import OpenAI

# Создаём клиент; ключ читается из переменной OPENAI_API_KEY.
client = OpenAI()

# Отправляем один запрос к модели.
response = client.responses.create(
    model="gpt-5.4",
    input="Объясни разницу между процессом и потоком",
)

# Выводим готовый текст ответа.
print(response.output_text)

Для генерации описания товара, классификации обращения или короткого пересказа этого достаточно. О том, как подключить OpenAI API к Python-приложению напрямую, без LangChain, мы рассказываем в отдельной статье.

LangChain становится полезнее, когда вокруг модели появляется многошаговый сценарий. Например, приложение должно выбрать функцию, выполнить её, передать результат обратно модели, сохранить состояние разговора и продолжить цикл. Фреймворк также помогает настраивать повторные попытки, приостанавливать выполнение перед чувствительными действиями и отслеживать ход работы агента.

Все эти механизмы можно написать самостоятельно. Для небольшого сценария собственный цикл из пары функций иногда понятнее большого фреймворка. Но по мере роста приложения разработчик начинает поддерживать ещё и собственный рантайм: состояние, переходы между шагами, ошибки и восстановление выполнения. В этот момент абстракция перестаёт быть модным словом и начинает экономить вечер пятницы.

Практический фильтр простой. Если приложение отправляет промпт и сразу получает ответ, используйте SDK провайдера. Если модель выбирает следующий шаг, вызывает несколько функций и сохраняет состояние между вызовами, дополнительный слой LangChain начинает окупаться. В прикладном смысле выражение «искусственный интеллект с LangChain» обычно означает именно это: модель принимает решение, а обычный код выполняет действие.

Как устроен LangChain 1.0

Перед кодом полезно разложить фреймворк по уровням. Тогда сбой Python-функции не придётся лечить переписыванием системного промпта, а проблему с сохранением состояния — искать в настройках модели.

create_agent собирает стандартный цикл

Функция create_agent принимает модель, инструменты и системный промпт. Она возвращает собранный на LangGraph агент, который можно запускать через invoke() или stream()

Во время работы модель получает историю сообщений и описания доступных инструментов. Затем она либо формирует итоговый ответ, либо возвращает структурированный вызов одного или нескольких инструментов. LangChain выполняет их, добавляет результаты в состояние агента и снова обращается к модели. Цикл заканчивается, когда модель больше не запрашивает инструменты или срабатывает условие остановки. 

Middleware меняет поведение между шагами

Middleware позволяет вмешиваться в выполнение агента, не переписывая сам цикл. Через before_model можно сократить историю перед очередным запросом, через wrap_model_call — динамически выбрать модель, переключиться на резервную модель или повторить запрос, а через wrap_tool_call — перехватить ошибку инструмента. Для логики до и после отдельных этапов также доступны before_agent, after_model и after_agent.

В LangChain 1.x middleware стало основным механизмом кастомизации агента. Логику маршрутизации, ограничений, ретраев и обработки ошибок теперь можно подключать через явные хуки, а не прятать во внутренних компонентах. Такой код проще читать и меньше шансов, что после обновления он начнёт общаться с разработчиком на языке stack trace.

LangGraph выполняет граф

create_agent работает поверх рантайма LangGraph. Он управляет состоянием и переходами между узлами, поддерживает потоковую выдачу и длительное выполнение. Если подключить checkpointer, LangGraph сохраняет состояние на отдельных шагах и может продолжить работу после паузы или сбоя.

Пока сценарий укладывается в стандартный цикл «модель → инструменты → модель», достаточно уровня LangChain. Если нужно заранее задать собственные ветвления, обязательные проверки, параллельные шаги или участие человека, граф удобнее описать напрямую в LangGraph.

LangChain-агенты: чем они отличаются от чат-ботов

Сценарный чат-бот обычно идёт по маршруту, который разработчик задал заранее. Код проверяет условия, вызывает нужную функцию и собирает ответ. Языковая модель может писать текст или классифицировать запрос, но решение о следующем шаге всё равно принимает программа.

В агентном приложении часть этого выбора передают модели. Агент получает цель, историю сообщений и описания доступных инструментов. Затем модель решает, нужен ли ей один из них, формирует аргументы и получает результат. После этого она может вызвать ещё один инструмент или ответить пользователю. В документации такой компонент называется agent, а в поиске часто встречается формулировка LangChain agent.

Агентный цикл выглядит так:

Агентный цикл

Например, агенту доступна функция get_order_status(). Пользователь спрашивает: «Когда доставят заказ A-1002?». Модель извлекает номер заказа и формирует вызов инструмента с нужным аргументом. LangChain запускает Python-функцию, получает статус из базы и возвращает его модели. Та уже превращает технический результат вроде in_transit в понятный ответ: «Заказ передан курьеру и должен приехать сегодня».

Важно: модель не исполняет Python-код внутри себя. Она только выбирает инструмент и подготавливает аргументы, а запуск функции контролирует приложение. Эта граница особенно важна для действий, которые меняют данные. Если агент умеет отменять заказы, менять адрес доставки или удалять записи, системный промпт «действуй осторожно» помогает примерно так же, как записка «не нажимать» на большой красной кнопке. Нужны права доступа, проверка аргументов и подтверждение человека для чувствительных операций.

Python + LangChain: собираем первого агента

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

Понадобятся Python 3.10+, основной пакет langchain, интеграция выбранного провайдера и модель с поддержкой tool calling. Текущий LangChain 1.x требует Python 3.10 или новее.

Создайте окружение и установите зависимости для OpenAI:

python -m venv .venv
source .venv/bin/activate
python -m pip install -U langchain langchain-openai

На Windows команда активации будет такой:

.venv\Scripts\Activate.ps1

Задайте модель и ключ через переменные окружения:

export LANGCHAIN_MODEL="openai:gpt-5.4"
export OPENAI_API_KEY="ваш-ключ"

Вместо облачного API можно использовать локальную модель через Ollama:

python -m pip install -U langchain langchain-ollama
ollama pull gpt-oss:20b
export LANGCHAIN_MODEL="ollama:gpt-oss:20b"

Проверьте, что выбранная модель действительно поддерживает tool calling. Сам факт запуска в Ollama этого не гарантирует. Возможности и параметры интеграции LangChain с Ollama описаны в актуальной документации.

Описываем инструмент

Инструментом может быть обычная Python-функция. Декоратор `@tool` превращает сигнатуру функции в схему, которую увидит модель.

# Импортируем декоратор для регистрации инструментов.
from langchain.tools import tool

# Создаём тестовое хранилище заказов.
ORDERS = {
    # Добавляем заказ, который уже передали в доставку.
    "A-1001": {"status": "передан в доставку", "delivery_date": "25 июля"},
    # Добавляем заказ, который пока комплектуют.
    "A-1002": {"status": "ожидает комплектации", "delivery_date": "28 июля"},
}

# Регистрируем функцию как инструмент агента.
@tool
def get_order_status(order_id: str) -> str:
    """Возвращает статус заказа и плановую дату доставки."""

    # Нормализуем идентификатор и ищем заказ.
    order = ORDERS.get(order_id.upper())

    # Возвращаем понятный ответ для неизвестного номера.
    if order is None:
        return f"Заказ {order_id} не найден."

    # Собираем строку, которую получит модель.
    return (
        f"Статус заказа {order_id.upper()}: {order['status']}. "
        f"Плановая дата доставки: {order['delivery_date']}."
    )

Аннотация order_id: str становится частью JSON-схемы аргументов. Докстринг помогает модели понять назначение инструмента. Описание «получает данные» оставляет слишком много пространства для творческого толкования; «возвращает статус заказа и дату доставки» объясняет задачу намного лучше.

Создаём модель и агента

Теперь инициализируем модель и передадим ей инструмент:

# Импортируем модуль для чтения переменных окружения.
import os

# Импортируем фабрику агентов LangChain 1.x.
from langchain.agents import create_agent

# Импортируем универсальный конструктор чат-моделей.
from langchain.chat_models import init_chat_model

# Читаем название модели из окружения.
model_name = os.getenv("LANGCHAIN_MODEL", "openai:gpt-5.4")

# Создаём модель выбранного провайдера.
model = init_chat_model(
    model_name,
    temperature=0,
)

# Собираем агента с одним инструментом.
agent = create_agent(
    model=model,
    tools=[get_order_status],
    system_prompt=(
        "Вы — помощник службы доставки. "
        "Используйте инструмент для вопросов о конкретном заказе. "
        "Не придумывайте статусы и даты."
    ),
)

create_agent связывает модель с инструментами и собирает цикл поверх LangGraph. В новом проекте на этом месте не нужны initialize_agent() и AgentExecutor.

Запускаем агента

Передадим сообщение через поле messages:

# Запускаем агента с вопросом пользователя.
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "Что происходит с заказом A-1002?",
            }
        ]
    }
)

# Получаем последнее сообщение из состояния агента.
final_message = result["messages"][-1]

# Печатаем содержимое ответа.
print(final_message.content)

Модель увидит вопрос о конкретном заказе, вызовет get_order_status с аргументом A-1002, получит данные и сформулирует ответ. Формулировка может отличаться, а статус и дата должны прийти из функции.

Пример результата:

Заказ A-1002 ожидает комплектации. Плановая дата доставки — 28 июля.

Полный листинг

# Импортируем модуль для чтения переменных окружения.
import os

# Импортируем фабрику агентов LangChain 1.x.
from langchain.agents import create_agent

# Импортируем универсальный конструктор чат-моделей.
from langchain.chat_models import init_chat_model

# Импортируем декоратор для регистрации инструментов.
from langchain.tools import tool

# Создаём тестовое хранилище заказов.
ORDERS = {
    # Добавляем заказ, который уже передали в доставку.
    "A-1001": {"status": "передан в доставку", "delivery_date": "25 июля"},
    # Добавляем заказ, который пока комплектуют.
    "A-1002": {"status": "ожидает комплектации", "delivery_date": "28 июля"},
}

# Регистрируем функцию как инструмент агента.
@tool
def get_order_status(order_id: str) -> str:
    """Возвращает статус заказа и плановую дату доставки."""

    # Нормализуем идентификатор и ищем заказ.
    order = ORDERS.get(order_id.upper())

    # Возвращаем понятный ответ для неизвестного номера.
    if order is None:
        return f"Заказ {order_id} не найден."

    # Собираем строку, которую получит модель.
    return (
        f"Статус заказа {order_id.upper()}: {order['status']}. "
        f"Плановая дата доставки: {order['delivery_date']}."
    )

# Читаем название модели из окружения.
model_name = os.getenv("LANGCHAIN_MODEL", "openai:gpt-5.4")

# Создаём модель выбранного провайдера.
model = init_chat_model(
    model_name,
    temperature=0,
)

# Собираем агента с инструментом и системной инструкцией.
agent = create_agent(
    model=model,
    tools=[get_order_status],
    system_prompt=(
        "Вы — помощник службы доставки. "
        "Используйте инструмент для вопросов о конкретном заказе. "
        "Не придумывайте статусы и даты."
    ),
)

# Запускаем агента с вопросом пользователя.
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "Что происходит с заказом A-1002?",
            }
        ]
    }
)

# Получаем последнее сообщение из состояния агента.
final_message = result["messages"][-1]

# Печатаем содержимое ответа.
print(final_message.content)

Большая скидка — 16% на все курсы Практикума

Если читаете эту статью, тогда вы точно разбираетесь в технологиях. Стать лучше и зарабатывать больше можно после курсов Практикума — по программированию, анализу данных и искусственному интеллекту.

До 17 сентября на все курсы действует скидка 16%, она применится автоматически при оплате. Потом цены станут выше, поэтому не откладывайте!

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

Добавляем системный промпт и память диалога

Системный промпт уже передан через system_prompt. В нём удобно фиксировать роль, правила выбора инструментов, формат ответа и запреты. Инструкции должны помогать модели принимать решения, а ограничения безопасности всё равно нужно дублировать кодом.

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

# Импортируем checkpointer для локальной разработки и тестов.
from langgraph.checkpoint.memory import InMemorySaver

# Создаём хранилище контрольных точек в памяти процесса.
checkpointer = InMemorySaver()

# Пересобираем агента с поддержкой истории диалога.
agent = create_agent(
    model=model,
    tools=[get_order_status],
    system_prompt=(
        "Вы — помощник службы доставки. "
        "Используйте инструмент для вопросов о конкретном заказе. "
        "Не придумывайте статусы и даты."
    ),
    checkpointer=checkpointer,
)

Каждому диалогу задают свой thread_id. Повторные вызовы с одинаковым идентификатором работают с одной историей:

# Задаём идентификатор текущего диалога.
config = {
    "configurable": {
        "thread_id": "support-dialog-42",
    }
}

# Сохраняем первое сообщение с номером заказа.
agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "У меня заказ A-1002.",
            }
        ]
    },
    config=config,
)

# Отправляем уточнение без повторения номера.
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "Когда его доставят?",
            }
        ]
    },
    config=config,
)

# Печатаем ответ, сформированный с учётом истории.
print(result["messages"][-1].content)

Агент восстановит номер из предыдущего сообщения и вызовет инструмент с A-1002. InMemorySaver подходит для примера и тестов; после перезапуска процесса история исчезнет. В продакшене нужен checkpointer с постоянным хранилищем, например PostgreSQL.

Checkpointer и store регулярно путают, хотя роли у них разные:

МеханизмЧто хранитОбласть действия
CheckpointerСообщения и состояние графаОдин диалог с конкретным thread_id
StoreПрофиль, предпочтения
и другие долговременные данные
Несколько диалогов и сессий

Номер заказа из текущей переписки логично хранить в состоянии потока. Язык интерфейса или сохранённый адрес могут понадобиться в следующем разговоре, поэтому для них подходит store — механизм долговременной памяти.

LangChain и LangGraph: что когда брать

LangChain и LangGraph занимают разные уровни одной архитектуры. Типового агента проще начать с create_agent. Собственный граф нужен, когда переходы между шагами становятся частью требований, а не внутренней деталью фреймворка.

СценарийПодход
Модель выбирает один из нескольких инструментовLangChain и create_agent
Нужны память, системный промпт и обработка ошибок инструментовLangChain с checkpointer и middleware
Маршрут содержит обязательные ветвления и возвратыЯвный граф LangGraph
Выполнение должно пережить перезапуск сервисаLangGraph с постоянным checkpointer
Перед удалением или оплатой нужна пауза на решение сотрудникаHuman-in-the-loop поверх LangGraph
Несколько агентов передают работу по собственным правиламLangGraph или специализированная надстройка

Само количество инструментов ещё не требует собственного графа. create_agent умеет выполнять многошаговый цикл, хранить состояние и обрабатывать повторные вызовы. LangGraph становится нужен, когда вы хотите явно определить: после проверки риска идти на ручное согласование, после отказа вернуться к уточнению данных, а после сбоя продолжить с последней сохранённой точки.

В LangGraph это поддерживается через persistence и checkpointing. Human-in-the-loop может остановить выполнение перед опасным инструментом, сохранить состояние и продолжить после решения человека.

Переход на более низкий уровень не требует выбрасывать инструменты и модели. create_agent уже возвращает LangGraph-граф. По мере роста проекта можно заменить готовую архитектуру своими узлами и переходами. То есть в случае чего не придётся переписывать всё с нуля. 

RAG: подключаем агента к своим документам

По умолчанию языковая модель не имеет доступа к внутренним правилам возврата, свежей документации проекта и корпоративной базе знаний. RAG — retrieval-augmented generation — решает эту проблему: приложение находит подходящие фрагменты документов и передаёт их модели вместе с вопросом.

Базовый процесс выглядит так:

Базовый процесс подключения RAG

Эмбеддинг превращает текст в числовой вектор. У близких по смыслу фрагментов получаются похожие векторы, поэтому поиск может связать вопрос «сколько времени есть на возврат» с абзацем «заказ можно вернуть в течение 14 дней», даже если слова не совпадают полностью.

Это полезная математика, а не волшебный порошок для PDF. Качество ответа по-прежнему зависит от исходных документов, способа разбиения текста и модели эмбеддингов.

Продолжаем предыдущий пример: модель, функция create_agent, декоратор @tool и инструмент get_order_status уже определены выше. Теперь добавим агенту поиск по внутренним документам.

Готовим документы

Создадим папку docs и положим в неё два текстовых файла:

docs/
├── delivery.txt
└── returns.txt

Содержимое delivery.txt:

Курьерская доставка занимает от одного до трёх рабочих дней.
После передачи заказа курьеру клиент получает уведомление.

Содержимое returns.txt:

Заказ можно вернуть в течение 14 дней с даты получения.
Для возврата нужно сохранить товарный вид и подтверждение покупки.

Для учебного примера используем Ollama: модель эмбеддингов будет работать локально, поэтому документы не придётся отправлять во внешний API.

Установим интеграцию и пакет для разбиения текста:

python -m pip install -U langchain-ollama langchain-text-splitters
ollama pull qwen3-embedding:8b

Строим поисковый индекс

Прочитаем файлы, разделим их на фрагменты и построим индекс:

# Импортируем класс для работы с путями.
from pathlib import Path


# Импортируем структуру документа LangChain.
from langchain_core.documents import Document


# Импортируем векторное хранилище в памяти процесса.
from langchain_core.vectorstores import InMemoryVectorStore


# Импортируем локальную модель эмбеддингов Ollama.
from langchain_ollama import OllamaEmbeddings


# Импортируем разделитель текста.
from langchain_text_splitters import RecursiveCharacterTextSplitter




# Указываем каталог с исходными документами.
docs_directory = Path("docs")


# Читаем все текстовые файлы и сохраняем название источника.
documents = [
    Document(
        page_content=path.read_text(encoding="utf-8"),
        metadata={"source": path.name},
    )
    for path in docs_directory.glob("*.txt")
]


# Останавливаем пример с понятной ошибкой, если папка оказалась пустой.
if not documents:
    raise RuntimeError(
        "В папке docs не найдены текстовые файлы."
    )


# Настраиваем размер фрагментов и небольшое перекрытие.
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=700,
    chunk_overlap=100,
)


# Делим документы на фрагменты.
chunks = text_splitter.split_documents(documents)


# Подключаем локальную модель для построения эмбеддингов.
embeddings = OllamaEmbeddings(
    model="qwen3-embedding:8b",
)


# Создаём временное векторное хранилище.
vector_store = InMemoryVectorStore(
    embedding=embeddings,
)


# Добавляем фрагменты документов в индекс.
vector_store.add_documents(
    documents=chunks,
)


# Создаём компонент поиска по ближайшим фрагментам.
retriever = vector_store.as_retriever(
    search_kwargs={"k": 3},
)

InMemoryVectorStore удобно использовать в учебном примере: не нужны отдельная база и конфигурация сервера. Но после перезапуска приложения индекс исчезнет вместе с процессом.

В рабочем сервисе векторы обычно хранят в PostgreSQL с pgvector, Qdrant, Milvus или другом постоянном хранилище. Благодаря общему интерфейсу LangChain реализацию можно заменить, не переписывая весь поиск. По крайней мере, это план. Миграции всё равно найдут способ напомнить о себе.

Превращаем поиск в инструмент

Сам по себе retriever только возвращает подходящие документы. Чтобы агент мог решать, когда запускать поиск, обернём его в инструмент:

# Регистрируем поиск по документам как инструмент агента.
@tool
def search_internal_docs(query: str) -> str:
    """Ищет информацию во внутренних правилах доставки и возврата."""


    # Получаем релевантные фрагменты.
    found_documents = retriever.invoke(query)


    # Возвращаем явный ответ, если поиск ничего не нашёл.
    if not found_documents:
        return "В документах не найдено подходящей информации."


    # Добавляем к каждому фрагменту название исходного файла.
    sections = [
        (
            f"Источник: "
            f"{document.metadata.get('source', 'неизвестный источник')}\n"
            f"{document.page_content}"
        )
        for document in found_documents
    ]


    # Склеиваем найденные фрагменты в один контекст.
    return "\n\n".join(sections)

Теперь у агента будут два инструмента:

  • get_order_status получает актуальный статус конкретного заказа;
  • search_internal_docs ищет правила в документации.

Передадим оба инструмента в create_agent:

# Создаём агента с доступом к заказам и внутренним документам.
agent = create_agent(
    model=model,
    tools=[
        get_order_status,
        search_internal_docs,
    ],
    system_prompt=(
        "Вы — помощник службы доставки. "
        "Для статусов заказов используйте get_order_status. "
        "Для правил доставки и возврата используйте search_internal_docs. "
        "Не придумывайте факты, которых нет в результатах инструментов."
    ),
)

Проверяем поиск по документам

Зададим вопрос, ответа на который нет в коде агента, но есть в файле returns.txt:

# Запускаем агента с вопросом по внутренним правилам.
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "Сколько дней есть на возврат заказа?",
            }
        ]
    }
)


# Получаем последнее сообщение агента.
final_message = result["messages"][-1]


# Печатаем ответ.
print(final_message.content)

Пример результата:

Заказ можно вернуть в течение 14 дней с даты получения.
Для возврата нужно сохранить товарный вид и подтверждение покупки.

Формулировка может отличаться, но срок и условия должны прийти из найденного документа. Если агент внезапно разрешает возврат через полгода без чека и товара, RAG где-то свернул не туда.

Для вопроса «Что происходит с заказом A-1002?» агент вызовет get_order_status. Для вопроса о правилах возврата — search_internal_docs. Решение о том, нужен ли поиск, принимает модель.

Такой подход называют agentic RAG. В двухшаговой схеме поиск запускается перед каждым обращением к модели независимо от вопроса. Agentic RAG гибче: агент сам выбирает, когда идти в документы. Двухшаговый вариант проще тестировать, а его задержки и стоимость легче предсказывать.

Для небольшого справочного сервиса часто достаточно двухшагового RAG. Агентный вариант становится полезен, когда модель выбирает между несколькими источниками: базой заказов, документацией, CRM или другими инструментами.

Альтернативы: когда LangChain усложняет задачу

У фреймворка есть налог на сложность. Ошибку приходится искать сразу в нескольких местах: в инструменте, сообщениях модели, middleware, состоянии графа и интеграции провайдера. Когда приложение делает один запрос без состояния, этот набор слоёв мешает сильнее, чем помогает. LangChain для такого сценария напоминает Kubernetes, который поставили ради одного cron-скрипта: технически возможно, но на созвонах появится слишком много новых существительных.

Для одиночных вызовов лучше взять официальный SDK провайдера. Если проект работает только с OpenAI и нужны готовые агенты, инструменты и сессии, можно рассмотреть OpenAI Agents SDK. В типизированном Python-проекте с упором на модели Pydantic и структурированные ответы удобнее может оказаться PydanticAI. Для сложного процесса с явными ветвлениями и восстановлением подходит прямое использование LangGraph.

Выбор зависит от центра тяжести проекта. Мультипровайдерность, готовый агентный цикл, инструменты, память и постепенный переход к графу дают LangChain сильную позицию. Минимальный сервис с одним вызовом API получит от него в основном дополнительные зависимости.

Если свести ответ на запрос «LangChain это что?» к одной фразе, получится так: LangChain — фреймворк для приложений, в которых модель участвует в многошаговом процессе, вызывает инструменты и работает с состоянием. Он полезен, когда оркестрация уже стала отдельной задачей. До этого момента самый технологичный выбор часто выглядит подозрительно просто: обычная функция и SDK провайдера.

Советуем дополнительно почитать

10 AI-навыков, которые должен освоить каждый разработчик — от промпт-инжиниринга для кода и оркестрации агентов до работы с LLM API, построения RAG-систем и оценки моделей под задачу, с планом на первые 30 дней.

MCP-серверы в Cursor и Claude: установка, настройка, первый запуск — как протокол Anthropic стандартизирует подключение инструментов к моделям и чем он отличается от собственных обёрток вроде декоратора @tool.

Вся база про токены в нейросетях: что это и как их эффективно использовать — как считаются входные и выходные токены и во что превращается растущая история диалога, когда агент хранит её в checkpointer.

Python-бэкенд в 2026: полный стек — фреймворки, БД, брокеры, линтеры и зависимости — что реально стоит в коммерческих проектах: FastAPI или Django, управление зависимостями, очереди, SQLAlchemy и Pydantic.

Как безопасно запускать чужой код: Firecracker microVM и тёплые пулы — изоляция для кода, который приходит от агента или интерпретатора, на microVM с запуском меньше 5 миллисекунд.

Бонус для читателей

Хотите прокачать скиллы, чтобы расти в карьере без лишней суеты — берите промокод на курсы Яндекс Практикума. Он даст скидку и превратит инвестиции в себя в выгодную сделку. Бесплатные вводные курсы в Практикуме тоже есть. Стартовать можно в любой момент, карту привязывать не нужно, если что.

Вам может быть интересно
medium