前端视角下认识AI Agent

当 AI 需要从"说"到"做"

大家好!今天我们来从前端视角介绍一下AI Agent。

在过去的一年里,我们见证了大型语言模型(LLM)的飞速发展。从 ChatGPT 到各种开源模型,它们强大的对话和内容生成能力,已经深刻地改变了我们获取信息和进行创作的方式。无论是写代码还是做翻译,AI 都已成为我们身边不可或缺的助手。但时至今日,我们对 AI 的期望已经不再满足于只能简单的"聊一聊", 我们更希望 AI 能够为我们处理更复杂的任务,在这一方面,单纯的 LLM 开始显得力不从心。

让我们设想一个具体的场景。假如我想去成都旅游五天,并向一个标准的 LLM 发出指令:"请综合考虑天气情况,为我规划一个从 明天到 7 月 5 日的成都详细旅游方案" 这里我们选择调用阿里最新的开源模型Qwen3-turbo模型会迅速生成一份看似周全的计划:

这份计划看上去内容详实,但仔细推敲就会发现几个关键问题:

  1. 日期不准确:我提问的日期是7月3日,正常来说应该规划的是7月4日到7月5日的旅行行程,它给出的是7月1日-7月5日的计划。
  2. 信息不实时:它提供的天气状况是基于历史数据的模糊描述,而非精准的实时天气预报。

很显然,这样的 LLM 无法真正解决我们的问题。我们需要的,不仅仅是一个信息提供者,而是一个能够理解我们的目标,并能自主完成一系列操作的智能"执行者"。

AI Agent 是什么

为了填补"信息"与"行动"之间的这道鸿沟,一个全新的概念应运而生,它就是我们今天的主角------AI Agent。自2025 年年初开始,AI Agent 正加速走向成熟,并开始在各个领域展现出巨大的潜力。相信不少人已经听说过了这个概念,但 AI Agent 究竟是什么?

简单来说,它不再仅仅是一个会"说"的模型,而是一个会"做"的系统。它以LLM为"大脑"进行思考和规划,并能通过调用各种"工具"(如搜索引擎、计算器甚至是各类应用的 API)来与现实交互或者执行任务,从而将复杂的目标分解并付诸实践。

事实上,这种转变已经悄然发生。例如,现在我们向最新版的 ChatGPT 提出同样的问题,得到的答案会大不相同:

我们会发现,ChatGPT 已经能够提供基于实时天气预报的建议,而且日期也准确。这正是因为它不再局限于自身的知识库,而是在回答之前,主动调用了联网搜索等工具来查询最新信息。

这种从被动回答到主动执行的转变,正是 AI Agent 的核心思想,也标志着 AI 正从一个"聊天伙伴"进化为一个真正的"智能代理"。


AI Agent 的构成

现在大家常用一个经典公式来概括它的核心构成:

AI Agent = LLM(大脑) + Memory(记忆) + Planning(规划) + Tools(工具)

让我们来逐一拆解这四个核心组件:

  • LLM (大语言模型): Agent 的核心引擎,充当"大脑"的角色。它负责理解用户意图,进行推理、分析和决策。所有复杂的逻辑判断和语言理解,都由它来完成。

  • Planning (规划): Agent 的"思考框架"。当面对复杂任务时(比如"规划旅行"),Agent 需要将其分解成一系列可执行的小步骤(1. 查天气 → 2. 查酒店 → 3. 查景点 → 4. 规划行程)。这种任务分解和规划能力是 Agent 自主性的关键。

  • Memory (记忆): Agent 的"笔记本"。它能记住之前的交互历史、任务的中间结果,甚至过去的成功经验和失败教训。这使得 Agent 在多轮对话和长期任务中能保持上下文连贯,而不是只有"七秒钟记忆的金鱼脑袋"。

  • Tools (工具): Agent 的"双手 ",是它与现实世界交互的桥梁。无论是 API 调用(如查询天气)、数据库查询,还是近期热门的 MCP Server 概念,这些都属于工具的范畴。


演示项目介绍

在理论讲解之前,让我先为大家介绍今天的演示项目------旅行规划助手。这个项目将贯穿后续的整个分享,帮助大家理解 AI Agent 从理论到实践的完整转化过程。

项目概览

这是一个基于多 Agent 协作的智能旅行规划系统。用户只需输入简单的旅行需求,比如"我想 7 月 6 号在成都玩三天,预算 3000 元左右",系统就会自动完成以下流程:

  1. 理解需求 - 提取目的地、时间、预算等关键信息
  2. 收集数据 - 调用真实天气 API 获取实时天气预报
  3. 智能规划 - 结合天气情况生成详细的逐日行程
  4. 实时反馈 - 可视化展示 AI"思考"的完整过程

系统架构

该演示项目采用了多 Agent 协作的架构模式,每个 Agent 专注于特定的任务领域:

