从零搞懂 AI Agent:一文读懂 ReAct 模式与代码实现
从零搞懂 AI Agent:一文读懂 ReAct 模式与代码实现
写在前面:几个你必须知道的概念
在正式进入 Agent 的世界之前,我们先快速过一遍文中会反复出现的关键词。如果你已经熟悉,可以跳过这一节。
| 术语 | 一句话解释 |
|---|---|
| LLM | 大语言模型,比如 GPT、DeepSeek,是 Agent 的“大脑” |
| Token | 大模型处理文本的最小单位,可以理解为“字词碎片” |
| Context | 模型每次处理任务时能“看到”的全部信息,即上下文 |
| Context Window | Context 能容纳的最大 Token 数量,相当于模型的“短期记忆容量” |
| Tool | Agent 用来感知或影响外部世界的函数,比如查天气、读文件 |
| MCP | 一个统一工具接入格式的标准协议,让工具调用更规范(后文有落地详解) |
| Agent | 能自主规划、调用工具、反复尝试,直至解决用户问题的程序 |
| Agent Skill | 专门写给 Agent 看的“说明书”,告诉它怎么用某个工具或完成某类任务 |
一、Agent 到底是什么?
Agent 这个词直译是“智能体”或“代理”。在 AI 领域,它指的是一个能自主感知环境、做出决策、执行行动并最终达成目标的智能系统。
你可以用一个简单的公式来理解它:
Agent = LLM(大脑) + 工具(手脚) + 记忆(经验)
拆开来看:
- LLM:负责理解用户意图、推理拆解任务、制定执行计划
- 工具:包括 API 接口、数据库查询、浏览器操作、代码执行器等,让 Agent 能“动手做事”
- 记忆:短期记忆(当前对话上下文)+ 长期记忆(向量数据库存储的历史经验)
简单说,Agent 不是一个只会聊天的 AI,而是一个能“自己动手把活干了”的 AI。
二、Agent 的核心运行模式:ReAct
2.1 什么是 ReAct?
ReAct 是 Reasoning(推理) + Acting(行动) 的缩写。它是目前最主流的 Agent 运行范式,核心理念是:让 AI 一边思考,一边行动,一边根据结果调整下一步。
它的运行流程是一个循环:
- Thought(思考):模型分析当前状态,决定下一步该做什么
- Action(行动):调用某个工具,或执行某个操作
- Observation(观察):获取工具返回的结果
- 回到第 1 步,循环往复,直到任务完成
就像人类解决问题一样:想一下 → 做一下 → 看一下结果 → 再想下一步。
2.2 一个真实例子:Agent 查天气
假设用户问:“今天北京天气怎么样?”
| 步骤 | 内容 |
|---|---|
| Thought | 用户想知道北京今天的天气,我需要调用天气 API |
| Action | get_weather(city="北京") |
| Observation | API 返回 "晴,25°C" |
| Thought | 已经拿到结果,可以直接回答用户了 |
| Final Answer | 北京今天晴天,气温 25°C |

2.3 ReAct 的实现原理:Prompt 工程
ReAct 模式之所以能工作,核心其实是精心设计的 Prompt。通过 Prompt 告诉 LLM:
- 它有哪些工具可用
- 它的输出必须遵循固定格式(
Thought/Action/Observation) - 它需要循环直到产出
Final Answer
典型的 ReAct Prompt 模板:
你是一个能调用工具的 AI Agent。
你可以使用以下工具:
- search(query): 搜索互联网
- calculator(expr): 计算数学表达式
- get_weather(city): 查询天气
请按照以下格式输出:
Thought: 你当前的思考
Action: 工具名称(参数)
Observation: 工具返回的结果
...(重复直到任务完成)
Final Answer: 最终答案
用户问题: {用户输入}
LLM 输出解析流程:
LLM 原始输出:
Thought: 用户要查北京天气,我需要调用天气工具
Action: get_weather(北京)
Observation: 晴天 25°C
Thought: 已获取天气,可以回答了
Final Answer: 北京今天晴天,25°C
解析逻辑:
- 检测到 Action → 解析工具名和参数 → 执行工具调用
- 将工具结果包装成 Observation 格式
- 继续调用 LLM,直到输出 Final Answer
而代码层面要做的事,就是解析 LLM 的输出:
- 如果包含
Action→ 解析工具名和参数 → 执行工具调用 - 将工具返回结果封装成
Observation格式 - 继续调用 LLM,直到出现
Final Answer
三、手把手构建一个 ReAct Agent
3.1 极简版:一个只查时间的 ReAct Agent
这个版本的目标是让你5 分钟内理解 ReAct 的核心骨架。
它包含什么?
- 1 个工具:
get_time(),只做一件事 - 1 个假的 LLM:不用 API,不花钱,纯逻辑模拟
- 1 个主循环:ReAct 的灵魂
运行流程示意
用户输入 "现在几点了"
↓
第 1 轮循环:
假 LLM 思考 → "用户想知道时间,我要调用 get_time()"
执行 get_time() → 返回 "当前时间: 14:30"
↓
第 2 轮循环:
假 LLM 看到观察结果 → "已经拿到了,可以回答"
输出最终答案 → 退出循环
3.1.2 代码结构拆解

