一、前言
大家好,我是唐某人~ 我非常乐意与大家分享我在AI领域的经验和心得。我的目标是帮助更多的前端工程师以及其他开发岗位的朋友,能够以一种轻松易懂的方式掌握AI应用开发的技能。希望我们能一起学习,共同进步!
1.1 前情回顾
在前文《教你如何用 JavaScript 实现 Agent 系统(一)------ Agentic System 概览》中,我详细介绍了Agent系统的基本概念,并探讨了构建一个基础的单体Agent的方法。
1.2 目标
最近一个月,我写了多篇关于人工智能的文章,并总结了一些经验。首先文章不宜过长,否则写作费劲,阅读也累,容易失去思路。其次,使用实际例子比过多理论更有效,这样不仅吸引人,还能带来成就感,内容也更生动具体、易于接受。
回顾和总结后,我计划将未来的内容分为四个部分来写,以提高效率并保证每部分都有深度。然后,我会通过实现一个"深度搜索"的应用来引导大家实践,希望能激发大家动手实践的兴趣。
未来的内容的核心是:学习四种主流的 Agent 设计模式,并且用这些模式实现四个不同版本的 "深度搜索"。

所以本篇的目的:
- 理论:学习 ReAct 模式的运行机制
- 实战:用 ReAct 设计模式实现"深度搜索"Agent
二、深度搜索
2.1 什么是深度搜索
在实践之前,我们先来了解一下什么是深度搜索?这里给大家演示两个产品,你自己也可要去体验一下。
通义千问的 "分析研究"
千问会基于用户提出的问题,首先生成一个研究分析框架。随后,它会在整个互联网范围内自动搜集相关信息,并通过持续的信息检索、分析与综合处理,最终产出一份详尽且全面的问题分析报告。

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

2.2 它是怎么玩的
它的本质是通过 LLM 与搜索工具结合来解决问题。具体步骤如下:
- LLM 分析解答问题还缺失哪些信息
- 利用搜索工具获取实时信息
- LLM 整合和分析信息后提供答案

比起让大模型只用预训练时的数据来回答问题,这种"深度搜索"方式在时效性和准确性上表现更好。所以,在处理学术研究、时事新闻、金融分析等领域的问题时,它特别合适。
三、什么是 ReAct
官方的说,ReAct(由Yao在2023年提出)是一种让 LLM 理通过思考、行动、观察的循环来完成任务的框架。简单的说,它就是一种提示词工程和代码实现的设计模式。其工作流程如下:
- 思考:确定下一步需要采取什么行动。
- 行动:输出指令,使AI调用外部工具。
- 观察:分析工具执行的结果。
- 回答:如果结果已足够解答问题,则组织答案。
- 循环:如果信息不足,重复上述步骤。
为了让大家对这个流程有一个更直观的理解,我准备了一张流程图供大家参考。

首先,我们需要明确两个关键角色以及它们的关键组成部分:
- 问题提出者:也就是发起对话的用户。
- Agent:由大型语言模型(LLM)、对话记忆以及工具调用功能组成。
简化后的内容如下:
- 用户提问。
- LLM(大语言模型)接收问题,分析并输出解决方案(思考阶段)。
- LLM 判断是否需要使用工具来辅助解决问题,该过程会发出调用指令(行动阶段)。
- 系统根据指示调用工具,并将结果反馈给用户。
- 该过程的**所有步骤(用户提问、LLM 思考、工具指令、工具结构)**都会记录在对话上下文中
- 对话上下文提交给 LLM 进行再次分析(观察阶段)。
- LLM 分析上下文,若需更多信息,则重复思考-行动-观察循环;否则直接提供答案。
四、代码实现
好的,我们已经掌握了理论基础,接下来就进入实战阶段吧!我们将通过构建一个 ReAct 版本的"深度搜索"项目,来更直观地体验和理解ReAct模式的工作原理。希望这个过程能让你有更深的体会!
4.1 封装增强版LLM
首先还是老套路,构建 Agent 的三个基础是 LLM、对话记忆、工具调用,所以我们得封装一个增强版本的 LLM。核心是功能:
- 支持设定系统提示词
- 自动记录对话上下文
- 自动调用工具并触发下一轮对话
因为这些内容在上一篇详细讲过,下面只展示一下核心代码,完整代码请看 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 基础类之后,接下来就可以开始设计提示词了。提示词的设计主要围绕以下三点展开:
- 明确角色:告诉 LLM 它扮演什么角色,以及它的任务是什么。
- 细化工具使用场景:补充核心工具在什么情况下使用,具体怎么用。
- 强调关键点:提醒 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 解决特定领域的问题,就得给它准备好解决问题需要的工具。下面是我们必须提供的几个工具:
- Thought(思考工具):这个工具的核心作用是让大模型先好好思考,并且把思考的过程展示出来。
- TavilySearch(搜索工具):用来在互联网上查找相关信息,帮助解决需要外部资料的问题。
- 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 总结
最后我们再来总结一下本篇的内容。
- 什么是深度搜索:基于 LLM + 搜索工具实现的一种 Agent。它能够分析用户问题,发现缺失的信息,自动搜索信息并进行分析,最后总结回答。
- ReAct:是一种让 LLM 理通过思考、行动、观察的循环来完成任务的 Agent 设计模式。本质是让 LLM 模仿人类 "先思考后行动"的思维,一步步的解决问题。
5.3 后文
下一遍会继续分享 Agent 的设计模式------如何用 Plan And Execute 模式实现深度搜索。如果你觉得内容对你有帮助,请关注我,我会持续更新~
原创不易,转载请私信我。