普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月26日首页

2026:拒绝 AI 焦虑,我手写了一个 ReAct 智能体

作者 辰枫
2026年1月26日 00:00

前言

2025年史称“Agent”元年,自从 ChatGPT 的爆火,自己也一直使用的是 Chat 类的 AI工具。其它类型的工具只是推特上看到,但是自己很少使用。有时在自己推特的 timline上会出现很多爆火的AI工具,刷多了会对于自己产生一些焦虑,但仅仅是产生(毕竟人在自己的舒适区,是很难自己走出来的)。加上最近的招聘市场和公司都很注重 Agent 这块,所以决定花时间好好研究下 Agent。为未来做准备,同时也会包装自己。

整体的学习大纲,用最近听到一句话来说“体系完整先于逻辑自洽和观点正误”,先学习整体的 Agent 的整体框架再慢慢研究。

什么是AI Agent

“AI agents are software systems that use AI to pursue goals and complete tasks on behalf of users. They show reasoning, planning, and memory and have a level of autonomy to make decisions, learn, and adapt.” — Google Cloud

AI 智能体是使用 AI来实现目标和代表用户完成任务的软件系统,其表现中推理、规划和记忆能力。并且有一定的自主性,能够自主学习、适应和做出决定

这里引用宝王老师对于 AI Agent 的定义:

AI Agent 是为了实现一个目标,循环调用工具的大语言模型。

  • 工具循环(Tools):模型调用工具 -> 获取结果 -> 继续推理
  • 有明确终点:为达成目标,而不是无限调用
  • 目标来源灵活:可以来自用户,也可以来自另一个 LLM
  • 基础记忆能力:通过对话历史保存上下文信息

image.png

AI Agent (AI 智能体)经典范式

ReAct范式

在 ReAct 范式出现之前,主流的方法可以分为两类:

  • “纯思考”:如思维链(Chain-of-Thought) ,引导模型进行复杂的逻辑推理,但无法与外界进行交互,容易生成幻觉
  • “纯行动”:模型直接输出执行的动作,缺乏规划和纠错能力

什么是 ReAct 范式

从字面上看ReAct 单词读和前端框架 React一样发音,只是把 A进行了大写,但是是完全不同的东西。

ReAct (Reason 和 Act)最早是由姚顺雨于 2022 年提出,其核心思想是模仿人类解决问题的方式,将思考(Reasoning)与行动(Acting)结合起来。形成一个“思考 -> 行动->观察”的循环。ReAct 的精妙之处在于,它认识到思考与行动是相辅相成的,思考指导行动,而行动的结果又会反过来为思考提供依据。在执行上的是通过一种特殊的Prompt 来引导 LLM,使其每一步都遵循一个固定的轨迹。

  1. Thought (思考) :在执行任务前,先进行分析当前任务情况、分析任务、规划下一步,或者反思上一步的结果
  2. Action (行动) :根据上一步的思考结果决定采用的具体行动,通常会调用外部的 Tools
  3. Observation (观察) :这是执行 Action 后从 Tools 返回的结果

在这个范式中,Agent会不断的 Loop这个流程,将新的观察结果追加到历史的记录中,形成一个不断增长的上下文,直到 Thought 中判断已经找到正确的答案,然后输出结果。整个过程中思考使任务更具有目标性,而行动又为下一次的思考提供了事实依据

image.png

ReAct适用场景

  • 需要外部知识的任务:查询实时信息
  • 需要精确计算的任务:数据计算问题
  • 需要与 API交互的任务:如操作数据库、调用某个 API 完成任务

什么是工具(Tools)

上面介绍了 ReAct 整体的范式,提到了一个概念 Tools,那么什么是 Tools 呢?

我们都知道 LLM是一个用固定数据训练出来的模型,有自主思考的能力相当于大脑,但是只有大脑还不够还要有“手和脚”与真实的外部世界交互。这些手和脚就是 Tools

