【实战智能体】《大模型应用开发_动手做AI_Agent》_116.[第6章 Agent实战之ReAct自动定价] 第一轮行动——工具执行搜索的完整链路追踪

从"黑盒魔法"到"透明手术":ReAct第一轮行动完整链路追踪,让你彻底看懂AI Agent是怎么"动脑子"的
本文是《大模型应用开发_动手做AI_Agent》第6章ReAct自动定价实战的核心拆解。很多新手学Agent时,总觉得大模型调用工具是"玄学"——输入一个问题, magically 就返回结果了。但真相是:每一次工具执行背后,都有一条清晰的"思考-行动-观察"链路。本文将带你用"显微镜"视角,完整追踪ReAct第一轮行动中"工具执行搜索"的全流程,从Prompt构造、LLM推理、工具选择、参数填充到结果返回,彻底搞懂Agent的"第一次出手"是怎么完成的。读完这篇,你不仅能调试Agent,更能设计Agent。
目录
- 核心认知篇:ReAct第一轮行动的本质理解
- 链路拆解篇:工具执行搜索的五步完整链路
- 实战追踪篇:代码级追踪与日志解读
- 调试心法篇:Agent开发者的"显微镜"使用手册
- 进阶思考篇:从第一轮行动看Agent设计哲学
嗨,大家好呀,我是你的老朋友精通代码大仙。接下来我们一起学习 《大模型应用开发_动手做AI_Agent》,震撼你的学习轨迹!
“万事开头难,然后中间难,最后结尾难。”
这句程序员圈的自嘲,放在学Agent开发上再贴切不过了。你是不是也有这样的经历:照着教程搭了个ReAct Agent,输入问题后看到控制台哗啦啦输出一堆日志,最后神奇地得到了正确答案。但你心里清楚——“我根本不知道刚才发生了什么”。
更扎心的是,一旦结果不对,你完全无从下手。改Prompt?加示例?调温度参数?全凭玄学试探。这种"黑盒焦虑"是很多新手卡在Agent开发门口的根本原因。
今天咱们就解决这个痛点。以ReAct自动定价场景为例,我会带你用"手术刀"级别的精度,完整追踪第一轮行动中"工具执行搜索"的每一个步骤。注意,是完整链路,不是概览,不是摘要,是每一环都掰开揉碎给你看。
一、核心认知篇:ReAct第一轮行动的本质理解
1.1 ReAct不是框架,是思维模式
很多新手第一次接触ReAct,以为是某个Python库——pip install react那种。其实不是。ReAct(Reasoning + Acting)是一种让大模型交替进行推理和行动的协作模式。
关键区别:传统模式是"闭卷考试",ReAct是"开卷考试+可以翻书"。第一轮行动,就是Agent拿到试卷后,**第一次决定"我要翻哪本书"**的过程。
1.2 第一轮行动的特殊性
为什么专门讲"第一轮"?因为后续轮次都是"观察→思考→行动"的循环,而第一轮是从"零上下文"开始的冷启动。
| 轮次 | 上下文状态 | 决策依据 |
|---|---|---|
| 第一轮 | 只有用户原始问题 | 完全依赖Prompt设计和工具描述 |
| 第二轮及以后 | 已有Thought+Action+Observation历史 | 可以基于之前的结果调整策略 |
这个差异极其关键。第一轮如果选错工具或填错参数,后面可能越跑越偏。就像你导航时第一步就输错目的地,后面再怎么优化路线都是徒劳。
1.3 工具执行搜索的本质
"工具执行搜索"不是简单的函数调用。它是一个多阶段决策过程:
用户意图理解 → 工具语义匹配 → 参数槽位填充 → 执行结果观测
很多新手误以为:只要工具描述写清楚,LLM就能自动选对。太天真了。我看过太多案例,工具描述明明写得很详细,模型还是选了错误的工具,或者把参数填得面目全非。
痛点案例:某同学做电商定价Agent,有两个工具:
search_competitor_price(product_name):查竞品价格search_cost_price(product_name):查成本价
他输入:“iPhone 15 Pro该定什么价?”
结果模型第一轮调用了search_cost_price,得到成本价后直接开始定价,完全没看竞品价格。为什么?因为Prompt里强调"要控制利润率",模型"误以为"成本价更重要。
这就是典型的第一轮行动偏差——模型基于有限的上下文,做出了局部最优但全局次优的选择。
二、链路拆解篇:工具执行搜索的五步完整链路
现在进入核心部分。我把第一轮行动拆解为五个紧密衔接的步骤,每一步都有明确的输入输出和决策逻辑。
2.1 第一步:Prompt构造——给LLM的"任务说明书"
这一步发生在LLM收到任何输入之前。系统需要把"原始问题"包装成LLM能理解的格式。
标准ReAct Prompt结构:
你是专业的电商定价助手,使用以下工具帮助用户定价。
工具列表:
1. search_market_price(product_name: str) -> 查询商品的市场均价
2. search_competitor_price(product_name: str, platform: str = "all") -> 查询竞品价格
3. calculate_profit(cost: float, price: float) -> 计算利润率
请按以下格式思考:
Thought: 你需要做什么
Action: 工具名称
Action Input: 参数JSON
开始!
用户问题:iPhone 15 Pro该定什么价?
新手常见错误:工具描述写得太简略,或者格式不统一。
# ❌ 错误示例:描述模糊,格式混乱
tools = [
{"name": "search", "desc": "查价格"}, # 查什么价格?参数是什么?
{"name": "calc", "desc": "计算"} # 计算什么?
]
# ✅ 正确示例:结构化描述,明确参数
tools = [
{
"name": "search_market_price",
"description": "查询指定商品在当前市场的平均售价,用于了解价格基准",
"parameters": {
"product_name": {"type": "string", "description": "商品完整名称,如'iPhone 15 Pro 256GB'"}
}
}
]
关键洞察:Prompt构造的质量,直接决定了第一轮行动的"起跑线"高度。工具描述不是写给人类看的,是写给LLM"理解"的。要用LLM的"语言习惯"来写——清晰、结构化、无歧义。
2.2 第二步:LLM推理——模型怎么"想"的
Prompt送进去后,LLM开始生成内容。在ReAct模式下,模型被训练(或通过Few-shot引导)输出特定格式。
典型生成过程:
Thought: 用户询问iPhone 15 Pro的定价建议。我需要先了解这款产品的市场价格水平,才能给出合理建议。我应该先查询市场均价。
Action: search_market_price
Action Input: {"product_name": "iPhone 15 Pro"}
这里有两个关键决策点:
| 决策点 | 说明 | 常见错误 |
|---|---|---|
| Thought生成 | 模型对任务的内部规划 | Thought过于简略或跑题 |
| Action解析 | 从Thought提取可执行动作 | Action名称拼写错误、格式不符 |
痛点案例:某同学发现模型总是输出"Final Answer"而不调用工具。排查后发现,他的Few-shot示例里最后一个例子是直接回答的,模型"学会"了偷懒。调整示例顺序后解决。
调试技巧:在这一步打断点,打印原始输出。不要依赖封装好的库,要看到模型"裸奔"时的真实输出。
2.3 第三步:工具选择——从arsenal里挑武器
拿到Action: search_market_price后,系统需要:
- 确认这个工具是否存在
- 获取工具的参数schema
- 准备进入参数填充阶段
新手噩梦场景:模型输出了Action: search market price(带空格),或者Action: SearchMarketPrice(驼峰命名),与注册名不匹配。
防御性编程建议:
# 工具注册时做名称归一化
def normalize_tool_name(name: str) -> str:
return name.lower().replace(" ", "_").replace("-", "_")
# 匹配时双向归一化
registered_tools = {normalize_tool_name(t.name): t for t in tools}
requested_name = normalize_tool_name(parsed_action)
tool = registered_tools.get(requested_name)
2.4 第四步:参数填充——精确制导的关键
这是最容易翻车的环节。模型需要把Thought中的意图,转化为符合schema的JSON参数。
理想情况:
Action Input: {"product_name": "iPhone 15 Pro"}
现实情况可能是:
Action Input: {"product": "iPhone 15 Pro"} # 参数名错误
Action Input: {"product_name": "iPhone"} # 参数值不完整
Action Input: {"product_name": "iPhone 15 Pro", "extra": "blah"} # 多余参数
Action Input: 不是有效的JSON # 格式完全错误
参数填充的三层校验:
实战技巧:在自动定价场景中,product_name的准确性至关重要。建议增加参数后处理:
def normalize_product_name(raw_name: str) -> str:
# 统一大小写
name = raw_name.strip()
# 扩展常见缩写
expansions = {
"15 pro": "iPhone 15 Pro",
"15pro": "iPhone 15 Pro",
"苹果15pro": "iPhone 15 Pro"
}
return expansions.get(name.lower(), name)
2.5 第五步:结果返回——观察与反思
工具执行完成后,结果被包装为Observation,送回LLM。第一轮行动到此结束。
Observation的标准格式:
Observation: [工具返回的原始结果,或经过精简处理的结果]
关键决策:Observation应该保持原始,还是做预处理?
| 策略 | 优点 | 缺点 |
|---|---|---|
| 原始返回 | 信息完整,LLM自主筛选 | 可能超出上下文长度,包含噪声 |
| 精简处理 | 节省token,聚焦关键信息 | 可能丢失LLM需要的有用细节 |
自动定价场景的建议:对搜索结果做结构化提取,保留核心数据字段。
# 原始搜索结果可能很长
raw_result = {
"items": [
{"platform": "京东", "price": 7999, "seller": "Apple旗舰店"},
{"platform": "天猫", "price": 7899, "seller": "xx数码"},
# ... 几十条数据
]
}
# 精简为Observation
observation = {
"market_price_range": [7899, 8299],
"average_price": 8049,
"sample_count": 15
}
小结:第一轮行动的完整链路,是从"用户问题"到"首次观察"的转化过程。Prompt构造定基调,LLM推理做决策,工具选择+参数填充保执行,结果返回供反思。每一环都可能是故障点,也都值得精细化打磨。
三、实战追踪篇:代码级追踪与日志解读
理论讲完,咱们上代码。我会展示一个完整的、可运行的追踪示例,并教你读日志的"显微镜"技巧。
3.1 最小可运行示例
import json
import re
from dataclasses import dataclass
from typing import Callable, Dict, Any
@dataclass
class Tool:
name: str
description: str
parameters: Dict[str, Any]
func: Callable
def execute(self, **kwargs):
# 参数校验简化版
return self.func(**kwargs)
class ReActTracer:
"""带完整链路追踪的ReAct执行器"""
def __init__(self, llm_client, tools: list):
self.llm = llm_client
self.tools = {t.name: t for t in tools}
self.history = [] # 追踪日志
def _build_prompt(self, query: str) -> str:
"""第一步:Prompt构造"""
tool_desc = "\n".join([
f"{name}: {t.description}\n参数: {json.dumps(t.parameters, ensure_ascii=False)}"
for name, t in self.tools.items()
])
prompt = f"""你是专业定价助手,按ReAct格式思考。
可用工具:
{tool_desc}
格式要求:
Thought: 你的思考过程
Action: 工具名称
Action Input: 参数JSON(严格符合工具参数要求)
用户问题:{query}
Thought:"""
return prompt
def _parse_llm_output(self, text: str) -> Dict:
"""第二步:解析LLM输出"""
# Thought提取
thought_match = re.search(r'Thought:\s*(.*?)(?=Action:|$)', text, re.DOTALL)
thought = thought_match.group(1).strip() if thought_match else ""
# Action提取
action_match = re.search(r'Action:\s*(\w+)', text)
action_name = action_match.group(1) if action_match else None
# Action Input提取
input_match = re.search(r'Action Input:\s*(\{.*?\})', text, re.DOTALL)
action_input = {}
if input_match:
try:
action_input = json.loads(input_match.group(1))
except json.JSONDecodeError as e:
self._log("PARSE_ERROR", f"JSON解析失败: {e}, 原始文本: {input_match.group(1)}")
return {
"thought": thought,
"action_name": action_name,
"action_input": action_input,
"raw_output": text
}
def _select_tool(self, name: str) -> Tool:
"""第三步:工具选择"""
if name not in self.tools:
available = list(self.tools.keys())
raise ValueError(f"工具'{name}'不存在。可用: {available}")
return self.tools[name]
def _fill_and_validate(self, tool: Tool, raw_params: Dict) -> Dict:
"""第四步:参数填充与校验"""
schema = tool.parameters
filled = {}
# 检查必需参数
for param_name, param_info in schema.items():
if param_name not in raw_params:
if param_info.get("required", True):
raise ValueError(f"缺少必需参数: {param_name}")
filled[param_name] = param_info.get("default")
else:
# 类型转换简化处理
filled[param_name] = raw_params[param_name]
# 检查多余参数
extra = set(raw_params.keys()) - set(schema.keys())
if extra:
self._log("PARAM_WARNING", f"忽略多余参数: {extra}")
return filled
def _execute_tool(self, tool: Tool, params: Dict) -> Any:
"""执行并返回Observation"""
self._log("TOOL_CALL", f"{tool.name}({params})")
result = tool.execute(**params)
self._log("TOOL_RESULT", result)
return result
def _log(self, stage: str, content: Any):
"""记录追踪日志"""
entry = {
"stage": stage,
"content": content,
"timestamp": len(self.history) # 简化时间戳
}
self.history.append(entry)
# 实时打印,方便调试
print(f"\n[{stage}]")
print(json.dumps(content, ensure_ascii=False, indent=2) if isinstance(content, (dict, list)) else content)
def run_first_round(self, query: str) -> Dict:
"""执行完整的第一轮行动"""
self._log("START", f"用户查询: {query}")
# Step 1: Prompt构造
prompt = self._build_prompt(query)
self._log("PROMPT", prompt)
# Step 2: LLM推理
llm_output = self.llm.complete(prompt) # 模拟LLM调用
self._log("LLM_RAW", llm_output)
parsed = self._parse_llm_output(llm_output)
self._log("PARSED", parsed)
# Step 3: 工具选择
tool = self._select_tool(parsed["action_name"])
self._log("TOOL_SELECTED", {"name": tool.name, "desc": tool.description[:50]})
# Step 4: 参数填充
validated_params = self._fill_and_validate(tool, parsed["action_input"])
self._log("PARAMS_VALIDATED", validated_params)
# Step 5: 执行与观察
observation = self._execute_tool(tool, validated_params)
return {
"thought": parsed["thought"],
"action": parsed["action_name"],
"observation": observation,
"history": self.history
}
# ============ 模拟运行 ============
# 模拟LLM(实际项目中替换为真实API)
class MockLLM:
def complete(self, prompt: str) -> str:
# 模拟第一轮行动的典型输出
return """我需要先查询iPhone 15 Pro的市场价格,了解当前市场行情后再给出定价建议。
Action: search_market_price
Action Input: {"product_name": "iPhone 15 Pro"}"""
# 定义工具
def mock_search_price(product_name: str):
# 模拟价格查询
prices = {
"iPhone 15 Pro": {"min": 7899, "max": 8299, "avg": 8049, "count": 23}
}
return prices.get(product_name, {"error": "未找到该商品"})
tools = [
Tool(
name="search_market_price",
description="查询商品市场均价,返回价格区间和统计数据",
parameters={
"product_name": {"type": "string", "required": True}
},
func=mock_search_price
)
]
# 执行追踪
tracer = ReActTracer(MockLLM(), tools)
result = tracer.run_first_round("iPhone 15 Pro该定什么价?")
3.2 日志解读技巧
运行上面的代码,你会看到结构化的追踪输出。教你怎么"读":
[PROMPT]阶段:检查工具描述是否被正确嵌入,格式是否符合预期。如果LLM输出异常,首先回查这里。
[LLM_RAW]阶段:这是模型的"脑电波"原始信号。重点关注:
- Thought是否体现了正确的任务分解
- Action名称是否精确匹配注册名
- Action Input是否为合法JSON
[PARSED]阶段:验证解析逻辑是否 robust。如果这里出现PARSE_ERROR,说明需要加强正则或增加重试机制。
[TOOL_SELECTED]阶段:确认工具匹配无误。如果经常选错工具,考虑优化工具描述或增加Few-shot示例。
[PARAMS_VALIDATED]阶段:检查参数转换是否符合预期。这是数据质量的关键防线。
[TOOL_CALL] → [TOOL_RESULT]阶段:确认执行链路通畅,结果格式统一。
3.3 常见翻车现场与诊断
| 现象 | 根因定位 | 修复策略 |
|---|---|---|
| 模型不输出Action,直接回答 | Prompt缺少格式约束/Few-shot示例不当 | 增加强制格式说明,调整示例顺序 |
| Action名称匹配失败 | 模型输出格式不标准(空格、大小写) | 名称归一化处理,增加容错匹配 |
| JSON解析失败 | 模型输出非标准JSON(单引号、注释等) | 增加JSON修复逻辑,或使用更宽松的解析器 |
| 参数名不匹配 | 模型对参数理解有误 | 优化参数描述,增加枚举示例 |
| 工具执行报错 | 参数值非法或工具内部错误 | 增加参数预处理,完善工具错误处理 |
真实案例:某次运行中,模型输出了:
Action Input: {'product_name': 'iPhone 15 Pro'}
注意是单引号。标准json.loads直接报错。解决方案:
import ast
def robust_json_parse(text: str) -> Dict:
"""容忍单引号等常见JSON变体"""
try:
return json.loads(text)
except json.JSONDecodeError:
try:
# 尝试用ast.literal_eval处理Python字面量
return ast.literal_eval(text)
except:
raise ValueError(f"无法解析: {text[:100]}")
四、调试心法篇:Agent开发者的"显微镜"使用手册
工具链有了,但调试Agent和调试普通程序完全不同。你需要学会"读模型的想法"。
4.1 断点应该打在哪里
传统调试:断点打在变量赋值处,检查状态。
Agent调试:断点打在信息转换的边界处。
最常被忽略的断点:LLM原始输出。太多人直接看解析后的结果,错过了模型"犹豫"、"犯错"的宝贵信号。
4.2 如何阅读模型的"脑电波"
Thought部分不是装饰,是可观测的推理过程。
健康的Thought特征:
- 明确提及任务目标
- 说明选择某工具的理由
- 体现对参数的思考
危险的Thought信号:
- 过于简略(“我需要查一下”)
- 跑题(开始讨论无关话题)
- 自我矛盾(先说查A,实际调B)
调试技巧:收集100条Thought样本,分类标注,找出模型的"惯性错误模式"。
4.3 从失败案例反推设计
当第一轮行动失败时,用逆向分析法:
结果错误 ← 观察质量差 ← 工具执行问题 ← 参数错误 ← 解析失败 ← LLM输出异常 ← Prompt引导不足
逐层回溯,找到最早的故障点。不要头痛医头,要在根源处修复。
案例:模型总是把"iPhone 15 Pro"和"iPhone 15 Pro Max"混淆,导致查询错误。
表面修复:在参数处理时做精确匹配。
深层修复:在Prompt中增加区分示例,在工具描述中强调型号精确性。
五、进阶思考篇:从第一轮行动看Agent设计哲学
5.1 第一轮行动的设计模式
基于追踪经验,我总结出三种第一轮启动模式:
| 模式 | 适用场景 | 特点 |
|---|---|---|
| 侦察兵模式 | 信息不足,需要探索 | 先调用宽泛搜索,获取概览 |
| 狙击手模式 | 信息明确,目标清晰 | 直接调用精确工具,一击即中 |
| 组合拳模式 | 多维度信息需求 | 并行调用多个工具(需架构支持) |
自动定价场景通常用侦察兵模式:先查市场均价,再视情况深入。
5.2 多工具场景的优先级策略
当工具数量增加时,第一轮选择的复杂度指数上升。
策略建议:
- 工具分类标签:给工具打"信息类"、“计算类”、"决策类"标签
- 依赖图构建:明确工具间的数据依赖关系
- 动态描述:根据上下文,动态调整工具描述的权重
5.3 从ReAct到更复杂的Agent架构
ReAct是Agent的"手动挡",适合理解原理。但生产环境往往需要"自动挡":
- Plan-and-Solve:先规划完整步骤,再执行
- Tree of Thoughts:多路径探索,择优而行
- Multi-Agent协作:多个专业Agent分工
但无论多复杂,第一轮行动的决策质量始终是瓶颈。本文追踪的链路,是所有复杂架构的共同基础。
写在最后
学Agent开发,最怕的就是"知其然不知其所以然"。看着Demo能跑,一改就崩;调参能work,换场景就废。这种无力感,我经历过,也知道怎么走出来。
今天的完整链路追踪,就是给你的"显微镜"。当你能看清每一步的数据流动、每一次模型的决策依据,你就从"调参师"变成了"架构师"。
ReAct的第一轮行动,看似简单,实则浓缩了Agent设计的核心挑战:如何让LLM在信息不完全的情况下,做出合理的首次决策。这个问题没有标准答案,但有系统的方法论——Prompt工程、工具设计、错误处理、反馈优化,环环相扣。
编程之路不易,但每一步成长都算数。Agent开发是AI时代的新战场,你现在花的时间,都是在搭建自己的技术护城河。
保持好奇,持续追踪,你也能成为代码高手。
关注私信备注:“资料代找获取”,全网计算机学习资料代找:例如:
《课程:2026 年多模态大模型实战训练营》
《课程:AI 大模型工程师系统课程 (22 章完整版 持续更新)》
《课程:AI 大模型系统实战课第四期 (2026 年开课 持续更新)》
《课程:2026 年 AGI 大模型系统课 23 期》
《课程:2026 年 AGI 大模型系统课 21 期》
《课程:AI 大模型实战课 8 期 (2026 年 2 月最新完结版)》
《课程:AI 大模型系统实战课三期》
《课程:AI 大模型系统课程 (2026 年 2 月开课 持续更新)》
《课程:AI 大模型全阶课程 (2025 年 12 月开课 2026 年 6 月结课)》
《课程:AI 大模型工程师全阶课程 (2025 年 10 月开课 2026 年 4 月结课)》
《课程:2026 年最新大模型 Agent 开发系统课 (持续更新)》
《课程:LLM 多模态视觉大模型系统课》
《课程:大模型 AI 应用开发企业级项目实战课 (2026 年 1 月开课)》
《课程:大模型智能体线上速成班 V2.0》
《课程:Java+AI 大模型智能应用开发全阶课》
《课程:Python+AI 大模型实战视频教程》
《书籍:软件工程 3.0: 大模型驱动的研发新范式.pdf》
《课程:人工智能大模型系统课 (2026 年 1 月底完结版)》
《课程:AI 大模型零基础到商业实战全栈课第五期》
《课程:Vue3.5+Electron + 大模型跨平台 AI 桌面聊天应用实战 (2025)》
《课程:AI 大模型实战训练营 从入门到实战轻松上手》
《课程:2026 年 AI 大模型 RAG 与 Agent 智能体项目实战开发课》
《课程:大模型训练营配套补充资料》
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)