Skip to content

Agent 2.0:基于 LCEL 的自主代理

Plan-and-Execute, ReAct 模式作为可组合链

在 LangChain 的演进中,Agent 一直是核心功能之一。LangChain V3 通过 LCEL (LangChain Expression Language) 重新设计了 Agent 架构,创建了更灵活、更可组合的 Agent 2.0。本章将深入探讨基于 LCEL 的自主代理实现,包括 Plan-and-Execute 和 ReAct 模式。

Agent 2.0 的核心概念

Agent 2.0 通过 LCEL 实现了高度的可组合性和灵活性:

typescript
// Agent 2.0 基础接口
interface BaseAgent extends Runnable<string, string> {
  // Agent 可以执行的工具
  tools: Tool[];
  
  // Agent 的执行逻辑
  execute(input: string): Promise<string>;
  
  // 流式执行
  streamExecute?(input: string): AsyncGenerator<string>;
}

// 工具接口
interface Tool {
  name: string;
  description: string;
  execute(input: string): Promise<string>;
}

// Agent 状态
interface AgentState {
  input: string;
  thoughts: string[];
  actions: Array<{
    tool: string;
    input: string;
    output: string;
  }>;
  finalAnswer?: string;
}

ReAct 模式实现

ReAct (Reasoning + Acting) 是 Agent 2.0 的核心模式之一:

typescript
// ReAct Agent 实现
class ReActAgent extends Runnable<string, string> {
  private llm: BaseLanguageModel;
  private tools: Tool[];
  private maxIterations: number;
  
  constructor(
    llm: BaseLanguageModel,
    tools: Tool[],
    maxIterations: number = 10
  ) {
    super();
    this.llm = llm;
    this.tools = tools;
    this.maxIterations = maxIterations;
  }
  
  async invoke(input: string): Promise<string> {
    let state: AgentState = {
      input,
      thoughts: [],
      actions: []
    };
    
    for (let i = 0; i < this.maxIterations; i++) {
      // 思考步骤
      const thought = await this.think(state);
      state.thoughts.push(thought);
      
      // 决策步骤
      const action = await this.decideAction(thought, state);
      
      // 如果是最终答案,返回结果
      if (action.tool === 'Final Answer') {
        return action.output;
      }
      
      // 执行工具
      const toolOutput = await this.executeTool(action.tool, action.input);
      state.actions.push({
        tool: action.tool,
        input: action.input,
        output: toolOutput
      });
      
      // 检查是否应该终止
      if (await this.shouldTerminate(state)) {
        return await this.finalize(state);
      }
    }
    
    return '达到最大迭代次数,未能找到解决方案';
  }
  
  private async think(state: AgentState): Promise<string> {
    const prompt = new PromptTemplate({
      template: `你是一个智能代理,需要解决用户的问题。请思考下一步应该做什么。

问题: {input}
已完成的步骤: {previousSteps}

请提供你的思考过程:`,
      inputVariables: ["input", "previousSteps"]
    });
    
    const previousSteps = this.formatPreviousSteps(state);
    
    const result = await prompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({
        input: state.input,
        previousSteps
      });
    
    return result;
  }
  
  private async decideAction(
    thought: string,
    state: AgentState
  ): Promise<{ tool: string; input: string }> {
    const toolsDescription = this.tools
      .map(tool => `${tool.name}: ${tool.description}`)
      .join('\n');
      
    const prompt = new PromptTemplate({
      template: `基于以下思考,决定下一步使用哪个工具:

思考: {thought}

可用工具:
{tools}

请只回答工具名称和输入,格式如下:
工具: <工具名称>
输入: <工具输入>

如果问题已解决,请回答:
工具: Final Answer
输入: <最终答案>`,
      inputVariables: ["thought", "tools"]
    });
    
    const result = await prompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({
        thought,
        tools: toolsDescription
      });
    
    // 解析结果
    const toolMatch = result.match(/工具:\s*(.*)/);
    const inputMatch = result.match(/输入:\s*(.*)/);
    
    return {
      tool: toolMatch ? toolMatch[1].trim() : 'Final Answer',
      input: inputMatch ? inputMatch[1].trim() : result
    };
  }
  