3.1.3 核心流程步骤过程
用户输入 "现在几点了"
↓
第1轮循环:
result = fake_llm("现在几点了", None)
# → {"thought": "用户想知道时间...", "action": "get_time()"}
↓
执行 get_time()
observation = "当前时间: 14:30"
↓
第2轮循环:
result = fake_llm("现在几点了", "当前时间: 14:30")
# → {"thought": "已经知道时间了", "final_answer": "..."}
↓
打印最终答案,退出循环
3.1.4 完整代码
# ============== 简化版 ReAct Agent ==============
class TimeReActAgent:
def __init__(self):
self.tools = {
"get_time": self.get_time
}
self.step_count = 0
# ========== 工具函数:获取当前时间 ==========
def get_time(self):
from datetime import datetime
return f"当前时间:{datetime.now()}"
# ========== 模拟的 LLM(假装是 AI 在思考) ==========
def fake_llm(self,user_input,observation=None):
self.step_count += 1
# ========== 时间逻辑 ==========
if "时间" in user_input or "几号" in user_input:
if observation and "当前时间" in observation:
return {
"thought":"我已经知道当前时间了,可以回答了",
"action": None,
"final_answer": f"用户的问题是: {user_input}\n{observation}"
}
return{
"thought": "用户想知道当前时间,我需要调用 get_time 工具",
"action": "get_time()",
"final_answer":None
}
# ========== 时间逻辑结束 ==========
# 兜底(这里不会执行到)
return {
"thought": "继续处理...",
"action": None,
"final_answer": "处理完毕"
}
# 解析 Action:从字符串中提取工具名和参数
def parse_action(self, action_str):
# 简单解析:找到第一个 '(' 和最后一个 ')'
tool_name = action_str[:action_str.index('(')]
args_str = action_str[action_str.index('(')+1:action_str.rindex(')')]
# 如果 args_str 是空的,返回空列表
if args_str.strip() == "":
args = []
else:
args = [arg.strip().strip("'").strip('"') for arg in args_str.split(',')]
return tool_name, args
# ReAct 主循环(核心!)
def run(self, user_input):
print(f"\n 用户: {user_input}")
print("=" * 50)
# 初始化
observation = None
final_answer = None
while True:
# 步骤 1: 思考(调用假的 LLM)
result = self.fake_llm(user_input, observation)
# 打印思考过程
print(f"\n 第 {self.step_count} 轮思考: {result['thought']}")
# 步骤 2: 检查是否完成了
if result['final_answer']:
final_answer = result['final_answer']
break
# 步骤 3: 执行行动(调用工具)
if result['action']:
print(f" 执行行动: {result['action']}")
# 解析工具名和参数
tool_name, args = self.parse_action(result['action'])
# 调用工具
if tool_name in self.tools:
observation = self.tools[tool_name](*args)
print(f" 观察结果: {observation}")
else:
observation = f"错误: 未找到工具 {tool_name}"
# 如果超过 5 轮,强制退出(防止死循环)
if self.step_count >= 5:
final_answer = "任务超时,请重试"
break
print("=" * 50)
print(f" 最终答案:\n{final_answer}")
# ============== 运行 ==============
if __name__ == "__main__":
agent = TimeReActAgent()
# 用户输入
user_task = input("请输入你的任务:")
# 执行
agent.run(user_task)
3.2 进阶版:接入真实 LLM 的 ReAct Agent
这个版本把“假的 LLM”替换成真实的 DeepSeek API,并加入了读文件、写文件、执行终端命令三个实用工具。
和极简版相比,它升级了什么?
| 对比维度 | 极简版 | 进阶版 |
|---|---|---|
| 大脑 | 硬编码逻辑 | 真实 LLM(DeepSeek) |
| 工具数量 | 1 个 | 3 个(读/写文件 + 终端命令) |
| 工具注册 | 手动写死 | 自动扫描函数签名和文档 |
| 上下文记忆 | 无 | 完整对话历史 |
| 实用性 | 演示用 | 可处理真实项目任务 |
3.2.1 核心流程
用户输入任务
↓
渲染系统 Prompt(动态注入工具列表 + 项目文件列表)
↓
while True:
↓
调用 DeepSeek API(真 AI 大脑)
↓
解析 AI 返回的 <thought> / <action> / <final_answer>
↓
如果是 action → 解析工具名和参数 → 执行工具
↓
将工具返回结果包装成 <observation> 追加到对话历史
↓
回到循环开头(AI 看到新观察结果,决定下一步)
↓
如果输出 <final_answer> → 返回最终答案
3.2.2 与简化TimeReActAgent相比

