Context Engineering 实战 02|System Prompt 是架构决策,不是写说明书
Context Engineering 实战 02|System Prompt 是架构决策,不是写说明书
Config Risk Assessment 系统上线第一周,指令遵循率 67%。System prompt 4000 token,把风险评估的所有规则、所有场景定义、所有输出格式都写在一起——一份完整的"操作手册"。
团队做的第一件事:排查模型具体在哪些指令上不遵循。
指令遵循情况分析(200 条配置变更评估): 指令位置 遵循率 ───────── ────── 前 500 token 的指令 89% ← 系统身份、输出格式 500-2000 token 71% ← 高风险规则 2000-3500 token 48% ← 中风险规则、边界条件 最后 500 token 81% ← 安全兜底提示中间 1500 token 的遵循率只有 48%——接近抛硬币。而这 1500 token 恰好放着最细致的评估规则:“数值类配置变化超过 50% 视为高风险”“开关类配置的核心功能关闭标记高风险”。
注意力 U 形曲线:开头高、结尾高、中间凹。4000 token 的 prompt 里,中段是注意力凹陷区——模型"看到了"这些规则,但分配的注意力权重不够,执行就打折。
这不是措辞问题。就算把中段规则的用词改得再精确,只要它还在这个物理位置,遵循率就上不去。
第一次错误的修复方向
看到中间规则遵循率低,团队的第一反应是"强调"。在中间规则前面加上"以下规则非常重要,必须严格遵循:"
没用。遵循率从 48% 到 52%——模型确实稍微多注意了一点,但加了强调语后,其他规则的遵循率反而降了 3 个百分点。总体效果:67% → 66%。
修复尝试 中间段遵循率 总体遵循率 ────── ────────── ───────── 原始 48% 67% 加"非常重要"强调语 52% 66% ← 此消彼长注意力是零和的。给中间段加权,就从其他地方抢了注意力。这不是优化措辞能解决的——是信息排布的结构性问题。
需要换个思路:不是把 4000 token 的 prompt 写得更好,而是把 4000 token 砍成 1500 token,把不需要每次都出现的内容移走。
分层架构:Base / Task / Guard
把 system prompt 拆成三层,像拆软件架构一样——每层职责单一,变化频率不同:
┌─────────────────────────────────────┐ │ Guard Layer │ ← 安全红线,放 prompt 末尾 │ "不确定时标注需人工复核" │ 50-100 tokens,很少变化 │ "不输出原始配置值" │ ├─────────────────────────────────────┤ │ Task Layer │ ← 当前任务的具体规则 │ 按配置类型动态注入 │ 200-500 tokens,每次不同 │ 只注入跟当前输入相关的规则 │ ├─────────────────────────────────────┤ │ Base Layer │ ← 系统身份 + 输出格式 │ "你是配置风险评估系统" │ 200-300 tokens,部署时固定 │ "输出:风险等级 + 理由 + 建议" │ └─────────────────────────────────────┘| 层级 | 放什么 | 不放什么 | 变化频率 | 预算 |
|---|---|---|---|---|
| Base | 系统身份、输出格式、核心流程定义 | 具体评估规则、示例 | 部署时固定 | 200-300 tokens |
| Task | 跟当前输入类型相关的规则 | 所有其他类型的规则 | 每次请求不同 | 200-500 tokens |
| Guard | 安全红线、fallback 策略 | 功能性规则 | 很少变化 | 50-100 tokens |
为什么 Guard 放在最后?利用 U 形曲线——prompt 末尾的注意力权重是第二高的。安全红线是"不管什么情况都不能违反"的约束,放在末尾比放在中间更有效。
为什么 Task Layer 在中间?因为 Task Layer 的内容跟当前输入高度相关。就算中间段的注意力稍低,模型在处理当前输入时自然会去参考跟输入类型匹配的规则——相关性弥补了位置劣势。
重构实录:四步从 4000 到 1200
Step 1:Base Layer — 从 800 token 到 120 token
原来的 prompt 开头:
你是一个专业的配置变更风险评估系统。配置变更风险评估是运维安全的重要 环节。你需要分析每一条配置变更记录,评估其风险等级。风险等级分为高、 中、低三级。高风险是指可能导致线上事故或严重性能下降的变更。中风险 是指可能影响部分功能但不会导致全局事故的变更。低风险是指常规变更, 不会影响系统稳定性。你需要综合考虑变更的幅度、影响范围、变更时间、 变更人历史记录等因素来做出判断...800 token 说了一大堆。模型需要从中提取的有效信息:我是什么系统、输入是什么、输出是什么。
重构后:
你是配置变更风险评估系统。 输入:一条配置变更记录(含变更前后值、配置项名称、变更人)。 输出 JSON:{"risk": "高|中|低", "reason": "一句话理由", "action": "建议操作"}三行。120 token。信息密度提升了 6 倍。模型不需要知道"配置变更风险评估的重要性"——它只需要知道输入什么、输出什么。
Step 2:Task Layer — 35 条规则变成 5-8 条动态规则
原来写在 prompt 里的 35 条规则:
- 数值类配置变化幅度超过 50% 标记为高风险 - 数值类配置变化幅度在 20%-50% 之间标记为中风险 - 生产环境数值归零标记为高风险 - 开关类核心功能关闭标记为高风险 - 开关类灰度开关变更标记为中风险 - 文本类配置 URL 变更到外部域名标记为高风险 ... 还有 29 条35 条规则约 2400 token。但评估一条"数值类配置变更"时,文本类和开关类的规则完全不需要出现——它们只是噪声。
移到规则库,运行时按配置类型注入:
RULE_STORE={"数值类":["变化幅度超过 50% → 高风险","变化幅度 20%-50% → 中风险","生产环境数值归零 → 高风险","重试次数超过 10 → 需人工确认",# ... 共 12 条],"开关类":[...],# 8 条"文本类":[...],# 9 条"通用":[...],# 6 条(所有类型都适用)}defget_task_rules(config_change):config_type=classify_config_type(config_change)specific=RULE_STORE.get(config_type,[])general=RULE_STORE["通用"]returnspecific+general# 通常 5-8 条评估一条数值类配置变更时,Task Layer 只注入数值类的 12 条 + 通用的 6 条中最相关的几条 ≈ 8 条规则,约 200 token。比 35 条全注入省了 90% 的 token。
Step 3:Guard Layer — 提取安全约束
原来散落在 prompt 各处的安全约束:
第 15 行:"如果不确定,不要强行给出结论" 第 28 行:"不要在输出中暴露原始配置值" 第 33 行:"涉及安全密钥的变更一律标记高风险"统一提取到 Guard Layer,放在 prompt 末尾:
安全约束(必须遵守): - 不确定时标注"需人工复核",不给确定结论 - 不输出原始配置值(脱敏处理) - 涉及密钥/凭证的变更一律高风险60 token。三条红线。放在 prompt 最后——U 形曲线的右端高注意力区。
Step 4:动态拼装
defbuild_prompt(config_change):base=BASE_LAYER# 固定 120 tokensrules=get_task_rules(config_change)# 动态 200-400 tokenstask=f"当前评估规则:\n"+"\n".join(f"-{r}"forrinrules)guard=GUARD_LAYER# 固定 60 tokensreturnf"{base}\n\n{task}\n\n{guard}"重构结果:
指标 V1(全量 prompt) V2(分层 prompt) ────── ────────────── ────────────── 总 prompt 长度 4000 tokens 380-580 tokens(动态) 规则数 全部 35 条 5-8 条(相关的) 指令遵循率 67% 82% 格式正确率 73% 94% 中间段规则遵循率 48% 79%格式正确率从 73% 到 94%——因为 Base Layer 里的 JSON 格式定义现在稳稳待在开头高注意力区,不再被 2400 token 的规则挤到中段。
中间段遵循率从 48% 到 79%——因为中间现在只有 5-8 条相关规则,每条分到的注意力权重是之前的 4-7 倍。
位置策略:放哪比写什么更重要
重构过程中发现了一个反直觉的现象。
Guard Layer 里有一条:“不确定时标注为需人工复核”。这条规则原来放在 prompt 第 15 行(大约第 400 token 的位置),遵循率 61%。移到 prompt 最后(Guard Layer 末尾),遵循率到 87%。
同一句话,同样的措辞,只是换了位置,遵循率差 26 个百分点。
这验证了一个设计原则:在 system prompt 里,位置策略比内容策略重要。你应该先决定"什么信息放哪",再去考虑"怎么写这条信息"。
几个经验法则:
位置 适合放什么 原因 ──── ──────── ──── prompt 开头 系统身份、输出格式 注意力最高区 prompt 中段 动态注入的规则 跟输入相关性高可弥补位置劣势 prompt 末尾 安全红线、硬约束 U 形曲线右端的高注意力区 紧邻用户输入的位置 对当前输入的特殊处理指令 距离近=注意力高负面约束比正面指令更有效。“不要在不确定时给出确定结论"比"在不确定时表示不确定"更好。原因跟注意力无关——是负面约束的边界更清晰。模型更容易理解"什么不能做”,因为违反条件是具体的;"应该做什么"的执行标准模糊,模型容易用自己的理解替代。
每层独立测试:分层的工程价值
分层架构的另一个好处:每层可以独立变更和测试。
Config Risk 团队后来新增了"IP 地址类配置"的规则。如果是全量 prompt,加 5 条新规则意味着重跑全部 eval——因为你不知道加了新内容后对已有规则的遵循率有没有影响。
分层后,只需要:
- 在 RULE_STORE 里加一个
"IP地址类"条目(5 条规则) - 写 20 条 IP 地址类配置变更的测试用例
- 只跑这 20 条 eval + 抽检其他类型各 10 条
全量 prompt 的回归测试成本:200 条 eval × 每条 $0.03 = $6.0 分层 prompt 的回归测试成本:40 条 eval × 每条 $0.03 = $1.2 更关键的是时间: 全量 prompt 跑完 eval 需要 25 分钟 分层 prompt 只需要 5 分钟改一条规则从"25 分钟全量回归"变成"5 分钟定向回归"。迭代速度提升了 5 倍。
这跟软件工程的道理一样:单体应用改一个功能要全量回归,微服务架构改一个服务只需要测那个服务 + 接口兼容性。System prompt 的分层就是 prompt 领域的"微服务化"。
什么时候不需要分层
如果你的 system prompt 短于 800 token,不需要分层。
800 token 以内的 prompt,注意力衰减不明显——开头到结尾的遵循率差距通常在 5% 以内。分层反而增加了工程复杂度。
一旦超过 1500 token,或者你发现某些指令"时灵时不灵"——检查一下这些指令在 prompt 里的位置。如果它们在中段,大概率是注意力凹陷的问题,该考虑分层了。
prompt 长度 分层必要性 ────────── ────────── < 800 tokens 不需要,一个文本文件就够 800-1500 tokens 可选,看遵循率是否波动 > 1500 tokens 强烈建议分层 > 3000 tokens 不分层几乎一定有注意力问题System Prompt 不是告诉模型"你是谁",是定义系统"哪些信息在什么条件下出现"。把它当架构图设计,不要当说明书写。