  private async executeTool(toolName: string, toolInput: string): Promise<string> {
    const tool = this.tools.find(t => t.name === toolName);
    if (!tool) {
      return `未知工具: ${toolName}`;
    }
    
    try {
      return await tool.execute(toolInput);
    } catch (error) {
      return `工具执行失败: ${error.message}`;
    }
  }
  
  private formatPreviousSteps(state: AgentState): string {
    let result = '';
    
    for (let i = 0; i < state.thoughts.length; i++) {
      result += `思考 ${i + 1}: ${state.thoughts[i]}\n`;
      
      if (i < state.actions.length) {
        const action = state.actions[i];
        result += `行动 ${i + 1}: 使用工具 "${action.tool}" 处理 "${action.input}"\n`;
        result += `结果 ${i + 1}: ${action.output}\n\n`;
      }
    }
    
    return result;
  }
  
  private async shouldTerminate(state: AgentState): Promise<boolean> {
    // 简单的终止条件:达到最大迭代次数或有最终答案
    return state.actions.length >= this.maxIterations ||
           state.actions.some(action => action.tool === 'Final Answer');
  }
  
  private async finalize(state: AgentState): Promise<string> {
    // 基于所有信息生成最终答案
    const prompt = new PromptTemplate({
      template: `基于以下所有信息,提供最终答案:

原始问题: {input}

所有步骤:
{steps}

最终答案:`,
      inputVariables: ["input", "steps"]
    });
    
    const steps = this.formatPreviousSteps(state);
    
    const result = await prompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({
        input: state.input,
        steps
      });
    
    return result;
  }
}

Plan-and-Execute 模式实现

Plan-and-Execute 模式将任务分解为规划和执行两个阶段:

typescript
// Plan-and-Execute Agent
class PlanAndExecuteAgent extends Runnable<string, string> {
  private planner: Runnable<string, string[]>;
  private executor: ReActAgent;
  private llm: BaseLanguageModel;
  
  constructor(
    llm: BaseLanguageModel,
    tools: Tool[],
    maxPlanSteps: number = 5
  ) {
    super();
    this.llm = llm;
    
    // 创建规划器
    this.planner = this.createPlanner(maxPlanSteps);
    
    // 创建执行器(基于 ReAct Agent)
    this.executor = new ReActAgent(llm, tools, 5);
  }
  
  private createPlanner(maxSteps: number): Runnable<string, string[]> {
    const prompt = new PromptTemplate({
      template: `你是一个任务规划专家。请将复杂任务分解为 {maxSteps} 个或更少的子任务。

任务: {input}

请按以下格式列出子任务:
1. 第一个子任务
2. 第二个子任务
3. 第三个子任务
...

子任务列表:`,
      inputVariables: ["input", "maxSteps"]
    });
    
    return prompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .pipe(new RunnableLambda((output: string) => {
        // 解析子任务列表
        return output
          .split('\n')
          .filter(line => /^\d+\./.test(line.trim()))
          .map(line => line.replace(/^\d+\.\s*/, '').trim());
      }));
  }
  
  async invoke(input: string): Promise<string> {
    // 第一阶段:规划
    console.log('开始任务规划...');
    const plan = await this.planner.invoke({
      input,
      maxSteps: 5
    });
    
    console.log('规划完成,子任务:');
    plan.forEach((task, index) => {
      console.log(`  ${index + 1}. ${task}`);
    });
    
    // 第二阶段:执行
    console.log('\n开始执行子任务...');
    const results: string[] = [];
    
    for (let i = 0; i < plan.length; i++) {
      console.log(`\n执行子任务 ${i + 1}: ${plan[i]}`);
      
      try {
        const result = await this.executor.invoke(plan[i]);
        results.push(result);
        console.log(`子任务 ${i + 1} 完成: ${result.substring(0, 100)}...`);
      } catch (error) {
        const errorMsg = `子任务 ${i + 1} 执行失败: ${error.message}`;
        results.push(errorMsg);
        console.error(errorMsg);
      }
    }
    
    // 第三阶段:整合结果
    console.log('\n整合所有结果...');
    const finalPrompt = new PromptTemplate({
      template: `基于以下子任务和结果,提供完整的答案:

原始任务: {input}

子任务和结果:
{results}

完整答案:`,
      inputVariables: ["input", "results"]
    });
    
    const resultsText = results
      .map((result, index) => `${index + 1}. ${plan[index]}: ${result}`)
      .join('\n\n');
    
    const finalAnswer = await finalPrompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({
        input,
        results: resultsText
      });
    
    return finalAnswer;
  }
}

