2825 字
14 分钟
页面浏览--访问次数--
20 种 Agent 设计模式(四):补充设计模式

有人问过我一个问题:提示词链和规划是不是都属于提示词层面的东西,有没有机制能强制校验 Agent 有没有跑题。

这个问题问得准。我把之前一条竞品分析链里的校验点全删掉,只留一段「请按以下三步执行」,结果第二步它就开始写报告,第三步又把第二步换个说法重复了一遍。

写下步骤等于告诉模型该怎么做,不等于它必须这么做。

系列前三篇分别写了控制流、多 Agent 协作和可靠性。这一篇补的是它们底下那层东西:软约束和硬校验各自长在哪里,谁能真的拦住跑题。

1. 一条没有校验的链#

那条链的任务是写竞品分析:提取竞品信息 → 分析优劣势 → 写报告。提示词里我明确写了「请按以下三步执行,每步单独输出」。跑出来的过程大概是这样:

第 1 步:已收集到 A、B、C 三家竞品的信息。
第 2 步:A 家最近在做行业整合,整个赛道从 2024 年以来……
(后面跟了八百多字行业历史)
第 3 步:综上所述,A 家在这轮行业整合中的位置如下……

第二步写成了行业综述,第三步把第二步的结论换个说法抄了一遍。整段输出读起来完整,没有报错,没有任何地方提醒我它偏了。

提示词给出的是顺序,不是约束。下面把两种实现分开看。

2. 提示词链的两种实现#

提示词链有两种写法,差别不在步骤本身,而在谁控制步骤之间的流转。

维度提示词版工程版
步骤写在哪同一条提示词里代码里的调用顺序
谁决定下一步的输入模型自己挑上一个函数的返回值
能不能跳步能不能,没有那个入口
跳步能不能被发现看不出来返回值对不上 schema 就暴露了
出错怎么定位只能整段重跑定位到具体某一步
改一步的成本改提示词,牵动其他步骤改那个函数

提示词版长这样:

prompt = """请按以下步骤完成任务:
1. 提取需求
2. 根据需求生成大纲
3. 根据大纲写正文
每一步单独输出,不要跳步。"""
answer = llm(prompt, user_input)

这段提示词给了模型一个顺序。它可以照做,能合并两步、跳过一步,或者在第二步顺手把第三步一起做完。你拿到的是最终那段文字,从里面分辨不出它是怎么走到这里的。

工程版长这样:

outline = llm("根据主题列一份提纲", topic)
draft = llm("按照提纲写初稿", outline)
final = llm("润色这段文字", draft)

第三步的输入就是第二步的返回值,模型没有跳过第二步的入口。它能自由发挥的范围被压到单次调用内部。

flowchart TB
  subgraph P[提示词版]
    A1[一条提示词] --> A2[模型自己排步骤]
    A2 --> A3[一段输出]
  end
  subgraph E[工程版]
    B1[步骤一] --> B2[步骤二]
    B2 --> B3[步骤三]
  end

工程版也不是万能的。它管住的是步骤之间的边界,单步内部的质量还是空的。第三步的提示词要求「只写三个自然段」,模型写了五个,调用顺序看不出来。这类问题要等校验层来接,见第 5 节。

3. 计划放哪儿#

规划模式有两套实现,区别在计划存在哪里。

resp = llm("先列出完成这个任务的步骤,然后按步骤执行。", task)

这里的计划是模型输出里的一段文字,躺在对话上下文里。后面几轮推理把它挤掉一部分,模型就可能自己长出新的步骤。执行到一半时,你也没法问它「第二步做完了吗」。

{
"goal": "写一份竞品分析报告",
"steps": [
{ "id": 1, "task": "收集竞品信息", "status": "done" },
{ "id": 2, "task": "分析优劣势", "status": "in_progress" },
{ "id": 3, "task": "撰写报告", "status": "pending" }
]
}

计划变成了一份数据结构。能改它的是代码,模型提交的是某一步的结果,而不是「我觉得现在该做第四步了」。

提示词里的计划系统里的计划
存在哪对话上下文结构化状态
谁能改模型代码
进度能不能查不能读 status 就行
失败之后怎么办让模型重写一段计划把这一步重置成 pending 再跑
重启之后还在吗不在在,只要状态落了盘

4. 软约束会怎么失效#

把上面那条链的翻车方式列出来,一共六种:

失效方式具体表现纯提示词能不能发现
跳步从需求直接跳到正文不能
合并步骤大纲和正文挤在一段里不能
插入无关内容竞品分析里写了半页行业历史不能
忘记目标报告越写越像市场调研不能
自造步骤多出「总结行业趋势」这一步不能
格式跑偏该出 JSON 的地方出了一段散文能,前提是你写了校验

前五种都要读输出才能判断,而读输出这件事如果没有外部标准,最后还是模型自己说了算。第六种有确定的判据,所以能写成代码拦住。

5. 四种硬校验#

5.1 结构校验#

def call_with_schema(prompt, payload, schema, retries=2):
for _ in range(retries + 1):
raw = llm(prompt, payload)
try:
data = json.loads(raw)
except json.JSONDecodeError:
continue
if validate(data, schema):
return data
raise SchemaError("连续三次都没有给出合规输出")

