Image

Как создавать эффективные агентные системы с помощью LangGraph

Создавайте рабочие процессы ИИ с помощью агентных фреймворков

Делиться

6d624a7434b313ce9be063fd45983b21

С появлением мощных моделей ИИ, таких как GPT-5 и Gemini 2.5 Pro, мы также наблюдаем рост числа агентных фреймворков, использующих эти модели. Эти фреймворки упрощают работу с моделями ИИ, абстрагируясь от множества проблем, таких как вызов инструментов, управление состоянием агентов и настройка с участием человека.

В этой статье я подробнее расскажу о LangGraph, одном из доступных фреймворков для агентного ИИ. Я использую его для разработки простого агентного приложения, последовательно демонстрируя преимущества пакетов для агентного ИИ. Я также рассмотрю плюсы и минусы использования LangGraph и других подобных фреймворков.

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

  • LangChain
  • LlamaIndex
  • CrewAI
Расширенный LangGraph

Зачем вам нужна агентская структура?

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

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

Сосредоточьтесь на решении именно той проблемы, которую вы пытаетесь решить. Все остальные (ранее решённые проблемы) следует передать на аутсорсинг другим приложениям.

Агентная структура необходима, поскольку она абстрагирует множество сложностей, с которыми вам не хотелось бы иметь дело:

  • Поддержание состояния. Не только история сообщений, но и вся другая информация, которую вы собираете, например, при выполнении RAG.
  • Использование инструментов. Вам не нужно настраивать собственную логику для выполнения инструментов. Вместо этого следует просто определить их и позволить агентской платформе управлять их вызовом. (Это особенно актуально для параллельного и асинхронного вызова инструментов.)

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

Основы LangGraph

Чтобы приступить к внедрению LangGraph, я начинаю с чтения документации, охватывающей:

  • Базовая реализация чат-бота
  • Использование инструмента
  • Поддержание и обновление состояния

LangGraph, как следует из названия, основан на построении графов и их выполнении по запросу. В графе можно определить:

  • Состояние (текущая информация, хранящаяся в памяти)
  • Узлы. Обычно это LLM или вызов инструмента, например, классифицирующий намерение пользователя или отвечающий на его вопрос.
  • Рёбра. Условная логика определяет, к какому узлу перейти следующим.

Все это вытекает из базовой теории графов.

Реализация рабочего процесса

LangGraph с маршрутизатором и инструментами

Я считаю, что один из лучших способов обучения — это просто пробовать всё самостоятельно. Поэтому я реализую простой рабочий процесс в LangGraph. Подробнее о построении этих рабочих процессов можно узнать в документации по рабочим процессам, основанной на блоге Anthropic «Building Effective Agents» (одна из моих любимых статей об агентах, о которой я писал в нескольких предыдущих статьях). Настоятельно рекомендую её прочитать.

Я создам простой рабочий процесс для определения приложения, в котором пользователь может:

  • Создавайте документы с текстом
  • Удалить документы
  • Поиск в документах

Для этого я создам следующий рабочий процесс:

  1. Определите намерение пользователя. Хочет ли он создать документ, удалить документ или выполнить поиск в документе?
  2. Учитывая результат шага 1, у меня будут разные процессы для обработки каждого из них.

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

Загрузка импорта и LLM

Сначала я загружу необходимые импорты и используемый мной LLM. Я буду использовать AWS Bedrock, хотя вы можете использовать и других поставщиков, как показано в шаге 3 этого руководства.

