教你如何用 JS 实现 Agent 系统(2)—— 开发 ReAct 版本的“深度搜索”

一、前言

大家好,我是唐某人~ 我非常乐意与大家分享我在AI领域的经验和心得。我的目标是帮助更多的前端工程师以及其他开发岗位的朋友,能够以一种轻松易懂的方式掌握AI应用开发的技能。希望我们能一起学习,共同进步!

1.1 前情回顾

在前文《教你如何用 JavaScript 实现 Agent 系统(一)------ Agentic System 概览》中,我详细介绍了Agent系统的基本概念,并探讨了构建一个基础的单体Agent的方法。

1.2 目标

最近一个月,我写了多篇关于人工智能的文章,并总结了一些经验。首先文章不宜过长,否则写作费劲,阅读也累,容易失去思路。其次,使用实际例子比过多理论更有效,这样不仅吸引人,还能带来成就感,内容也更生动具体、易于接受。

回顾和总结后,我计划将未来的内容分为四个部分来写,以提高效率并保证每部分都有深度。然后,我会通过实现一个"深度搜索"的应用来引导大家实践,希望能激发大家动手实践的兴趣。

未来的内容的核心是:学习四种主流的 Agent 设计模式,并且用这些模式实现四个不同版本的 "深度搜索"。

所以本篇的目的:

  1. 理论:学习 ReAct 模式的运行机制
  2. 实战:用 ReAct 设计模式实现"深度搜索"Agent

二、深度搜索

2.1 什么是深度搜索

在实践之前,我们先来了解一下什么是深度搜索?这里给大家演示两个产品,你自己也可要去体验一下。

通义千问的 "分析研究"

千问会基于用户提出的问题,首先生成一个研究分析框架。随后,它会在整个互联网范围内自动搜集相关信息,并通过持续的信息检索、分析与综合处理,最终产出一份详尽且全面的问题分析报告。

豆包的"深入研究"

可以根据用户的问题,自动规划研究、全网搜集资料并进行综合分析,最终提供一份分析报告。

2.2 它是怎么玩的

它的本质是通过 LLM 与搜索工具结合来解决问题。具体步骤如下:

  1. LLM 分析解答问题还缺失哪些信息
  2. 利用搜索工具获取实时信息
  3. LLM 整合和分析信息后提供答案

比起让大模型只用预训练时的数据来回答问题,这种"深度搜索"方式在时效性和准确性上表现更好。所以,在处理学术研究、时事新闻、金融分析等领域的问题时,它特别合适。

三、什么是 ReAct

官方的说,ReAct(由Yao在2023年提出)是一种让 LLM 理通过思考、行动、观察的循环来完成任务的框架。简单的说,它就是一种提示词工程和代码实现的设计模式。其工作流程如下:

  1. 思考:确定下一步需要采取什么行动。
  2. 行动:输出指令,使AI调用外部工具。
  3. 观察:分析工具执行的结果。
  4. 回答:如果结果已足够解答问题,则组织答案。
  5. 循环:如果信息不足,重复上述步骤。

为了让大家对这个流程有一个更直观的理解,我准备了一张流程图供大家参考。

首先,我们需要明确两个关键角色以及它们的关键组成部分:

  1. 问题提出者:也就是发起对话的用户。
  2. Agent:由大型语言模型(LLM)、对话记忆以及工具调用功能组成。

简化后的内容如下:

  1. 用户提问。
  2. LLM(大语言模型)接收问题,分析并输出解决方案(思考阶段)。
  3. LLM 判断是否需要使用工具来辅助解决问题,该过程会发出调用指令(行动阶段)。
  4. 系统根据指示调用工具,并将结果反馈给用户。
  5. 该过程的**所有步骤(用户提问、LLM 思考、工具指令、工具结构)**都会记录在对话上下文中
  6. 对话上下文提交给 LLM 进行再次分析(观察阶段)。
  7. LLM 分析上下文,若需更多信息,则重复思考-行动-观察循环;否则直接提供答案

四、代码实现

好的,我们已经掌握了理论基础,接下来就进入实战阶段吧!我们将通过构建一个 ReAct 版本的"深度搜索"项目,来更直观地体验和理解ReAct模式的工作原理。希望这个过程能让你有更深的体会!

4.1 封装增强版LLM

首先还是老套路,构建 Agent 的三个基础是 LLM、对话记忆、工具调用,所以我们得封装一个增强版本的 LLM。核心是功能:

  1. 支持设定系统提示词
  2. 自动记录对话上下文
  3. 自动调用工具并触发下一轮对话

因为这些内容在上一篇详细讲过,下面只展示一下核心代码,完整代码请看 github.com/zixingtangm... 的实现。