ReAct智能体实战

了解完了 ReAct 整体的概念,下面我们通过一个具体的需求来实际体验下。

需求:你需要编写一个智能体,用于分析用户的需求,回答用户的问题

例如:小米手机的最新手机是哪一款?

需求分析

想要解决这个问题,我们需为Agent 提供一个网页搜索的工具。让 LLM 调用去实时搜索信息,然后基于搜索的结果让 LLM再判断是不是正确的结果。

image.png

编码实现
系统提示词(Prompt)

基于 ReAct 整体的机制,我们需要设置一个提示词。主要包含以下点:

  1. 定义LLM 的角色或使用场景
  2. 定义 LLM可使用的工具
  3. 定义 LLM响应的格式,从而方便的让程序处理
  4. 定义用户的问题和整个思考的过程即动态上下文

根据以上几点,我们设计的系统提示词如下:

export const REACT_PROMPT_TEMPLATE = `
请注意, 你是一个有能力调用外部工具的智能助手

可用工具如下:
{tools}

请严格按以下格式回应:

Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动
Action: 你采取的行动必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`:调用一个可用工具
- `Finish[最终答案]`: 当你认为已经获取最终答案时

现在请开始解决以下问题:
Question: {question}
History: {history}
`;
工具(Tools)

按提示词的设计,这里我们先编写给LLM调用的工具函数,选用 SerpApi,根据官方提供的 SDK 封装一个函数(使用前需要去官网注册一个账号获取免费的额度)。

SearchApi 是一个强大的实时SERP API,可提供来自Google 搜索、Google 招聘、YouTube、Google 新闻等搜索引擎集合的结构化数据

import { getJson } from 'serpapi';
import { requireEnv } from './utils';

/**
 * 基于 SerApi实现的网页搜索工具
 * @param query
 */
export async function search(query: string): Promise<string> {
  try {
    const apiKey = requireEnv('SERPAPI_API_KEY');
    if (!apiKey) {
      return '错误:SERPAPI_API_KEY没有定义在.env 文件中';
    }
    
    const params = {
      engine: 'google',
      q: query,
      api_key: apiKey,
      gl: 'cn',
      hl: 'zh-cn',
    };

    const results = await getJson(params);

    if (Array.isArray(results.answer_box_list)) {
      return results.answer_box_list.join('\n');
    }

    if (results.answer_box && results.answer_box.answer) {
      return results.answer_box.answer;
    }

    if (
      results.knowledge_graph &&
      results.knowledge_graph.description
    ) {
      return results.knowledge_graph.description;
    }

    if (
      Array.isArray(results.organic_results) &&
      results.organic_results.length > 0
    ) {
      // 如果没有直接答案,则返回前三个有机结果的摘要
      const snippets = results.organic_results
        .slice(0, 3)
        .map((res: any, i: number) => {
          return `[${i + 1}] ${res.title || ''}\n${res.snippet || ''}`;
        });
      return snippets.join('\n\n');
    }

    return `对不起,没有找到关于 '${query}' 的信息。`;
  } catch (error) {
    return `发生错误: ${error}`;
  }
}

为了后续我们方便的添加新的 Tools 我们需要构造一个通用的工具构造器

export type Tools = {
  description: string;
  func: Function;
};

/**
 * 工具执行器,负责管理和执行工具
 */
export class ToolsExecutor {
  tools: Record<string, Tools>;

  constructor() {
    this.tools = {};
  }

  registerTool(name: string, desc: string, func: Function) {
    if (name in Object.keys(this.tools)) {
      console.warn('同名工具已存在,将进行覆盖');
    }

    this.tools[name] = { description: desc, func: func };
    console.info(`工具 ${name} 已注册 \n`);
  }

  getTool(name: string) {    
    if (Object.keys(this.tools).includes(name)) {
      return this.tools[name]?.func;
    }
  }

  getAvailableTools() {
    const allTools = [];
    for (const [name, info] of Object.entries(this.tools)) {
      allTools.push(`- ${name}: ${info?.description}`);
    }

    return allTools.join('\n');
  }
}

接下来我们测试这些函数

import { search } from "./tools";
import * as dotenv from 'dotenv';

function main() {
  dotenv.config();
  const executor = new ToolsExecutor();
  executor.registerTool(
    'Search',
    '基于 SerApi实现的网页搜索工具',
    search
  );

  const toolFunc = executor.getTool('Search');

  console.log('可用工具列表:');
  console.log(executor.getAvailableTools());

  console.log(`\n 执行 Search 工具:`);
  if (toolFunc) {
    toolFunc('人工智能的未来是什么?').then((result: string) => {
      console.log('搜索结果:');
      console.log(result);
    });
  }
}
main();

>>>>>>>>> 执行结果如下:
工具 Search 已注册 

可用工具列表:
- Search: 基于 SerApi实现的网页搜索工具

 执行 Search 工具:
搜索结果:
[1] 人工智能的未来
AI 未来的定义将是转向用于实验的开源大规模模型,以及开发更小、更高效的AI 模型,以提高易用性并降低成本。

[2] 2025,人工智能走向何方?我们如何拥抱变化?
“当前,新一代人工智能等数字技术正在爆发式发展,将进一步推动制造业的效率变革,未来有望带动工厂的研发、生产、组织和服务全方面的革新。”中国信息通信研 ...

[3] 中国人工智能的未来之路
在人口老龄化的大背景下,人工智能对推动中国未来的经济增长将起到至关重要的作用。中国的劳动年龄人口最早将在2024年达到峰值,并在之后的50年中减少五分之一。麦肯锡全球 ...
实现Agent

首先,我们要对接大模型厂商提供的服务,OpenAI的接口规范,已经成为 LLM 行业的接口标准。国内的厂商基本都兼容了此标准

免费的厂商服务?现在基本国内的大模型厂商在注册账号之后基本都会提供一定的免费额度,我这里使用的火山引擎下的火山方舟的服务。

先安装open ai sdk npm install openai,然后我们基于这个 SDK 封装一个通用的llm 对接类,方便后续复用

import OpenAI from 'openai';
import { requireEnv } from './utils';
import { type ChatCompletionMessageParam } from 'openai/resources';

/**
 *  Hello Agents 定制的 LLM 客户端
 *  它用于调用任何兼容 OpenAI 接口的服务,并默认使用流式响应
 */
export class HelloAgentsLLM {
  model: string;
  apiKey: string;
  baseUrl: string;
  timeout: number;
  client: OpenAI;

  constructor(
    model?: string,
    apiKey?: string,
    baseUrl?: string,
    timeout: number = 60,
  ) {
    this.model = model || requireEnv('LLM_MODEL_ID');
    this.apiKey = apiKey || requireEnv('LLM_API_KEY');
    this.baseUrl = baseUrl || requireEnv('LLM_BASE_URL');
    this.timeout = requireEnv('LLM_TIMEOUT')
      ? Number(requireEnv('LLM_TIMEOUT'))
      : timeout;

    if (!this.model || !this.baseUrl || !this.apiKey) {
      throw new Error(
        '模型 ID、API 密钥和服务地址必须被提供或在.env 文件中定义',
      );
    }

    // console.log(this.baseUrl)

    this.client = new OpenAI({
      baseURL: this.baseUrl,    
      apiKey: this.apiKey,
      // timeout: this.timeout Error: Request timed out.
    });
  }

  async think(messages: Array<ChatCompletionMessageParam>, temperature = 0) {
    console.log('开始调用大模型');
    try {
      const response = await this.client.chat.completions.create({
        model: this.model,
        messages: messages,
        temperature: temperature,
        stream: true,
      });

      console.log('调用大模型成功 \n');

      const collectedContent = [];
      for await (const chunk of response) {
        // 处理每个 chunk        
        const c = chunk.choices[0]?.delta.content || "";
        collectedContent.push(c);
      }
      return collectedContent.join("")
    } catch (e) {
        console.error(`调用 LLM API时发生错误: ${e}`)
        return
    }
  }
}

接下,我们按 ReAct 范式的规范,实现 ReActAgent。

  1. 在整个 Agent中我们需要初始化:llm客户端、可调用的工具、整个交互的历史信息
export class ReActAgent {
  llmClient: HelloAgentsLLM;
  toolsExecutor: ToolsExecutor;
  maxStep: number;
  history: string[];

  constructor(
    llmClient: HelloAgentsLLM,
    toolsExecutor: ToolsExecutor,
    maxStep = 5) {
        this.llmClient = llmClient;
        this.toolsExecutor = toolsExecutor;
        this.maxStep = maxStep;
        this.history = [];
      }
  }
  1. 接下来我们来实现核心的 run 方法:
async run(question: string) {
   // 1. 重置整体流程
    this.history = [];
    let currentStep = 0;

    // 2. 循环调用LLM
    while (currentStep <= this.maxStep) {
      currentStep += 1;

      console.log(`--- 执行第 ${currentStep} 步 ---`);

      // 2.1 根据提示词的模版,传入参数。获取完整版本提示词 
      const toolsDesc = this.toolsExecutor.getAvailableTools();
      // 历史上下文拼接
      let historyStr = this.history.join('\n');

      const prompt = formatPrompt(REACT_PROMPT_TEMPLATE, {
        tools: toolsDesc,
        question: question,
        history: historyStr,
      });

      let messages: Array<ChatCompletionMessageParam> = [
        {
          role: 'user',
          content: prompt,
        },
      ];
        
      // 2.2 请求 LLM,获取响应
      const responseTxt = await this.llmClient.think(messages);

      if (!responseTxt) {
        console.error(`错误:大模型返回异常`);
        break;
      }
 }

之前我们要求 LLM 在返回时必须带有以下格式:

请严格按以下格式回应:

Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动
Action: 你采取的行动必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`:调用一个可用工具
- `Finish[最终答案]`: 当你认为已经获取最终答案时