可组合的 Agent 组件

通过 LCEL 实现可组合的 Agent 组件:

typescript
// 工具执行链
class ToolExecutionChain extends Runnable<{ tool: string; input: string }, string> {
  private tools: Record<string, Tool>;
  
  constructor(tools: Tool[]) {
    super();
    this.tools = {};
    tools.forEach(tool => {
      this.tools[tool.name] = tool;
    });
  }
  
  async invoke(input: { tool: string; input: string }): Promise<string> {
    const tool = this.tools[input.tool];
    if (!tool) {
      return `未知工具: ${input.tool}`;
    }
    
    try {
      return await tool.execute(input.input);
    } catch (error) {
      return `工具执行失败: ${error.message}`;
    }
  }
}

// 思考链
class ThinkingChain extends Runnable<AgentState, string> {
  private llm: BaseLanguageModel;
  
  constructor(llm: BaseLanguageModel) {
    super();
    this.llm = llm;
  }
  
  async invoke(input: AgentState): Promise<string> {
    const prompt = new PromptTemplate({
      template: `作为智能代理,分析当前情况并决定下一步行动:

问题: {input}
已完成步骤: {previousSteps}

思考:`,
      inputVariables: ["input", "previousSteps"]
    });
    
    const previousSteps = input.actions
      .map((action, index) => 
        `${index + 1}. 使用 ${action.tool} 处理 "${action.input}",结果: ${action.output}`
      )
      .join('\n');
    
    return await prompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({
        input: input.input,
        previousSteps: previousSteps || '无'
      });
  }
}

// 决策链
class DecisionChain extends Runnable<{ thought: string; tools: Tool[] }, { tool: string; input: string }> {
  private llm: BaseLanguageModel;
  
  constructor(llm: BaseLanguageModel) {
    super();
    this.llm = llm;
  }
  
  async invoke(input: { thought: string; tools: Tool[] }): Promise<{ tool: string; input: string }> {
    const toolsDescription = input.tools
      .map(tool => `${tool.name}: ${tool.description}`)
      .join('\n');
      
    const prompt = new PromptTemplate({
      template: `基于思考结果,选择合适的工具:

思考: {thought}

可用工具:
{tools}

选择:
工具: <工具名>
输入: <工具输入>`,
      inputVariables: ["thought", "tools"]
    });
    
    const result = await prompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({
        thought: input.thought,
        tools: toolsDescription
      });
    
    const toolMatch = result.match(/工具:\s*(.*)/);
    const inputMatch = result.match(/输入:\s*(.*)/);
    
    return {
      tool: toolMatch ? toolMatch[1].trim() : '',
      input: inputMatch ? inputMatch[1].trim() : ''
    };
  }
}

// 组合式 Agent
class ComposableAgent extends Runnable<string, string> {
  private thinkingChain: ThinkingChain;
  private decisionChain: DecisionChain;
  private toolExecutionChain: ToolExecutionChain;
  private llm: BaseLanguageModel;
  private tools: Tool[];
  private maxIterations: number;
  
  constructor(
    llm: BaseLanguageModel,
    tools: Tool[],
    maxIterations: number = 10
  ) {
    super();
    this.llm = llm;
    this.tools = tools;
    this.maxIterations = maxIterations;
    
    this.thinkingChain = new ThinkingChain(llm);
    this.decisionChain = new DecisionChain(llm);
    this.toolExecutionChain = new ToolExecutionChain(tools);
  }
  
