06 - 智能体轨道 - 自主决策 🤖
欢迎来到智能体(Agent)的世界!这条轨道探索如何让AI自主规划、决策和行动。
核心问题: 如何在不确定环境中做出最优决策?
🎯 本轨道内容
| 算法 | 核心问题 | 解决方案 | 应用 | 难度 |
|---|---|---|---|---|
| Bandit | 探索vs利用 | ε-greedy, UCB | 推荐系统、A/B测试 | ⭐⭐ |
| Minimax | 零和对抗 | 极小化极大搜索 | 棋类游戏 | ⭐⭐⭐ |
| MCTS | 大搜索空间 | 蒙特卡洛树搜索 | AlphaGo、游戏AI | ⭐⭐⭐⭐ |
| ReAct | 推理+行动 | Thought→Action→Observation | LLM Agent | ⭐⭐⭐⭐ |
| Memory Network | 长期记忆 | 可寻址外部记忆 | 问答、对话 | ⭐⭐⭐ |
学习路径: Bandit(基础) → Minimax(完美信息) → MCTS(部分信息) → ReAct(语言Agent)
🎰 算法一: Multi-Armed Bandit - 探索与利用
什么是Bandit?
问题: K个老虎机(arm),每个有未知奖励分布,玩N次,如何最大化总奖励?
核心矛盾:
- Exploration (探索): 尝试不确定的arm,收集信息
- Exploitation (利用): 选择已知最好的arm,获取奖励
经典策略:
- ε-Greedy:
以ε概率: 随机探索
以1-ε概率: 选择当前最优arm
- UCB (Upper Confidence Bound):
选择: argmax_a [ Q(a) + c·√(ln(t)/N(a)) ]
Q(a): arm a的平均奖励
N(a): arm a被选次数
t: 总尝试次数
c: 探索系数
对话1: Bandit问题在LLM中的应用是什么?
🤔 学生: Bandit看起来是强化学习,和LLM有什么关系?
💡 导师: LLM的很多场景本质是Bandit问题!
应用1: Prompt选择
问题: 有5个不同prompt模板,哪个效果最好?
Bandit建模:
- K=5个arm(5个prompt)
- 奖励: 用户满意度评分
- 目标: 快速找到最优prompt,同时不浪费太多尝试
UCB策略:
初期: 每个prompt都试几次(探索)
后期: 集中在高分prompt(利用)
应用2: 模型路由
问题: 多个LLM(GPT-4, Claude, Llama),每个成本和质量不同
Bandit建模:
- arm: 选择哪个模型
- 奖励: 质量/成本比
- 约束: 成本预算
Contextual Bandit:
根据查询类型(简单/复杂)动态选模型
简单查询 → Llama(便宜)
复杂查询 → GPT-4(贵但准)
应用3: RLHF中的探索
PPO训练时:
- 生成多条回答候选(探索)
- 奖励模型打分
- 平衡"高奖励回答"vs"新尝试"
没有足够探索 → mode collapse
过度探索 → 训练慢
✨ 关键洞察: Bandit是”在线学习”的基础——边用边学,快速适应,是LLM部署中的核心技术。
代码片段: UCB算法
import numpy as np
class UCB:
def __init__(self, n_arms, c=2.0):
"""
Upper Confidence Bound算法
Args:
n_arms: arm数量
c: 探索系数(通常取1-2)
"""
self.n_arms = n_arms
self.c = c
self.q_values = np.zeros(n_arms) # 平均奖励估计
self.n_pulls = np.zeros(n_arms) # 每个arm被拉次数
self.t = 0 # 总步数
def select_arm(self):
"""选择arm"""
# 先确保每个arm至少被拉一次
if self.t < self.n_arms:
return self.t
# UCB: Q(a) + c·√(ln(t)/N(a))
ucb_values = self.q_values + self.c * np.sqrt(
np.log(self.t) / (self.n_pulls + 1e-10)
)
return np.argmax(ucb_values)
def update(self, arm, reward):
"""更新估计"""
self.n_pulls[arm] += 1
self.t += 1
# 增量均值更新: Q_new = Q_old + (r - Q_old)/N
self.q_values[arm] += (reward - self.q_values[arm]) / self.n_pulls[arm]
def regret(self, optimal_reward):
"""累积遗憾: 最优策略 - 实际奖励"""
return optimal_reward * self.t - np.sum(self.q_values * self.n_pulls)
# 使用示例
bandit = UCB(n_arms=5, c=2.0)
for round in range(1000):
arm = bandit.select_arm()
reward = pull_arm(arm) # 与环境交互
bandit.update(arm, reward)
if round % 100 == 0:
print(f"Round {round}: Best arm estimate = {np.argmax(bandit.q_values)}")关键点:
- 不确定性奖励: √(ln(t)/N(a)) 大 → 该arm不确定性高 → 优先探索
- 自动平衡: 拉得多的arm,不确定性降低,探索减少
- 理论保证: Regret = O(√(t·ln(t))) (次线性增长)
♟️ 算法二: Minimax - 极小化极大搜索
什么是Minimax?
场景: 两人零和博弈(我得1分=你失1分),完全信息(棋盘可见)
核心思想:
- Max玩家: 最大化自己得分
- Min玩家: 最小化Max得分(即最大化自己)
递归定义:
Minimax(node, depth, isMax):
if 终局 or depth=0:
return 评估(node)
if isMax:
best = -∞
for child in children(node):
val = Minimax(child, depth-1, False)
best = max(best, val)
return best
else:
best = +∞
for child in children(node):
val = Minimax(child, depth-1, True)
best = min(best, val)
return best
Alpha-Beta剪枝: 避免搜索明显劣势分支,加速10-100倍
对话2: Minimax能用于LLM Agent吗?
🤔 学生: Minimax是游戏AI,LLM Agent怎么用?
💡 导师: 可以,但需要重新定义”对手”和”得分”!
应用1: 辩论Agent
场景: 两个LLM辩论
Agent A(正方): 支持观点X
Agent B(反方): 反对观点X
Minimax建模:
- Max: Agent A,最大化"说服力评分"
- Min: Agent B,最小化A的说服力(即攻击A论点)
搜索树:
A: "X是正确的,因为理由1" (得分+3)
B: "理由1有漏洞Y" (得分-2)
A: "漏洞Y可以这样解释" (得分+1)
...
应用2: 红队测试
场景: 测试LLM安全性
Attacker(Max): 尝试让LLM输出有害内容
Defender(Min): LLM拒绝并给安全回答
Minimax搜索:
Attacker思考: "什么prompt最可能绕过防护?"
Defender思考: "如何识别和拒绝恶意prompt?"
局限性:
- 搜索空间爆炸: 语言空间远大于棋盘
- 评估函数难定义: 什么是”好回答”? (主观)
- 非零和: 辩论可能双赢/双输
改进:
- MCTS (下一个算法): 用采样替代完全搜索
- Self-Play: AlphaGo式自我对弈训练评估函数
✨ 关键洞察: Minimax的”对抗思维”适用于任何竞争场景,但语言Agent需要更灵活的搜索策略(如MCTS)。
🌳 算法三: MCTS - 蒙特卡洛树搜索
什么是MCTS?
问题: Minimax在围棋(分支因子250+)等大空间游戏中不可行
MCTS方案: 不完全搜索,用采样估计价值
四步循环:
1. Selection(选择): 从根沿UCB最优路径下降
2. Expansion(扩展): 遇到未访问节点,扩展一个子节点
3. Simulation(模拟): 从新节点随机rollout到终局
4. Backpropagation(回传): 更新路径上所有节点的统计
UCT公式 (MCTS的Selection):
UCT(node) = win_rate(node) + c·√(ln(N_parent)/N_node)
win_rate: 该节点胜率
N_parent: 父节点访问次数
N_node: 该节点访问次数
对话3: MCTS如何用于LLM规划?
🤔 学生: MCTS是围棋AI,和LLM Agent有什么联系?
💡 导师: MCTS的本质是”规划搜索”,LLM Agent也需要规划!
应用: LLM + MCTS做多步推理
例子: 数学题求解
问题: "小明有5个苹果,给了2个,买了3个,现在有几个?"
传统LLM:
直接生成答案 "6个" (可能错!)
LLM + MCTS:
1. Selection: 选择最有希望的推理路径
"先算给了后剩多少"
2. Expansion: LLM生成下一步
"5 - 2 = 3"
3. Simulation: LLM继续推理到底
"3 + 3 = 6,答案是6"
4. Backpropagation: 检查答案
正确 → 这条路径score+1
错误 → score-1
重复1000次 → 选择访问次数最多的路径
Tree-of-Thoughts (ToT):
论文: Yao et al., 2023
核心: 用MCTS搜索思维链(Chain-of-Thought)
相比标准CoT:
CoT: 单条线性推理链
ToT: 树状探索多条推理路径,选最优
效果: GSM8K数学题准确率 74% → 85%
代码结构:
class LLM_MCTS:
def search(self, problem, num_simulations=100):
root = Node(problem)
for _ in range(num_simulations):
# 1. Selection
node = self.select(root)
# 2. Expansion
if not node.is_terminal():
child = self.expand(node) # LLM生成下一步
node = child
# 3. Simulation
reward = self.rollout(node) # LLM推理到底,评估
# 4. Backpropagation
self.backpropagate(node, reward)
# 选择最优路径
return self.best_path(root)
def expand(self, node):
"""用LLM生成子节点"""
prompt = f"继续推理:\n{node.state}\n下一步:"
next_step = llm.generate(prompt)
return Node(node.state + "\n" + next_step)
def rollout(self, node):
"""模拟到终局并评估"""
final_answer = llm.generate(node.state + "\n最终答案:")
return evaluate(final_answer) # 检查正确性优势:
- 探索多样性: 不只走一条路
- 自我纠错: 错误路径自然被淘汰
- 置信度: 访问次数高 = 多次验证 = 可靠
挑战:
- LLM调用成本高(每次Simulation要多次生成)
- 需要可靠的reward signal(如何评估中间步骤?)
✨ 关键洞察: MCTS把LLM从”单次生成”变成”搜索+验证”,大幅提升复杂推理任务的准确性。
💬 算法四: ReAct - 推理与行动
什么是ReAct?
ReAct (Yao et al., 2022) = Reasoning + Acting
核心思想: LLM不只生成答案,还能调用工具、观察结果,迭代推理
循环结构:
Thought (推理): "我需要查询最新GDP数据"
Action (行动): search("2024年美国GDP")
Observation (观察): "搜索结果: 28.78万亿美元"
Thought: "现在对比中国的..."
Action: search("2024年中国GDP")
Observation: "18.57万亿美元"
Thought: "美国GDP是中国的1.55倍"
Answer: "2024年美国GDP约为中国的1.55倍"
对话4: ReAct vs 标准Prompt有什么区别?
🤔 学生: 这不就是让LLM调用API吗?ReAct特别在哪?
💡 导师: ReAct的突破是”显式推理过程” — 不只调API,还解释为什么调。
标准Function Calling:
User: "查查今天天气"
LLM: [调用weather_api("北京")]
API: "晴,25度"
LLM: "今天北京晴天,25度"
问题: LLM像”转发器”,不理解为什么查天气
ReAct:
User: "我明天要去北京,需要带伞吗?"
Thought: "需要知道明天北京的天气,特别是会不会下雨"
Action: weather_api("北京", date="明天")
Observation: "明天北京:多云转阴,降水概率70%,温度18-24度"
Thought: "降水概率70%很高,有可能下雨,应该带伞"
Action: None (无需更多查询)
Answer: "建议带伞,明天北京降水概率70%"
区别:
| 维度 | Function Calling | ReAct |
|---|---|---|
| 推理过程 | 隐式(模型内部) | 显式(可见Thought) |
| 工具选择 | 规则/分类器 | 推理驱动 |
| 多步规划 | 困难 | 自然(迭代Thought) |
| 可解释性 | 低 | 高(每步有理由) |
ReAct的威力: 多步工具组合
User: "帮我预订明天去上海的航班"
Thought 1: "需要先查明天的航班"
Action 1: search_flights(from="北京", to="上海", date="明天")
Observation 1: "MU5180, 08:00, ¥850 | CA1234, 14:00, ¥720"
Thought 2: "用户没说时间偏好,应该问一下"
Action 2: ask_user("您偏好早上还是下午的航班?")
Observation 2: "早上"
Thought 3: "选MU5180,08:00的航班,现在预订"
Action 3: book_flight("MU5180")
Observation 3: "预订成功,订单号ABC123"
Answer: "已为您预订明天08:00的MU5180航班,订单号ABC123"
没有ReAct: 可能直接预订第一个,或全部列出让用户选(差体验)
✨ 关键洞察: ReAct让LLM从”Pattern matching”升级为”Agent” — 能规划、执行、观察、调整,接近人类解决问题的方式。
对话5: ReAct的Prompt怎么设计?
🤔 学生: ReAct的Thought/Action/Observation是训练出来的吗?
💡 导师: 大多数是Prompt工程,不需要专门训练!
ReAct Prompt模板:
你是一个助手,可以使用以下工具:
- search(query): 搜索互联网
- calculator(expr): 计算数学表达式
- ask_user(question): 向用户提问
严格按照以下格式回答:
Thought: [你的推理过程]
Action: [工具名(参数)]
收到Observation后继续:
Thought: [基于observation的推理]
Action: [下一步工具] 或 Answer: [最终答案]
示例:
User: 100的平方根是多少?
Thought: 需要计算100的平方根
Action: calculator("sqrt(100)")
Observation: 10.0
Thought: 计算结果是10
Answer: 100的平方根是10
现在开始:
User: {user_query}
关键技巧:
- Few-shot Examples: 给2-3个示例教会格式
- Strict Format: 用正则或parser解析Action
- Max Iterations: 限制最多10步,防止死循环
伪代码:
def react_loop(user_query, max_iter=10):
context = REACT_PROMPT + f"\nUser: {user_query}\n"
for i in range(max_iter):
# LLM生成Thought + Action
response = llm.generate(context)
# 解析Action
action = parse_action(response) # 正则匹配"Action: tool(args)"
if action.is_final_answer():
return action.answer
# 执行工具
observation = execute_tool(action.tool, action.args)
# 添加到context
context += response + f"\nObservation: {observation}\n"
return "抱歉,未能在限定步数内完成"
def parse_action(text):
"""从LLM输出中提取Action"""
# 正则: Action: tool_name(arg1, arg2, ...)
match = re.search(r'Action:\s*(\w+)\((.*)\)', text)
if match:
return Action(tool=match[1], args=match[2])
# 检查是否Final Answer
if 'Answer:' in text:
return Action(is_final=True, answer=text.split('Answer:')[1].strip())
raise ParseError("未找到有效Action")常见问题:
- 格式不稳定: LLM不严格遵守Thought/Action格式
- 解决: 用强约束Prompt + 后处理parser
- 死循环: 一直调用同一工具
- 解决: 记录历史,prompt提示”不要重复”
- 幻觉Action: 调用不存在的工具
- 解决: 严格校验工具名,拒绝无效Action
✨ 关键洞察: ReAct证明了”结构化Prompt”的威力 — 不需要训练,只需好的格式设计,就能让LLM表现出Agent行为。
🧠 算法五: Memory Network - 外部记忆
什么是Memory Network?
问题: Transformer的上下文长度有限(如4k tokens),如何处理长对话/大知识库?
Memory Network (Weston et al., 2014): 给模型配备可读写的外部记忆
结构:
查询q → Memory [m1, m2, ..., mN] → 相关记忆 → 回答a
四步:
- Input: 把新信息存入Memory
- Generalization: 编码query和memory为向量
- Output: 从memory检索相关片段(Attention!)
- Response: 基于检索结果生成答案
对话6: Memory Network vs RAG有什么区别?
🤔 学生: 这不就是RAG(检索增强生成)吗?
💡 导师: 非常接近,但Memory Network更通用!
RAG (Retrieval-Augmented Generation):
用户query → 向量数据库检索 → Top-K文档 → 拼接prompt → LLM生成
特点:
- 只读memory(文档库)
- 检索=语义相似度搜索
- 用于知识密集型QA
Memory Network:
用户query → Memory(可读写!) → 检索+推理 → 更新Memory → 回答
特点:
- 可读可写(动态memory)
- 多跳推理(查询A→得B→再查询B)
- 用于对话、任务规划
对比:
| 维度 | RAG | Memory Network |
|---|---|---|
| Memory类型 | 静态文档 | 动态,可更新 |
| 检索方式 | 向量相似度 | Attention权重 |
| 推理能力 | 单跳 | 多跳 |
| 写入 | 离线索引 | 在线写入 |
Memory Network的”多跳推理”例子:
Memory:
m1: "Tom住在纽约"
m2: "纽约在美国东海岸"
m3: "美国东海岸时区是EST"
Query: "Tom所在时区是什么?"
第1跳: query → m1 → "Tom住在纽约"
第2跳: "纽约" → m2 → "纽约在美国东海岸"
第3跳: "东海岸" → m3 → "东海岸时区是EST"
Answer: "EST"
RAG单跳: query → m1 → “Tom住在纽约” (缺信息!)
现代融合: LangChain的ConversationBufferMemory
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
# 写入
memory.save_context(
{"input": "我叫Alice"},
{"output": "你好Alice!"}
)
# 读取
history = memory.load_memory_variables({})
# {'history': "Human: 我叫Alice\nAI: 你好Alice!"}
# 下次对话自动携带history
chain = ConversationChain(llm=llm, memory=memory)
chain.run("我叫什么名字?") # "你叫Alice"✨ 关键洞察: Memory Network是”可学习的数据库” — RAG是其特例(只读检索)。未来Agent需要动态记忆:记住对话、更新信念、规划任务。
代码片段: 简化Memory Network
class MemoryNetwork:
def __init__(self, embedding_model):
self.memories = [] # 存储 (text, embedding)
self.embed = embedding_model
def store(self, text):
"""存入新记忆"""
emb = self.embed(text)
self.memories.append((text, emb))
def retrieve(self, query, k=3):
"""检索top-k相关记忆"""
q_emb = self.embed(query)
# 计算相似度
scores = [
cosine_sim(q_emb, m_emb)
for text, m_emb in self.memories
]
# Top-K
top_k_idx = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:k]
return [self.memories[i][0] for i in top_k_idx]
def answer(self, query):
"""基于记忆回答"""
relevant = self.retrieve(query, k=3)
context = "\n".join(relevant)
prompt = f"记忆:\n{context}\n\n问题:{query}\n答案:"
return llm.generate(prompt)
# 使用
memory = MemoryNetwork(embedding_model)
# 存储知识
memory.store("法国的首都是巴黎")
memory.store("巴黎的著名景点是埃菲尔铁塔")
memory.store("埃菲尔铁塔高324米")
# 多跳查询
answer = memory.answer("法国首都的著名景点有多高?")
# 内部检索到3条记忆,组合推理
# "法国首都是巴黎,巴黎著名景点是埃菲尔铁塔,高324米"改进方向:
- 选择性写入: 不是所有对话都存,过滤重要信息
- 遗忘机制: 旧记忆衰减,防止memory爆炸
- 分层记忆: 短期(对话)/长期(知识)/工作(任务)记忆分离
📊 五大Agent算法对比
| 算法 | 场景 | 决策方式 | 信息 | 难度 |
|---|---|---|---|---|
| Bandit | 推荐、A/B测试 | 探索vs利用 | 部分(每次拉arm) | ⭐⭐ |
| Minimax | 棋类、对抗 | 完全搜索 | 完全(棋盘) | ⭐⭐⭐ |
| MCTS | 复杂游戏、规划 | 采样搜索 | 完全(可模拟) | ⭐⭐⭐⭐ |
| ReAct | LLM Agent | 推理+工具调用 | 部分(API返回) | ⭐⭐⭐⭐ |
| Memory | 长对话、QA | 检索+推理 | 外部知识库 | ⭐⭐⭐ |
综合应用: 复杂Agent系统
用户query
↓
Memory: 检索历史对话
↓
ReAct: 规划工具调用
↓
MCTS: 搜索最优行动序列
↓
Bandit: 选择模型/策略
↓
执行 & 更新Memory
🎯 本章总结
智能体的核心能力:感知→推理→行动→学习
演化路径:
规则Agent (if-then)
↓
搜索Agent (Minimax, MCTS)
↓
学习Agent (RL, Bandit)
↓
语言Agent (ReAct, Memory)
↓
多Agent协作 (未来)
LLM Agent的关键:
- 规划: MCTS式多步搜索
- 工具使用: ReAct式推理+行动
- 记忆管理: Memory Network长期知识
- 自我改进: 从失败中学习(Bandit探索)
挑战:
- 长期规划(超过10步)
- 环境复杂性(真实世界不确定)
- 安全性(Agent自主行动的风险)
本轨道结束! 你已完成所有6条轨道,掌握了从基础到前沿的47个核心算法!
下一步:
🎉 恭喜完成no-magic交互式教程!