接下来,我们只要使用正则表达式,来提取 LLM 的响应即可:

    /**
     * 解析 LLM 输出,提取 Thought 和 Action
     */
    private _parseOutput(text: string): { thought: string | null; action: string | null } {
        const thoughtMatch = text.match(/Thought: (.*)/);
        const actionMatch = text.match(/Action: (.*)/);
        const thought = thoughtMatch?.[1]?.trim() ?? null;
        const action = actionMatch?.[1]?.trim() ?? null;
        return { thought, action };
    }
  /**
   * 解析 Action 字符串,提取工具名称和输入
   */
  private _parseAction(actionText: string): [string | null, string | null] {
    const match = actionText.match(/(\w+)[(.*)]/);
    if (match) {
      return [match[1] ?? null, match[2] ?? null];
    }
    return [null, null];
  }

  /**
   * 解析 Action 输入,提取参数
   */
  private _parseActionInput(text: string): string | null {
    const inputMatch = text.match(/\w+[(.*)]/);
    console.log(inputMatch)
    return inputMatch && typeof inputMatch[1] === 'string' ? inputMatch[1] : null;
  }
  • _parseOutput:负责从 LLM返回的消息中提取 Thought 和 Action 两个最主要的部分
  • _parseAction:负责Action 字符串中,提取工具名和参数,例如:Search[Oppo],工具名:Search,参数:Oppo

