智能体构建的三种经典套路:从零开始理解ReAct、Plan-and-Solve和Reflection
学完大语言模型的基础后,下一步就是让模型"动"起来,也就是构建智能体。智能体的核心很简单:让大模型能调用外部工具(比如搜索引擎、计算器、API),从而完成它自己做不到的事。但怎么组织它的"思考"和"行动"呢?这就像不同的人有不同的做事风格。第四章介绍了三种最经典的构建套路,亲手实现它们,比直接用现成框架更能理解背后的门道。
一、 基础准备:让模型能使用工具
在开始任何一种套路之前,我们得先解决一个基本问题:如何告诉并允许大模型去使用工具?
这主要靠两部分:
-
定义工具:就像给模型一个工具箱。每个工具要有明确的名字、用途描述(这个描述至关重要,模型就靠它来判断什么时候用哪个工具)和具体的执行函数。
-
设计交互格式 :我们需要和模型约定一个"暗号"。通常让模型的输出包含两部分:Thought (内心独白,解释它为什么这么做)和Action (具体行动指令,比如
Search[关键词])。然后,我们的程序会解析这个Action,调用对应工具,并把工具返回的结果整理成一段清晰的Observation(观察),塞回给模型进行下一步思考。
有了这个基础,三种不同的"做事风格"就可以登场了。
二、 套路一:ReAct ------ 边想边做的"侦探"
核心思想 :模仿人类解决未知问题的过程,采用 思考(Reason)-行动(Act)-观察(Observe) 的循环。模型不会在一开始就定死所有步骤,而是走一步看一步,根据上一步的结果动态调整下一步计划。
工作流程:
- 接到问题(比如"华为最新手机是什么?")。
- 思考:分析问题,决定下一步做什么("我需要搜索最新信息")。
- 行动 :执行行动(调用
Search["华为最新手机"])。 - 观察:获得搜索结果,并总结成文本。
- 带着这个新观察,回到第2步继续思考,直到它认为可以给出最终答案。
好比一个侦探破案:他不会有一个完美的预设剧本,而是根据每一条新线索(观察)来推理(思考)下一步该问谁、查哪里(行动)。
- 优点:非常灵活,能适应需要探索和外部信息输入的任务(如查天气、搜新闻)。整个过程透明,能看到它的"心路历程"。
- 缺点:一步步串行进行,可能比较慢;如果模型某一步"想歪了",可能陷入死循环或错误路径。
三、 套路二:Plan-and-Solve ------ 先规划后执行的"建筑师"
核心思想:把"规划"和"执行"彻底分开。先花时间制定一个详细的计划蓝图,然后严格按蓝图执行。
工作流程:
- 规划阶段 :模型只做一件事------把复杂任务分解成一步步清晰的子任务列表。例如,对于问题"三天共卖出多少苹果?",计划可能是:
["计算周二销量", "计算周三销量", "计算总销量"]。 - 执行阶段:另一个专门的模块,严格按照上面的计划列表,一步步执行。执行每一步时,都知道整体计划和之前步骤的结果。
好比建筑师盖楼:必须先画出完整的设计图纸(规划),然后施工队才能按图施工(执行),不会中途随意改结构。
- 优点:对于逻辑清晰、可分解的任务(如数学应用题、写报告),结构稳定,目标不容易跑偏。
- 缺点:计划是静态的,如果执行中发现计划有误或环境变了,难以中途调整。另外,需要为"规划"单独调用一次模型,有额外开销。
四、 套路三:Reflection ------ 反复打磨的"作家"
核心思想 :为智能体加上 "自我反思和修改" 的能力。先出一个初稿,然后自我批判,不断优化。
工作流程:
- 执行:用任何一种方法(比如ReAct或Plan-and-Solve)生成一个初始结果或方案(比如一段代码、一个答案)。
- 反思:让模型(或另一个评审角色)审查这个初稿,找出其中的错误、逻辑漏洞或可改进点,生成具体的"反馈意见"。
- 优化:根据初稿和反馈意见,让模型重新生成一个更好的版本。
- 可以重复2-3步多次,进行多轮迭代优化。
好比作家写文章:先快速完成初稿,然后通读反思(这里表述不清,那里论据不足),最后修改润色,甚至重写某些段落,直到满意。
- 优点:能显著提升最终成果的质量,尤其适合对正确性、优雅性要求高的任务(如代码生成、报告撰写)。
- 缺点:迭代过程耗时更长,计算成本更高。
五、 小结:怎么选?
这三种套路没有绝对的好坏,关键看你的任务是什么:
| 任务特点 | 推荐套路 | 类比 |
|---|---|---|
| 需要查资料、与外部交互,路径不确定 | ReAct | 侦探查案 |
| 逻辑清晰、步骤分明,侧重内部推理 | Plan-and-Solve | 建筑师施工 |
| 对结果质量要求极高,不怕多花时间打磨 | Reflection | 作家改稿 |
当然,你也可以组合使用它们。比如,用 Plan-and-Solve 来制定总体策略,用 ReAct 来完成其中需要探索的步骤,最后用 Reflection 来优化整个方案。
亲手实现一遍这些套路,最大的收获不是代码本身,而是真正理解了不同智能体设计背后的权衡:是追求灵活还是稳定?是看重速度还是质量?想清楚这些问题,你就不再只是框架的使用者,而能成为智能体应用的设计者了。
习题
第四章习题 第一题解答
1. 三种范式在"思考"与"行动"的组织方式上的本质区别
这三种范式的核心区别在于如何处理"规划"(思考)与"执行"(行动)的时序关系和反馈机制。
-
ReAct (Reasoning and Acting)
其本质是 "边想边做,动态调整" 。它将思考与行动紧密耦合在一个循环中(Thought → Action → Observation)。在每个时间步,智能体基于当前所有历史信息进行即时思考,并执行单个行动,然后立即根据行动结果调整下一步的思考。这种范式强调环境适应性和动态纠错能力,规划是渐进式、反应式的。
形式化表达 : ( t h t , a t ) = π ( q , ( a 1 , o 1 ) , ... , ( a t − 1 , o t − 1 ) ) (th_t, a_t) = \pi(q, (a_1, o_1), \ldots, (a_{t-1}, o_{t-1})) (tht,at)=π(q,(a1,o1),...,(at−1,ot−1))。 -
Plan-and-Solve
其本质是 "先谋后动,严格执行" 。它将流程明确解耦为两个串行阶段:规划阶段和执行阶段。在规划阶段,智能体一次性生成一个完整的、静态的行动计划 P = π plan ( q ) P = \pi_{\text{plan}}(q) P=πplan(q)。在执行阶段,智能体严格按计划步骤逐一执行 s i = π solve ( q , P , ( s 1 , ... , s i − 1 ) ) s_i = \pi_{\text{solve}}(q, P, (s_1, \dots, s_{i-1})) si=πsolve(q,P,(s1,...,si−1)),每一步的思考主要关注如何完成当前步骤,而非修改全局计划。这种范式强调结构性和目标一致性,规划是前瞻性、一次性的。
-
Reflection
其本质是 "事后反思,迭代优化" 。它在一次完整的"执行"之后,引入了一个独立的"反思-优化"循环。智能体首先通过某种方法生成一个初始输出("初稿"),然后由一个反思模型对其进行评审,生成结构化反馈 F i = π reflect ( Task , O i ) F_i = \pi_{\text{reflect}}(\text{Task}, O_i) Fi=πreflect(Task,Oi),最后优化模型根据反馈生成改进版输出 O i + 1 = π refine ( Task , O i , F i ) O_{i+1} = \pi_{\text{refine}}(\text{Task}, O_i, F_i) Oi+1=πrefine(Task,Oi,Fi)。这种范式不直接改变"思考-行动"的组织方式,而是为其增加了一个质量提升回路,核心价值在于通过自我批判和迭代来显著提升解决方案的最终质量。
2. 智能家居控制助手的基础架构选择
我会选择 以 ReAct 范式为基础架构,并融合 Reflection 机制。
为什么以 ReAct 为基础:
智能家居环境是典型的动态、部分可观察、序贯的环境。用户习惯的"自动调节"意味着智能体需要持续感知环境状态(如时间、温度、光照、人体传感器信号),并根据这些实时观察进行决策。
- 动态性与实时响应:ReAct 的步进式"感知-思考-行动"循环非常适合处理持续变化的环境。例如,当传感器检测到有人离开房间,智能体需要立即思考并执行关灯、调节空调等动作,这种"边想边做"的模式能保证低延迟响应。
- 处理不确定性:传感器数据可能有噪声或延迟,用户行为也可能有例外。ReAct 的机制允许智能体根据每一次行动后的新观察来动态调整后续行动,具备很强的环境适应和纠错能力。
- 多工具协同:控制灯光、空调、窗帘等涉及调用多个不同的工具(设备API)。ReAct 范式天然支持在思考过程中进行工具选择,可以根据复杂的上下文决定调用一个组合动作。
为什么需要融合 Reflection:
"根据用户习惯自动调节"是一个需要长期学习和优化的任务。单纯的 ReAct 擅长即时反应,但缺乏对长期策略的优化能力。
- 优化习惯模型:可以定期(例如每天深夜)启动一个 Reflection 循环,回顾一天内的所有控制决策、用户手动干预记录以及环境数据。反思模型可以分析这些数据,评估当前习惯学习算法的有效性,并提出优化建议。然后优化模型据此更新内部的习惯模型或策略。
- 提升决策质量:对于复杂的场景(如"举办聚会"模式),Reflection 可以帮助生成和优化更精细、更节能或更舒适的控制方案,而不仅仅是即时反应。
因此,ReAct 负责日常的实时感知与控制,Reflection 负责周期性的策略与模型优化,两者结合可以构建一个既灵敏又智能的助手。
3. 三种范式的组合使用与混合架构设计
可以。这三种范式并非互斥,它们解决的是智能体工作流中不同层面的问题,可以有机组合。
混合范式架构设计:一个"高级研究与报告生成智能体"
适用场景:需要完成复杂、开放域的研究任务并生成高质量报告,例如"分析量子计算对金融风险管理领域的潜在影响与挑战"。
架构设计:
顶层:Plan-and-Solve 作为总指挥
- 角色:接收用户复杂任务,进行高层、宏观的任务分解。
- 输出 :生成一个结构化研究计划,例如:
- 阶段一:理解量子计算基本原理与当前发展水平
- 阶段二:调研金融风险管理现有技术与痛点
- 阶段三:交叉分析量子计算在金融风控的具体应用场景与可行性
- 阶段四:识别主要挑战(技术、伦理、监管)
- 阶段五:撰写综合分析报告
- 作用:确保整个复杂任务不偏离主线,有清晰的阶段性目标。
中层:ReAct 作为每个阶段的执行引擎
- 角色:被分配去执行计划中的每一个具体阶段(子任务)。
- 工作模式:对于每个子任务,启动一个 ReAct 循环。它会自主思考,调用各种工具(如学术搜索引擎、文献数据库、专业术语解释工具),收集和整合信息,直到完成该阶段的调研目标,输出阶段摘要。
- 作用:提供探索性、自适应的问题解决能力,灵活获取和整合外部知识。
底层:Reflection 作为每个阶段和最终输出的质量优化器
- 角色 :
- 阶段内反思:在每个 ReAct 阶段执行过程中或结束后,对收集到的信息、初步结论进行反思,评估其相关性、可信度和完整性,可能触发 ReAct 进行补充搜索或修正推理。
- 阶段间反思:在一个阶段完成后,反思该阶段成果是否充分满足了计划的要求,为进入下一阶段做好准备。
- 终稿反思:在所有阶段完成后,对初步生成的完整报告进行多维度反思(逻辑连贯性、论据充分性、语言专业性、格式规范性),驱动多轮优化,产生最终的高质量报告。
- 作用:确保每一个中间步骤和最终输出的深度、准确性和严谨性,克服单一范式的幻觉或浅层问题。
总结
在这个混合架构中,Plan-and-Solve 提供了骨架(规划),ReAct 填充了血肉(执行),Reflection 打磨了品质(优化)。它适用于任何需要系统性规划、深度探索和高质量产出的复杂知识工作场景,如战略咨询、学术研究、复杂技术方案设计等。
习题
习题1:三种范式的本质区别与选择
- 三种范式在"思考"与"行动"的组织方式上有什么本质区别?
• ReAct(思考-行动-观察循环):它的核心是 "交织"。思考和行动在每一步都紧密耦合,像一个实时策略游戏。模型每轮都先"想"一下(分析现状、规划下一步),然后立刻"做",并根据"做"的结果(观察)来调整下一轮的"想"。这是一个动态、适应性的循环。
• Plan-and-Solve(先规划后执行):它的核心是 "分离"。把"思考"(制定完整计划)和"行动"(执行计划)彻底分成两个阶段,像瀑布流开发模型。先集中所有精力做顶层设计,生成一个步骤清单,然后几乎不带思考地、机械地按清单执行。
• Reflection(执行-反思-优化):它的核心是 "迭代"。它把"思考"升级为"批判性思考",把"行动"升级为"版本迭代"。首先生成一个版本(执行),然后以一个评审员的视角挑毛病(反思),最后根据反馈生成改进版(优化)。思考服务于质量的递进,而非单步的决策。 - 设计"智能家居控制助手",你会选择哪种范式?为什么?
我会选择 以ReAct为基础,融合Reflection机制。
• 为什么不是纯Plan-and-Solve? 家居环境是动态、部分可观察的。用户习惯会变,传感器数据(如温度、光照)实时变化,一个预先制定的静态计划很难适应。比如计划"晚上7点开灯",但今天阴天,6点天就黑了,计划就失效了。
• 为什么ReAct更合适? 它能很好地处理动态环境。例如:
• Thought: "当前光照传感器显示很暗,但时间才下午6点,比平常早。我需要检查用户是否有'根据光照开灯'的偏好。"
• Action: 查询用户偏好["光照触发开灯"]
• Observation: "用户设置了'光照低于50lux自动开灯'。"
• Thought: "符合条件,执行开灯。"
• Action: 控制灯具["打开"]
这种边感知、边判断、边执行的方式非常适合家居场景。
• 为什么加入Reflection? 用于长期优化。例如,助手可以定期(比如每周)反思自己的控制记录:"过去一周,每次下雨天用户都会手动调高空调温度,我是否应该学习这个模式,自动关联'下雨'和'提高设定温度'?" 通过反思来优化未来的决策策略。 - 是否可以将这三种范式进行组合使用?请设计一个混合范式架构。
完全可以,而且强大的智能体系统往往是混合的。我设计一个 "分层-循环-迭代" 架构:
• 顶层 (Plan-and-Solve):负责接收宏大、复杂的任务(如"为我规划一个健康的周末"),并进行高层分解。输出一个粗粒度计划:["制定健身计划", "安排饮食", "推荐放松活动"]。
• 中层 (ReAct):每个高层计划项由一个专门的ReAct子智能体执行。例如"制定健身计划"子智能体,会循环进行:思考用户体能历史 -> 调用健身知识库工具 -> 生成初步方案 -> 思考方案合理性 -> 调整... 直到完成一个具体的健身计划。
• 底层 (Reflection):每个子智能体在输出结果前,进行自我反思优化。同时,在顶层计划全部执行完毕后,一个全局Reflection模块会回顾整个周末规划的质量,评估用户满意度(可通过后续反馈或传感器数据),生成改进报告,用于优化未来类似任务的规划逻辑。
适用场景:复杂的个人生活助理、项目管理系统、多阶段研发流程自动化。这些任务既有需要宏观规划的顶层设计,又有需要灵活执行的具体步骤,还需要持续的经验积累和优化。
习题2:ReAct输出解析的脆弱性与改进 - 当前正则表达式解析方法的潜在脆弱性?
• 格式偏离:如果LLM没有严格按照 Thought: ... 和 Action: ... 的格式输出,比如写成 想法: 或 动作:,或者忘记换行,正则表达式就会匹配失败。
• 内容嵌套:如果 Thought 或 Action 的内容中本身包含了"Thought:"或"Action:"这样的字符串,或者 Action 的参数里包含了复杂的、不配对的括号 ],正则表达式可能会错误地截断或匹配。
• 模型"废话":LLM可能在格式化的输出前后加上一些自然语言描述,如"好的,我将按照要求输出:",这会干扰解析。
• 多Action或缺失:理论上,模型可能输出多个Action,或只输出Thought没有Action,简单的正则可能无法处理这些边缘情况。 - 除了正则表达式,还有哪些更鲁棒的输出解析方案?
• 结构化输出(JSON模式):这是最鲁棒的方法。在提示词中明确要求LLM输出一个严格的JSON对象,例如 {"thought": "...", "action": "..."}。然后使用 json.loads() 解析。现代LLM的API(如OpenAI)甚至原生支持请求 response_format={ "type": "json_object" },强制模型输出合法JSON。
• 使用解析库(如Pydantic):定义严格的Pydantic模型来描述输出结构,结合验证逻辑。这不仅能解析,还能进行数据类型验证。
• 文法解析器:对于非常复杂的、接近自然语言的指令,可以使用更高级的解析器,但杀鸡用牛刀。
• LLM二次解析:用一个轻量、快速的模型(或同一个模型的新调用)专门来解析前一个模型的非结构化输出,将其转换为结构化数据。成本高,但容错性也高。 - 尝试修改代码,使用一种更可靠的输出格式。
方案:采用 JSON模式 替代原有的文本模板。
修改提示词模板:
REACT_JSON_PROMPT_TEMPLATE = """
你是一个有能力调用外部工具的智能助手。
可用工具如下:
{tools}
你必须以严格的JSON格式回应,包含且仅包含两个键:"thought" 和 "action"。
- "thought": 字符串,你的思考过程。
- "action": 字符串,必须是以下格式之一:
{``{tool_name}}[{``{tool_input}}]表示调用工具。Finish[最终答案]表示任务完成。
示例:{"thought": "我需要搜索...", "action": "Search[华为手机]"}
现在,请解决以下问题:
问题: {question}
历史: {history}
"""
修改解析方法:
import json
def _parse_output(self, text: str):
"""解析LLM的JSON输出"""
try: # 1. 尝试从文本中提取JSON块(更安全)
start = text.find('{')
end = text.rfind('}') + 1
if start == -1 or end == 0:
raise ValueError("未找到JSON结构")
json_str = text[start:end]
data = json.loads(json_str)
thought = data.get("thought")
action = data.get("action")
return thought, action
except (json.JSONDecodeError, ValueError, KeyError) as e:
print(f"JSON解析失败: {e}, 原始文本: {text[:100]}...") # 可以在这里加入fallback逻辑,例如尝试用旧的正则方法
return None, None
优缺点对比:
• 优点:稳定性极大提升。JSON是标准格式,解析库成熟,边界情况少。模型遵循JSON格式的能力通常很强。
• 缺点:提示词稍复杂,需要提供示例。对于极老的或非主流的模型,JSON支持可能不佳。输出内容中如果包含未转义的特殊字符(如引号、换行),仍可能导致JSON解析失败,但概率远低于正则匹配失败。
习题3:工具调用扩展与实践
- 为ReAct智能体添加"计算器"工具。
步骤: - 实现计算器函数:使用Python的eval需极度谨慎(安全风险),应使用安全的数学表达式解析库(如asteval)或自己实现一个简单的四则运算解析器。
- 注册到ToolExecutor。
import ast
import operator
class SafeCalculator:
"""一个安全的计算器工具"""
_allowed_operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.USub: operator.neg,
}
@staticmethod
def calculate(expression: str) -> str:
"""计算数学表达式,仅支持基本算术和幂运算"""
try:
# 使用ast解析,确保安全
tree = ast.parse(expression, mode='eval')
result = SafeCalculator._eval_node(tree.body)
return str(result)
except (SyntaxError, TypeError, ZeroDivisionError, KeyError) as e:
return f"计算错误: {e}"
@staticmethod
def _eval_node(node):
if isinstance(node, ast.Constant): # Python 3.8+ 使用ast.Constant
return node.value
elif isinstance(node, ast.Num): # Python 3.7及以下
return node.n
elif isinstance(node, ast.BinOp):
left = SafeCalculator._eval_node(node.left)
right = SafeCalculator._eval_node(node.right)
op_type = type(node.op)
if op_type not in SafeCalculator._allowed_operators:
raise TypeError(f"不允许的操作符: {node.op}")
return SafeCalculator._allowed_operators[op_type](left, right)
elif isinstance(node, ast.UnaryOp):
operand = SafeCalculator._eval_node(node.operand)
op_type = type(node.op)
if op_type not in SafeCalculator._allowed_operators:
raise TypeError(f"不允许的操作符: {node.op}")
return SafeCalculator._allowed_operators[op_type](operand)
else:
raise TypeError(f"不支持的表达式节点: {node}")
注册工具
tool_executor.registerTool(
name="Calculator",
description="用于计算数学表达式,支持加减乘除和幂运算。输入应为一个字符串表达式,如'(123 + 456) * 789 / 12'。",
func=SafeCalculator.calculate
) 2. 设计"工具选择失败"的处理机制。
可以在ReActAgent.run的循环中加入失败计数和引导逻辑:
class ReActAgent:
def init (self, ...):
...
self.consecutive_failures = 0 # 连续失败计数器
self.max_failures = 3 # 最大容忍失败次数
def run(self, question: str):
...
while current_step < self.max_steps:
...
# 在执行Action并得到observation后
if "错误" in observation or "未找到" in observation:
self.consecutive_failures += 1
print(f"⚠️ 行动失败,连续失败次数: {self.consecutive_failures}")
if self.consecutive_failures >= self.max_failures:
# 引导机制:在历史中插入一条明确的系统提示
guidance = "\n系统提示:你似乎在选择工具或参数上遇到了困难。请仔细阅读可用工具的描述,确保你调用的工具名称完全匹配,并且输入参数是工具所期望的格式。"
self.history.append(f"Observation: {observation}{guidance}")
self.consecutive_failures = 0 # 重置计数器,给予新机会
else:
self.history.append(f"Observation: {observation}")
else:
self.consecutive_failures = 0 # 成功则重置
self.history.append(f"Observation: {observation}")
更高级的机制可以是:在达到失败阈值时,触发一个专门的"纠错"子流程,让另一个LLM(或同一个模型用不同的提示词)分析失败历史,并给出具体的调整建议,然后将建议作为强化的Observation反馈给主智能体。3. 工具数量大增(50-100个)时的工程优化。
当工具数量爆炸式增长时,直接把所有工具描述塞进提示词会导致上下文过长、成本剧增,且LLM选择工具的性能会下降。
优化方案:
• 动态工具检索:不要一次性提供所有工具。维护一个工具向量数据库。当智能体需要行动时,先根据当前的Thought内容,用嵌入模型(Embedding)检索最相关的Top K(例如3-5个)工具,只把这几个工具的描述放入提示词。这类似于RAG(检索增强生成)在工具选择上的应用。
• 分层工具目录:对工具进行分类(如"搜索类"、"计算类"、"数据查询类"、"系统控制类")。智能体先决定大类,再在大类下选择具体工具。这可以减少单次决策的选项数量。
• 工具描述优化:工具描述需要高度精炼、标准化,包含明确的关键词,便于检索。可以设计工具描述模板,如"功能:[一句话],输入格式:[示例],输出格式:[示例]"。
• 元工具(Meta-Tool):提供一个ListTools或SearchTools的工具,当智能体不确定时,可以先用这个工具查询可用工具列表,然后再调用。这相当于把工具发现过程也纳入了循环。
习题4:Plan-and-Solve的动态重规划与对比
-
如何设计"动态重规划"机制?
静态计划的致命弱点是无法应对执行时的意外。动态重规划需要让执行器具备反馈能力,并能触发规划器的重新运行。
设计:
-
在执行器Executor.execute的每一步,不仅执行,还要进行结果验证。验证可以是简单的(如检查结果是否为数字),也可以复杂(用另一个LLM调用判断结果是否合理)。
-
如果验证失败,或工具调用返回错误,执行器将当前步骤标记为"阻塞",并收集阻塞上下文(问题、原计划、执行到哪一步、发生了什么错误)。
-
触发重规划器。重规划器接收阻塞上下文,其提示词调整为:"原始计划在执行步骤X时失败,原因是Y。请基于当前情况(已完成步骤A、B、C的结果分别为...),重新生成一个从步骤X开始的新计划,以规避该问题。"
-
用新计划替换旧计划中未完成的部分,继续执行。
class DynamicPlanAndSolveAgent:
def run(self, question: str):
plan = self.planner.plan(question)
attempt = 0
max_replan_attempts = 2
while attempt < max_replan_attempts: result, success, failure_step, failure_reason = self.executor.execute_with_monitoring(question, plan) if success: return result else: print(f"计划在步骤{failure_step}失败: {failure_reason},尝试重规划...") # 获取已成功步骤的历史 partial_history = self.executor.get_history_up_to(failure_step) # 重规划 new_plan = self.replanner.replan(question, plan, failure_step, failure_reason, partial_history) if new_plan: plan = new_plan # 用新计划继续 attempt += 1 else: break return "任务失败,经过多次重规划仍无法解决。" -
对比Plan-and-Solve与ReAct:处理"预订商务旅行"任务。
"预订商务旅行"任务特点:多步骤(机票、酒店、租车)、步骤间有依赖(先有日期才能订酒店)、需要查询外部API(比价、库存)、有明确约束(预算、时间、偏好)。这是一个半结构化任务。
• Plan-and-Solve更合适。
• 理由:任务虽复杂,但步骤逻辑相对清晰,可以预先规划出一个主干流程:[确定出差日期 -> 查询并选择航班 -> 根据航班到达时间选择酒店 -> 根据酒店位置选择租车点]。先规划能确保不遗漏关键环节,并且可以一次性收集所有必要参数(如日期、城市),避免在循环中反复向用户确认。执行阶段可以严格按此流程调用各预订API,效率高,不易跑偏。
• ReAct的劣势:在探索中可能会陷入细节循环,比如在比较多个航班选项时来回搜索,或者忘记预订的后续环节。对于这种目标明确、路径相对固定的任务,ReAct的灵活性反而可能成为效率的负担。
-
设计"分层规划"系统及其优势。
设计:
• 高层规划器:接收模糊目标(如"开发一个手机App")。输出抽象阶段:["需求分析与设计", "前端开发", "后端开发", "测试与部署"]。每个阶段是一个目标,而非具体动作。
• 中层规划器/执行器:每个高层阶段由一个专门的智能体负责。例如"前端开发"智能体接收到目标后,会进行自己的规划:["搭建React Native环境", "实现登录页面UI", "集成状态管理"...],然后执行。它可能内部采用ReAct范式来调用代码生成、UI审查等工具。
• 优势:
-
管理复杂性:将巨型任务分解为模块,分而治之,符合人类项目管理思维。
-
关注点分离:不同层次的智能体可以专精于不同领域(产品经理、前端工程师、后端工程师)。
-
灵活性:高层规划可以较稳定,而底层执行可以很灵活。例如,"测试与部署"阶段可以根据"前端开发"和"后端开发"的实际产出,动态生成具体的测试用例和部署脚本。
-
并行与协作:在高层规划确定后,某些阶段(如前端、后端开发)理论上可以并行执行,由不同的智能体团队协作,并通过共享记忆或消息总线同步信息。
习题5:Reflection机制的深入思考
-
使用不同模型进行反思与执行的影响。
• 反思用更强模型,执行用更快/更便宜模型:
• 积极影响:反思(评审)是要求更高的任务,需要深度分析、批判性思维和广博知识。更强的模型(如GPT-4)能提供更精准、深刻的反馈,从而引导优化方向更正确。执行(生成)任务相对直接,用更快更便宜的模型(如Claude Haiku、本地小模型)可以大幅降低成本、提高响应速度。这是一种性价比优化策略。
• 潜在风险:如果两个模型的知识、风格差异太大,执行模型可能无法完全理解或有效落实反思模型提出的"高级"建议,导致优化效果打折扣。需要确保它们对任务的基本理解一致。
-
反思终止条件的合理性及更智能的设计。
当前条件("反馈包含'无需改进'"或"达到最大迭代次数")是基础但粗糙的。
• 问题:"无需改进"是模型的主观判断,可能误判。最大迭代次数是硬性限制,可能没优化够就停了,或浪费资源空转。
更智能的终止条件设计:
-
量化评估阈值:在每次优化后,不仅让模型反思,还让一个"评估器"对当前版本进行量化评分(例如,代码的效率、可读性、正确性各打1-5分)。设定一个目标总分阈值(如12分),达到即终止。也可以设定连续两次迭代分数提升小于某个增量(如0.5分)时终止,表明收敛。
-
多维度投票:让反思从多个角度进行(如"算法专家"、"安全专家"、"用户体验专家"),如果多数专家认为"无需改进",则终止。这比单视角更可靠。
-
资源预算管理:综合考量已消耗的API费用、时间和迭代次数,设定一个综合预算。当任何一项接近预算时,选择当前最优版本输出并终止。
-
设计"学术论文写作助手"的多维度Reflection。
这个Reflection机制需要一个评审委员会,而非单个评审员。
• 执行:LLM根据主题生成论文初稿(包括标题、摘要、引言、方法、实验、结论)。
• 多维度反思(并行或串行调用不同角色的反思提示词):
-
逻辑连贯性评审员:检查各部分是否逻辑自洽,引言是否引出问题,方法是否解决问题,实验是否验证方法,结论是否总结成果。
-
方法创新性评审员:批判方法部分,是否足够新颖,与现有工作对比是否清晰,技术细节是否扎实。
-
学术语言与表达评审员:检查语言是否正式、准确,术语使用是否规范,句式是否多样,有无语法错误。
-
引用与格式评审员:检查参考文献引用是否完整、恰当,格式是否符合目标会议/期刊要求。
-
实验严谨性评审员(如果涉及):检查实验设计是否合理,数据是否充分,分析是否深入,图表是否清晰。
• 优化:将来自多个评审员的反馈汇总,可能需要对冲突的反馈进行权衡(例如,创新性评审员建议增加一个复杂模块,但语言评审员建议简化表达)。优化提示词需要指导模型综合处理这些反馈,生成修订稿。
• 迭代:可以设定每一轮只聚焦解决1-2个最严重的问题(根据评审员给出的严重等级),逐轮细化,避免单轮修改过多导致文章风格撕裂。
习题6:提示词工程分析
-
对比ReAct和Plan-and-Solve提示词的结构差异。
• ReAct提示词:像一个实时操作手册。它强调"格式规约"(必须输出Thought/Action)和"动态上下文"(注入History)。结构是循环式的,旨在引导模型在单步内完成"感知->决策->输出指令"的完整动作。它的核心是控制输出格式和维持会话状态。
• Plan-and-Solve提示词:像一个项目任务书。它强调"角色设定"(规划专家)和"输出格式约束"(必须是Python列表)。它不关心动态历史,因为规划是一次性的。它的核心是激发分解思维和强制结构化输出,以便后续程序能稳定解析和执行。
• 差异服务于核心逻辑:ReAct的提示词设计是为了适配其循环、交互的本质;Plan-and-Solve的提示词设计是为了适配其阶段化、管道化的本质。
-
修改Reflection提示词角色设定的影响。
• 原设定("极其严格的代码评审专家"):引导模型聚焦于正确性、性能、算法效率等硬核技术指标。反馈可能严厉,倾向于推荐根本性的算法变更。
• 新设定("注重代码可读性的开源项目维护者"):引导模型聚焦于命名清晰度、函数拆分、注释完整性、代码风格统一等可维护性指标。反馈可能更温和,倾向于重构和美化,而不是重写算法。
• 总结:角色设定是提示词的"灵魂",它直接定义了模型的视角、价值观和评估标准。改变角色设定,会显著改变反思的侧重点和最终优化方向。这说明了在Reflection范式中,设计一个与最终目标对齐的"评审角色"至关重要。
-
为智能体添加Few-shot示例的效果。
以ReAct的Action解析为例,在提示词中加入一个完整的成功示例:
示例:
用户:华为最新手机是什么?
Thought: 用户询问的产品信息可能已更新,我需要搜索最新资料。
Action: Search[华为最新手机型号]
Observation: 根据搜索结果,华为最新旗舰手机是Mate 60 Pro...
Thought: 已获得信息,可以给出答案。
Action: Finish[华为目前最新旗舰手机是Mate 60 Pro,其主要卖点包括...]
效果:
• 显著提升格式遵循能力:模型会模仿示例的结构、用词和节奏来输出,大大降低格式错乱的概率。
• 明确工具使用范例:展示了如何正确调用Search工具和Finish动作。
• 降低歧义:展示了从问题到最终答案的完整推理链条,帮助模型理解任务的全貌。
• 潜在缺点:占用上下文令牌,增加成本。如果示例不够典型或存在偏差,也可能将模型引入特定的思维定式。
习题7:综合场景设计------"客服智能体"
-
选择哪种范式(或组合)作为核心架构?
我会选择 以Plan-and-Solve为主干,嵌入ReAct和Reflection模块 的混合架构。
• 主干用Plan-and-Solve:因为客服退款流程有相对固定的阶段,符合"先规划后执行"。规划阶段能确保不遗漏关键环节:[理解用户理由 -> 查询订单与物流 -> 根据政策判断 -> 生成回复 -> 发送邮件]。
• 关键环节嵌入ReAct:在"理解用户理由"和"根据政策判断"环节,问题可能复杂,需要探索。例如,用户理由描述模糊,智能体可能需要通过ReAct循环,主动提问澄清(调用一个"向用户提问"的工具),或搜索知识库来理解特定术语。
• 关键环节嵌入Reflection:在"生成回复"之后,发送邮件之前,必须进行Reflection。反思重点是:回复语气是否得体(避免激怒用户)?逻辑是否清晰?是否涵盖了所有已查询到的信息?是否严格遵守了公司政策?根据反思结果优化邮件措辞。同时,整个流程结束后,可以进行一次全局Reflection,用于优化未来的决策策略(如发现某类情况总是判断失误)。
-
这个系统需要哪些工具?
至少需要以下工具:
-
订单查询工具:连接公司内部数据库或订单系统API,根据用户ID或订单号查询订单详情、支付状态、购买时间。
-
物流状态查询工具:连接物流系统API,查询该订单的当前物流轨迹和签收状态。
-
公司政策知识库工具:一个RAG系统,向量化存储公司的所有退款、售后政策文档。智能体可以自然语言提问(如"商品已拆封但损坏如何处理?"),工具返回相关的政策条款。
-
邮件发送工具:连接邮件服务器API(如SMTP),能够发送格式化的邮件。
-
(可选) 用户历史交互查询工具:查询该用户过去的客诉、退款记录,辅助判断信用。
-
如何设计提示词确保决策既符合公司利益,又能保持用户友好?
提示词需要植入双重约束:
• 系统角色设定:"你是XX公司的智能客服专员,你的首要职责是公平、准确地执行公司的售后政策,维护公司利益。同时,你必须以专业、友善、共情的方式与用户沟通,维护客户关系。"
• 在规划器和执行器的提示词中具体化:
• 对于政策判断步骤:"请严格依据提供的政策条款进行判断。如果政策存在模糊地带,请采取对公司风险较低且对用户相对公平的保守解释。"
• 对于邮件生成步骤:"邮件开头应表达对用户问题的理解和关怀。然后清晰、有条理地说明你的调查结果(订单信息、物流状态)和政策依据。最后明确告知决策结果。即使结果是拒绝退款,也要表达歉意并提供替代解决方案(如维修、折扣券等),保持语气诚恳、坚定。"
-
产品上线后可能面临的风险和挑战及技术应对手段?
• 风险1:决策错误或不一致。智能体可能误解政策或用户描述,做出错误退款决定。
• 应对:建立 "置信度-人工复核"流水线。智能体输出决策时,必须同时输出一个自我评估的置信度分数。低于阈值的case自动流转给人工客服复核。所有决策和邮件内容记录在案,便于审计和后续模型训练。
• 风险2:生成不当或敏感回复。模型可能生成带有偏见、冒犯性或泄露内部信息的回复。
• 应对:在Reflection环节之后,邮件发送之前,增加一个 "安全与合规过滤器"。这可以是一个专门训练的分类模型或规则集,检查邮件内容是否包含敏感词、负面情绪过激、泄露隐私等,拦截不合格的邮件。
• 风险3:被恶意用户利用或攻击。用户可能用精心构造的描述来欺骗智能体,绕过政策。
• 应对:在工具层面,严格验证输入(如订单号格式、用户身份)。在流程层面,对于高频或高金额的退款申请,自动触发更严格的检查流程(如要求上传凭证图片,并调用图像识别工具进行初步审核)。
• 风险4:系统可解释性差。当出现纠纷时,无法说清AI为何做出某个决定。
• 应对:系统必须完整记录整个Plan-and-Solve-ReAct-Reflection的完整轨迹,包括每一步的Thought、调用的工具及原始返回结果、反思的反馈。这个"决策日志"是解释和调试的根本。
• 挑战:与现有系统集成。工具需要对接多个内部系统(订单、物流、邮件),接口稳定性、数据格式统一是工程挑战。
• 应对:为每个内部系统开发稳定、有容错机制的API客户端,并在工具层做好异常处理和日志记录。
项目地址:hello-agents