3.2.3 逐模块拆解
(1)init(初始化)
def __init__(self, tools: List[Callable], model: str, project_directory: str):
self.tools = { func.__name__: func for func in tools } # 工具注册
self.model = model # 模型名(deepseek-v4-flash)
self.project_directory = project_directory # 项目目录(给 AI 看文件列表)
self.client = OpenAI(
base_url="https://api.deepseek.com", # DeepSeek API 地址
api_key=ReActAgent.get_api_key(), # 从 .env 读 API Key
)
(2)run(主循环)
def run(self, user_input: str):
messages = [
{"role": "system", "content": self.render_system_prompt(...)}, # 系统提示
{"role": "user", "content": f"<question>{user_input}</question>"}
]
while True:
content = self.call_model(messages) # 调用真 AI
# 解析 AI 返回的 <thought>、<action>、<final_answer>
# 执行工具 → 追加 observation → 继续循环
(3)render_system_prompt(告诉 AI 它是谁)
def render_system_prompt(self, system_prompt_template: str) -> str:
tool_list = self.get_tool_list() # 自动生成工具列表
file_list = ", ".join(
os.path.abspath(os.path.join(self.project_directory, f))
for f in os.listdir(self.project_directory)
)
return Template(system_prompt_template).substitute(
operating_system=self.get_operating_system_name(), # Windows/macOS/Linux
tool_list=tool_list, # 工具描述
file_list=file_list # 项目文件列表
)
作用:把"你能用这些工具:read_file(file_path)、write_to_file(…)"动态拼到系统提示里,告诉 AI 它有什么武器。
(4)get_tool_list(自动提取工具信息)
def get_tool_list(self) -> str:
for func in self.tools.values():
name = func.__name__ # "read_file"
signature = str(inspect.signature(func)) # "(file_path)"
doc = inspect.getdoc(func) # "用于读取文件内容"
tool_descriptions.append(f"- {name}{signature}: {doc}")
效果:只要你在外面写了 def read_file(file_path): “”“用于读取文件内容”“”,这里就能自动生成 - read_file(file_path): 用于读取文件内容,AI 就能看懂怎么调用。
(5)parse_action(解析 AI 的输出)
AI 返回:write_to_file(“hello.py”, “print(‘Hello’)”)
这个函数把它拆成:
- tool_name = “write_to_file”
- args = [“hello.py”, “print(‘Hello’)”]
然后执行 self.tools"write_to_file"
(6)call_model(真的调用 AI)
def call_model(self, messages):
response = self.client.chat.completions.create(
model=self.model, # "deepseek-v4-flash"
messages=messages, # 对话历史
)
content = response.choices[0].message.content
messages.append({"role": "assistant", "content": content}) # 保存 AI 的回答
return content
关键:messages.append 把 AI 的回答保存到对话历史,下一次调用时 AI 能看到自己之前说了什么,实现记忆。
(7)生产级升级:用 Function Calling 替代正则解析(必看!)
上文的正则解析仅用于演示原理。真实生产环境必须使用 LLM 原生的 Function Calling,模型直接返回结构化的 JSON,彻底告别字符串解析的噩梦。
# DeepSeek / OpenAI 原生工具调用示例
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"}
},
"required": ["city"]
}
}
}
]
response = client.chat.completions.create(
model="deepseek-v3",
messages=[{"role": "user", "content": "北京天气"}],
tools=tools, # 把工具架构传给模型
tool_choice="auto" # 让模型自动决定是否调用
)
# 模型直接返回 JSON,而非难啃的字符串
tool_call = response.choices[0].message.tool_calls[0]
function_name = tool_call.function.name # "get_weather"
function_args = json.loads(tool_call.function.arguments) # {"city": "北京"}
优势:参数自动校验、支持多工具并行调用、100% 规避解析异常。
(8)工具注册的终极形态:MCP 协议
你刚刚看到的 tools = {func.__name__: func} 是“硬注册”,换一个项目就要重写代码。
MCP(模型上下文协议) 的作用是:让所有工具以统一的 JSON Schema 格式对外暴露,Agent 不用提前写死工具函数,而是运行时动态发现工具(类似微服务的服务注册中心)。
// MCP 标准工具描述
{
"mcp_server": "filesystem",
"tools": [
{
"name": "read_file",
"input_schema": {"path": "string"},
"output_schema": {"content": "string", "size": "int"}
}
]
}
有了 MCP,你的 ReAct Agent 只需解析这套标准格式,就能零代码接入任何遵守 MCP 的第三方工具市场——这才是 MCP 出现在你术语表里的真正含义。
(9)完整代码(正则版,仅供学习参考)
import ast
import inspect
import os
import re
from string import Template
from typing import List, Callable, Tuple
import click
from dotenv import load_dotenv
from openai import OpenAI
import platform
from prompt_template import react_system_prompt_template
class ReActAgent:
def __init__(self, tools: List[Callable], model: str, project_directory: str):
self.tools = { func.__name__: func for func in tools }
# 工具从外部传入,动态注册
self.model = model
self.project_directory = project_directory
self.client = OpenAI(
base_url="https://api.deepseek.com",
api_key=ReActAgent.get_api_key(),
)
def run(self, user_input: str):
messages = [
{"role": "system", "content": self.render_system_prompt(react_system_prompt_template)},
{"role": "user", "content": f"<question>{user_input}</question>"}
]
while True:
# 请求模型
content = self.call_model(messages)
# 检测 Thought
thought_match = re.search(r"<thought>(.*?)</thought>", content, re.DOTALL)
if thought_match:
thought = thought_match.group(1)
print(f"\n\n💭 Thought: {thought}")
# 检测模型是否输出 Final Answer,如果是的话,直接返回
if "<final_answer>" in content:
final_answer = re.search(r"<final_answer>(.*?)</final_answer>", content, re.DOTALL)
return final_answer.group(1)
# 检测 Action
action_match = re.search(r"<action>(.*?)</action>", content, re.DOTALL)
if not action_match:
raise RuntimeError("模型未输出 <action>")
action = action_match.group(1)
tool_name, args = self.parse_action(action)
print(f"\n\n🔧 Action: {tool_name}({', '.join(args)})")
# 只有终端命令才需要询问用户,其他的工具直接执行
should_continue = input(f"\n\n是否继续?(Y/N)") if tool_name == "run_terminal_command" else "y"
if should_continue.lower() != 'y':
print("\n\n操作已取消。")
return "操作被用户取消"
try:
observation = self.tools[tool_name](*args)
except Exception as e:
observation = f"工具执行错误:{str(e)}"
print(f"\n\n🔍 Observation:{observation}")
obs_msg = f"<observation>{observation}</observation>"
messages.append({"role": "user", "content": obs_msg})
def get_tool_list(self) -> str:
"""生成工具列表字符串,包含函数签名和简要说明"""
tool_descriptions = []
for func in self.tools.values():
name = func.__name__
signature = str(inspect.signature(func))
doc = inspect.getdoc(func)
tool_descriptions.append(f"- {name}{signature}: {doc}")
return "\n".join(tool_descriptions)
def render_system_prompt(self, system_prompt_template: str) -> str:
"""渲染系统提示模板,替换变量"""
tool_list = self.get_tool_list()
file_list = ", ".join(
os.path.abspath(os.path.join(self.project_directory, f))
for f in os.listdir(self.project_directory)
)
return Template(system_prompt_template).substitute(
operating_system=self.get_operating_system_name(),
tool_list=tool_list,
file_list=file_list
)
@staticmethod
def get_api_key() -> str:
"""Load the API key from an environment variable."""
load_dotenv()
api_key = os.getenv("DEEPSEEK_API_KEY")
if not api_key:
raise ValueError("未找到 DEEPSEEK_API_KEY 环境变量,请在 .env 文件中设置。")
return api_key
def call_model(self, messages):
print("\n\n正在请求模型,请稍等...")
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
)
content = response.choices[0].message.content
messages.append({"role": "assistant", "content": content})
return content
def parse_action(self, code_str: str) -> Tuple[str, List[str]]:
match = re.match(r'(\w+)\((.*)\)', code_str, re.DOTALL)
if not match:
raise ValueError("Invalid function call syntax")
func_name = match.group(1)
args_str = match.group(2).strip()
# 手动解析参数,特别处理包含多行内容的字符串
args = []
current_arg = ""
in_string = False
string_char = None
i = 0
paren_depth = 0
while i < len(args_str):
char = args_str[i]
if not in_string:
if char in ['"', "'"]:
in_string = True
string_char = char
current_arg += char
elif char == '(':
paren_depth += 1
current_arg += char
elif char == ')':
paren_depth -= 1
current_arg += char
elif char == ',' and paren_depth == 0:
# 遇到顶层逗号,结束当前参数
args.append(self._parse_single_arg(current_arg.strip()))
current_arg = ""
else:
current_arg += char
else:
current_arg += char
if char == string_char and (i == 0 or args_str[i-1] != '\\'):
in_string = False
string_char = None
i += 1
# 添加最后一个参数
if current_arg.strip():
args.append(self._parse_single_arg(current_arg.strip()))
return func_name, args
def _parse_single_arg(self, arg_str: str):
"""解析单个参数"""
arg_str = arg_str.strip()
# 如果是字符串字面量
if (arg_str.startswith('"') and arg_str.endswith('"')) or \
(arg_str.startswith("'") and arg_str.endswith("'")):
# 移除外层引号并处理转义字符
inner_str = arg_str[1:-1]
# 处理常见的转义字符
inner_str = inner_str.replace('\\"', '"').replace("\\'", "'")
inner_str = inner_str.replace('\\n', '\n').replace('\\t', '\t')
inner_str = inner_str.replace('\\r', '\r').replace('\\\\', '\\')
return inner_str
# 尝试使用 ast.literal_eval 解析其他类型
try:
return ast.literal_eval(arg_str)
except (SyntaxError, ValueError):
# 如果解析失败,返回原始字符串
return arg_str
def get_operating_system_name(self):
os_map = {
"Darwin": "macOS",
"Windows": "Windows",
"Linux": "Linux"
}
return os_map.get(platform.system(), "Unknown")
def read_file(file_path):
"""用于读取文件内容"""
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
def write_to_file(file_path, content):
"""将指定内容写入指定文件"""
with open(file_path, "w", encoding="utf-8") as f:
f.write(content.replace("\\n", "\n"))
return "写入成功"
def run_terminal_command(command):
"""用于执行终端命令"""
import subprocess
run_result = subprocess.run(command, shell=True, capture_output=True, text=True)
return "执行成功" if run_result.returncode == 0 else run_result.stderr
@click.command()
@click.argument('project_directory',
type=click.Path(exists=True, file_okay=False, dir_okay=True))
def main(project_directory):
project_dir = os.path.abspath(project_directory)
tools = [read_file, write_to_file, run_terminal_command]
agent = ReActAgent(tools=tools, model="deepseek-v4-flash", project_directory=project_dir)
task = input("请输入任务:")
final_answer = agent.run(task)
print(f"\n\n✅ Final Answer:{final_answer}")
if __name__ == "__main__":
main()
3.2.4 怎么运行
1.安装依赖
pip install openai click python-dotenv
2.创建 .env 文件
DEEPSEEK_API_KEY=你的DeepSeek密钥
3.创建 prompt_template.py
react_system_prompt_template = """
你是一个能调用工具的 AI Agent。
操作系统: $operating_system
可用工具: $tool_list
项目文件: $file_list
用户问话用 <question> 包裹。
每次思考写 <thought>...</thought>。
需要工具写 <action>工具名(参数)</action>。
回答用 <final_answer>...</final_answer>。
"""
4.运行
python react_agent.py 你的项目目录
四、Agent 的记忆体系与容错机制(ReAct 的暗线)
4.1 短期记忆 vs 长期记忆
ReAct 循环里只有 messages 对话列表,这是 短期记忆(受 Context Window 限制)。一旦对话超过 128k Token,早期信息就被“遗忘”了。
长期记忆靠的是 RAG(检索增强生成):
- 外部知识库:历史对话、项目文档、API 手册向量化存入向量数据库(如 Chroma、Pinecone)
- 检索触发:每次 ReAct 循环的
Thought阶段,Agent 先向量检索相关记忆 - 注入上下文:检索结果拼接到当前 Prompt 中
# 伪代码:带长期记忆的 ReAct
def thought_phase(user_query):
# 1. 从向量库检索相关记忆
memories = vector_db.search(user_query, top_k=3)
# 2. 拼接到系统提示
enhanced_prompt = f"历史相关经验:{memories}\n当前任务:{user_query}"
# 3. 走正常的 ReAct 循环
return llm.think(enhanced_prompt)
这样 Agent 才不会“失忆”,这也是企业级 Agent 的标配。
4.2 ReAct 的三大“翻车现场”与容错策略
| 翻车场景 | 现象 | 解决方案 |
|---|---|---|
| 工具报错 | 读不存在的文件,AI 反复尝试读同一个文件 | 将错误信息作为 Observation 喂给 LLM,要求它 “换一个路径” 或 “换一个工具” |
| 陷入循环 | Thought 一直在重复,没有进展 | 在 System Prompt 中增加 “如果连续 3 次 Observation 相同,必须改变策略” 的指令 |
| 规划爆炸 | 一个简单问题拆成 20 步,Token 耗尽 | 引入 Plan-And-Execute(详见第六章),每一步执行前校验“这一步是否必要” |
实战代码补丁(在 System Prompt 中加一条铁律):
重要规则:如果同一个 Action 被执行超过 2 次且 Observation 完全相同,
必须在 Thought 中明确说“此路不通,我换一种方法”,并尝试新的 Action。
五、ReAct 运行时序图
用户 → Agent.run()
↓
渲染系统 Prompt
↓
while 循环 ────────┐
↓ │
调用 LLM API │
↓ │
解析输出 │
↓ │
┌────┴────┐ │
↓ ↓ │
Action Final Answer │
↓ │
执行工具 │
↓ │
获取 Observation │
↓ │
追加到对话历史 ────────┘
↓
返回最终答案
六、进阶模式:Plan-And-Execute
Plan-And-Execute 是 ReAct 的升级版,核心区别在于:
| 对比维度 | ReAct | Plan-And-Execute |
|---|---|---|
| 规划方式 | 边做边想(动态调整) | 先全盘规划,再逐步执行 |
| 执行方式 | 单步推理 + 单步行动 | 一次性生成完整计划,再顺序执行 |
| 适用场景 | 简单任务、探索性任务 | 复杂多步骤任务、有明确流程的任务 |
运行流程示例
用户: "帮我写一个网页爬虫"
Step 1: 全盘规划(一次性生成完整计划)
→ 计划:
1. 导入 requests 和 BeautifulSoup
2. 发送 HTTP 请求
3. 解析 HTML
4. 提取所需数据
5. 保存到 CSV 文件
Step 2: 按计划逐步执行
→ 执行第 1 步 → 观察结果 → 执行第 2 步 → ...
Plan-And-Execute 的优势在于减少推理次数、提高执行效率,但对 LLM 的规划能力要求更高。
七、常见踩坑与调试指南(新手必看)
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
RuntimeError: 模型未输出 <action> | LLM 偷懒直接回答了,没走工具调用 | 在 System Prompt 里强调“必须先输出 Action,除非你有 Final Answer” |
KeyError: 'get_time' | 工具名拼写错误 | 打印 self.tools.keys() 核对,或让 LLM 在 Action 中严格使用工具列表中的名称 |
| API 超时 | DeepSeek 接口响应慢 | 在 client.chat.completions.create 中加 timeout=60 参数 |
| 中文乱码 | 读写文件未指定编码 | 所有 open() 必须强制加 encoding="utf-8" |
| 死循环卡死 | 没有设置最大循环步数 | 代码中必须包含 if step_count >= MAX_STEPS: break 逻辑 |
调试绝招:在 call_model 中打印完整的 messages,看 LLM 到底接收了什么样的上下文,90% 的问题都出在 Prompt 拼接上。
八、总结
本文从概念到代码,一步步拆解了 AI Agent 的核心运行模式 —— ReAct。你可以把这篇文章当作一份“Agent 入门地图”:
- 概念层:理解 Agent = LLM + 工具 + 记忆
- 模式层:掌握 ReAct 的 Thought → Action → Observation 循环
- 代码层:从极简版到真实 API 版,亲手构建一个可运行的 Agent
- 工业层:了解 Function Calling、MCP 协议、RAG 长期记忆与容错策略
理解了这些,你就具备了进一步探索更复杂 Agent 系统(如多 Agent 协作、AutoGPT、Plan-And-Execute 等)的基础。如果在实际运行中踩了坑,欢迎对照第七章的调试指南逐一排查。
更多推荐



所有评论(0)