当前项目中有三个专家Agent,分别是:

  • AnalyzerAgent(需求分析专家): 负责解析用户输入,提取目的地、时间、预算等关键信息。
  • WeatherAgent(天气查询专家): 专门处理天气相关查询,结合天气情况生成旅行期间的出行建议。
  • PlannerAgent(行程规划专家): 综合前两个 Agent 的分析结果,生成详细且实用的旅行方案。

所有 Agent 之间的协作流程和数据传递都由AgentCoordinator(协调器)统一管理,并将最终结果实时展示给用户。

当前项目中使用了 3 个工具,分别是:

  • getCurrentDateTool: 获取当前日期(特在用户输入相对时间如"明天"、"下个月 1 号"等场景下)。
  • getLocationIdTool: 将城市名转换为天气 API 所需的 LocationId(由于天气 API 的特殊要求,查询时只接受地点对应的LocationId作为入参)。
  • getWeatherTool: 调用 API 获取LocationId对应地点指定日期的天气数据。

AI Agent 的核心

LLMAI Agent 的核心,在AI Agent 系统中,这种核心地位体现在系统中的每个子Agent都只是通过prompt预先规划好的LLM 。脱离LLM,Agent的智能性就无从谈起,更无法主动完成任务。

演示项目中,每个子 Agent 背后都调用了 Qwen3 的 API,通过 prompt 为 LLM 赋予特定的角色设定和任务目标。在实际开发中,不同的 Agent 也可以调用不同的 LLM 模型,从而实现专业化分工,让每个 Agent 在各自领域发挥最大优势。比如,处理复杂任务可以调用参数量大的LLM提升准确性,针对简单项目可以调用参数量较小的LLM来提升效率,针对特定领域的任务可以调用专门微调过的模型。

ts 复制代码
export const analyzerAgent: Agent = {
  name,
  description,
  inputPrompt,
  inputExample,
  systemPrompt: `
    ## 角色设定
    ${description}
    ## 任务描述
    你需要根据用户的需求,从中提取出如下与旅行相关的信息:
    1. 目的地[destination], 用户想要去的地方. 必填, 输出文本, 如'北京'
    2. 旅行天数[duration], 用户计划旅行的天数. 必填, 输出不带单位的纯数字, 如'3'
    3. 出发时间[startDate], 用户计划出发的时间. 必填, 输出文本格式为'YYYY-MM-DD', 如'2025-07-01'
    4. 所有日期[allDates], 用户计划旅行的所有日期. 必填, 多个日期之间用','分割, 如'2025-07-01,2025-07-02,2025-07-03'
    5. 预算范围[budget], 用户旅行的总预算. 必填, 输出不带单位的纯数字, 如'1000'
    6. 偏好类型[preferences], 用户偏好的景点或地点类型. 选填, 输出文本, 如'美食'
    7. 额外要求[extraRequirements],用户旅行的额外要求. 选填, 输出文本, 如'必须去故宫'
    ## 输入信息
    ${inputPrompt}
    ## 输入示例
    ${inputExample}
    ## 输出格式
    请以JSON格式返回结果。
    ## 请按照以下格式返回结果:
    {
      "destination": "目的地", 
      "duration": "旅行天数",
      "startDate": "出发时间",
      "allDates": "所有日期",
      "budget": "预算范围",
      "preferences": "偏好类型",
      "extraRequirements": "额外要求"
    }
    ## 示例[必须严格参考格式与风格]
    ### 示例1
    #### 用户输入
    2025年7月1日去北京3天,预算1200元,我喜欢自然风光, 最想去故宫
    #### 输出
    {
      "destination": "北京",
      "duration": 3,
      "startDate": "2025-07-01",
      "allDates": "2025-07-01,2025-07-02,2025-07-03",
      "budget": "1200",
      "preferences": "自然风光",
      "extraRequirements": "去故宫"
    }
    ### 示例2
    #### 用户输入
    今年国庆节在成都玩两天,每天计划400元
    #### 输出
    {
      "destination": "成都",
      "duration": 2,
      "startDate": "2025-10-01",
      "allDates": "2025-10-01,2025-10-02",
      "budget": "800",
      "preferences": "",
      "extraRequirements": ""
    }
`,
  async getJSONResult(
    input: AgentInput,
    addRecord?: (record: RecordItem) => void
  ): Promise<string> {
    addRecord?.({
      id: `analyzerAgent_${Date.now()}`,
      name: name,
      type: "agent",
      desc: `开始分析...`,
      content: input.query,
      contentType: "text",
      createdAt: Date.now(),
    });
    const plan = await chatCompletion(
      {
        messages: [
          { role: "system", content: this.systemPrompt },
          { role: "user", content: input.query },
        ],
        tools: [tools.getCurrentDateTool.schema],
      },
      (toolName) => {
        if (toolName === tools.getCurrentDateTool.schema.function.name) {
          const currentDate = tools.getCurrentDateTool.execute();
          addRecord?.({
            id: `analyzerAgent_${Date.now()}`,
            name: toolName,
            type: "tool",
            desc: `获取当前日期...`,
            content: currentDate,
            contentType: "text",
            createdAt: Date.now(),
          });
          return `当前日期是${currentDate}`;
        }
        return "";
      }
    );
    addRecord?.({
      id: `analyzerAgent_${Date.now()}`,
      name: name,
      type: "agent",
      desc: `分析完成`,
      content: plan,
      contentType: "json",
      createdAt: Date.now(),
    });

    return plan;
  },

  async makeTextResult(
    planResult: string,
    addRecord?: (record: RecordItem) => void
  ): Promise<string> {
    try {
      const parseResult = JSON.parse(planResult);
      addRecord?.({
        id: `analyzerAgent_${Date.now()}`,
        name: name,
        type: "agent",
        desc: `开始格式化输出...`,
        content: planResult,
        contentType: "json",
        createdAt: Date.now(),
      });

      // 生成结构化的分析结果
      const analysis = `
    **用户旅行意图分析结果**
    🏕️ **目的地**:${parseResult.destination}
    ⏰ **旅行天数**:${parseResult.duration}天
    📅 **出发时间**:${parseResult.startDate}
    📅 **所有日期**:${parseResult.allDates}
    🎯 **偏好类型**:${(parseResult.preferences || ["观光"]).join("、")}
    💰 **预算范围**:${parseResult.budget || "中等"}
    👥 **出行人数**:${parseResult.travelers || 1}人**
    `;

      addRecord?.({
        id: `analyzerAgent_${Date.now()}`,
        name: name,
        type: "agent",
        desc: `格式化输出完成`,
        content: analysis,
        contentType: "text",
        createdAt: Date.now(),
      });
      return analysis;
    } catch (error) {
      console.error("需求组织失败:", error);
      return `需求组织失败: ${error}`;
    }
  },
};

