Перейти к содержимому
Оригинал
Juejin · ИИ-программирование· HjhIron·· 14 часов назадИзбранное редакциейОценка ИИ60

LangGraph без лишней теории: от проблем одиночного агента к практической оркестрации нескольких агентов

Краткий обзор ИИ

Статья начинается с проблем одиночного агента — больших затрат контекста, информационных помех и невозможности параллельной работы. Автор показывает, что разделение на несколько агентов снижает расход токенов и повышает точность, и приводит формулу: агент = LLM + Harness.

Почему это важно

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

Полный текст · Перевод ИИ

I. Зачем нам нужны мультиагентные системы?

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

  1. Огромные накладные расходы на контекст: в архитектуре с одним агентом описания всех инструментов и промпты каждой функции свалены в системный промпт.
  2. Сильные информационные помехи: для выполнения конкретной функции на самом деле нужна лишь часть промпта, но одиночный агент каждый раз тащит весь контекст. Это не только приводит к более высокому расходу токенов, но что ещё хуже — большое количество нерелевантной информации снижает эффективность рассуждений модели и повышает вероятность ошибок.
  3. Сложности с параллелизмом: у одной LLM только один «мозг», она может думать лишь шаг за шагом и не способна по-настоящему работать параллельно.

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

Три ключевых преимущества мультиагентной системы

  • Высокая точность решений и меньший расход токенов: хотя число вызовов LLM может вырасти, каждый раз передаётся только необходимый минимальный промпт без лишней информации — и совокупная стоимость, наоборот, снижается.
  • Параллельное мышление и обработка задач: главный агент раздаёт задачи, подагенты обрабатывают их параллельно (например, агент-программист пишет код, а агент-тестировщик синхронно пишет тесты в духе TDD) — общая эффективность выше.
  • Обсуждение между несколькими ролями и более сильная самокоррекция: заимствуя «судебный» режим AutoGen, за счёт дебатов между ролями можно значительно повысить надёжность результата.

Формула агента: Agent = LLM + Harness (tool + mcp + rag + skill + ...).

II. LangChain vs LangGraph: от линейности к сетевой структуре

В области оркестрации мультиагентных систем наши инструменты тоже развиваются:

  • LangChain: силён в оркестрации линейных рабочих процессов (базовые модули: llm api, document loaders, splitter, embedding, vector store, output parser, memory).
  • LangGraph: создан специально для оркестрации сетевых рабочих процессов. Он вводит State (граф состояний), состоящий из рабочих узлов + способа организации (API), и идеально поддерживает взаимодействие сложных агентов.

Ключевые концепции LangGraph

В LangGraph всё есть граф (Graph):

  • Начальный узел (START): инициализирует состояние.
  • Рабочий узел (Node): его задача — принять State и вернуть новый State.
  • Соединение рабочих узлов (Edge): задаёт направление переходов между узлами.
  • Ветвления и циклы (Conditional Edges): динамически определяют следующий шаг на основе State.
  • Конечное состояние (END): завершение рабочего процесса.

Кроме того, критически важна персистентность (Persistence). Чтобы справляться с прерыванием, сбоем, паузой или необходимостью авторизации при выполнении агента, нужно сохранять State в память (MemoryServer / MemorySaver) или в базу данных (SQLite, Redis), чтобы продолжить выполнение в следующий раз и не запускать всё заново.


III. Практика LangGraph

Ниже мы шаг за шагом, от основ к продвинутому, реализуем четыре ключевые возможности LangGraph.

1. Построение базового графа и визуализация (basic-graph.mjs)

Всё начинается с самого базового Graph. Нам нужно определить состояние (State) и узлы (Node).

javascript

import {
  Annotation, // 注释 工作流的状态值的描述 数据 state
  END,        // 结束节点
  START,      // 开始节点
  StateGraph, // 状态图 流程编排器 节点的组织
} from '@langchain/langgraph';

// Объявление состояния: с помощью Annotation объявляем поле
const StateAnnotation = Annotation.Root({
  text: Annotation({ // 只有 text 字段
    // _prev — состояние перед текущим узлом, next — состояние, полученное текущим узлом
    // Как reducer обрабатывает изменения состояния
    reducer: (_prev, next) => next, // js 数组reduce 消消乐 text状态如何变
    default: () => "", // 默认值
  })
});