解析响应结果并执行工具函数

const {thought, action} = this._parseOutput(responseTxt);
if(thought) {
    console.info(`思考:${thought} \n`)
  }

  if(!action) {
    console.log('没有下一步Action, 流程终止')
    break;
  }
  
  if(action.startsWith('`Finish')) {
    const finalAnswer = this._parseActionInput(action)
    console.log(`最终答案:${finalAnswer} \n`)
    return finalAnswer;
  }

  const [toolName, toolInput] = this._parseAction(action);
  if(!toolInput || !toolName) {
    continue;
  }

  console.log(`行动: ${toolName} ${toolInput}\n`)

  const toolFun = this.toolsExecutor.getTool(toolName)
  let observation = `错误: 未找到名为 '${toolName}' 的工具`
  if(toolFun) {
    // 使用 call 来实现工具函数的执行
    observation = await toolFun.call(null, toolInput)
  }

这段代码就是,解析 LLM响应的结果,如果以 Finish 开始就代表 LLM已经给出了最终的结果,如果不是则需要提取Action,并执行 Action获取响应结果。

最后把本次调用响应的结果(observation),放入到 history 中,供下次 LLM 思考参考

console.log(`观察 ${observation} \n\n`)
this.history.push(`Action: ${action}`)
this.history.push(`Observation: ${observation}`)