  async invoke(input: string): Promise<string> {
    let state: AgentState = {
      input,
      thoughts: [],
      actions: []
    };
    
    for (let i = 0; i < this.maxIterations; i++) {
      // 思考
      const thought = await this.thinkingChain.invoke(state);
      state.thoughts.push(thought);
      
      // 决策
      const decision = await this.decisionChain.invoke({
        thought,
        tools: this.tools
      });
      
      // 检查是否完成
      if (decision.tool === 'Final Answer' || !decision.tool) {
        return decision.input;
      }
      
      // 执行工具
      const toolOutput = await this.toolExecutionChain.invoke({
        tool: decision.tool,
        input: decision.input
      });
      
      state.actions.push({
        tool: decision.tool,
        input: decision.input,
        output: toolOutput
      });
    }
    
    // 最终总结
    const summaryPrompt = new PromptTemplate({
      template: `基于所有步骤,提供最终答案:

问题: {input}
步骤: {steps}

答案:`,
      inputVariables: ["input", "steps"]
    });
    
    const steps = state.actions
      .map((action, index) => 
        `${index + 1}. ${action.tool}: ${action.output}`
      )
      .join('\n');
    
    return await summaryPrompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({
        input,
        steps
      });
  }
}

实际应用示例

让我们看一个完整的实际应用示例,展示如何使用 Agent 2.0 解决复杂问题:

typescript
// 实用工具实现
class CalculatorTool implements Tool {
  name = 'Calculator';
  description = '用于执行数学计算,支持基本运算 (+, -, *, /, ^, sqrt, sin, cos, etc.)';
  
  async execute(input: string): Promise<string> {
    try {
      // 安全的数学表达式求值
      const result = Function(`"use strict"; return (${input})`)();
      return `计算结果: ${result}`;
    } catch (error) {
      return `计算错误: ${error.message}`;
    }
  }
}

class SearchTool implements Tool {
  name = 'Search';
  description = '用于搜索网络信息';
  
  async execute(input: string): Promise<string> {
    // 模拟搜索功能
    // 实际应用中会调用真实的搜索引擎 API
    return `搜索结果: 关于"${input}"的相关信息...`;
  }
}

class DateTimeTool implements Tool {
  name = 'DateTime';
  description = '获取当前日期和时间';
  
  async execute(input: string): Promise<string> {
    return `当前时间: ${new Date().toLocaleString()}`;
  }
}

class WeatherTool implements Tool {
  name = 'Weather';
  description = '获取天气信息';
  
  async execute(input: string): Promise<string> {
    // 模拟天气查询
    return `天气信息: ${input} 当前天气晴朗,温度 25°C`;
  }
}

// Agent 2.0 应用示例
class Agent20Demo {
  private reactAgent: ReActAgent;
  private planExecuteAgent: PlanAndExecuteAgent;
  private composableAgent: ComposableAgent;
  
  constructor() {
    const tools: Tool[] = [
      new CalculatorTool(),
      new SearchTool(),
      new DateTimeTool(),
      new WeatherTool()
    ];
    
    const llm = new ChatOpenAI({ modelName: 'gpt-3.5-turbo' });
    
    this.reactAgent = new ReActAgent(llm, tools, 5);
    this.planExecuteAgent = new PlanAndExecuteAgent(llm, tools, 5);
    this.composableAgent = new ComposableAgent(llm, tools, 5);
  }
  
  async demonstrateReAct(): Promise<void> {
    console.log('=== ReAct Agent 演示 ===\n');
    
    const question = '计算 2023 年距离 2050 年还有多少天?';
    console.log(`问题: ${question}\n`);
    
    const result = await this.reactAgent.invoke(question);
    console.log(`答案: ${result}\n`);
  }
  
  async demonstratePlanAndExecute(): Promise<void> {
    console.log('=== Plan-and-Execute Agent 演示 ===\n');
    
    const task = '帮我制定一个周末旅行计划,包括目的地推荐、预算估算和行程安排';
    console.log(`任务: ${task}\n`);
    
    const result = await this.planExecuteAgent.invoke(task);
    console.log(`计划: ${result}\n`);
  }
  
  async demonstrateComposable(): Promise<void> {
    console.log('=== 可组合 Agent 演示 ===\n');
    
    const question = '今天是星期几?计算 100 天后是星期几?';
    console.log(`问题: ${question}\n`);
    
    const result = await this.composableAgent.invoke(question);
    console.log(`答案: ${result}\n`);
  }
  