在应用层,针对LLM能做一般只有sdk的调用和prompt的设计。

LLM SDK的调用

现在绝大多数LLM都兼容openai的调用方式,sdk相对简单,下面我们重点介绍工具调用的实现机制。

ts 复制代码
import OpenAI from "openai";

// 基础模型API调用
export async function chatCompletion(
  options: ChatCompletionOptions,
  toolCallBack?: (
    toolName: string,
    toolArgs?: Record<string, any>
  ) => string | Promise<string>
): Promise<string> {
  try {
    const params: any = getParams(options);

    const response = await client.chat.completions.create(params);

    if (response.choices[0]?.message?.tool_calls?.length) {
      const message = response.choices[0].message;
      const toolCall = response.choices[0].message?.tool_calls[0];
      const toolName = toolCall.function.name;
      const toolArgs = JSON.parse(toolCall.function.arguments);
      const toolResult = await toolCallBack?.(toolName, toolArgs);
      let toolInfo = {
        role: "tool" as const,
        content: toolResult?.toString() || "",
      };

      const newParams: any = getParams({
        ...options,
        messages: [
          ...options.messages,
          message as ChatMessage,
          toolInfo,
        ],
      });
      return chatCompletion(newParams, toolCallBack);
    } else {
      return response.choices[0]?.message?.content || "";
    }
  } catch (error) {
    console.error("API调用失败:", error);
    throw new Error("AI服务暂时不可用,请稍后再试");
  }
}

Prompt的设计

良好的prompt设计可以使LLM更有效更准确的完成目标任务。设计prompt已经成为了一门专门的工程技术。由于其涉及的内容较多,本文不展开讲。感兴趣的可以查看prompt工程指南


AI Agent 如何"行动"

前面我们提到,LLM 是 Agent 的大脑,Tools 是 Agent 的四肢。从本质上讲,Tools 就是 LLM 获取外部信息和执行操作的方式,它可以是 Web API 调用、数据库查询、文件读写操作等。

在我们的旅行规划助手中,使用了两种不同形式的工具:

  1. 纯函数形式的工具 : getCurrentDateTool
  2. Web API 形式的工具 : getLocationIdToolgetWeatherTool

虽然这两种工具的实现形式不同,但它们与 LLM 的交互方式都是Function Calling(函数调用)

Function Calling

Function Calling是什么

Function Calling 的核心思想是:我们用代码定义函数,并将函数的描述信息(函数名、功能说明、参数列表及类型)提供给 LLM。当 LLM 在"思考"阶段认为需要执行某个操作时,它不会直接执行,而是生成一个包含函数名和参数的特定格式 JSON 对象,请求我们调用相应函数。我们解析这个 JSON 后,在代码环境中执行对应函数,再将执行结果返回给 LLM,LLM 会结合最新结果开始新一轮的"思考"

