在 2026 年的 Agent 项目里,Prompt 当然还要写。该停下来的,是靠感觉一遍遍补规则:改一句,跑一个例子,碰巧通过,再继续往 System Prompt 里塞下一条。
手改最麻烦的地方,不是打字慢,而是改动很难隔离。测试集 A 里,Agent 忘了调用 X 工具,于是你补上“必须优先使用 X 工具”;到了测试集 B,它又在不该调用时强行调用。旧问题消失了,新问题却被写进 Prompt。
我们此前在 《告别 Prompt 工程:2026 年 Agent 自主规划能力的真实分水岭》 中谈过同一个变化:当任务包含工具调用、长上下文和多步规划时,只盯着一句提示词已经不够。一旦再接入 MCP、多模态输入或动态重规划,Prompt 更像系统配置,而不是一段写得漂亮的说明文字。
《“通通”之外,BIGAI 还做了一套会改计划的智能体》 里的 TongAgents 就属于这种场景:Agent 会在执行中改计划,不能指望一份不断加长的 System Prompt 兜住所有分支。
越改越长,问题反而更难定位
手写 Prompt 常见的麻烦有三类:
- 规则互相打架:当 System Prompt 拉到上百行,新增一句“必须”可能改变另一条规则的优先级。人很难只靠阅读判断最终效果。
- 改动缺少回归测试:每次调整一两个词,都要重新跑旧样本。只看当前案例,很容易把局部修复误当成整体提升。
- 换模型后表现漂移:针对 Llama 4 调过的 Prompt,搬到 Qwen 3.0 或 Mistral 后,仍要用同一套样本重测,不能凭“写法差不多”判断它会继续有效。
所以,自动优化的重点不是让模型随意重写 Prompt,而是让每次重写都有测试依据。
把 Prompt 放进一个可重复的反馈环
TextGrad 和 DSPy 这类框架提供了一种更容易复用的做法:把模型输出交给评测器,由评测器指出缺口,再让优化器据此修改 Prompt。TextGrad 常把这类自然语言反馈称为“文本梯度”。术语并不重要,关键是改写后的版本必须回到同一套样本上复测。
所谓 Prompt-to-Prompt,可以拆成一条很普通的流水线:
样本集 → Generator Agent → Critic Agent → Optimizer Agent → 新 Prompt → 回归测试
其中有四个必要角色:
- 样本集(Golden Set):可以先整理 20—50 个典型输入,并为每个输入写清期望结果,也就是 Ground Truth。
- 执行体(Generator Agent):拿当前版本的 Prompt 运行任务,保存实际输出。
- 评测器(Critic Agent / LLM-as-a-Judge):对照期望结果,判断缺了什么,并给出具体原因。
- 优化器(Optimizer Agent):读取当前 Prompt 和失败案例,补足约束,同时尽量保留已经有效的部分。
人在这里负责定义“什么算对”,模型负责尝试不同写法。两者不能颠倒。
先用一个客服样本跑通
下面保留一个具体场景:用户称卡被扣了 100 元,自己没有订购服务,而且情绪激动;期望输出必须包含安抚情绪、核对账单、承诺 24 小时内答复。
示例只放了 1 条数据,最多循环 3 个 Epoch,调用模型是 gpt-4o。它适合说明流程,不适合直接上线。
import openai
# 1. 准备输入与期望结果
golden_dataset = [
{
"input": "我的卡被扣了 100 元,但我没有订过这个服务,气死了!",
"expected": "【安抚情绪】+【核对账单】+【承诺24小时内答复】"
}
]
# 2. 当前版本的 Prompt
current_prompt = "你是一个客服,请回答用户的问题。"
def run_agent(system_prompt, user_input):
response = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
]
)
return response.choices[0].message.content
# 3. 运行、评测、改写
for epoch in range(3):
print(f"--- Epoch {epoch} ---")
bad_cases = []
for case in golden_dataset:
output = run_agent(current_prompt, case["input"])
critic_prompt = f"标准要求:{case['expected']}\n实际输出:{output}\n请指出实际输出漏掉了哪些要点,用一句话总结缺点。"
critic_feedback = run_agent("你是一个严格的质检员。", critic_prompt)
if "未" in critic_feedback or "漏" in critic_feedback or "差" in critic_feedback:
bad_cases.append({
"input": case["input"],
"feedback": critic_feedback
})
if not bad_cases:
print("当前样本均通过,停止迭代。")
break
optimizer_instruction = f"""
现有的提示词:{current_prompt}
在以下案例中表现不佳:
{bad_cases}
请重写提示词,针对这些反馈补足约束,同时保留已经有效的要求。
"""
current_prompt = run_agent(
"你负责根据失败案例修改 Prompt。",
optimizer_instruction
)
print(f"新生成的 Prompt:\n{current_prompt}\n")
这段循环能说明闭环,但它不能证明“跑 3 轮就能达到生产水位”。代码里至少还有三处需要补:
golden_dataset只有 1 条案例,无法判断换一种说法后是否仍然有效。- 失败判断只检查“未、漏、差”三个字。如果 Critic 回答“未发现缺漏”,代码同样可能把它当成坏案例。
- Generator、Critic 和 Optimizer 最终都调用 gpt-4o,同一个模型的判断盲点可能在三个角色中重复出现。
正式使用时,可以让 Critic 返回固定字段,例如 pass 和 missing_points。每一轮还要保存 Prompt 版本、输入、输出和评分,否则出了问题也无法回滚。
样本比“顶级专家”这类措辞更重要
上面的客服样本有一个可取之处:期望结果不是一段漂亮话术,而是三个能检查的动作——先安抚,再核对账单,并明确 24 小时内答复。
如果 Ground Truth 只写一段理想回复,Critic 很可能只比较措辞是否相似;把标准拆成动作后,它才更容易指出到底漏了哪一步。
起步时可以先做 20—50 个典型样本,但不要把所有样本都交给 Optimizer。还要留出一部分未见数据集,也就是 Holdout Set,只用于最终评测。否则优化器可能越来越会回答测试集里的老问题,换个输入就失效。
回归测试也不能只看当前分数。每轮修改后,都要检查此前已经通过的案例有没有重新失败。自动优化真正省下的,是逐字猜测的时间,不是测试时间。
Token 预算和安全边界要分开处理
如果用 50 个样本跑 5 轮,Generator、Critic 和 Optimizer 都会反复调用模型,Token 消耗会快速累积。一种可选分工是让本地的 Qwen-2.5-14B 承担执行体,再让云端的 Claude 3.5 Sonnet 负责 Critic 和 Optimizer。它不是固定答案,具体组合仍要看任务质量、延迟和预算。
过度拟合则要靠样本拆分解决。Golden Set 用来发现问题和改写 Prompt,Holdout Set 只做验收。两套数据不能混用。
更重要的是权限。退款、资金写入、删除数据库等动作,不能只靠 Prompt 里的“禁止执行”来约束。我们在 《客服 Agent 听懂情绪后,为什么还不能直接发券退款》 中讨论过类似问题:高风险操作必须由代码层的 Guardrails、权限检查和审批流程控制。
Prompt 可以决定 Agent 怎么回答,权限系统必须决定它能不能执行。
真正应该减少的,是人工逐字调词;真正不能外包的,是样本、判分标准和权限边界。让 Optimizer 生成不同版本,再用同一套测试筛掉坏版本。这样不保证每一轮都会变好,但至少能知道改了什么、为什么保留,也能随时退回上一版。