При разработке приложений на основе больших языковых моделей (LLM, Large Language Model) встает вопрос: вызывать ли модель напрямую через API (например, OpenAI) или использовать специализированные фреймворки вроде LangChain или LangGraph. Ниже мы рассмотрим, с какими сложностями сталкивается разработчик при прямом использовании LLM, и как LangChain и LangGraph помогают упростить создание сложных диалоговых и агентных систем. Также приведем примеры кода, сравнивая прямые вызовы с использованием этих фреймворков, и обсудим, когда их применение оправдано.
Проблемы при прямом вызове LLM API
Прямое обращение к модели через API (например, openai.chat.completions
) кажется простым, но при разработке полноценных приложений быстро возникают следующие сложности:
-
Отсутствие памяти и контекста. LLM по своей природе не «помнит» предыдущих сообщений, если вы сами не включите их в каждый новый запрос. Без механизма памяти каждая новая реплика обрабатывается моделью изолированно, без учета прошлого диалога. Это означает, что для реализации диалога разработчик сам должен хранить историю переписки и отправлять ее вместе с каждым запросом. В противном случае модель будет забывать, о чем шла речь ранее, и диалог развалится.
-
Управление длинными диалогами. У каждой модели ограничен размер контекстного окна (количество токенов для входа и выхода). Если диалог или запрос очень длинный, его может не поместиться в один вызов модели. При прямом подходе разработчику приходится самостоятельно решать, как сокращать историю: удалять старые сообщения, суммировать их или применять иной подход к сжатию контекста. Это дополнительная логика, которую нужно реализовать вручную.
-
Интеграция с внешними источниками и инструментами. Часто требуется, чтобы модель использовала внешние данные или выполняла действия: например, поиск в интернете, запрос к базе знаний, вычисление формулы или вызов другой функции. Напрямую LLM этого делать не может — разработчик должен сам определить, когда и что вызвать вне модели. Например, если пользователь спросил о свежих новостях, код приложения должен распознать этот запрос, вызвать поиск новости через API, затем вставить результаты в следующий промпт для модели. Реализация такой логики (определение намерения, вызов инструмента, обработка ответа, возвращение в модель) существенно усложняет код.
-
Многошаговые цепочки вызовов. Сложные задачи могут требовать разбить решение на несколько шагов. Например, сначала нужно проанализировать запрос пользователя, затем совершить несколько промежуточных обращений к модели или инструментам (пошаговый вывод, планирование, получение данных), и лишь потом сформировать окончательный ответ. Без фреймворков разработчик вручную пишет всю оркестровку: как передавать данные между шагами, как формировать промежуточные промпты, как обрабатывать ошибки на каждом этапе.
-
Обработка состояния и переменных. В сложных сценариях может появиться необходимость хранить не только историю сообщений, но и некоторое состояние между вызовами: например, результаты предыдущих действий, флаги или параметры диалога. При прямом вызове всю эту информацию нужно хранить в структуре данных (например, словарь) и постоянно обновлять и прокидывать дальше по коду, что ведет к ошибкам и усложняет поддержку.
-
Логирование, отладка и повторное использование. Простой вызов API скрывает от нас, что происходит «под капотом». Если нужно залогировать промежуточные решения модели, приходится модифицировать код в нескольких местах. Повторное использование кода без фреймворка тоже сложнее: например, если вы написали цепочку из нескольких шагов в виде функций, нет стандартизированного интерфейса, чтобы переиспользовать ее в другом проекте. Каждый раз придется копировать и адаптировать код.
Таким образом, прямой вызов OpenAI API для прототипа или единичного запроса подходит, но по мере роста сложности логики начинает требовать все больше «ручной работы».
Как упрощают работу LangChain и LangGraph
LangChain – это популярный фреймворк, предлагающий высокоуровневые абстракции для построения приложений на основе LLM. Он решает многие из перечисленных проблем, предоставляя готовые компоненты:
-
Цепочки вызовов (Chains). Фреймворк вводит концепцию Chain – последовательности шагов, которые могут включать вызовы моделей и произвольный Python-код. Это позволяет декларативно описывать поток данных. LangChain предлагает готовые цепочки (например, последовательная цепочка, цепочка вопросов-ответов с поиском по документам и т.д.) и даёт возможность создавать собственные, что упрощает организацию сложных процессов (например:вопрос пользователя → формирование запроса → поиск документов → передача найденного текста и исходного вопроса в LLM для ответа).
-
Шаблоны промптов и форматирование. При прямом использовании API часто приходится конкатенировать строки, вставлять переменные в текст запроса, заботиться о правильных разделителях и формате. LangChain предлагает класс PromptTemplate, который позволяет описать шаблон запроса с параметрами и затем подставлять конкретные значения. Это делает код чище и уменьшает риск ошибок форматирования.
-
Интеграция инструментов и агенты. Одно из самых мощных возможностей LangChain – агенты (Agents). Агент – это обертка вокруг LLM, которая может сама решать, какие действия совершить для получения ответа. В LangChain агенту можно предоставить набор инструментов (функций), например: поисковый движок, калькулятор, база знаний и т.д. Во время диалога агент анализирует запрос пользователя и может выбирать, какой инструмент вызвать, а когда вернуться к модели для формирования финального ответа. Фреймворк берет на себя организацию этого цикла: парсит ответ модели, выявляет намерение воспользоваться инструментом, передает нужные аргументы, получает результат и возвращает его в модель в виде нового контекста. Разработчику не нужно самому писать парсинг мыслей модели и логику вызова — достаточно определить доступные инструменты и тип агента (LangChain поддерживает различные стратегии, например ReAct). Это существенно облегчает создание ботов, которые могут, к примеру, сами делать веб-поиск по требованию или выполнять вычисления.
-
Логирование и отладка. Встроенная поддержка логирования (например, флаг
verbose=True
у цепочек) позволяет видеть шаги выполнения: какой промпт отправлен модели, какой ответ получен, какой инструмент вызван и с какими параметрами и т.д. Это значительно облегчает отладку сложных цепочек. Кроме того, сообщество LangChain предлагает инструменты вроде LangSmith для отслеживания и отладки агентов в продакшене. Если напрямую дергать API, подобный уровень прозрачности пришлось бы реализовывать самостоятельно. -
Память, управление контекстом и сохранение состояния. Изначально LangChain предоставлял встроенные механизмы памяти (например, классы
ConversationBufferMemory
,ConversationSummaryMemory
), позволяющие автоматически сохранять историю диалога и подставлять её в новые запросы. Такой подход превращает LLM в систему с памятью, где предыдущие взаимодействия автоматически включаются в контекст. Начиная с релиза LangChain v0.3, рекомендуется использовать механизм сохранения состояния (persistence) из экосистемы LangGraph для интеграции памяти в приложения на базе LangChain.
Проще говоря, LangChain автоматизирует многие шаблонные задачи: сохранение контекста, формирование промптов, выбор инструментов, последовательное выполнение шагов. Разработчику остается сосредоточиться на логике высокого уровня (какую задачу решить), вместо написания большого количества обслуживающего кода. Ниже мы сравним на примере, как один и тот же сценарий выглядит через прямой вызов и через LangChain.
Как помогает LangGraph для сложных сценариев
Если LangChain закрывает потребности в линейных цепочках и базовых агентах, то LangGraph идет дальше для сложных и разветвленных сценариев. Он особенно полезен, когда логика взаимодействия выходит за рамки простой последовательности шагов.
-
Графовое представление логики. Вместо жёсткой последовательности действий вы описываете логику в виде графа, где узлы представляют вызовы LLM, использования инструментов или проверки условий, а переходы между узлами могут быть условными. Например, можно задать:Если пользователь попросил найти информацию – перейти в узел поиска, затем вернуться к формированию ответа; если вопрос простой – сразу сгенерировать ответ моделью.Такой подход значительно упрощает реализацию сложной логики по сравнению с вложенными конструкциями if/else или большим количеством отдельных вызовов.
-
Координация нескольких агентов. LangGraph изначально спроектирован для мультиагентных систем. LangGraph изначально спроектирован для мультиагентных систем. Вы можете определить несколько специализированных агентов (например, один для общения с пользователем, другой для поиска информации, третий для выполнения вычислений) и настроить их взаимодействие через граф, задавая порядок и условия активации каждого агента.
-
Хранение состояния и контекста между узлами. LangGraph поддерживает централизованное состояние (State), которое доступно всем узлам графа. Например, состояние может содержать историю сообщений, результаты инструментов, флаги циклов и т.п. В LangGraph есть механизмы сохранения этого состояния между вызовами, в том числе во внешних хранилищах (SQLite, PostgreSQL, S3 и др.). Это обеспечивает надежность и воспроизводимость: даже если приложение перезапустится, агент может продолжить с прежнего состояния.
-
Обработка ошибок и контроль выполнения. В сложных процессах важно уметь обрабатывать ошибки на любом этапе. LangGraph предоставляет средства для отлова исключений, повторных попыток и определения альтернативных путей выполнения. Это позволяет избежать тупиковых состояний и реализовать гибкую стратегию восстановления при сбоях.
-
Визуализация и отладка графа. Благодаря декларативному описанию логики в виде графа, можно использовать инструменты вроде LangGraph Studio для визуализации последовательности действий агентов. Это существенно упрощает понимание и отладку сложных сценариев, делая структуру приложения более прозрачной.
Резюмируя LangGraph пригодится, когда ваш сценарий выходит за рамки линейного диалога. Если нужно реализовать сложный диалоговый автомат или систему из нескольких взаимодействующих агентов, описать это в виде графа будет проще и надежнее, чем пытаться уложить в один монолитный агент или писать все вручную. Далее мы рассмотрим небольшой пример, как с помощью LangGraph можно задать ветвление логики.
Примеры кода
Рассмотрим несколько простых примеров, демонстрирующих разницу между прямым использованием OpenAI API и использованием LangChain/LangGraph для той же задачи.
1. Прямой вызов OpenAI API (без фреймворков)
Предположим, мы хотим реализовать простой чат-бот, который отвечает пользователю, учитывая контекст беседы. Без фреймворков нам придется напрямую вызывать метод API и самостоятельно поддерживать историю сообщений:
import openai
# Инициализируем историю диалога списком сообщений для API
messages = [{
"role": "system",
"content": "Ты — помощник, отвечай развернуто и вежливо."
}]
# Функция для отправки пользовательского сообщения и получения ответа
def send_message(user_input):
# Добавляем сообщение пользователя в историю
messages.append({"role": "user", "content": user_input})
# Вызываем OpenAI ChatCompletion с полной историей
completion = openai.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
# Извлекаем ответ ассистента
assistant_reply = completion.choices[0].message.content
# Добавляем ответ ассистента в историю, чтобы сохранить контекст
messages.append({"role": "assistant", "content": assistant_reply})
return assistant_reply
# Пример диалога
print("Assistant:", send_message("Привет! Меня зовут Александр"))
print("Assistant:", send_message("Как меня зовут?"))
Результат:
Assistant: Привет, Александр! Рад с тобой познакомиться. Как я могу помочь тебе сегодня?Assistant: Тебя зовут Александр. Если есть что-то конкретное, о чем ты хочешь поговорить или задать вопрос, не стесняйся!
Что здесь происходит: мы вручную ведем список messages, каждый раз передавая всю историю в openai.
Модель на стороне OpenAI будет получать полный контекст (наши прошлые сообщения) и генерировать ответ, который мы добавляем обратно в историю. Код явно управляет контекстом диалога. Если мы не будем добавлять предыдущие сообщения, модель забудет, о чем шла речь. Кроме того, если история станет слишком длинной, нам самим придется решить, как ее сократить (например, удалять или суммировать старые сообщения).
Без фреймворка код для более сложных задач разрастается. Допустим, если бы нам нужно было при определенном запросе делать поиск в интернете, мы бы добавили в send_message проверку текста на ключевые слова, вызов внешнего API поиска, затем вставку результатов в messages перед следующим запросом к модели. Всё это нужно программировать вручную, отлаживать и поддерживать.
2. Тот же чат-бот с использованием LangChain
Теперь решим ту же задачу с помощью LangChain. Мы воспользуемся готовой цепочкой ConversationChain
, которая уже включает память диалога. В LangChain достаточно указать модель и тип памяти – остальное берется на себя:
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import START, MessagesState, StateGraph
# Инициализация модели
llm = ChatOpenAI(
model_name="gpt-4o-mini",
temperature=0)
# Создаём граф
workflow = StateGraph(state_schema=MessagesState)
# Функция вызова LLM
def call_model(state: MessagesState):
response = llm.invoke(state["messages"])
return {"messages": response}
# Определяем простейший граф
workflow.add_edge(START, "model")
workflow.add_node("model", call_model)
# Добавляем in-memory checkpointer
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
config = {"configurable": {"thread_id": "bob-conversation-1"}}
output = app.invoke({"messages": [HumanMessage("Привет! Меня зовут Александр")]}, config)
output["messages"][-1].pretty_print()
output = app.invoke({"messages": [HumanMessage("Как меня зовут?")]}, config)
output["messages"][-1].pretty_print()
Разумеется, код LangGraph сложнее прямого примера, и для простого случая выглядит избыточно. Однако представьте расширение этого сценария: можно добавить еще ветки (например, получение данных из API, или отдельного агента для вопросов по базе знаний), можно организовать цикл к примеру. Также можем теперь добавить персистентность. Таким образом, даже очень сложная логика с множеством разветвлений, циклов и агентов, хранением состояния описывается через декларирование узлов и связей. Это улучшает поддержку и масштабирование кода.
3. Более сложный пример - агент, публикующий новостную статью
Рассмотрим упрощенный пример, как с помощью LangChain и LangGraph можно создать интеллектуального агента, который объединяет возможности языковой модели и внешних инструментов для выполнения сложных задач.
Этот агент реализован на основе prebuild ReAct агенте из библиотеки LangGraph (https://arxiv.org/abs/2210.03629)
Примерная структура кода:
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langchain.agents import AgentExecutor
from langchain_core.tools import Tool
llm = ChatOpenAI(
model_name="gpt-4o-mini")
def fetch_rss(feed_url: str) -> str:
# ... Логика получения RSS новостей по заданному URL ...
return "ЗДЕСЬ ДОЛЖЕН БЫТЬ ОТВЕТ ОТ RSS"
def scrape_web(url: str) -> str:
# ... Логика сбора данных со страницы по URL ...
return "ЗДЕСЬ ДОЛЖЕН БЫТЬ ОТВЕТ ОТ WEB SCRAPER"
# Определяем инструменты, доступные агенту:
rss_tool = Tool(
name="RSSFetcher",
func=fetch_rss,
description="Извлекает данные из RSS-ленты по заданному URL."
)
scraper_tool = Tool(
name="WebScraper",
func=scrape_web,
description="Собирает краткое описание стартапа со страницы по указанному URL."
)
tools = [rss_tool, scraper_tool]
query = f"""
Возьми один случайный свежий технологический стартап, посети его домашнюю страницу и напиши статью о нем.
"""
agent = create_react_agent(llm, tools)
agent_executor = AgentExecutor(agent=agent, tools=tools)
result = agent.invoke({"messages": [("human", query)]})
print(result['messages'][-1].content)
Примерно такой бот сейчас автоматически публикует новости в моём новостном телеграм-канале.
Итог: когда использовать LangChain и LangGraph
Когда оправдано использование фреймворков:
-
Если вы создаете чат-бот или другое приложение, где важна память о контексте и ведение длинного диалога, LangChain и LangGraph значительно упростят жизнь. Он автоматически управляет историей сообщений и избавит от ручного формирования каждого промпта.
-
Если ваша задача требует многошагового решения (например, сначала достать данные, потом обработать, потом ответить) – используйте LangChain. С его помощью вы можете строить цепочки вызовов и легко интегрировать внешние инструменты через агентов. Это снижает количество шаблонного кода и вероятность ошибок в логике.
-
Когда вы планируете сложную систему с несколькими агентами или ветвящейся логикой, лучше сразу рассмотреть LangGraph. Он пригодится, если один LLM не справляется со всем и нужно распределить задачи между разными компонентами, а также если сценарий подразумевает разные пути выполнения. LangGraph обеспечивает порядок и управление в таких случаях, решая проблемы координации, состояния и надежности выполнения.
-
В production-системах, где важны надежность, контроль и отладка, фреймворки тоже дают преимущества. LangChain позволяет легко логировать шаги агента, а LangGraph – контролировать каждое действие агента вплоть до возможности отката или повторного выполнения узла. Это помогает делать приложения более предсказуемыми и понятными.
Когда можно обойтись без них:
-
Если вам нужно просто разово вызвать модель для получения ответа (например, сгенерировать текст на основе подсказки) и больше ничего — нет особого смысла привносить зависимость на целый фреймворк. Прямой вызов API с этим справится прекрасно. Фреймворки добавляют небольшие накладные расходы и сложность настройки, что излишне для тривиальных задач.
-
В случаях, когда логика вашего приложения очень простая, а контекст минимален (например, одна просьба — один ответ, без диалога), ручное использование API будет более явным и контролируемым. Вы точно знаете, какой промпт отправляется, и результат сразу получаете — добавление LangChain может быть избыточным.
-
Когда вы только экспериментируете с моделью в ноутбуке или скрипте, чтобы понять, как она отвечает, тоже можно вызывать напрямую. Фреймворки же стоят того, когда вы уже переходите от эксперимента к построению структурированного приложения.
Подводя итог, LangChain и LangGraph — мощные инструменты для разработки на основе LLM, особенно когда приложение выходит за рамки одного вопроса-ответа. LangChain отлично подходит для большинства задач с линейными сценариями, предоставляя удобные абстракции. LangGraph же становится полезен, когда требуется память, максимальный контроль, сложная логика или множество агентов, работающих сообща.
Автор: ai-agent-dev