第 24 课: Agent 基础 — ReAct 模式

3 阅读5分钟

课程目标

理解 Agent 的核心范式 —— ReAct (Reasoning + Acting)。精读 createReactAgent() 工厂函数与 ReactAgent 类的实现,掌握 Agent 执行循环、节点路由与终止条件。


24.1 Agent vs Chain:本质区别

Chain 是线性管道:Prompt → Model → Parser,数据单向流动,执行一次即结束。

Agent 引入了循环

用户输入 → LLM 思考 → 需要工具?
                         ├─ 是 → 执行工具 → 将结果反馈给 LLM → 再次思考 → ...
                         └─ 否 → 直接输出最终回答 → 结束

这就是 ReAct 模式的核心:Reasoning(推理)+ Acting(行动) 的交替执行。LLM 在每一步决定是调用工具还是直接回答。


24.2 createAgent() 与 ReactAgent

LangChain.js 的 Agent 系统基于 LangGraph 的 StateGraph 构建。createAgent() 是工厂函数,返回 ReactAgent 实例。

源码位置: libs/langchain/src/agents/index.ts

export function createAgent(params: CreateAgentParams): ReactAgent {
  return new ReactAgent(params);
}

ReactAgent 类的构造函数完成了所有图的构建工作:

源码位置: libs/langchain/src/agents/ReactAgent.ts:161

export class ReactAgent<Types extends AgentTypeConfig> {
  #graph: CompiledStateGraph<any, any, any, any, any, any, unknown>;
  #agentNode: AgentNode<any, AnyAnnotationRoot>;
  #defaultConfig: RunnableConfig;

  constructor(public options: CreateAgentParams, defaultConfig?: RunnableConfig) {
    // 1. 验证 model 参数
    if (!options.model) {
      throw new Error("`model` option is required to create an agent.");
    }
    // 2. 检查 LLM 没有预绑定工具
    validateLLMHasNoBoundTools(options.model);
    // 3. 合并 middleware 提供的工具
    const middlewareTools = options.middleware
      ?.filter(m => m.tools).flatMap(m => m.tools) ?? [];
    const toolClasses = [...(options.tools ?? []), ...middlewareTools];
    // 4. 创建状态 schema
    const { state, input, output } = createAgentState(...);
    // 5. 构建 StateGraph,添加节点和边
    const workflow = new StateGraph(state, { input, output, context: ... });
    workflow.addNode(AGENT_NODE_NAME, this.#agentNode);
    workflow.addNode(TOOLS_NODE_NAME, toolNode);
    // 6. 添加条件边(路由逻辑)
    workflow.addConditionalEdges(AGENT_NODE_NAME, this.#createModelRouter(...));
    // 7. 编译图
    this.#graph = workflow.compile({ checkpointer, store, name });
  }
}

24.3 ReAct 图的三个核心节点

在 ReAct 模式中,图由三类节点组成:

节点常量名职责
Agent 节点AGENT_NODE_NAME ("model_request")调用 LLM,决定下一步行动
Tools 节点TOOLS_NODE_NAME ("tools")执行工具调用
ENDEND图的终止点

执行流程

START → [beforeAgent][beforeModel] → model_request → [afterModel]
                                              │
                   ┌──────────────────────────┤
                   │                          │
              有 tool_calls              无 tool_calls
                   │                          │
                   ▼                          ▼
                 tools ─────────────→ [afterAgent] → END
              (执行工具)         (回到 beforeModel 继续循环)

24.4 模型路由:决定继续还是终止

#createModelRouter() 是路由的核心,它检查 LLM 的输出决定下一步去向:

源码位置: libs/langchain/src/agents/ReactAgent.ts:803

#createModelRouter(exitNode: string | typeof END = END) {
  return (state: Record<string, unknown>) => {
    const builtInState = state as unknown as BuiltInState;
    const messages = builtInState.messages;
    const lastMessage = messages.at(-1);

    // 如果最后一条消息不是 AIMessage,或者没有 tool_calls → 终止
    if (
      !AIMessage.isInstance(lastMessage) ||
      !lastMessage.tool_calls ||
      lastMessage.tool_calls.length === 0
    ) {
      return exitNode;  // 路由到 END
    }

    // 有 tool_calls → 路由到 tools 节点
    // v2 模式:每个 tool_call 作为独立的 Send 任务
    const regularToolCalls = lastMessage.tool_calls.filter(
      (toolCall) => !toolCall.name.startsWith("extract-")
    );
    return regularToolCalls.map(
      (toolCall) => new Send(TOOLS_NODE_NAME, { ...state, lg_tool_call: toolCall })
    );
  };
}

终止条件总结

  1. LLM 返回纯文本(无 tool_calls)→ 直接终止
  2. LLM 返回的 tool_calls 都是结构化输出提取(extract- 前缀)→ 终止
  3. 达到 recursionLimit(LangGraph 层面的安全阀)→ 强制终止

24.5 工具执行版本:v1 vs v2

ReactAgent 支持两种工具执行策略,通过 version 参数控制:

版本策略适用场景
v1所有 tool_calls 在一个节点内通过 Promise.all 并行执行工具调用子图或长时间异步操作
v2(默认)每个 tool_call 通过 Send API 分派为独立图任务需要每次调用独立 checkpoint、独立容错
// v2 模式下的路由
return regularToolCalls.map(
  (toolCall) => new Send(TOOLS_NODE_NAME, { ...state, lg_tool_call: toolCall })
);

24.6 AgentNode — 模型调用节点

AgentNode 负责实际调用 LLM。它是 RunnableCallable 的子类。

源码位置: libs/langchain/src/agents/nodes/AgentNode.ts

核心职责:

  • 将工具列表绑定到模型(bindTools()
  • 注入系统消息(systemPrompt
  • 处理中间件的 wrapModelCall 钩子
  • 处理结构化输出响应(responseFormat

24.7 模型类型判断

Agent 系统需要判断模型实例的类型,以决定如何绑定工具:

源码位置: libs/langchain/src/agents/model.ts

export type AgentLanguageModelLike = RunnableInterface<
  BaseLanguageModelInput, LanguageModelOutput
>;

export function isBaseChatModel(model: AgentLanguageModelLike): model is BaseChatModel {
  return (
    "invoke" in model &&
    typeof model.invoke === "function" &&
    "_streamResponseChunks" in model
  );
}

createAgent 支持两种模型输入方式:

  • 字符串"openai:gpt-4o" — 通过 initChatModel() 动态初始化
  • 实例new ChatOpenAI({ model: "gpt-4o" }) — 直接使用

24.8 响应处理:ToolStrategy 与 ProviderStrategy

responseFormat 参数控制如何获取结构化输出:

源码位置: libs/langchain/src/agents/responses.ts

策略实现方式适用场景
ToolStrategy将 schema 转为工具,让 LLM 通过 tool_call 返回结构化数据通用,兼容性好
ProviderStrategy使用 Provider 的原生 JSON schema 输出能力OpenAI gpt-4o 等支持的模型
// 自动选择策略
const useProviderStrategy = hasSupportForJsonSchemaOutput(model);
if (isInteropZodObject(responseFormat)) {
  return useProviderStrategy
    ? [ProviderStrategy.fromSchema(responseFormat)]
    : [ToolStrategy.fromSchema(responseFormat)];
}

24.9 实战练习:创建第一个 ReAct Agent

import { z } from "zod";
import { createAgent, tool } from "langchain";

// 定义工具
const calculator = tool(
  async ({ expression }) => {
    try {
      return String(eval(expression));  // 仅作示例
    } catch {
      return "计算错误,请检查表达式";
    }
  },
  {
    name: "calculator",
    description: "计算数学表达式",
    schema: z.object({
      expression: z.string().describe("要计算的数学表达式"),
    }),
  }
);

const search = tool(
  async ({ query }) => `搜索结果:关于「${query}」的信息...`,
  {
    name: "search",
    description: "搜索互联网信息",
    schema: z.object({
      query: z.string().describe("搜索关键词"),
    }),
  }
);

// 创建 Agent
const agent = createAgent({
  model: "openai:gpt-4o",
  tools: [calculator, search],
  systemPrompt: "你是一个有用的助手,可以使用计算器和搜索工具。",
});

// 调用 Agent
const result = await agent.invoke({
  messages: [{ role: "user", content: "计算 (15 * 23) + 42 等于多少?" }],
});

// 观察完整的消息历史(包括推理过程)
for (const msg of result.messages) {
  console.log(`[${msg.constructor.name}] ${msg.content}`);
}

// 流式执行
const stream = await agent.stream({
  messages: [{ role: "user", content: "搜索 LangChain.js 的最新版本" }],
});

for await (const step of stream) {
  for (const update of Object.values(step)) {
    if (update && typeof update === "object" && "messages" in update) {
      for (const message of update.messages) {
        console.log(message.content);
      }
    }
  }
}

24.10 源码精读路线

优先级文件关注点
P0agents/ReactAgent.ts:161-700ReactAgent 构造函数 — 图的构建逻辑
P0agents/ReactAgent.ts:803-854#createModelRouter() — 路由决策核心
P0agents/index.ts:668-697createAgent() 工厂函数与重载签名
P1agents/model.ts模型类型判断辅助函数
P1agents/responses.ts:54-162ToolStrategy — 结构化输出的工具策略
P2agents/nodes/AgentNode.tsAgent 节点的 LLM 调用实现
P2agents/nodes/ToolNode.ts工具节点的执行逻辑

本课收获总结

级别你应该掌握的
🟢 基础理解 Agent = LLM + Tools + 推理循环;能用 createAgent() 创建基本 Agent
🔵 中阶掌握 ReAct 图的三个核心节点和执行流程;理解终止条件
🟡 高阶理解 #createModelRouter() 的路由逻辑;区分 v1/v2 工具执行版本
🟠 资深分析 Agent 的状态管理与消息累积策略;理解 ToolStrategy vs ProviderStrategy
🔴 架构评估 ReAct vs Plan-and-Execute 等不同 Agent 范式;理解基于 StateGraph 的 Agent 架构设计

下一课预告

第 25 课深入 Agent 状态与注解系统 —— 理解 AgentStateAnnotation 系统和 reducer 函数如何管理 Agent 的对话状态。