当然,在 while 循环中设置的有 max_step。程序并不会无限循环,达到一定次数自动停止。

下面是运行 console 结果:

工具 Search 已注册 

--- 执行第 1 步 ---
开始调用大模型
调用大模型成功 

Thought: 为了回答这个问题,我需要查找小米最新发布的手机型号及其主要卖点。由于小米会定期发布新手机,我需要通过搜索工具获取最新的信息。

Action: `Search[小米最新手机型号和卖点]`
思考:为了回答这个问题,我需要查找小米最新发布的手机型号及其主要卖点。由于小米会定期发布新手机,我需要通过搜索工具获取最新的信息。 

行动: Search 小米最新手机型号和卖点

观察 [1] 小米全系列选购指南:2026年爆款机型+各系列核心卖点_手机
小米数字系列定位3999元以上高端市场,是追求旗舰性能与均衡体验用户的首选。小米17搭载骁龙8 Gen5处理器,配备2K LTPO直屏与5300mAh电池,支持90W快充,机身 ...

[2] 手机 - 小米商城
Redmi 14C. 599起. CPU型号MediaTek Helio G81-Ultra ; Redmi Note 14 Pro. 1149起 · CPU型号天玑7300-Ultra ; Xiaomi 17 Ultra 徕卡版. 7999起 · CPU型号第五代骁龙8至尊版 ...

[3] 小米17 Ultra的最强卖点,却成了用户退货的理由
小米17 Ultra徕卡版因变焦环松动问题引发用户不满,官方回应称是设计预留活动空间所致,但未能消除质疑。该问题涉及产品设计、制造公差和用户体验。 


--- 执行第 2 步 ---
开始调用大模型
调用大模型成功 

Thought: 根据搜索结果,小米最新的手机型号是小米17 Ultra 徕卡版。它的卖点包括搭载第五代骁龙8至尊版处理器,以及徕卡版的影像系统。不过,搜索结果也提到了一些用户对变焦环松动的反馈。为了更全面地了解这款手机的卖点,我需要进一步搜索小米17 Ultra 徕卡版的详细规格和官方宣传信息。

Action: `Search[小米17 Ultra 徕卡版 官方卖点]`
思考:根据搜索结果,小米最新的手机型号是小米17 Ultra 徕卡版。它的卖点包括搭载第五代骁龙8至尊版处理器,以及徕卡版的影像系统。不过,搜索结果也提到了一些用户对变焦环松动的反馈。为了更全面地了解这款手机的卖点,我需要进一步搜索小米17 Ultra 徕卡版的详细规格和官方宣传信息。 

行动: Search 小米17 Ultra 徕卡版 官方卖点

观察 [1] Xiaomi 17 Ultra 徕卡版
Xiaomi 17 Ultra 徕卡版,方方面面尽徕卡。徕卡相机感设计,徕卡经典红标,中框精雕横滚花,镌刻徕卡铭文。创新大师变焦环,如相机般精密操控;支持功能自定义,时刻服务你的 ...