// Определяем узел
// Функция — это узел
// Возвращаемое значение — состояние следующего узла
// reducer обрабатывает изменения состояния
const step1 = (state) => ({text: `${state.text} -> step1`});
const step2 = (state) => ({text: `${state.text} -> step2`});

// Создаём оркестратор графа рабочего процесса
const graph = new StateGraph(StateAnnotation)
  // Объявляем все узлы
  .addNode("step1", step1)
  .addNode("step2", step2)
  .addEdge(START, "step1")
  .addEdge("step1", "step2")
  .addEdge("step2", END)
  .compile(); // 编译工作流 执行

// Визуализируем граф
// mermaid — инструмент для рисования диаграмм из текста: простой markdown, автоматически генерирует блок-схему
// Визуализируем всю цепочку переходов между узлами.
const drawable = await graph.getGraphAsync();
const mermaid = drawable.drawMermaid({ withState: true });
console.log(mermaid);

2. Условные ветвления и маршрутизация (conditional-routing.mjs)

В реальных задачах нам нужно распределять входящие данные пользователя по разным узлам для обработки (например: математические вычисления vs светская беседа).

javascript

import {
  Annotation, END, START, StateGraph
} from '@langchain/langgraph';

// Объявление состояния
const StateAnnotation = Annotation.Root({
  query: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
  route: Annotation({ reducer: (_prev, next) => next, default: () => "chat" }),
  answer: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
});

// Объявление узлов: направление можно выбирать
const router = (state) => {
  const isMath = /[+-*/]/.test(state.query); // 匹配加减乘除任意一个运算符
  // Куда идти дальше?
  // Если это математическая задача — идём в узел math, иначе — в узел chat
  return {
    route: isMath ? "math" : "chat",
  }
}

const mathNode = (state) => {
  try {
    return {
      answer: String(eval(state.query)) // 匹配
    }
  } catch {
    return {
      answer: "表达式无法计算"
    }
  }
}

const chatNode = (state) => ({
  answer: `你说的是:${state.query}`,
})

const graph = new StateGraph(StateAnnotation)
  // Объявляем узел маршрутизации, name — имя узла
  .addNode("router", router)
  .addNode("math", mathNode)
  .addNode("chat", chatNode)
  .addEdge(START, "router") // 走向固定
  // Условный переход: проверяем значение state.route — math или chat
  // Условие ветвления
  .addConditionalEdges(
    "router",
    (state) => state.route,
    {
      math: "math",
      chat: "chat",
    },
  )
  .addEdge("math", END)
  .addEdge("chat", END)
  .compile(); // 编译状态图 执行

// Тестируем вызов
console.log("result:", await graph.invoke({query: "你好"}));
console.log("result:", await graph.invoke({query: "1+2"}));

3. Циклы и механизм повторных попыток (loop-retry.mjs)

Агенты часто сталкиваются со сценариями повторных попыток после сбоя. С помощью условных рёбер мы легко реализуем цикл.

javascript

import {
  Annotation, END, START, StateGraph
} from '@langchain/langgraph';

const StateAnnotation = Annotation.Root({
  tries: Annotation({ reducer: (_prev, next) => next, defaultValue: 0 }),
  ok: Annotation({ reducer: (_prev, next) => next, default: () => false }),
  message: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})

const attempt = (state) => {
  const tries = state.tries + 1;
  const ok = tries >= 3;
  return {
    tries,
    ok,
    message: ok ? `第${tries}次成功尝试` : `第${tries}次失败`,
  }
}

const graph = new StateGraph(StateAnnotation)
  .addNode("attempt", attempt)
  .addEdge(START, "attempt")
  .addConditionalEdges("attempt", (state) => state.ok ? "done" : "retry", {
    retry: "attempt", // 循环回到 attempt 节点
    done: END,
  })
  .compile();

console.log("result:", await graph.invoke({tries: 0}));

4. Персистентность состояния и изоляция между пользователями (checkpointer-memory.mjs)

Как заставить агента помнить контекст? Как различать сессии разных пользователей? LangGraph предоставляет MemorySaver.

javascript