«»» Создайте рабочий процесс обработчика документов, в котором пользователь может создать новый документ в базе данных (в настоящее время это просто словарь), удалить документ из базы данных, задать вопрос о документе «»» из typing_extensions import TypedDict, Literal из langgraph.checkpoint.memory import InMemorySaver из langgraph.graph import StateGraph, START, END из langgraph.types import Command, interrupt из langchain_aws import ChatBedrockConverse из langchain_core.messages import HumanMessage, SystemMessage из pydantic import BaseModel, Field из IPython.display import display, Image из dotenv import load_dotenv import os load_dotenv() aws_access_key_id = os.getenv(«AWS_ACCESS_KEY_ID») или «» aws_secret_access_key = os.getenv(«AWS_SECRET_ACCESS_KEY») или «» os.environ[«AWS_ACCESS_KEY_ID»] = aws_access_key_id os.environ[«AWS_SECRET_ACCESS_KEY»] = aws_secret_access_key llm = ChatBedrockConverse( model_id=»us.anthropic.claude-3-5-haiku-20241022-v1:0″, # это идентификатор модели (добавлено us. перед идентификатором в платформе) region_name=»us-east-1″, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, ) document_database: dict[str, str] = {} # словарь с ключом: имя файла, значением: текст в документе

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

Определение графа

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

  • добавить_документ
  • удалить_документ
  • ask_document

# Определить класс состояния State(TypedDict): ввод: str решение: str | None вывод: str | None # Схема для структурированного вывода, используемая в качестве логики маршрутизации class Route(BaseModel): step: Literal[«add_document», «delete_document», «ask_document»] = Field( description=»Следующий шаг в процессе маршрутизации» ) # Дополнить LLM схемой для структурированного вывода router = llm.with_structured_output(Route) def llm_call_router(state: State): «»»Направить пользовательский ввод в соответствующий узел»»» # Запустить дополненный LLM со структурированным выводом, который будет служить логикой маршрутизации decision = router.invoke( [ SystemMessage( content=»»»Направить пользовательский ввод в одно из следующих 3 намерений: — 'add_document' — 'delete_document' — 'ask_document' Вам нужно вернуть только намерение, а не любой другой текст. «»» ), HumanMessage(content=state[«input»]), ] ) return {«decision»: decision.step} # Условная функция ребра для маршрутизации к соответствующему узлу def route_decision(state: State): # Верните имя узла, который вы хотите посетить следующим if state[«decision»] == «add_document»: return «add_document_to_database_tool» elif state[«decision»] == «delete_document»: return «delete_document_from_database_tool» elif state[«decision»] == «ask_document»: return «ask_document_tool»

Я определяю состояние, в котором мы сохраняем пользовательский ввод, решение маршрутизатора (одно из трёх намерений), а затем обеспечиваю структурированный вывод от LLM. Структурированный вывод гарантирует, что модель ответит одним из трёх намерений.

Продолжая, я определю инструменты, которые мы используем в этой статье, по одному для каждого намерения.

# Узлы def add_document_to_database_tool(state: State): «»»Добавление документа в базу данных. По заданному пользовательскому запросу извлекаем имя файла и содержимое документа. Если не указано, документ в базу данных не добавляется.»»» user_query = state[«input»] # извлечение имени файла и содержимого из пользовательского запроса filename_prompt = f»По заданному пользовательскому запросу извлекаем имя файла документа: {user_query}. Возвращает только имя файла, а не какой-либо другой текст.» output = llm.invoke(filename_prompt) filename = output.content content_prompt = f»По заданному пользовательскому запросу извлекаем содержимое документа: {user_query}. Возвращает только содержимое, а не какой-либо другой текст.» output = llm.invoke(content_prompt) content = output.content # добавить документ в базу данных document_database[filename] = content return {«output»: f»Документ {filename} добавлен в базу данных»} def delete_document_from_database_tool(state: State): «»»Удаляет документ из базы данных. По заданному пользовательскому запросу извлекает имя файла документа для удаления. Если не указано, документ из базы данных не удаляется.»»» user_query = state[«input»] # извлекает имя файла из пользовательского запроса filename_prompt = f»По заданному пользовательскому запросу извлекает имя файла документа для удаления: {user_query}. Возвращает только имя файла, а не какой-либо другой текст.» output = llm.invoke(filename_prompt) filename = output.content # удалить документ из базы данных, если он существует, если нет, вернуть информацию об ошибке, если filename отсутствует в document_database: return {«output»: f»Документ {filename} не найден в базе данных»} document_database.pop(filename) return {«output»: f»Документ {filename} удален из базы данных»} def ask_document_tool(state: State): «»»Задать вопрос о документе. По заданному запросу пользователя извлечь имя файла и вопрос для документа. Если не указано, вопрос о документе не задается.»»» user_query = state[«input»] # извлечь имя файла и вопрос из запроса пользователя filename_prompt = f»По заданному следующему запросу пользователя извлечь имя файла документа, о котором нужно задать вопрос: {user_query}. Возвращает только имя файла, а не какой-либо другой текст.» output = llm.invoke(filename_prompt) filename = output.content question_prompt = f»Данный следующий запрос пользователя, извлеките вопрос о документе: {user_query}. Верните только вопрос, а не какой-либо другой текст.» output = llm.invoke(question_prompt) question = output.content # задать вопрос о документе, если имя файла отсутствует в document_database: return {«output»: f»Документ {filename} не найден в базе данных»} result = llm.invoke(f»Документ: {document_database[filename]}nnВопрос: {question}») return {«output»: f»Результат запроса документа: {result.content}»}

И наконец, строим граф с узлами и ребрами:

# Построение рабочего процесса router_builder = StateGraph(State) # Добавление узлов router_builder.add_node(«add_document_to_database_tool», add_document_to_database_tool) router_builder.add_node(«delete_document_from_database_tool», delete_document_from_database_tool) router_builder.add_node(«ask_document_tool», ask_document_tool) router_builder.add_node(«llm_call_router», llm_call_router) # Добавление ребер для соединения узлов router_builder.add_edge(START, «llm_call_router») router_builder.add_conditional_edges( «llm_call_router», route_decision, { # Имя, возвращаемое route_decision : Имя следующего узла для посещения «add_document_to_database_tool»: «add_document_to_database_tool», «delete_document_from_database_tool»: «delete_document_from_database_tool», «ask_document_tool»: «ask_document_tool», }, ) router_builder.add_edge(«add_document_to_database_tool», END) router_builder.add_edge(«delete_document_from_database_tool», END) router_builder.add_edge(«ask_document_tool», END) # Компиляция рабочего процесса memory = InMemorySaver() router_workflow = router_builder.compile(checkpointer=memory) config = {«configurable»: {«thread_id»: «1»}} # Показать рабочий процесс дисплей(Изображение(router_workflow.get_graph().draw_mermaid_png())

Последняя функция отображения должна отобразить график, как показано ниже:

Граф маршрутизатора LangGraph

Теперь вы можете опробовать рабочий процесс, задавая вопрос для каждого намерения.

Добавить документ:

user_input = «Добавить документ «test.txt» с содержимым «Это тестовый документ» в базу данных» state = router_workflow.invoke({«input»: user_input}, config) print(state[«output»] # -> Документ test.txt добавлен в базу данных

Поиск документа:

user_input = «Дайте мне краткое описание документа «test.txt»» state = router_workflow.invoke({«input»: user_input}, config) print(state[«output»]) # -> Краткий, общий тестовый документ с простым описательным предложением.

Удалить документ :

user_input = «Удалить документ test.txt из базы данных» state = router_workflow.invoke({«input»: user_input}, config) print(state[«output»]) # -> Документ test.txt удален из базы данных

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

Более сильные варианты использования агентов

Разница между агентными рабочими процессами и полностью агентными приложениями иногда сбивает с толку. Однако, чтобы разделить эти два термина, я воспользуюсь цитатой из книги «Создание эффективных агентов» издательства Anthropic:

Рабочие процессы — это системы, в которых LLM и инструменты координируются посредством предопределённых путей кода. Агенты же — это системы, в которых LLM динамически управляют собственными процессами и использованием инструментов, сохраняя контроль над тем, как они выполняют задачи.

Большинство задач, которые вы решаете с помощью LLM, будут использовать шаблон рабочего процесса, поскольку большинство задач (по моему опыту) предопределены и должны иметь заранее установленный набор ограничений. В приведённом выше примере при добавлении/удалении/поиске документов вам обязательно следует настроить предопределённый рабочий процесс, определив классификатор намерений и действия, которые нужно выполнить для каждого намерения.

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

Если вы хотите создать более автономные агентные системы, вы можете прочитать об этом подробнее здесь.

Плюсы и минусы LangGraph

Плюсы

Три моих главных плюса LangGraph:

  • Легко настроить
  • С открытым исходным кодом
  • Упрощает ваш код

Настроить LangGraph и быстро запустить его было просто. Особенно если следовать документации или передавать её в Cursor и задавать ему задачи по реализации определённых рабочих процессов.

Более того, исходный код LangGraph открыт, что означает, что вы можете продолжать использовать его, независимо от того, что происходит с компанией-разработчиком или какие изменения она решит внести. Я думаю, это критически важно, если вы хотите развернуть его в рабочей среде. Наконец, LangGraph также упрощает значительную часть кода и абстрагирует значительную часть логики, которую пришлось бы писать на Python самостоятельно.

Минусы

Однако у LangGraph есть и некоторые недостатки, которые я заметил в ходе внедрения.

  • Все еще удивительно много шаблонного кода
  • Вы столкнетесь с ошибками, характерными для LangGraph.

При реализации собственного рабочего процесса я чувствовал, что мне всё равно придётся добавить много шаблонного кода. Хотя объём кода был определённо меньше, чем если бы я реализовал всё с нуля, я был удивлён объёмом кода, который пришлось добавить для создания относительно простого рабочего процесса. Однако, думаю, отчасти это связано с тем, что LangGraph пытается позиционировать себя как инструмент с меньшим объёмом кода, чем, например, многие функции LangChain (что, на мой взгляд, хорошо, поскольку LangChain, на мой взгляд, слишком абстрагируется, что затрудняет отладку кода).

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

Краткое содержание

В целом, я считаю LangGraph полезным пакетом при работе с агентными системами. Настройка моего рабочего процесса путем предварительной классификации намерений и последующего применения различных потоков в зависимости от намерения была относительно простой. Более того, я думаю, что LangGraph нашёл хорошую золотую середину между отсутствием абстрагирования всей логики (что затеняет код и затрудняет отладку) и фактическим абстрагированием проблем, с которыми я не хочу сталкиваться при разработке своей агентной системы. Реализация подобных агентных фреймворков имеет как положительные, так и отрицательные стороны, и, на мой взгляд, лучший способ найти этот компромисс — самостоятельно реализовать простые рабочие процессы.

👉 Найдите меня в соцсетях:

🧑‍💻 Свяжитесь с нами

🔗 LinkedIn

🐦 X / Твиттер

✍️ Средний

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

Источник: towardsdatascience.com

✅ Найденные теги: Как, новости
Каталог бесплатных опенсорс-решений, которые можно развернуть локально и забыть о подписках

галерея

Фото сгенерированных лиц: исследование показывает, что люди не могут отличить настоящие лица от сгенерированных
Нейросети построили капитализм за трое суток: 100 агентов Claude заперли…
Скетч: цифровой осьминог и виртуальный мир внутри компьютера с человечком.
Сцена с жестами пальцами, где один жест символизирует "VPN", а другой "KHP".
‼️Paramount купила Warner Bros. Discovery — сумма сделки составила безумные…
Скриншот репозитория GitHub "Claude Scientific Skills" AI для научных исследований.
Структура эффективного запроса Claude с элементами задачи, контекста и референса.
Эскиз и готовая веб-страница платформы для AI-дизайна в современном темном режиме.
ideipro logotyp
Image Not Found
Звёздное небо с галактиками и туманностями, космос, Вселенная, астрофотография.

Система оповещения обсерватории Рубина отправила 800 000 сигналов в первую ночь наблюдений.

Астрономы будут получать оповещения о небесных явлениях в течение нескольких минут после их обнаружения. Теренс О'Брайен, редактор раздела «Выходные». Публикации этого автора будут добавляться в вашу ежедневную рассылку по электронной почте и в ленту новостей на главной…

Мар 2, 2026
Женщина с длинными тёмными волосами в синем свете, нейтральный фон.

Расследование в отношении 61-фунтовой машины, которая «пожирает» пластик и выплевывает кирпичи.

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

Мар 2, 2026
Черный углеродное волокно с текстурой плетения, отражающий свет.

Материал будущего: как работает «бессмертный» композит

Учёные из Университета штата Северная Каролина представили композит нового поколения, способный самостоятельно восстанавливаться после серьёзных повреждений.  Речь идёт о модифицированном армированном волокном полимере (FRP), который не просто сохраняет прочность при малом весе, но и способен «залечивать» внутренние…

Мар 2, 2026
Круглый экран с изображением замка и горы, рядом электронная плата.

Круглый дисплей Waveshare для креативных проектов

Круглый 7-дюймовый сенсорный дисплей от Waveshare создан для разработчиков и дизайнеров, которым нужен нестандартный экран.  Это IPS-панель с разрешением 1 080×1 080 пикселей, поддержкой 10-точечного ёмкостного сенсора, оптической склейкой и защитным закалённым стеклом, выполненная в круглом форм-факторе.…

Мар 2, 2026

Впишите свой почтовый адрес и мы будем присылать вам на почту самые свежие новости в числе самых первых