typescript 复制代码
export class Block {
  // 其他属性......

  // 对话内容
  private messages: Message[] = [];

  constructor(config: BlockConfig) {
    // 初始化
  }

  // 对话
  public async invoke(messages?: Message[]): Promise<AssistantMessage> {
    const { model, apiKey, baseUrl, response_format } = this.llmBaseConfig;

    // 记录用户对话
    if (messages) {
      this.messages.push(...messages);
    }

    // 调用 LLM API
    const res = await fetch();

    // LLM 回复的文本内容
    let assistantMessage = '';
    // 工具调用指令
    let tools: Record<number, ToolCall> = {};

    const reader = res.body?.getReader();
    const decoder = new TextDecoder();
    while (true) {
      // 流式的存储 LLM 的回复以及工具调用指令
    }

    // 最终回复
    let message: AssistantMessage;

    // 没有工具调用,会直接回复
    if (assistantMessage && Object.keys(tools).length === 0) {
      message = new AssistantMessage(assistantMessage);
      this.messages.push(message);
    }

    // 有工具调用,需要调用工具,自动触发一轮新的对话
    if (Object.keys(tools).length > 0) {
      // 提取参数
      const tool_calls = Object.values(tools).map((tool) => tool);
      // 记录 LLM 的工具调用指令
      this.messages.push(message);

      // 执行全部的工具
      const callToolTasks = Object.values(tools).map(async (tool) => {
        let result = '';
        try {
          result = await this.tools.call(tool.function.name, JSON.parse(tool.function.arguments));
        } catch (error) {
          result = `${tool.function.name} 执行异常`;
        }
        return JSON.stringify(result);
      });
      const toolResults = await Promise.all(callToolTasks);


      // 每个工具的结果,创建一个 tool message 存入对话上下文中
      const toolResultMessages = toolResults.map((result, index) => {
        console.table([{ node: this.name, type: 'tool', json: JSON.stringify(result) }]);
        return new ToolMessage(result, tools[index].id);
      });
      this.messages.push(...toolResultMessages);

      // 触发新一轮的对话
      return await this.invoke();
    }

    return message;
  }
}

4.2 设定提示词

有了 Block 基础类之后,接下来就可以开始设计提示词了。提示词的设计主要围绕以下三点展开:

  1. 明确角色:告诉 LLM 它扮演什么角色,以及它的任务是什么。
  2. 细化工具使用场景:补充核心工具在什么情况下使用,具体怎么用。
  3. 强调关键点:提醒 LLM 在执行任务前一定要先思考,并且要一步一步来,不能跳过任何环节。
tsx 复制代码
const prompt = `
你是一个专业的搜索研究助理,你需要搜集到准确、实时的信息,然后通过总结和分析来解决用户的问题。

以下是可用的工具和使用场景:
1.Thought: 用于观察现有的上下文内容,思考是否具备回答问题的条件,如不满足则思考接下来还要搜集什么信息、分析什么信息
2.TavilySearch: 用于搜索互联网的相关内容
3.GetCurrentDate: 获取当前的时间,以保证搜集、分析、回答的内容是与用户期望的时间相差不远的

注意:
1. 你必须在调用 TavilySearch、GetCurrentDate 工具的前后调用 Thought 工具,用于观察现有上下文的内容,思考后续的步骤
2. 所有的工具不能并行调用,必须按顺序,逐一的调用
3. 你需要一步一步的思考
`;

声明一个 ReActAgent 类,在初始化的时候创建 Block 实例并设定系统提示词

typescript 复制代码
export class ReActAgent {
  private agent: Block;
  constructor() {
    this.agent = new Block({
      name: 'agent',
      instruction: prompt,
    });
  }
}

4.3 工具设定

想让 Agent 解决特定领域的问题,就得给它准备好解决问题需要的工具。下面是我们必须提供的几个工具:

  1. Thought(思考工具):这个工具的核心作用是让大模型先好好思考,并且把思考的过程展示出来。
  2. TavilySearch(搜索工具):用来在互联网上查找相关信息,帮助解决需要外部资料的问题。
  3. GetCurrentDate(获取时间工具):在处理一些需要知道当前时间的问题时,可以用这个工具来获取最新的日期或时间信息。

简单来说,就是给 Agent 配备好"思考"、"查资料"和"看时间"的能力,让它能更高效地完成任务。

Thought

强制 LLM 把思考的过程,通过工具调用的形式输出到对话的上下文中

typescript 复制代码
export const thoughtTool: Tool = {
  type: 'function',
  function: {
    name: 'Thought',
    description: '用于输出用于思考解决用户的问题,需要做什么以及这么做的原因',
    parameters: {
      type: 'object',
      properties: {
        thought: {
          type: 'string',
          description: '思考的内容',
        },
      },
      required: ['thought'],
    },
  },
  func: async (args: { [key: string]: any }) => {
    return `Thought: ${args.thought}`;
  },
};