[2] Xiaomi 17 Ultra 徕卡版立即购买-小米商城
Xiaomi 17 Ultra 徕卡版 ; 299元. Xiaomi 17 Ultra 保值换新服务 · 299元 ; MiCare 无忧版已省500元. 加购后碎屏保升级为2年多次,详见服务介绍 · 499元 ; 延长保1349元. 性能 ...

[3] 小米17 Ultra的最强卖点,却成了用户退货的理由
小米17 Ultra徕卡版因变焦环松动问题引发用户不满,官方回应称是设计预留活动空间所致,但未能消除质疑。该问题涉及产品设计、制造公差和用户体验。 


--- 执行第 3 步 ---
开始调用大模型
调用大模型成功 

Thought: 根据搜索结果,小米最新的手机型号是小米17 Ultra 徕卡版。它的卖点包括徕卡相机感设计、徕卡经典红标、中框精雕横滚花、镌刻徕卡铭文,以及创新的变焦环设计。虽然变焦环设计引发了一些用户的不满,但官方解释这是设计预留的活动空间。因此,可以确认小米17 Ultra 徕卡版是小米最新的手机,其卖点主要集中在徕卡合作的高端相机体验和设计上。

Action: `Finish[小米最新的手机是小米17 Ultra 徕卡版。它的主要卖点包括徕卡相机感设计、徕卡经典红标、中框精雕横滚花、镌刻徕卡铭文,以及创新的变焦环设计,提供如相机般的精密操控体验。]`
思考:根据搜索结果,小米最新的手机型号是小米17 Ultra 徕卡版。它的卖点包括徕卡相机感设计、徕卡经典红标、中框精雕横滚花、镌刻徕卡铭文,以及创新的变焦环设计。虽然变焦环设计引发了一些用户的不满,但官方解释这是设计预留的活动空间。因此,可以确认小米17 Ultra 徕卡版是小米最新的手机,其卖点主要集中在徕卡合作的高端相机体验和设计上。 
-- 变量 ---
[
  'Finish[小米最新的手机是小米17 Ultra 徕卡版。它的主要卖点包括徕卡相机感设计、徕卡经典红标、中框精雕横滚花、镌刻徕卡铭文,以及创新的变焦环设计,提供如相机般的精密操控体验。]',
  '小米最新的手机是小米17 Ultra 徕卡版。它的主要卖点包括徕卡相机感设计、徕卡经典红标、中框精雕横滚花、镌刻徕卡铭文,以及创新的变焦环设计,提供如相机般的精密操控体验。',
  index: 1,
  input: '`Finish[小米最新的手机是小米17 Ultra 徕卡版。它的主要卖点包括徕卡相机感设计、徕卡经典红标、中框精雕横滚花、镌刻徕卡铭文,以及创新的变焦环设计,提供如相机般的精密操控体验。]`',
  groups: undefined
]
最终答案:小米最新的手机是小米17 Ultra 徕卡版。它的主要卖点包括徕卡相机感设计、徕卡经典红标、中框精雕横滚花、镌刻徕卡铭文,以及创新的变焦环设计,提供如相机般的精密操控体验。

注意

由于大模型能力的不同,不确认我们的搜索结果也一样。我这里使用的是deepseek-v3-250324这个模型执行的结果。

最后,想看全部的代码请在这里查看github.com/javaswing/l…

总结

本文,通过对于 ReAct 范式的介绍,并结合 HelloAgent一书中的例子进行了实践。对于 Agent的开发有了体感。征途刚刚开始。

参考

  1. cloud.google.com/discover/wh…
  2. aiagent.app/
  3. baoyu.io/blog/ai-age…
  4. datawhalechina.github.io/hello-agent…
  5. www.ibm.com/think/topic…
  6. react-lm.github.io/
❌
❌