上方这张图完整地展示了 模型 调用外部工具函数工作流程:

  1. 开发者首先定义一个查询指定地点天气的工具函数 get_weather(location)。同时,将该定义以及消息:"What's the weather in Paris?" 发送给了模型。
  2. 模型分析后,决定使用该工具。它不会自己执行,而是返回一个 Tool Calls 的"指令"以及对应的参数'Paris'。
  3. 开发者解析模型返回的结果后,将参数传递到本地的工具函数中并调用。
  4. 本地函数返回结果 { temperature: 14} 后,我们将这个结果并入之前的消息再重新发送给模型。
  5. 型整合信息,生成最终自然语言回答:"It's currently 14°C in Paris."。

Function Calling的调用

在函数调用中主要有两个关键步骤,我们以旅行规划助手中的 getLocationIdTool 为例进行详细讲解:

1. 工具函数定义阶段

函数定义的目的是告知模型自身的用途以及需要哪些参数。它包括以下字段:

  • name: 工具函数的名称
  • description: 详细描述何时以及如何使用该函数
  • parameters : 定义函数输入参数的 JSON Schema
json 复制代码
{
  "type": "function",
  "function": {
    "name": "get_location_id",
    "description": "将地点名称转换为查询天气所需的LocationId",
    "parameters": {
      "type": "object",
      "properties": {
        "address": {
          "type": "string",
          "description": "地点名称,如'九寨沟'"
        }
      },
      "required": ["address"]
    }
  }
}

在函数定义时,建议遵循以下最佳实践:

  • 明确描述函数和每个参数的格式和用途,以及输出内容的含义
  • 通过 system prompt 准确描述模型应该何时(以及何时不)使用各个函数
  • 提供少量示例来帮助模型更好地理解函数的使用场景

完成定义后,需要将定义发送给模型,模型会根据定义来决定是否使用该工具。

typescript 复制代码
import { OpenAI } from "openai";
const openai = new OpenAI();

const tools = [
  {
    type: "function",
    function: {
      name: "get_location_id",
      description: "将地点名称转换为查询天气所需的LocationId",
      parameters: {
        type: "object",
        properties: {
          address: {
            type: "string",
            description: "地点名称,如'九寨沟'",
          },
        },
        required: ["address"],
      },
    },
  },
];

const messages = [
  {
    role: "user",
    content: "成都对应的LocationId是多少?",
  },
];

const completion = await openai.chat.completions.create({
  model: "qwen-turbo",
  messages,
  tools,
});

请求参数:

2. 工具函数调用阶段

当模型需要调用函数时,响应中会包含一个 tool_calls 数组,每个元素都有一个 id 和一个包含 name 及参数的 function 对象。

响应结果:

接下来我们解析 function 对象中的 namearguments 字段,并调用对应的函数。