TavilySearch

可以先去 www.tavily.com/ 申请一个 API KEY,会有一定的免费调用额度。

申请完 API KEY 以后,在项目中配置这个 KEY,并下载 @tavily/core 这包就可以使用了。

typescript 复制代码
import { tavily } from '@tavily/core';

const tvly = tavily({ apiKey: process.env.TAVILY_API_KEY! });

export const tavilySearchTool: Tool = {
  type: 'function',
  function: {
    name: 'TavilySearch',
    description: '用于搜索互联网的相关内容',
    parameters: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: '搜索的查询内容',
        },
      },
      required: ['query'],
    },
  },
  func: async (args: { [key: string]: any }) => {
    try {
      const res = await tvly.search(args.query);
      const data = res.results.map(({ title, url, content }) => ({
        title,
        url,
        content,
      }));
      return JSON.stringify(data);
    } catch (error) {
      return 'Error: Tavily 搜索失败';
    }
  },
};

GetCurrentDate

获取当前的时间。核心是为了确保 LLM 回答问题的时效性,让 LLM 知道当前的时间。

typescript 复制代码
export const getCurrentDateTool: Tool = {
  type: 'function',
  function: {
    name: 'GetCurrentDate',
    description: '获取当前日期',
  },
  func: async () => {
    return new Date().toLocaleDateString();
  },
};

配置工具

将相关的工具配置好。完成代码看这里:github.com/zixingtangm...

typescript 复制代码
import { getCurrentDateTool } from '../tools/get-current-date';
import { tavilySearchTool } from '../tools/tavily-search';
import { thoughtTool } from '../tools/thought';

export class ReActAgent {
  private agent: Block;
  constructor() {
    this.agent = new Block({
      name: 'agent',
      instruction: prompt,
      tools: new Tools([thoughtTool, tavilySearchTool, getCurrentDateTool]),
    });
  }
}

4.4 测试效果

这里我们来测试一下,询问"今天关于 AI 的热点新闻有哪些?"

你可以看到,现在的 LLM 已经学会了一种"先想后做"的方式来一步步解决我们的问题。它会先用 Thought 工具把自己的思考过程写出来,然后调用 GetCurrentDate 来获取当前时间,接着再用 TavilySearch 去查找我们需要的信息......

其实这就是 ReAct 的运行流程,非常的简单。当然,你还可以尝试问更多有意思的问题 😏

五、最后

5.1 思考

现在你已经能够基于 ReAct 模式开发一个"深度搜索"的智能体应用了。但是不知道你是否发现,我们的设计存在一定的问题。你是否有想过,如果 LLM 一直认为搜索的信息还不够,然后不停的迭代搜索会出现哪些问题呢?

先思考,我们下一篇会详细讲解这个问题。

5.2 总结

最后我们再来总结一下本篇的内容。

  1. 什么是深度搜索:基于 LLM + 搜索工具实现的一种 Agent。它能够分析用户问题,发现缺失的信息,自动搜索信息并进行分析,最后总结回答。
  2. ReAct:是一种让 LLM 理通过思考、行动、观察的循环来完成任务的 Agent 设计模式。本质是让 LLM 模仿人类 "先思考后行动"的思维,一步步的解决问题。

5.3 后文

下一遍会继续分享 Agent 的设计模式------如何用 Plan And Execute 模式实现深度搜索。如果你觉得内容对你有帮助,请关注我,我会持续更新~

原创不易,转载请私信我。

相关推荐
中微子2 小时前
深入剖析 useState产生的 setState的完整执行流程
前端
FIT2CLOUD飞致云2 小时前
九月月报丨MaxKB在不同规模医疗机构的应用进展汇报
人工智能·开源
阿里云大数据AI技术2 小时前
【新模型速递】PAI-Model Gallery云上一键部署Qwen3-Next系列模型
人工智能
遂心_2 小时前
JavaScript 函数参数传递机制:一道经典面试题解析
前端·javascript
小徐_23332 小时前
uni-app vue3 也能使用 Echarts?Wot Starter 是这样做的!
前端·uni-app·echarts
袁庭新2 小时前
全球首位AI机器人部长,背负反腐重任
人工智能·aigc
RoyLin2 小时前
TypeScript设计模式:适配器模式
前端·后端·node.js
机器之心2 小时前
谁说Scaling Law到头了?新研究:每一步的微小提升会带来指数级增长
人工智能·openai
遂心_3 小时前
深入理解 React Hook:useEffect 完全指南
前端·javascript·react.js