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。但评估一条"数值类配置变更"时,文本类和开关类的规则完全不需要出现------它们只是噪声。
移到规则库,运行时按配置类型注入:
python
RULE_STORE = {
"数值类": [
"变化幅度超过 50% → 高风险",
"变化幅度 20%-50% → 中风险",
"生产环境数值归零 → 高风险",
"重试次数超过 10 → 需人工确认",
# ... 共 12 条
],
"开关类": [...], # 8 条
"文本类": [...], # 9 条
"通用": [...], # 6 条(所有类型都适用)
}
def get_task_rules(config_change):
config_type = classify_config_type(config_change)
specific = RULE_STORE.get(config_type, [])
general = RULE_STORE["通用"]
return specific + 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:动态拼装
python
def build_prompt(config_change):
base = BASE_LAYER # 固定 120 tokens
rules = get_task_rules(config_change) # 动态 200-400 tokens
task = f"当前评估规则:\n" + "\n".join(f"- {r}" for r in rules)
guard = GUARD_LAYER # 固定 60 tokens
return f"{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 不是告诉模型"你是谁",是定义系统"哪些信息在什么条件下出现"。把它当架构图设计,不要当说明书写。