团队给 Agent 加了评估指标,跑完一看——准确率 72%,工具调用失败率 18%,幻觉率 9%。数字很清楚,下一步却卡住了:改了提示词压幻觉,工具调用失败率反而涨了;换了个工具描述格式,准确率又掉了两分。能测量,但修一个就坏另一个,这是当前 Agent 开发中最让人头疼的循环。
微软的 Vivek Bhaduria、Luis Quintanilla 和 Saket Sathe 团队最近提出了一个解法:别把 Agent 调优当成"手工打磨",把它当成搜索问题。搜索什么?搜索一组 Agent 配置(提示词、工具描述、规划策略、温度参数等),使得它在评估指标上的综合得分最优,且不引入回归。
"测量容易修复难"到底难在哪
先看一个典型场景。假设你有一个客服 Agent,评估维度有三项:
| 维度 | 当前得分 | 目标 |
|---|---|---|
| 意图识别准确率 | 72% | ≥85% |
| 工具调用成功率 | 82% | ≥95% |
| 幻觉率 | 9% | ≤3% |
你发现幻觉主要来自"过度推理",于是把提示词从"请详细分析用户问题"改成"仅根据已知信息回答"。幻觉率降到 4%,但意图识别准确率跌到 65%——Agent 变保守了,遇到模糊意图直接放弃。
再试一招:给工具描述加更多示例。工具调用成功率升到 90%,但幻觉率反弹到 7%——Agent 看到丰富的工具示例后,开始"创造性"组合不存在的能力。
这就是核心困境:Agent 的行为由多个耦合因素共同决定,单点修改的副作用不可预测。手工调优本质上是在一个高维空间里做无方向的随机游走。
搜索问题的重新定义
微软团队的思路是:既然单点修改不可靠,那就把整个配置空间显式化,用系统化的搜索策略来探索它。
具体来说,把 Agent 调优建模为:
- 搜索空间 S:所有可调参数的组合——提示词模板、工具描述格式、系统消息结构、采样参数(temperature、top_p)、规划策略(ReAct vs Plan-and-Execute)、检索策略等
- 目标函数 f(s):对配置 s 在评估集上的综合得分(可以是加权求和,也可以是约束优化——比如"幻觉率必须 ≤3%,在此约束下最大化准确率")
- 搜索策略:如何在 S 中高效找到使 f(s) 最优的 s*
这和超参数搜索、prompt optimization 的思路一脉相承,但维度更高、约束更复杂。
优化循环的四步结构
微软提出的"Agent 优化循环"可以拆成四个阶段:
1. 定义评估基准——不是"跑几个例子看看",而是建立可重复的评估流水线:固定测试集、固定评分规则、固定运行环境。每次改动后,全量回归跑一遍。
2. 参数化配置空间——把所有可调因素变成可枚举、可版本化的变量。提示词不再是"一段文字",而是带占位符的模板;工具描述不再是"随便写写",而是有固定 schema 的结构化数据。
3. 执行搜索——根据策略在配置空间中采样,每次采样生成一个候选 Agent 配置,跑评估,记录得分。搜索策略可以是网格搜索、随机搜索、贝叶斯优化,甚至基于 LLM 的元优化(让另一个 LLM 来提议下一组参数)。
4. 回归检测与锁定——找到更优配置后,不是直接上线,而是和当前最佳配置做对比,确认所有维度都不退步,再锁定为新基线。
下面用一个可运行的示例来落地这个思路。
实践:用 Python 搭一个最小 Agent 优化循环
这个示例用 OpenAI API 构建一个简单的问答 Agent,然后在三个评估维度上做约束搜索。你可以直接改造为自己的 Agent 和评估逻辑。
"""
最小 Agent 优化循环示例
依赖: openai>=1.0, pydantic>=2.0
运行前设置: export OPENAI_API_KEY=sk-xxx
"""
import os
import json
import itertools
from dataclasses import dataclass, field
from typing import List, Dict, Tuple
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
# ── 1. 参数化配置空间 ──────────────────────────────────
@dataclass
class AgentConfig:
"""把所有可调因素变成可枚举的变量"""
system_prompt_template: str # 系统提示词模板
temperature: float # 采样温度
tool_description_style: str # 工具描述风格: "minimal" | "detailed" | "example_rich"
planning_strategy: str # 规划策略: "direct" | "react" | "plan_then_execute"
# 定义搜索空间:每个维度的候选值
SEARCH_SPACE = {
"system_prompt_template": [
"你是一个客服助手,仅根据提供的知识库回答用户问题。如果不确定,请说'我无法确认'。",
"你是一个客服助手,先判断用户意图,再从知识库中检索答案。回答时引用来源。",
"你是一个客服助手,按步骤思考:1)识别意图 2)检索信息 3)组织回答。不确定时标注[不确定]。",
],
"temperature": [0.0, 0.3, 0.7],
"tool_description_style": ["minimal", "detailed", "example_rich"],
"planning_strategy": ["direct", "react"],
}
def enumerate_configs() -> List[AgentConfig]:
"""网格搜索:枚举所有组合"""
keys = list(SEARCH_SPACE.keys())
values = [SEARCH_SPACE[k] for k in keys]
configs = []
for combo in itertools.product(*values):
configs.append(AgentConfig(**dict(zip(keys, combo))))
return configs
# ── 2. 定义评估基准 ──────────────────────────────────
@dataclass
class EvalResult:
intent_accuracy: float # 意图识别准确率
tool_success_rate: float # 工具调用成功率
hallucination_rate: float # 幻觉率
# 固定测试集(实际项目中应从文件加载)
EVAL_DATASET = [
{"query": "我的订单什么时候发货", "expected_intent": "物流查询",
"expected_answer_contains": "发货", "knowledge_snippet": "标准订单3个工作日内发货"},
{"query": "退款政策是什么", "expected_intent": "退款咨询",
"expected_answer_contains": "退款", "knowledge_snippet": "7天内无理由退款"},
{"query": "你们支持哪些支付方式", "expected_intent": "支付咨询",
"expected_answer_contains": "支付", "knowledge_snippet": "支持微信支付、支付宝、银行卡"},
{"query": "我想投诉上次的服务", "expected_intent": "投诉",
"expected_answer_contains": "投诉", "knowledge_snippet": "投诉请拨打400-123-4567"},
{"query": "帮我查一下航班信息", "expected_intent": "航班查询",
"expected_answer_contains": None, "knowledge_snippet": None}, # 超范围问题
]
def evaluate_config(config: AgentConfig) -> EvalResult:
"""对单个配置跑全量评估"""
intent_correct = 0
tool_success = 0
hallucination = 0
total = len(EVAL_DATASET)
for item in EVAL_DATASET:
# 构造完整系统消息
system_msg = config.system_prompt_template
if item["knowledge_snippet"]:
system_msg += f"\n\n知识库片段:{item['knowledge_snippet']}"
# 构造工具描述(简化示意)
tool_desc = _build_tool_description(config.tool_description_style)
# 构造用户消息(根据规划策略调整)
user_msg = _format_user_query(item["query"], config.planning_strategy)
# 调用 LLM
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_msg + "\n\n可用工具:" + tool_desc},
{"role": "user", "content": user_msg},
],
temperature=config.temperature,
max_tokens=300,
)
answer = response.choices[0].message.content
# 评分逻辑(实际项目中应更精细)
if item["expected_intent"]:
# 简化:检查回答是否包含预期关键词
if item["expected_answer_contains"] and item["expected_answer_contains"] in answer:
intent_correct += 1
tool_success += 1
elif item["expected_answer_contains"] is None:
# 超范围问题,正确做法是拒绝
if "无法" in answer or "不确定" in answer or "不支持" in answer:
intent_correct += 1
hallucination += 0
else:
hallucination += 1
else:
hallucination += 1
else:
if "无法" in answer or "不确定" in answer:
intent_correct += 1
else:
hallucination += 1
return EvalResult(
intent_accuracy=intent_correct / total,
tool_success_rate=tool_success / total,
hallucination_rate=hallucination / total,
)
def _build_tool_description(style: str) -> str:
if style == "minimal":
return "search_knowledge_base(query: str) -> str: 搜索知识库"
elif style == "detailed":
return ("search_knowledge_base(query: str) -> str: 搜索内部知识库,"
"返回匹配的文档片段。query应为具体问题关键词。")
else: # example_rich
return ("search_knowledge_base(query: str) -> str: 搜索内部知识库。\n"
"示例: search_knowledge_base('退款政策') -> '7天内无理由退款'\n"
"示例: search_knowledge_base('发货时间') -> '3个工作日内发货'")
def _format_user_query(query: str, strategy: str) -> str:
if strategy == "direct":
return query
elif strategy == "react":
return f"问题:{query}\n请按 Thought -> Action -> Observation -> Answer 的格式回答。"
else:
return query
# ── 3. 执行搜索 + 回归检测 ──────────────────────────────
@dataclass
class SearchState:
best_config: AgentConfig = None
best_result: EvalResult = None
best_score: float = -1.0
history: List[Dict] = field(default_factory=list)
def compute_score(result: EvalResult,
hallucination_threshold: float = 0.05) -> float:
"""约束优化:幻觉率超过阈值则得分为0,否则加权求和"""
if result.hallucination_rate > hallucination_threshold:
return 0.0 # 硬约束:幻觉率必须 ≤5%
return 0.5 * result.intent_accuracy + 0.3 * result.tool_success_rate
def check_regression(new_result: EvalResult,
old_result: EvalResult) -> bool:
"""回归检测:任何维度退步超过5%则判定为回归"""
if old_result is None:
return False
if new_result.intent_accuracy < old_result.intent_accuracy - 0.05:
return True
if new_result.tool_success_rate < old_result.tool_success_rate - 0.05:
return True
if new_result.hallucination_rate > old_result.hallucination_rate + 0.03:
return True
return False
def run_optimization_loop() -> SearchState:
state = SearchState()
configs = enumerate_configs()
print(f"搜索空间大小: {len(configs)} 个配置")
for i, config in enumerate(configs):
result = evaluate_config(config)
score = compute_score(result)
state.history.append({
"iteration": i,
"config": str(config),
"result": result,
"score": score,
})
# 只有得分更高且无回归才更新最佳配置
if score > state.best_score and not check_regression(result, state.best_result):
print(f"✅ 新最佳配置 (iter {i}): score={score:.3f}")
print(f" 意图准确率={result.intent_accuracy:.2f} "
f"工具成功率={result.tool_success_rate:.2f} "
f"幻觉率={result.hallucination_rate:.2f}")
state.best_config = config
state.best_result = result
state.best_score = score
elif score > state.best_score:
print(f"⚠️ 得分更高但有回归 (iter {i}): score={score:.3f},跳过")
else:
print(f" iter {i}: score={score:.3f},未超越当前最佳")
return state
# ── 4. 运行 ──────────────────────────────────────────
if __name__ == "__main__":
state = run_optimization_loop()
print("\n" + "="*60)
print("优化结果:")
print(f"最佳得分: {state.best_score:.3f}")
print(f"最佳配置: {state.best_config}")
print(f"最佳评估结果: {state.best_result}")
运行前确保设置了 OPENAI_API_KEY。这个示例做了几件关键的事:
- 配置空间显式化:提示词模板、温度、工具描述风格、规划策略全部枚举,不再是"改改试试"
- 硬约束+软优化:幻觉率超过 5% 直接得零分,不会出现"总分高但幻觉飙升"的情况
- 回归检测:新配置即使总分更高,如果任何单项退步超过阈值,也不会被采纳
实际项目中搜索空间远比这大,网格搜索不现实。可以替换 enumerate_configs() 为随机采样或贝叶斯优化:
import random
def random_sample_configs(n: int = 20) -> List[AgentConfig]:
"""随机搜索:采样 n 个配置"""
configs = []
for _ in range(n):
configs.append(AgentConfig(
system_prompt_template=random.choice(SEARCH_SPACE["system_prompt_template"]),
temperature=random.choice(SEARCH_SPACE["temperature"]),
tool_description_style=random.choice(SEARCH_SPACE["tool_description_style"]),
planning_strategy=random.choice(SEARCH_SPACE["planning_strategy"]),
))
return configs
几个容易踩的坑
评估集太小或太偏。 5 条测试数据只能做演示,真实项目至少 50-100 条,覆盖边界场景和对抗样本。评估集本身也要版本化管理——每次优化循环锁定评估集版本,避免"改题目来凑分数"。
搜索空间定义太粗或太细。 太粗(只调温度和一段提示词)搜不出差异;太细(每个措辞变体都算一个维度)组合爆炸。微软团队的建议是:先粗搜定位大方向,再在最优区域附近细搜。这和超参数搜索的"先随机搜索再局部精调"策略一致。
忽略成本约束。 每次评估都要调 LLM API,网格搜索 3×3×3×2=54 个配置,每个跑 5 条测试,就是 270 次调用。用 gpt-4o 跑一遍可能花几十美元。实际操作中应先用便宜模型(gpt-4o-mini)做粗筛,再用强模型做精调验证。
回归阈值设得太松或太紧。 5% 的退步容忍度是示例值,实际要看业务容忍度。客服场景幻觉率退步 3% 可能就不可接受,但内部工具场景可以更宽松。阈值应该和业务方一起定,而不是工程师拍脑袋。
什么时候该用这个思路
不是所有 Agent 都需要这么重的优化流程。判断标准:
- Agent 已上线且有稳定评估流水线 → 适合。你已经有数据,只是修不好。
- Agent 还在原型阶段 → 不适合。评估集都不稳定,搜索结果没有参考价值。
- 只有 1-2 个可调维度 → 不适合。手工调比搜更快。
- 5 个以上耦合维度,且改动经常互相打架 → 正是搜索思路的用武之地。
微软团队把这个框架开源在了 GitHub 上(项目名 agent-optimization-loop),提供了更完整的评估流水线模板和搜索策略实现。上面的示例是简化版,核心逻辑一致:把"凭感觉改提示词"变成"在配置空间里做约束搜索"。
这个思路的深层启示是——Agent 的行为不是提示词的线性函数,而是多个因素的非线性组合。面对非线性系统,系统化搜索比直觉调试可靠得多。