import {
  Annotation, END, START, MemorySaver, StateGraph
} from '@langchain/langgraph';

const StateAnnotation = Annotation.Root({
  // Сессия — связанные сеансы: кто-то, количество посещений
  visitCount: Annotation({ reducer: (_prev, next) => next, default: () => 0 }),
  message: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})

// Узел подсчёта
function recordVist(state) {
  const visitCount = state.visitCount + 1;
  const message = visitCount === 1
    ? "这是你在本会话里第1次进入。"
    : `这是你在本会话里第${visitCount}次进入。`;
  return {
    visitCount,
    message,
  }
}

const graph = new StateGraph(StateAnnotation)
  .addNode("recordVist", recordVist)
  .addEdge(START, "recordVist")
  .addEdge("recordVist", END)

const checkpointer = new MemorySaver(); // 实例化一个内存保存器
const app = graph.compile({
  checkpointer // 挂载持久化
})

// Изоляция нескольких пользователей (различаются по thread_id)
const user1 = { configurable: { thread_id: "用户-小张" } };
const user2 = { configurable: { thread_id: "用户-小李" } };

const res1 = await app.invoke({}, user1); // 小张第一次
console.log(res1);
const res2 = await app.invoke({}, user1); // 小张第二次
console.log(res2);
const res3 = await app.invoke({}, user1); // 小张第三次
console.log(res3);
const res4 = await app.invoke({}, user2); // 小李第一次,独立计数
console.log(res4);

5. Прерывание и возобновление: Human-in-the-loop (graph-interrupt.mjs)

Для чувствительных операций (например, перевода средств) нужно подтверждение человеком. LangGraph позволяет прервать выполнение графа и возобновить его после получения ввода пользователя.

javascript

import {
  Annotation, END, START, StateGraph, Command, interrupt, MemorySaver
} from '@langchain/langgraph';
import { createInterface } from 'node:readline/promises';

// Схема графа
const StateAnnotation = Annotation.Root({
  actionSummary: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
  userInput: Annotation({ reducer: (_prev, next) => next, default: () => "" }),
})

const showTransfer = () => ({
  actionSummary: "向张三转账 $100"
})

const waitConfirm = (state) => {
  const text = interrupt({ // 中断
    hint: "终端里输入[确认]或者备注后回车,图才会继续",
    actionSummary: state.actionSummary
  });
  return { userInput: String(text) }
}

const graph = new StateGraph(StateAnnotation)
  .addNode("showTransfer", showTransfer)
  .addNode("waitConfirm", waitConfirm)
  .addEdge(START, "showTransfer")
  .addEdge("showTransfer", "waitConfirm")
  .addEdge("waitConfirm", END)
  .compile({ checkpointer: new MemorySaver() })

const config = { configurable: { thread_id: "interrupt-demo" } };
const paused = await graph.invoke({}, config);
console.log("待你确认", paused.__interrupt__?.[0]?.value);

// Узел — возобновление выполнения бизнес-процесса
const rl = createInterface({
  input: process.stdin,
  output: process.stdout
});
const line = (await rl.question("> ")).trim();
console.log("你输入了", line);
await rl.close();

// Возобновляем выполнение с помощью Command и передаём ввод пользователя
const done = await graph.invoke(new Command({resume: line}), config);
console.log("done:", done);

IV. Заключение

От линейной компоновки LangChain к сетевой структуре LangGraph — неизбежный путь ИИ-агентов от «одиночной игры» к «командной работе».

С помощью LangGraph мы легко можем реализовать:

  1. Общее состояние: с помощью Annotation.Root и reducer мы точно управляем переходами состояния.
  2. Гибкое управление потоком: через условные рёбра (addConditionalEdges) реализуются сложная маршрутизация (Router) и циклы (Loop/Retry).
  3. Персистентность продакшен-уровня: вместе с MemorySaver / SqliteSaver и thread_id обеспечивает изоляцию сессий разных пользователей и возобновление с точки останова.
  4. Human-in-the-loop: через interrupt() и Command(resume) на ключевых этапах бизнес-процесса вводится ручная проверка.

Двери для совместной работы нескольких агентов уже открыты — попробуйте LangGraph в своём бизнесе прямо сейчас!

Источник: Juejin · ИИ-программирование · juejin.cn