  async runAllDemos(): Promise<void> {
    await this.demonstrateReAct();
    await this.demonstratePlanAndExecute();
    await this.demonstrateComposable();
  }
}

// 高级 Agent:自适应 Agent
class AdaptiveAgent extends Runnable<string, string> {
  private agents: Array<{ agent: Runnable<string, string>; weight: number }>;
  private llm: BaseLanguageModel;
  
  constructor(llm: BaseLanguageModel, tools: Tool[]) {
    super();
    this.llm = llm;
    
    this.agents = [
      { 
        agent: new ReActAgent(llm, tools, 5), 
        weight: 0.4 
      },
      { 
        agent: new PlanAndExecuteAgent(llm, tools, 3), 
        weight: 0.3 
      },
      { 
        agent: new ComposableAgent(llm, tools, 5), 
        weight: 0.3 
      }
    ];
  }
  
  async invoke(input: string): Promise<string> {
    // 根据问题类型选择最合适的 Agent
    const agentType = await this.selectAgent(input);
    const selectedAgent = this.agents.find(a => 
      a.agent.constructor.name.includes(agentType)
    ) || this.agents[0];
    
    console.log(`选择 Agent 类型: ${agentType}`);
    return await selectedAgent.agent.invoke(input);
  }
  
  private async selectAgent(input: string): Promise<string> {
    const prompt = new PromptTemplate({
      template: `根据问题类型,选择最合适的 Agent:

问题: {input}

Agent 类型选项:
1. ReAct - 适合需要推理和工具调用的复杂问题
2. PlanAndExecute - 适合需要分解和规划的大型任务
3. Composable - 适合一般性问题

只回答 Agent 类型名称:`,
      inputVariables: ["input"]
    });
    
    const result = await prompt
      .pipe(this.llm)
      .pipe(new StringOutputParser())
      .invoke({ input });
    
    return result.trim();
  }
}

// 使用自适应 Agent
async function demonstrateAdaptiveAgent() {
  console.log('=== 自适应 Agent 演示 ===\n');
  
  const tools: Tool[] = [
    new CalculatorTool(),
    new SearchTool(),
    new DateTimeTool(),
    new WeatherTool()
  ];
  
  const adaptiveAgent = new AdaptiveAgent(
    new ChatOpenAI({ modelName: 'gpt-3.5-turbo' }),
    tools
  );
  
  // 测试不同类型的问题
  const testCases = [
    '计算 (2 + 3) * 4 - 5 的值',
    '帮我规划一次为期一周的欧洲旅行',
    '现在几点了?'
  ];
  
  for (const testCase of testCases) {
    console.log(`问题: ${testCase}`);
    const result = await adaptiveAgent.invoke(testCase);
    console.log(`答案: ${result}\n`);
  }
}

// 运行演示
async function runAgentDemos() {
  console.log('🚀 LangChain Agent 2.0 演示\n');
  
  // 基础演示
  const demo = new Agent20Demo();
  await demo.runAllDemos();
  
  // 自适应 Agent 演示
  await demonstrateAdaptiveAgent();
}

// 如果直接运行此文件,则执行演示
if (require.main === module) {
  runAgentDemos().catch(console.error);
}

总结

Agent 2.0 通过 LCEL 实现了更灵活、更可组合的自主代理架构:

  1. ReAct 模式 - 结合推理和行动的经典模式,通过思考-行动循环解决问题
  2. Plan-and-Execute 模式 - 将复杂任务分解为规划和执行两个阶段
  3. 可组合组件 - 通过 LCEL 将 Agent 功能拆分为独立的可重用组件
  4. 自适应选择 - 根据问题类型动态选择最合适的 Agent 策略
  5. 工具集成 - 灵活的工具调用机制,扩展 Agent 能力

这些特性使得 Agent 2.0 能够处理更复杂的任务,提供更智能的解决方案,并且具有更好的可维护性和扩展性。

在下一章中,我们将探讨 Tool Calling:如何让 LLM 调用函数并解析参数,了解如何实现更精确的工具调用机制。