判据是确定的:字段有没有漏、类型对不对、枚举值在不在范围内。代价是重试,最坏情况多两次调用。

5.2 目标校验#

ON_TOPIC = """原始目标:{goal}
这一步的输出:{output}
判断这段输出是否仍然服务于原始目标。
只返回 JSON:{{"on_topic": true, "reason": "偏离原因"}}"""
def check_on_topic(goal, output):
return json.loads(llm(ON_TOPIC.format(goal=goal, output=output), ""))

判定交给另一个模型,判据是自然语言写的一句话。这里多出一个新的不确定性:判定器自己会不会看走眼。上线之前先攒一批人工标过的输出当黄金集,量出它的准确率,再让它拦主流程。

5.3 状态机校验#

def run_plan(plan, step_impl):
while True:
running = [s for s in plan["steps"] if s["status"] == "in_progress"]
if running:
raise PlanViolation("上一步还没收尾")
step = next((s for s in plan["steps"] if s["status"] == "pending"), None)
if step is None:
return plan
step["status"] = "in_progress"
try:
step["result"] = step_impl[step["id"]](plan)
step["status"] = "done"
except Exception as exc:
step["status"] = "failed"
step["error"] = str(exc)

循环只从 pending 里取步骤,取之前先确认没有正在跑的步骤。跳步在这里没有实现路径:第三步想跑,它得先变成 pending,而只有代码能改 status,模型交回来的只是一段结果。

5.4 权限与审批点#

这三类检查放在调用层,不放在提示词里:

  • 工具白名单:模型请求调用列表外的工具,直接拦下。提示词里写「不要调用删除接口」,挡不住它。
  • 参数范围:退款金额超过阈值就转人工,规则写在代码里。
  • 审批断点:高风险步骤停下来等人确认,确认结果写进状态,进程重启之后不会丢。

6. 四层结构#

flowchart TD
  A[提示词层:告诉模型做什么] --> B[编排层:决定调谁、传什么]
  B --> C[校验层:判输出合不合规、还在不在题上]
  C -- 不合规 --> B
  C -- 合规 --> D[人工层:高风险步骤停下来等人]
  D -- 驳回 --> B
  D -- 批准 --> E[交付]
层硬还是软靠什么生效失效的时候
提示词层软模型的遵循程度跑题了也没人知道
编排层半硬代码里的调用顺序顺序能保证,单步内部的质量保证不了
校验层硬schema、规则、判定器不合规就打回重做
人工层硬审批动作没批准进不了下一步

7. 最小骨架#

把四种校验拼成一个循环,大概四十行:

def run_pipeline(goal, plan, steps, next_input, approver=None):
while True:
step = next((s for s in plan["steps"] if s["status"] == "pending"), None)
if step is None:
return plan
if approver and step["id"] in approver["ids"]:
if approver["ask"](goal, step) != "approve":
step["status"] = "rejected"
return plan
step["status"] = "in_progress"
spec = steps[step["id"]]
data = call_with_schema(spec["prompt"], next_input, spec["schema"])
verdict = check_on_topic(goal, data)
if not verdict["on_topic"]:
step["status"] = "off_topic"
step["reason"] = verdict["reason"]
return plan
next_input = data
step["status"] = "done"

循环停下来的时候只有四种状态:done 是走完了,failed 是调用失败,off_topic 是判定跑题,rejected 是人工驳回。任何一种停下来,调用方都能从 plan 里读到停在哪一步、为什么停。这段代码做的事情,是把「跑题」从一个读输出才能察觉的现象,变成一条带原因的状态记录。

8. 加在哪一层#

每一层都有成本。结构校验的重试和目标校验的判定器都是额外的模型调用,延迟和 token 直接跟着涨;判定器要维护黄金集,规则要跟着业务改。

任务类型建议到哪一层理由
内部草稿、日志摘要结构校验出错了没人看见,重跑一遍就行
对外内容、客服回复结构校验 + 目标校验 + 状态机跑题会被用户直接看到
花钱、发信、改数据上面三层 + 权限校验与审批断点失败的代价不可逆

顺序上我建议从 schema 开始,再加状态机,然后是目标校验,最后才是审批断点。反着做,前期投入会花在还用不到的地方。

9. 脉络表#

要回答的问题靠什么
该按什么顺序做提示词链、规划
这一步的输出合不合格结构校验
这一步还在不在题上目标校验
能不能进下一步状态机
能不能用这个工具权限校验
出错之后怎么办异常处理与恢复
该不该让人看一眼人类在环

配合前三条看会更顺:控制流、多 Agent 与记忆、可靠性。这一篇补的是它们共用的那套校验骨架。

下一步可以玩什么:

  • 给现有的链加一个 schema,把跳步变成能检测的失败,而不是等到读输出才发现。
  • 把计划从提示词搬进 JSON,跑一遍,看模型卡在哪一步。
  • 给判定器攒三十条人工标过的样本,先量准确率再上线。
  • 挑一个高风险步骤做审批断点,把人驳回的原因记下来看分布。
20 种 Agent 设计模式(四):补充设计模式
https://cnyicheng.top/posts/agent-design-patterns-supplement/
作者
伊橙
发布于
2026-09-27
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取