用 ReAct 搭一个简单 Agent:零基础操作指南

用 ReAct 搭一个简单 Agent:零基础操作指南

跟着做完,你会得到一个"会自己判断要不要用工具"的 Agent。全程约 15 分钟。

摘要:本文用 15 分钟带你从零搭一个会自己判断"要不要用工具"的 Agent。先搞懂 ReAct 的"思考→行动→观察"循环,再按模型是否支持 function calling 选路线:不支持就走路线 A(ReAct 文本协议,默认路径),支持就走路线 B(原生 function calling)。随后依次完成确认配置、跑通最小示例、读懂 60 行核心代码、添加自定义工具、联网搜索,最后附常见报错对照表与进阶玩法。

配套代码(在 02-demo-agent/ 下):

完整代码地址:https://download.csdn.net/download/applehunqiu/93727036

脚本 用途 适用模型
ReAct_minimal.py 最小版,一个查时间的工具 所有模型
ReAct_local.py 完整版,含联网搜索 所有模型
ReAct_toolcalling.py 不加载 ReAct 提示词的写法 仅支持 function calling 的模型

改造细节见:改造说明-本地模型接入.md。


第 0 步:先搞懂 ReAct 在干什么

普通大模型只能凭记忆回答;Agent 的区别是它可以在"思考"和"动手"之间循环:

复制代码
Thought(思考)→ Action(决定用哪个工具)→ Observation(工具的真实结果)
      ↑                                                    ↓
      └────────────────── 拿结果再思考,直到能回答 ─────────┘
                              ↓
                        Final Answer(最终答案)

举个例子,问"现在几点了":

环节 内容
Thought 我不知道当前时间,需要用工具
Action CurrentTime(工具名)
Action Input None(传给工具的参数)
Observation 2026-10-20 20:57:52(工具真实返回)
Final Answer 现在的时间是 20:57:52

关键认知:模型只负责"决定用什么工具、传什么参数",真正干活的是你写的 Python 函数。 所以时间是真实的,不是模型编的。


选路:先判断你的模型属于哪一类

这是最重要的一步,它决定你后面用哪个脚本、要不要加载 ReAct 提示词。

判断标准只有一条:模型能不能原生 function calling(即模型能不能直接返回"我要调哪个函数、参数是什么"的结构化结果,而不是用自然语言描述)。

复制代码
你的模型支持 function calling 吗?
│
├─ 支持(OpenAI / DeepSeek / 百炼 / 新版 Ollama 模型)
│     → 走"路线 B":第 6 步,用 create_tool_calling_agent,不用 ReAct 提示词
│       更稳、更快,推荐
│
├─ 不支持(本项目的内网本地模型、老模型、纯文本接口)
│     → 走"路线 A":第 2~5 步,用 ReAct 提示词规定文本格式
│       这是本教程默认路径
│
└─ 根本不需要工具(纯问答、翻译、总结)
      → 不是 Agent,用 prompt | llm 就够了,两种都不需要

怎么快速判断?跑一次就知道:

bash 复制代码
python ReAct_toolcalling.py
  • 输出里出现工具执行结果(真实时间)→ 支持,走路线 B;
  • 输出 ⚠️ 当前 MODEL_PROVIDER=local ... 不支持原生 function calling,并且答案是编的 → 不支持,走路线 A。

本项目当前的 MODEL_PROVIDER=local(内网网关,背后是 deepseek-r1:32b)属于不支持这一类,所以本教程默认带你走路线 A。


第 1 步:确认配置

所有配置都在 02-demo-agent/.env,与示例代码放在一起。先进入该目录自检:

bash 复制代码
cd "/Users/cqj/Documents/projects:ai-agents-labs/大模型应用开发 动手做AI Agent/64217-ai-agents-20240606/02-demo-agent"
python llm_config.py

看到 ✅ 在线 就可以继续。输出大致是:

复制代码
配置文件: /.../64217-ai-agents-20240606/02-demo-agent/.env
当前配置: provider=ollama model=qwen2.5:7b-instruct target=http://localhost:11434/v1
服务探测: ✅ 在线,可用模型 1 个:qwen2.5:7b-instruct

想换模型只改 .env 里这一行,不用动代码:

bash 复制代码
MODEL_PROVIDER=local       # 本地 / 内网网关(当前)
# MODEL_PROVIDER=ollama    # 本机 Ollama:ollama serve && ollama pull qwen2.5:7b-instruct
# MODEL_PROVIDER=deepseek  # DeepSeek,需填 DEEPSEEK_API_KEY
# MODEL_PROVIDER=openai    # OpenAI 官方,需填 OPENAI_API_KEY

用 OpenAI / DeepSeek 时链路最稳(它们原生支持 stop 参数);

用 local 内网网关时项目已内置兼容处理,但模型偶尔会偷懒漏调工具,属正常现象。


第 2 步:跑通最小示例(5 分钟)

bash 复制代码
python ReAct_minimal.py

注意先进入 02-demo-agent 目录再执行(配置就在该目录下)。

成功的样子:

复制代码
> Entering new AgentExecutor chain...
Thought: Do I need to use a tool? Yes
Action: CurrentTime
Action Input:
2026-10-20 20:57:52          ← 这一行是工具真实返回的
Final Answer: 现在的时间是2026年10月20日 20:57:52。

> Finished chain.
现在的时间是2026年10月20日 20:57:52。

怎么确认工具真的被调用了?把系统时间对一下------一致就说明是真的执行了代码,不是模型编的。


第 3 步:读懂这 60 行代码

ReAct_minimal.py 的全部逻辑只有 4 件事。

① 准备模型(1 行)

python 复制代码
from llm_config import build_llm
llm = build_llm()

build_llm() 读 .env 里的 MODEL_PROVIDER,返回对应的模型对象。你不用关心它是 OpenAI 还是内网网关。

② 写一个工具(4 行)

工具就是普通 Python 函数,没有任何特殊之处:

python 复制代码
def get_current_time(_: str = "") -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

再用 Tool 包一层,告诉模型这个工具叫什么、什么时候该用:

python 复制代码
tools = [
    Tool(
        name="CurrentTime",                                    # 模型要写的名字
        func=get_current_time,                                 # 真正执行的函数
        description="获取当前日期和时间,当你需要知道现在是什么时候时使用",  # 模型靠这句判断该不该用
    )
]

最重要的实践要点:description 写不好,Agent 就不会用这个工具。

要写清楚"什么时候用",而不是只写"这个工具是干嘛的"。

反例:description="时间工具" → 模型不知道什么时候该调它。

③ 加载 ReAct 提示词(1 行)

python 复制代码
prompt = hub.pull("hwchase17/react-chat").partial(chat_history=[])

这份提示词不执行任何操作,它只是一份"格式契约",干三件事:

  1. 把工具清单({tools})和合法工具名({tool_names})告诉模型;
  2. 规定输出格式 Thought / Action / Action Input / Final Answer,好让解析器用正则把模型的话拆成"要调什么工具、传什么参数";
  3. 留出 {agent_scratchpad} 占位符,AgentExecutor 每轮把历史步骤拼回这里,模型才能"看到"自己做过什么,形成多步循环。

第一次运行会联网拉取,之后会缓存。

两个新手常见的误解:

  • ❌ "所有 Agent 都必须加载它" → 不是。只有走文本协议的 ReAct Agent 需要(即路线 A)。模型支持 function calling 时走路线 B,不需要它。
  • ❌ "提示词能让模型变聪明" → 不能。它只是约束输出格式。漏写一行格式就会崩 ------比如格式里没有 Action Input: 这一行,模型输出 CurrentTime(),解析器直接报 Could not parse LLM output。