typescript 复制代码
async function getLocationId({ address }: { address: string }) {
  return new Promise((resolve, reject) => {
    fetch(
      `/qweatherapi/geo/v2/city/lookup?location=${address}&key=${qweatherApiKey}`
    )
      .then((res) => res.json())
      .then(({ location }) => {
        resolve(location[0].id);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

const toolCalls = completion.choices[0].message.tool_calls;

for (const toolCall of toolCalls) {
  const name = toolCall.function.name;
  const args = JSON.parse(toolCall.function.arguments);

  if (name === "get_location_id") {
    const result = await getLocationId({address:args.location});
    console.log(result);
  }
}

执行完毕后,需要将工具调用的结果返回给模型,模型会根据结果继续生成响应。

typescript 复制代码
messages.push(completion.choices[0].message); // 将模型生成的消息添加到消息列表中
messages.push({
  role: "tool",
  tool_call_id: toolCall.id,
  content: result.toString(),
}); // 将工具调用的结果添加到消息列表中

// 重新调用模型
const completion = await openai.chat.completions.create({
  model: "qwen-turbo",
  messages,
  tools,
});

新调用的请求参数:

之后LLM就会根据复合的消息内容生成最终的结果。

MCP

除了传统的 Function Calling,AI Agent 的工具调用正朝着MCP (Model Context Protocol) 方向快速发展。

MCP 是由 Anthropic 提出的开放协议,其目的是在为 AI 应用和外部数据源/工具之间建立安全、标准化的连接。

MCP的架构

和 Function Calling 简单直接的调用不同, MCP 遵循的是 Client-Server 架构,下方有一个 MCP 架构的示意图:

  • MCP Hosts: 发起 MCP 请求的宿主应用程序,"旅行规划助手"这个项目就可以看做是一个 MCP 宿主应用。宿主应用内部一般会集成一个或多个MCP客户端,宿主应用负责任务编排,管理对话状态以及将需要外部数据或信息的调用任务分派给MCP客户端。
  • MCP Clients: MCP客户端是在宿主应用内部与MCP服务器通信的代理,客户端负责与MCP服务器进行协议交互,包括能力协商(handshake)、请求转发、结果接收等。当模型在对话中需要调用某个工具或获取资源时,宿主会通过对应的客户端向服务器发送请求。
  • MCP Server: 提供 MCP 服务的实体,可以是独立进程,也可以是服务。可以运行在本地,也可以运行在远端。MCP 服务器会连接到实际的后端系统(数据库、文件系统、外部API等),并按照MCP规范提供统一的接口供MCP客户端调用。

MCP的通信方式

MCP基于JSON-RPC 2.0协议进行通信,所有消息(请求、响应、通知、错误)均采用JSON结构。它支持两种主要传输方式:STDIO(标准输入输出,适用于本地集成)和HTTP+SSE(基于HTTP的Server-Sent Events,用于远程服务)。本地部署时,宿主可启动一个MCP服务器进程,并通过STDIO管道直接读写数据;远程部署时,客户端通过HTTP连接服务器的SSE端点,保持长连接以接收服务器推送的消息。

详细了解可以查看modelcontextprotocol


多 Agent 协作

对于需求相对简单的项目(如我们的旅行规划需求),使用单个 Agent 完全可以胜任。但随着系统功能日益复杂,单个 Agent 往往会力不从心。此时我们可以考虑将不同功能模块拆分给专门的 Agent,并将它们组合成一个 Multi-Agent 系统。

使用多代理系统的主要优势包括:

  • 任务聚焦: 每个 Agent 可以专注于特定领域,成为该领域的"专家",比如专门写代码的"程序员 Agent"和专门做 UI 设计的"设计师 Agent"。这比让一个"全能"Agent 处理所有事务要高效且可靠得多。
  • 问题分解: 可以将复杂问题分解给不同的 Agent,支持并行处理或接力完成。
  • 独立优化: 我们可以独立优化和升级某个专家 Agent,而不影响整个系统的其他部分。

Multi-Agent 架构模式

Multi-Agent 架构通常有以下几种主要模式:

  • Network(网络模式): 每个代理都可以与其他每个代理直接通信,任何代理都可以决定接下来要调用哪个其他代理。
  • Supervisor(主管模式): 每个代理都与一个主管代理通信,主管代理负责决定接下来应调用哪个代理。
  • Hierarchical(分层模式): 代理可以有多个层级,每个层级可以有多个代理。
  • Custom Workflow(自定义工作流模式): 各代理仅与一部分代理通信,流程的各个部分是确定性的,只有其中一些代理可以决定接下来要调用哪些其他代理。

演示项目中的"主管-专家"模式

在演示项目中,我们使用的是 Supervisor 模式 。由 Coordinator 扮演主管的角色,按照预编排好的静态流程,协调专家 Agent 的调用逻辑并管理 Agent 之间的信息传递。具体的流程如下:

用户指令: 我想7月6号在成都玩三天,预算3000元左右

  1. AgentCoordinator 接收到指令后, 按预定顺序分发任务:

    • Step 1: "AnalyzerAgent,提取用户需求:目的地、时间、预算等关键信息"
    • Step 2: "WeatherAgent,根据分析结果查询成都 7 月 6-8 日的天气情况"
    • Step 3: "PlannerAgent,结合需求分析和天气信息,生成详细的三天行程"
  2. 三个专家 Agent 依次执行:

    • AnalyzerAgent 调用 getCurrentDateTool 获取当前日期
    • WeatherAgent 调用 getLocationIdTool, getWeatherTool 获取天气数据
    • PlannerAgent 综合前两个 Agent 的结果,生成详细方案
  3. 专家 Agent 完成后,将结果返回给 AgentCoordinator,然后交由页面进行展示

这种模式大大提升了系统的模块化、可扩展性和处理复杂问题的能力。

在演示系统中,为了使不同的 Agent 之间能够顺畅地传递信息,我们必须严格控制前一个 Agent 的输出格式。如果各个Agent由不同的团队协作开发,如何提高协作效率,减少因输出格式不一致而导致的沟通成本呢?这里就引出了一个新概念:A2A

A2A

与 MCP 类似,A2A 也是一种开放的标准化协议。不同的是,A2A 专门面向不同 AI 代理之间的信息传递场景,定义了代理之间如何交换数据以及如何处理这些数据。

A2A 是由 Google 在 2024 年 4 月推出的标准,目前仍在完善中,因此这里不做深入展开。更详细的信息可以参考 A2A 官方文档


AI Agent 如何"思考"

不知各位有没有注意到,前文中描述AI Agent时反复提到一个关键词自主。AI Agent自主性的基石是LLM,那么如何让AI Agent能够更加自主的思考和规划呢?

这就涉及两种核心的AI Agent模式:

  • Plan-and-Execute(规划-执行模式)
  • ReAct(推理-行动框架)

Plan-and-Execute 模式

演示项目中的 AgentCoordinator 使用的是静态的 Agent 协作流程,在编排层面无需 Agent 参与。我们预先规定了每个 Agent 的执行顺序以及输入输出格式。这种模式对于当前的简单需求完全够用,但当需要构建能够处理复杂任务的 Agent 时,静态编排方式就力不从心了。

ts 复制代码
// 静态编排流程
async execute(userQuery: string): Promise<AgentResults> {
    try {
      //  第一步:需求分析
      const { textResult: analysisResult, jsonResult: analysisJsonResult } =
        await this.executeAgentWithCallback(analyzerAgent, userQuery);
      this.addRecord({
        id: `coordinator_${Date.now()}`,
        name: this.name,
        type: "supervisor",
        desc: "需求分析任务完成",
        content: "",
        contentType: "",
        createdAt: Date.now(),
      });

      // 第二步:天气收集
      const { textResult: weatherResult, jsonResult: weatherJsonResult } =
        await this.executeAgentWithCallback(
          weatherAgent,
          `旅行地点: ${JSON.parse(analysisJsonResult).destination};旅行日期: ${
            JSON.parse(analysisJsonResult).allDates
          }`
        );
      // 第三步:行程规划
      const { jsonResult: plannerJsonResult, textResult: plannerResult } =
        await this.executeAgentWithCallback(
          plannerAgent,
          `
          用户旅行意图分析结果:
          ${analysisResult}
          用户旅行期间的天气信息与建议:
          ${weatherResult}
          `
        );

      // 返回包含所有Agent结果的完整数据
      return {
        analysis: {
          text: analysisResult,
          json: analysisJsonResult,
        },
        weather: {
          text: weatherResult,
          json: weatherJsonResult,
        },
        planner: {
          text: plannerResult,
          json: plannerJsonResult,
        },
      };
    } catch (error) {
      console.error("Agent协作执行失败:", error);
      throw error;
    }
  }

举个例子,假设我们要构建一个"办公助手"系统,用户可以提出任何办公相关的任务:

  • 用户输入:"帮我写一篇关于 AI Agent 的报告"

    • Agent 调用链:联网搜索 Agent → 报告生成 Agent → 报告审核 Agent
  • 用户输入:"帮我分析这份 Excel 中的数据并绘制图表"

    • Agent 调用链:数据分析 Agent → 图表生成 Agent
  • 用户输入:"请为这份分析好的数据绘制相应的图表"

    • Agent 调用链: 图表生成 Agent

不同目标对应的 Agent 调用链完全不同,显然无法通过硬编码实现流程编排。这时我们可以依赖 LLM 来分析用户需求,制定 Agent 调用计划,然后按计划执行。这种 "制定计划 → 按计划执行" 的过程,就是 Plan-Execute 模式的核心思想。

Plan-Execute 模式有两个核心阶段:

  1. 规划(Planning)阶段:Agent 首先对整个复杂任务进行宏观分析,生成高层次的执行计划(步骤列表)。
  2. 执行(Execution)阶段:Agent 按照制定好的计划逐一执行每个步骤,执行完毕后可根据需要调整计划。

结合这种模式,我们可以将'主管-专家'架构中的'主管'角色设计为一个使用LLM的Agent,由这个Agent去根据用户的输入以及各个专家agent的能力,动态制定Agent调用计划。

我们可以改造演示项目,编写一个名为SmartSupervisor的主管Agent

ts 复制代码
/**
 * 主管Agent
 * 通过大模型分析用户输入,智能决定Agent调用顺序和选择
 */
export class SmartSupervisor {
  public name = "SmartSupervisor";
  private plan: TaskPlan | null = null;
  private outputPool: any[] = [];
  private finalResult: AgentResults = {
    analysis: {
      text: "",
      json: "",
    },
    weather: {
      text: "",
      json: "",
    },
    planner: {
      text: "",
      json: "",
    },
  };
  private addRecord: (record: RecordItem) => void;
  private gateKeeper: GateKeeper;

  private readonly planningPrompt = `
  ## 角色设定
  你是一名资深的Agent管理专家,在你的团队下有"PlannerAgent"、"WeatherAgent"、"AnalyzerAgent"三个专家Agent。你的任何是根据用户的输入,规划出合理的Agent执行计划。
  ## 团队中的各Agent的系统设定如下(系统设定中含有角色设定, 任务描述, 输入信息, 输出格式以及示例等):
  - PlannerAgent: ${plannerAgent.systemPrompt}
  - WeatherAgent: ${weatherAgent.systemPrompt}
  - AnalyzerAgent: ${analyzerAgent.systemPrompt}
  ## 规划原则
  1. **熟悉团队成员**: 熟悉团队各Agent的系统设定, 熟悉他们的职责以及要求的输入信息, 输出格式等
  2. **信息完整性分析**: 分析用户已提供的信息,确定缺失的关键信息
  3. **Agent选择优化**: 结合团队中各Agent的系统设定, 确定需要调用的Agent
  4. **执行顺序优化**: 根据信息依赖关系确定最优调用顺序
  5. **效率优先**: 在保证质量的前提下,尽量减少Agent调用次数
  6. **不要代劳**: 只完成你的规划工作, 不要代劳团队成员职责内的任务
  7. **智能规划**: 如果用户输入中已经包含了某个Agent的职责内的信息, 则不需要调用该Agent

  ## 输出格式
  请严格按照以下JSON格式输出执行计划:
  你应该输出一个数组,数组中每个元素都是一个对象,对象中包含agentName和reason字段,agentName为当前agent的名称,reason为选择当前agent的原因。
  ### 输出示例:
  [
      {
        agentName: "当前agent的名称",# AnalyzerAgent"|"WeatherAgent"|"PlannerAgent"
        reason: "选择当前agent的原因",
      },
      {
        agentName: "当前agent的名称",# AnalyzerAgent"|"WeatherAgent"|"PlannerAgent"
        reason: "选择当前agent的原因",
      }
    ]
  `;

  constructor(addRecord: (record: RecordItem) => void) {
    this.addRecord = addRecord;
    this.gateKeeper = new GateKeeper(addRecord);
  }

  /**
   * 主要的任务执行入口 - Plan-and-Execute模式
   */
  async execute(query: string): Promise<any> {

    this.outputPool = [
      {
        makerName: "user",
        outputText: query,
        outputJson: "",
      },
    ];
    // Plan阶段:通过LLM智能规划
    this.plan = await this.intelligentPlanning(query);

    // Execute阶段:执行规划的任务
    const result = await this.executeStep(this.plan!);

    return result;
  }

  /**
   * 智能规划阶段 - 通过LLM分析用户输入,制定执行计划
   */
  private async intelligentPlanning(query: string): Promise<any> {
    const userInput = `用户输入为: "${query}"`;

    const response = await chatCompletion({
      messages: [
        { role: "system", content: this.planningPrompt },
        { role: "user", content: userInput },
      ],
    });

    const planData = JSON.parse(response);

    const planId = `intelligent_plan_${Date.now()}`;

    // 根据LLM建议的执行顺序创建步骤
    const steps: PlanStep[] = planData.map(
      (agentConfig: any, index: number) => {
        const step: PlanStep = {
          id: `step_${agentConfig.agentName.toLowerCase()}_${
            Date.now() + index
          }`,
          title: agentConfig.agentName,
          description: agentConfig.reason,
          inputs: {},
          outputs: {},
          done: false,
          observeDone: false,
          error: null,
        };
        return step;
      }
    );

    const plan: TaskPlan = {
      id: planId,
      steps,
      originalInput: query,
      createdAt: Date.now(),
    };

    return plan;
  }

  /**
   * 执行计划
   */
  private async executeStep(plan: TaskPlan): Promise<any> {
    let index = 0;
    for await (const step of plan.steps) {
      console.log(`🔄 [${this.name}] [execute] 执行步骤: ${step.title}`);
      this.addRecord({
        id: `plan_${Date.now()}`,
        name: this.name,
        type: "supervisor",
        desc: `分配任务到: ${step.title}`,
        content: "",
        contentType: "",
        createdAt: Date.now(),
      });
      const agent = {
        AnalyzerAgent: analyzerAgent,
        WeatherAgent: weatherAgent,
        PlannerAgent: plannerAgent,
      }[step.title];

      const gateKeeperResult = await this.gateKeeper.makeParams({
        agentName: step.title,
        outputPool: this.outputPool,
      });

      const jsonResult = await agent!.getJSONResult(
        {
          query: gateKeeperResult.targetAgentInput,
        },
        this.addRecord
      );

      const textResult = await agent!.makeTextResult(
        jsonResult,
        this.addRecord
      );

      this.outputPool.push({
        makerName: step.title,
        outputText: textResult,
        outputJson: JSON.parse(jsonResult),
      });

      this.plan!.steps[index].outputs = {
        jsonResult: jsonResult,
        textResult: textResult,
      };
      index++;
    }

    for (const step of this.plan!.steps) {
      if (step.title === "AnalyzerAgent") {
        this.finalResult.analysis = {
          text: step.outputs?.textResult,
          json: step.outputs?.jsonResult,
        };
      } else if (step.title === "WeatherAgent") {
        this.finalResult.weather = {
          text: step.outputs?.textResult,
          json: step.outputs?.jsonResult,
        };
      } else if (step.title === "PlannerAgent") {
        this.finalResult.planner = {
          text: step.outputs?.textResult,
          json: step.outputs?.jsonResult,
        };
      }
    }
    return this.finalResult;
  }
}

此时运行项目得到的大致执行流程如下:

用户指令我想7月6号在成都玩三天,预算3000元左右

📝 规划阶段:

这是一个复杂的旅行规划任务,需要分解处理。合理的执行计划应该是:

  1. 调用 AnalyzerAgent 分析用户需求,提取关键信息
  2. 调用 WeatherAgent 查询目的地天气情况
  3. 调用 PlannerAgent 结合前两步结果生成详细行程

🚀 执行阶段:

  • Step 1:需求分析
    • 委派 AnalyzerAgent 执行需求分析
  • Step 2:天气查询
    • 委派 WeatherAgent 执行天气查询
  • Step 3:行程规划
    • 委派 PlannerAgent 执行行程规划

Plan--Execute 模式通过分离"规划"和"执行",不仅提升了 AI Agent 的可维护性,还能够提升流程执行的灵活性。比如说, 演示项目中如果用户在输入环节已经提供了足够的需求信息以及天气信息, 主管Agent就会忽略AnalyzerAgent和WeatherAgent, 通过PlannerAgent直接生成报告。


ReAct模式

ReAct 的名字非常直观,就是 推理(Reason)行动(Act) 的结合。它模拟了人类解决问题的基本模式:推理 → 行动 → 观察 → 再思考...,形成一个闭环,直到任务完成。

这个模式各个环节所完成的任务如下:

  • 推理:LLM 分析任务并将其分解为多个步骤。它计划要采取的行动,并根据可用的信息和工具决定如何解决问题。
  • 行动:按照计划执行每个步骤,在此期间可以调用工具来获取外部信息。
  • 观察:执行每个动作后,代理会观察结果并将相关信息保存在内存中 。这种跟踪使它能够跟踪过去的作并建立在以前的观察结果之上,因此它不会重复自身或丢失上下文。
  • 再思考:根据观察结果调整计划,并重复上述过程,直到任务完成

我们可以通过对比Plan--Execute来更深入地理解这种模式:

维度 Plan--Execute 模式 ReAct模式
流程结构 两阶段:先整体规划(Plan),再分步执行(Execute)。 交叉式 :在同一对话或循环中,推理(Reasoning)行动(Acting) 交替进行。
模块职责 - 规划器 :负责生成高层行动计划(步骤序列)。 - 执行器:负责具体动作的落实和监控。 无明显分层,模型在同一上下文中同时承担推理与输出动作指令。
决策节奏 先全局后局部:先确定完整计划,再执行各子步骤。 即推即做:每次推理后立即执行一次动作,依据反馈再推理下一步。
适用场景 - 复杂、多步骤任务(如机器人操作、业务流程自动化)。 - 需要严格顺序与资源分配的场景。 - 信息检索、问答对话等交互式场景。 - 步骤灵活、依赖外部反馈即时调整的任务。

再对比之前的场景,ReAct模式Agent的执行流程大致如下:

用户指令我想7月6号在成都玩三天,预算3000元左右

  • Step 1:需求分析
    • 🧠 Thought(推理):用户提出旅行计划,但没有说明出发地和偏好,第一步我应该解析需求
    • ⚙️ Action:调用 AnalyzerAgent 对用户输入进行信息提取
    • 👁️ Observation(观察):分析结果是否满足要求
  • Step 2:天气查询
    • 🧠 Thought(推理):好的,用户意图基本清晰。为了合理安排行程,我需要知道成都这几天的天气
    • ⚙️ Action:调用 WeatherAgent 查询 7月6日--8日 成都天气
    • 👁️ Observation(观察):分析结果是否满足要求。
  • Step 3:行程规划
    • 🧠 Thought(推理):好,我知道了天气情况。可以开始安排合理的行程
    • ⚙️ Action:调用 PlannerAgent 进行行程规划
    • 👁️ Observation(观察):分析结果是否满足要求

需要注意的是Plan--Execute模式与ReAct模式并不是两个互斥的概念,面对复杂任务时,如果有必要我们可以将两个模式结合起来,构建出更完善可靠的Agent系统。

总结

让我们快速回顾一下核心要点:

  1. AI Agent 的本质LLM + Memory + Planning + Tools,其核心是LLM ,关键特性是自主性
  2. 工具调用Function CallingMCP Server都是Agent感知外部世界获取外部世界信息的方式。
  3. 协作架构多 Agent 系统通过专业分工,能够更好地解决复杂问题。
  4. 思考模式Plan-and-ExecuteReAct是两种不同的思考模式,它们之间并不互斥。

AI领域的变化日新月异,各种概念层出不穷。由于时间关系,文中提到的诸如MCP、A2A以及Agent的另一核心部分Memory没有详细展开。构建Agent也有很多其他方面的概念,比如Context、Human-in-the-loop等等本文也没有涉及。建议大家自行再去探索,因为抛开作为AI应用入口的Web端,就AI应用层来说,各大流行SDK或框架所支持的语言除了python一般都是JS/TS(比如 OpenAI SDK、langChain、langGraph、Transformers.js),前端开发者与AI领域的距离并没有想象中那么遥远。

谢谢大家!

相关推荐
万少1 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL1 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl021 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang1 小时前
前端如何实现电子签名
前端·javascript·html5
今天又在摸鱼1 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿1 小时前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端
百锦再1 小时前
Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
前端·javascript·vue.js·vue·web·reactive·ref
win4r1 小时前
🚀 SuperClaude让Claude Code编程能力暴增300%!小白秒变顶尖程序员!19个专业命令+9大预定义角色,零编程经验也能开发复杂项目,完全碾
aigc·ai编程·claude
jingling5551 小时前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架