它也不是唯一选择 :你可以自己写,只要包含"工具清单 + 格式规范 + scratchpad 占位符"三要素即可,效果等价。项目里的 REACT_PROMPT_CN 和 ReAct.py 里的离线兜底版都是自写的同款。我们实测官方 react-chat 版在本项目网关上格式遵循度最好,所以选它做默认。

④ 组装并执行(8 行)

python 复制代码
agent = build_react_agent(llm, tools, prompt)
executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,              # 打印每一步,调试期务必打开
    max_iterations=3,          # 最多循环 3 轮,防止模型死循环
    handle_parsing_errors="你的格式不对,请严格按 Action 和 Action Input 两行输出。",
)
print(executor.invoke({"input": "现在几点了?"})["output"])

三个参数各自的意义:

参数 作用
verbose=True 打印 Thought / Action / Observation,调试全靠它
max_iterations 循环上限,用完会提示 Agent stopped due to iteration limit
handle_parsing_errors 模型格式写错时,把这句话当作 Observation 反馈给它重试,而不是直接崩

build_react_agent 是本项目对 create_react_agent 的封装:用本地/内网网关时会自动挂上兼容解析器(解决网关不支持 stop 导致工具不执行的问题),用 OpenAI/DeepSeek 时行为与教材原生写法完全一致。


第 4 步:加上你自己的工具

以"查天气"为例,只需两步。

第一步,写函数(这里用假数据,你换成真实 API 即可):

python 复制代码
def get_weather(city: str) -> str:
    return f"{city}:晴,26℃"

第二步,加到 tools 列表:

python 复制代码
tools = [
    Tool(name="CurrentTime", func=get_current_time,
         description="获取当前日期和时间,当你需要知道现在是什么时候时使用"),
    Tool(name="Weather", func=get_weather,
         description="查询指定城市的天气,当用户问某城市天气时使用,输入为城市名,如 北京"),
]

然后提问:"北京天气怎么样?" → 模型会自己输出 Action: Weather / Action Input: 北京。

加工具的两条经验:

  1. 工具不要贪多,2~4 个最好,多了模型容易选错;
  2. 工具函数名要见名知意,模型是靠名字和描述来选的。

第 5 步:想联网搜索就用完整版

bash 复制代码
python ReAct_local.py

回车用默认问题,或输入你自己的问题。它比最小版多了:

  • Search 工具(DuckDuckGo,无需 Key)
  • safe_search():搜索失败时返回错误文本而不是崩溃
  • 服务探测:跑之前先确认模型服务在线

第 6 步:路线 B------模型支持 function calling 时不用提示词的写法

前提 :先把 .env 改成 MODEL_PROVIDER=deepseek(或 openai)并填好对应的 Key。

bash 复制代码
python ReAct_toolcalling.py

核心代码只有三处不同:

python 复制代码
# ① 用的是 create_tool_calling_agent,不是 create_react_agent
from langchain_classic.agents import create_tool_calling_agent

# ② 提示词只是角色说明,没有 Thought/Action 那套格式规范
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个乐于助人的助手,需要获取实时信息时请使用工具。"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),   # 仍然要留,用于回传历史
])

# ③ 不需要 handle_parsing_errors(没有文本格式要解析)
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

两种写法对比:

路线 A:ReAct 文本协议 路线 B:原生 function calling
组装函数 create_react_agent create_tool_calling_agent
要不要 ReAct 提示词 要(规定输出格式) 不要(只需角色说明)
工具怎么传给模型 写在提示词文本里 走 API 的 tools 字段
模型怎么表达"要调工具" 输出 Action: xxx 文本 返回结构化 tool_calls
依赖 stop 参数 依赖(网关不支持就得加兼容层) 不依赖
稳定性 受模型格式遵循度影响 更稳
适用模型 所有模型 仅支持 function calling 的模型

如果你忘了判断、在 local 网关上跑了路线 B,会看到这样的输出(实测):

复制代码
⚠️  当前 MODEL_PROVIDER=local,该后端不支持原生 function calling。
    换成 MODEL_PROVIDER=openai 或 deepseek 后再跑本脚本;
    不支持时请改用 ReAct_minimal.py(文本协议版)。

> Entering new AgentExecutor chain...
您好!当前时间为北京时间下午3点15分。      ← 工具没被调用,时间是编的
> Finished chain.

注意对比:路线 A 会真的执行 CurrentTime 并给出真实时间,路线 B 在不支持的模型上直接编一个。判断"工具到底有没有被执行",就看答案里的数字是不是真的------这是本教程最重要的一条排错直觉。


常见报错对照表

报错 / 现象 原因 解决
未知的 MODEL_PROVIDER .env 里值写错 只能是 openai / deepseek / ollama / dashscope / local / ollama_native / huggingface
服务探测:❌ 无法连接 服务没起,或被代理拦住 起服务;或把 MODEL_TRUST_ENV 改成 0 绕过代理
答案里的时间/数据明显是编的 模型没真正调用工具 用 local 网关时属已知现象;改 MODEL_PROVIDER=deepseek 或 openai
Agent stopped due to iteration limit 循环到上限还没答完 调大 max_iterations,或把问题拆细一点
search timed out / RateLimit 搜索引擎网络受限 换 Tavily,或先注释掉 Search 工具只用 CurrentTime
首次调用卡住三四分钟 网关冷启动(32B 模型) 正常,等一等;后续调用会快很多
Could not parse LLM output 模型没按格式输出 已由 handle_parsing_errors 自动重试;持续失败说明该模型不适合跑 ReAct
Prompt missing required variables 自写提示词漏了变量 四个变量 tools / tool_names / input / agent_scratchpad 缺一不可;用 react-chat 还得多一个 chat_history
用 create_tool_calling_agent 却没调工具 模型不支持 function calling 换 MODEL_PROVIDER=deepseek / openai,或改回路线 A 用 create_react_agent

下一步可以玩什么

  1. 换到路线 B :把 MODEL_PROVIDER 改成 deepseek,再跑 ReAct_toolcalling.py,体会两种路线的差异;
  2. 加记忆 :把 chat_history=[] 换成真实历史消息,Agent 就能记住上下文;
  3. 结构化输出:给 Agent 限定输出 JSON,方便下游程序消费;

还有一类 Agent 连工具都不需要(纯问答、摘要、翻译),那种直接 prompt | llm 就行,

不加载任何 Agent 提示词------先判断"要不要工具",再判断"用哪条路线",能少走很多弯路。

相关推荐
像风一样自由20201 小时前
59.实战为RAG系统加入评估监控权限和审计
人工智能·智能体
QYR-分析1 小时前
汽车可开启全景天窗:座舱舒适核心配置,智能化升级打开产品溢价空间
大数据·人工智能·汽车
king13598745501 小时前
LiteRT Tensor API:C++ 编写端侧模型图,XNNPACK、GPU 与 NPU 怎么选?
开发语言·c++·人工智能·chatgpt·架构·aigc
企业数字化笔记1 小时前
AI视频怎么做超分辨率增强?逐帧处理、画幅控制与音画验收
网络·人工智能·音视频
解决问题no解决代码问题1 小时前
Java 金融后端转 LLM 应用开发 Day3:LLM 写作 / 阅读 / 聊天能力拆解与核心边界(附金融场景落地方法)
人工智能
CS_Zero1 小时前
大模型时代的SLAM
人工智能·机器学习·slam
wei091301 小时前
深度解读Work Agent长程任务执行机制
人工智能
尹玉玺1 小时前
从原子残片到天道公理:以德谟克利特思想全貌,重新定界西方唯物认知的历史层级与边界
人工智能·算法·架构
Raas1001 小时前
2026年AI网关推荐?MAI Gateway(魔芋企业级AI网关)等主流方案横评
大数据·人工智能·ai·gateway·ai网关·mai gateway