大模型指令与元指令--系统提示词工程化实战

目录
摘要
系统提示词是元指令最大的工程载体,写得好坏直接决定应用的输出协议、边界与成本。本文把它的写作拆成角色、边界、协议、风格四块结构,给出一份生产级实例与版本管理、回归测试的代码化方案。后半篇覆盖 token 预算、RAG 分工、多智能体元指令、泄露防御四个进阶场景。
1. 系统提示词的解剖
第 1 篇确立了元指令的判据:以模型行为为作用对象。系统提示词是元指令最集中的落点,也是工程上最常被写坏的一份文本。把它拆成结构,坏点才看得见。
1.1 四大块结构
一份合格的系统提示词由四个功能块组成,各自回答一个固定问题:
图:系统提示词的四大功能块
图下解读:四块缺任何一块,对应的行为就交给模型默认习惯。默认习惯与业务预期不一致时,线上表现就是随机漂移。
四块的职责与写坏的症状一一对应:
| 功能块 | 回答的问题 | 典型规模 | 写坏的症状 |
|---|---|---|---|
| 角色定义 | 你是谁 | 两到三句 | 口吻漂移,专业度不稳 |
| 能力边界 | 做什么不做什么 | 三到五条 | 越界答题,拒绝缺失 |
| 输出协议 | 输出长什么样 | 一段格式约定 | 解析失败,下游报错 |
| 风格规则 | 用什么语言腔调 | 不超过三条 | 中英混杂,风格跳变 |
规模一栏是经验区间。角色写半页纸、风格写十几条的版本都见过,实际效果不如精简版,原因在下一节。
1.2 每块写多细:可判定性判据
粒度的判据只有一条:能不能判定违规。能力边界里的"只处理工单分诊"可判定,越界回答一眼可查;风格规则里的"既要亲切又要专业"不可判定,模型只能各拍各的。
两种写法的差距看角色块就够:
弱写法:你是一个乐于助人的助手。
强写法:你有五年运维背景的工单分诊员,面向内部运维同事,
可以直接使用术语,不需要解释基础概念。
弱写法没有错,但它把"乐于助人"的解释权完全交给模型。强写法把受众、知识背景、术语尺度全部钉死,同样的四个问题在两份提示词下会得到两档稳定性的回答。
边界讨论:规则总量要有上限。第 1 篇的宪法式规则集实验给出的经验值是二十条以内,超过后服从率明显下滑。写不下时,正确动作是拆任务族模板,不是往一份里硬塞。
可判定性判据的落地方法:写完每条规则后在旁边标注"怎么验"。能写出验证动作的留,写不出的改写成能验的,改不动的删掉。这个动作三十秒一条,一整份提示词十分钟过完,产出的验证清单直接就是第 3 章回归用例集的种子。
判据还能反过来用:模型表现不稳的地方,先查对应的规则是不是不可判定。线上口吻忽正式忽随意,多半是风格块写了"亲切且专业"这类双重要求,模型在两个解释间随机摆。把双重要求拆成两条可判定的单句,摆动立刻消失。
1.3 从 Demo 到生产的三道坎
同一份系统提示词,demo 与生产之间隔着三道坎:
| 坎 | demo 表现 | 生产要求 |
|---|---|---|
| 输出协议 | 肉眼看着像 JSON | 字段级可解析可校验 |
| 边界行为 | 常见问题答得好 | 越界与拒答路径全覆盖 |
| 成本 | 提示词多长无所谓 | token 预算与缓存策略 |
三道坎对应后文三章的工程动作:协议坎靠回归测试与结构化输出,边界坎靠用例与仲裁,成本坎靠预算表与压缩。demo 的通过标准是演示成功,生产的通过标准是失败可控。
写提示词之前先想清楚目标形态,能省掉一半返工。给老板演示的版本和扛线上流量的版本,从第一行就该不一样。
三道坎还有个时间维度容易被忽略:demo 阶段没踩过的坑,生产头一周会集中踩一遍。预留一周的灰度缓冲,把三道坎在真实流量下各过一遍,比上线后救火便宜一个数量级。灰度期间的通过标准提前写死:解析失败率、越界回答率、token 成本三项,任何一项超线就暂停放量。
结构清楚了,下一章动手写一份完整的。
2. 从零写一份生产级系统提示词
2.1 完整实例:工单分诊助手
选一个真实业务场景:运维平台要把用户工单转给正确的处理组。下面这份系统提示词四块齐全,可以直接投产:
// 来源:自实现 / triage_system_prompt.txt(生产骨架)
你是「快修云」工单分诊助手。
## 角色定义
你有五年运维背景,负责把用户工单转给正确的处理组。
## 能力边界
只处理工单分诊。用户询问价格、法律、医疗话题时,回复超出
范围并引导创建普通咨询工单。
无法确定处理组时归入待人工分诊,不允许猜测。
## 输出协议
每次输出一个 JSON 对象,字段如下:
category:处理组,取值 network、database、account 或 pending
confidence:0 到 1 之间的置信度
reason:不超过 30 字的分诊理由
禁止输出 JSON 以外的任何文字。
## 风格规则
使用简体中文。reason 面向运维同事,允许使用术语。
源码说明:四块各就各位之外,还有两个容易忽略的设计。能力边界里写了兜底动作"归入待人工分诊",不确定时有路可走,模型才不会硬猜。输出协议用字段级描述而不是给整段示例,省 token 且无歧义。
2.2 迭代:写完才是开始
系统提示词没有一次写对的,迭代靠固定循环:
图:系统提示词的迭代循环
图下解读:循环的入口是固定用例集,出口是灰度。没有固定用例集的迭代等于裸奔,每次改动的影响范围无从知晓。
迭代期内最常见的四类修改动因,各自对应不同的功能块:
| 修改动因 | 典型表现 | 应改的块 |
|---|---|---|
| 解析失败率上升 | 下游报 JSON 错误 | 输出协议 |
| 越界回答 | 答了不该答的问题 | 能力边界 |
| 口吻投诉 | 太随意或太生硬 | 角色定义 |
| 成本超标 | 单次请求 token 超预算 | 全部,做压缩 |
2.3 写作顺序与同级评审
四大块的写作顺序影响质量。推荐顺序是能力边界最先写,因为它定义做不做;输出协议次之,它对接下游;角色定义第三,它决定口吻;风格规则最后,多数场景它是可选项。
这个顺序的反直觉之处在角色后置。多数人从"你是一个…"起笔,写完角色才开始想边界,结果边界被角色的热情稀释。先把不做的写清楚,角色的活动范围自然收敛,两块不会打架。
评审用同构检查:四大块各自一栏,逐行问三个问题,这条可判定吗、与其他块冲突吗、哪个用例能击穿它。同级评审一小时能过完一份中型提示词,发现的冲突比线上运行一周更多。
评审还有个前置条件容易漏:评审人手里的用例集要与写手一致。写手心里的边界用例与评审人的不同,同一份提示词会得出相反的评价。开评前先对齐用例集十分钟,比评审中争吵一小时有效。评审的产出不只是修改意见,还有更新后的用例集,两份资产同步演进。
动因定位到块,修改就不至于牵一发动全身。这份迭代能力要成立,前提是提示词有版本和测试,这正是下一章的主题。
评审的产出还要有个固定格式:一张标记表,每条规则一行,三列分别是规则编号、判定结果(通过、存疑、不通过)、备注。存疑的规则标记具体的用例编号,下次评审时优先复查。这张表本身进版本库,与提示词文件一起提交,让每次评审的结论都可追溯。
3. 版本管理与回归测试
3.1 提示词注册表
提示词改动没有版本管理,回滚就靠翻聊天记录。一个够用的注册表三十行不到:
# 来源:自实现 / prompt_registry.py(提示词注册表)
import time
from pathlib import Path
class PromptRegistry:
def __init__(self, base_dir: str = "prompts"):
self.base = Path(base_dir)
def save(self, name: str, text: str) -> str:
# 版本号用时间戳,天然递增且不可复用
version = time.strftime("%Y%m%d%H%M%S")
path = self.base / name / f"{version}.txt"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
return version
def load(self, name: str, version: str = "latest") -> str:
d = self.base / name
if version == "latest":
version = sorted(p.stem for p in d.glob("*.txt"))[-1]
return (d / f"{version}.txt").read_text(encoding="utf-8")
if __name__ == "__main__":
reg = PromptRegistry()
v = reg.save("triage", "你是快修云工单分诊助手")
print(v, len(reg.load("triage")))
源码说明:save 永远新增文件不覆盖,任何历史版本都可回取;load 默认取最新,测试时显式传版本号做对照。注册表落盘后,git 只需管目录,回滚变成指定版本号的字符串。
图:带版本管理的发布回路
图下解读:回路的关键是发布与代码同构:版本号、回归、灰度、回滚四件套一件不少。提示词从"一段字符串"升级成"一个可运维的制品"。
3.2 回归测试:固定用例集
固定用例集是迭代的锚点。用例数量十到三十条,覆盖正常、边界、越界三类,断言只检查输出协议与关键判定:
# 来源:自实现 / test_triage_prompt.py(回归测试骨架)
import json
from prompt_registry import PromptRegistry
CASES = [
{"in": "数据库连不上了,报错连接超时",
"want": {"category": "database"}},
{"in": "帮我算算企业版一年多少钱",
"want": {"category": None}}, # 越界,应拒答引导
{"in": "密码忘了怎么找回",
"want": {"category": "account"}},
]
def run_cases(system_text: str, chat) -> int:
# chat 为接真实或模拟模型的回调函数
passed = 0
for c in CASES:
resp = chat(system_text, c["in"])
try:
out = json.loads(resp)
except json.JSONDecodeError:
continue # 协议失败直接判不过
if out.get("category") == c["want"]["category"]:
passed += 1
return passed
if __name__ == "__main__":
reg = PromptRegistry()
cur = reg.load("triage")
print("通过用例数", run_cases(cur, chat))
源码说明:断言分两级,JSON 可解析是协议级,category 判对是语义级。两级分开统计后,失败原因一眼可辨:协议挂了查输出协议块,语义挂了查能力边界块。
回归要对照跑:新版本与当前线上版本各跑一遍,通过数只增不减才能发布。偶发失败用固定随机种子与低温采样压平,把模型随机性从测试变量里剔除。
4. token预算与压缩
4.1 窗口是预算表
上下文窗口是稀缺资源,每一块都该有配额。以下是一份 8K 窗口的示例分配,数字本身可调,配额思想不可省:
| 用途 | 预算 | 占比 | 说明 |
|---|---|---|---|
| 系统提示词 | 800 | 一成 | 四大块齐全的常量 |
| 少样本示例 | 1200 | 一成半 | 两到三个精简示例 |
| 检索上下文 | 3500 | 近半 | RAG 检索结果 |
| 对话历史 | 1500 | 不足两成 | 滑动窗口截断 |
| 输出预留 | 1000 | 一成多 | 按业务最长输出定 |
预算不齐的常见后果是输出被截断:输入把窗口吃光,模型写到一半戛然而止。输出预留是雷打不动的一格,谁也不能挪用。
粗估工具让预算可执行:
# 来源:自实现 / token_est.py(中文 token 粗估)
def est_tokens(text: str) -> int:
# 中文一字按一点五个 token,其他文本四字符一个 token
cn = sum(1 for ch in text if "\u4e00" <= ch <= "\u9fff")
other = len(text) - cn
return int(cn * 1.5 + other / 4)
if __name__ == "__main__":
s = "你是快修云工单分诊助手,输出 JSON 格式的分诊结果。"
print(est_tokens(s)) # 约 30 token
源码说明:粗估误差在两成以内,做预算表足够。精确计数要用目标模型自己的 tokenizer,上线前以精确数为准校一次表。
图:超限时各块的让位顺序
图下解读:让位顺序反映信息的新鲜度排序:历史最旧先砍,检索次之,示例最后。系统提示词与输出预留不在让位序列里,前者是行为契约,后者是交付空间。
4.2 压缩手段与底线
预算内的压缩按收益排序:删连接词与重复限定、合并同类规则、示例瘦身。一段三百 token 的系统提示词,压到两百 token 通常不损行为,因为删掉的多是礼貌性填充。
底线只有一条:输出协议不可压。字段定义、取值范围、禁止事项一个字都不能省,协议块的每个词都在为解析成功率付费。压协议省下的 token,会以十倍的代价还在线上解析失败里。
边界讨论:压缩有收益递减。系统提示词压到一百 token 以下时,四大块开始互相侵占,行为稳定性先于成本崩掉。合理区间是一百五到八百 token,低于下限通常说明职责拆得太散,高于上限该拆任务族模板。
预算表也不是一次写就的文档。每月复算一次:系统提示词的长度随规则增删在变,检索片段的平均长度随文档库在变,两个漂移源都足够悄悄吃掉输出预留。复算只要跑一遍 est_tokens 加一段统计,十分钟的成本,换的是不发生线上截断事故。
系统提示词管行为,检索管事实,两件事的边界下一章划清。
压缩还有个心理陷阱值得点名:压缩过程本身有成就感,每删掉一段都觉得赚了 token,但没人回头验证删掉的规则是否真的无效。正确的动作是每次压缩后跑一遍回归用例集,通过数不降才算压缩成功。不跑回归的压缩等于闭眼删代码,偶尔会删到承重墙。
5. 系统提示词与RAG的配合
5.1 分工边界
RAG(检索增强生成)负责把外部知识实时拼进上下文。它与系统提示词的分工原则一句话:行为进提示词,事实进检索库。
图:一次 RAG 请求的组装时序
图下解读:组装器是分层思想的落点。系统提示词是常量骨架,检索片段是动态血肉,两者在组装器相遇,各管各的失效模式:行为漂移查提示词版本,答案过期查检索库更新。
四条判据决定一段内容放哪边:
| 判据 | 放系统提示词 | 放检索库 |
|---|---|---|
| 变化频率 | 稳定不变 | 随时更新 |
| 体量 | 几百 token 内 | 无上限 |
| 内容性质 | 行为约定 | 事实数据 |
| 失效后果 | 行为漂移 | 答案过期 |
5.2 最常见的错位
典型错位是把领域知识写进系统提示词:产品价格表、故障案例库、API 文档,几百行贴进去。三个恶果随即而来:每次请求都为不相关的知识付 token;价格一改就要发版提示词;模型引用的还是旧价。
反向错位同样存在:把行为规则做成检索文档。规则只在被检索命中时生效,没命中就失忆,分诊助手的输出协议时有时无,多半是这种病。
排查方法用四条判据逐项过:变化频率高于每周的,挪检索库;体量超一千 token 的,挪检索库;内容是行为约定的,挪系统提示词。挪完跑一遍回归用例集,行为无回退即切换完成。
错位的修复有个顺序陷阱:把知识从提示词挪到检索库时,系统提示词要同步加一条"基于检索内容回答,检索不到就承认不知道"。没有这条规则,模型会继续按旧习惯硬答,知识已挪走但行为没跟上,幻觉率反而先涨再降。
错位的自查还有个五分钟版本:数一数系统提示词里有多少行是"事实陈述"(可以归真假的句子)而不是"行为约定"(规定做什么的句子)。事实陈述超过三成,基本可以判定有错位。逐行标注一遍,标为"事实"的行就是需要挪走的候选清单。过渡期至少跑两周回归,确认新行为稳定后才算完成。
5.3 知识边界的灰区处理
检索不到答案时,模型的反应由系统提示词决定。三种预置模式:
| 模式 | 系统提示词动作 | 适用场景 |
|---|---|---|
| 严格拒答 | 无证据即回答不知道 | 合规、医疗、法务 |
| 置信声明 | 给答案并标注证据不足 | 内部辅助决策 |
| 兜底转人工 | 引导转人工通道 | 高价值客服 |
灰区行为必须显式声明,因为模型的默认习惯是硬答。声明之外还要写判定标准:什么算无证据。检索结果与问题相关度低于阈值、只命中部分关键词,都写成可执行的规则,让灰区有边界。
灰区是幻觉的高发地带,也是系统提示词与 RAG 配合的最前线。这里的规则每写清楚一条,线上的幻觉投诉就少一类,投入产出比高于在提示词里堆十句"不要编造"。
灰区的规则也有量化方法:对三种模式各配一组测试用例,一组有证据、一组部分命中、一组完全无关,看模型在每组的反应是否与预置模式一致。不一致的用例说明判定标准写得太抽象,模型理解出了偏差,需要在系统提示词里给出更具体的边界描述。这个测试十五分钟跑一轮,灰区规则的迭代靠它驱动。
单智能体的分工到此闭环。当系统里出现多个智能体,元指令的分层要再升一级,这是下一章的话题。
6. 多智能体场景的元指令分工
6.1 三角色与三份元指令
多智能体系统里最容易犯的错,是给每个智能体抄同一份万能系统提示词。正确做法是按角色分管辖权,规划者、执行者、审查者各持一份互不重叠的元指令。
图:三角色的元指令分工
图下解读:三份元指令各管一段,规划者定怎么拆,执行者定怎么调工具,审查者定怎么验收。任何一份越界管辖,打回循环就会空转:审查者忍不住亲自改稿,执行者开始自作主张定目标。
调用侧的代码模式,用同一个客户端分发不同系统提示词:
# 来源:自实现 / agent_call.py(OpenAI 兼容客户端调用 vLLM)
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1", # vLLM 起的兼容端点
api_key="EMPTY", # 本地服务无需鉴权
)
def call_agent(system: str, user: str) -> str:
resp = client.chat.completions.create(
model="Qwen2.5-7B-Instruct", # vLLM 启动时加载的模型名
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
temperature=0.2, # 协议型输出用低温
max_tokens=512,
)
return resp.choices[0].message.content
if __name__ == "__main__":
print(call_agent(PLANNER_SYSTEM, "用户反馈登录后白屏"))
源码说明:三份系统提示词来自第 3 章的注册表,按角色名加载。调用参数也随角色分化:执行者要稳定走低温,规划者可以在脑暴场景放宽温度,参数与元指令同属行为配置,应该一起版本化。
6.2 管辖权划分表
| 角色 | 元指令管辖 | 禁止染指 |
|---|---|---|
| 规划者 | 任务拆分与执行顺序 | 具体工具调用细节 |
| 执行者 | 工具调用规范与重试 | 任务全局目标 |
| 审查者 | 验收标准与驳回理由 | 重新执行任务 |
管辖权表的价值在故障定位:多智能体输出异常时,先判断是哪一角色的越权行为,再定位到对应那份系统提示词的功能块修。没有这张表,排查会在三份提示词之间来回打转。
管辖权表还有个建设期的用法:写三份系统提示词之前,先把这张表画出来贴在旁边,逐条检查自己的每条规则是否越界。表比代码先行的原因很实际:三份提示词都写完再对照,越界的地方已经互相纠缠,拆起来事倍功半。先画表再写词,每份从第一行就在正确的管辖范围内。
角色的数量也可以按这张表扩展。四角色以上的系统,每加一个角色就在表里加一行,同时检查新角色的管辖是否与已有角色重叠。超过五个角色后,管辖权矩阵的复杂度会超过人能维护的上限,此时应考虑分组分层而不是继续加平级角色。
7. 泄露风险与防泄露
7.1 Sydney 事件复盘
2023 年 2 月,必应聊天机器人被用户用话术套出了内部系统提示词,代号 Sydney 与完整规则清单直接展示在对话里。手法并不复杂:诱导模型忽略先前指令、进入调试模式、把系统段当素材复述。
图:系统提示词的泄露路径
图下解读:路径的起点无法封死,用户输入里混进指令是言论自由的副产品。能设防的位置在第二到第三步之间:让系统段被复述的成本足够高、收益足够低。
事件后的行业共识很清醒:系统提示词不是保密边界,任何写进去的内容都要当作"可能被公开"来对待。
7.2 防泄露四手段
| 手段 | 做法 | 挡住什么 |
|---|---|---|
| 不放秘密 | 密钥口令永不进提示词 | 直接泄密 |
| 输出过滤 | 检测规则原文回显并拦截 | 复述式泄露 |
| 结构分离 | 规则拆条编号,泄露可定位 | 批量泄露 |
| 监控告警 | 异常复述请求触发告警 | 定向套取 |
四手段里第一条是底线也是全部前提。密钥放在提示词里,等于把家门钥匙贴在门上,后面三手段只是延缓被发现的时间。
边界讨论:防泄露无法根除,只能抬高门槛。模型对系统段的服从是概率行为,足够长的对抗话术总能找到缝隙。系统化的攻防是第 9 篇的主题,本篇只需要记住结论:秘密进环境变量,规则当公开文本写。
四手段的实施优先级按成本排:第一条零成本纯纪律,第一天就能做到;第二与第四条各需一段代码,一周内能落地;第三条需要重构提示词结构,适合配合下一次大版本。四条不必同时上线,但第一条不能等,它依赖的是意识而不是工程。
7.3 泄露发生后的响应预案
泄露不是如果而是何时的问题,预案要在事前写好:
| 动作 | 时限 | 负责角色 |
|---|---|---|
| 确认泄露范围与版本 | 一小时内 | 值班工程师 |
| 评估泄露内容敏感级 | 两小时内 | 安全负责人 |
| 修补并重新发布提示词 | 当天 | 提示词负责人 |
| 复盘并回填对抗用例 | 一周内 | 三方共审 |
预案的第一原则是内容分级。泄露的只是行为规则,影响是定向攻击成本下降;泄露了密钥或内部数据,走的是完全不同的安全事件流程。分级决定响应节奏,混级响应要么过度要么不足。
第二原则是修补后必须回填。同一话术能套出这一版,就能套出下一版相似写法,把攻击样本加入对抗用例集,同类攻击才会真正失效。
预案每年至少演练一次。未演练过的预案在真实事件里执行延误远超预期,常见卡点是没有预设谁有权临时下线提示词。演练把这些卡点提前暴露,成本半天。
8. 反模式与检查清单
8.1 五大反模式
全文收敛成五条反面清单,出现的任何一条都指向明确的修正动作:
| 反模式 | 症状 | 修正动作 |
|---|---|---|
| 万能提示词 | 一份提示词服务所有任务 | 按任务族拆模板 |
| 规则打架 | 同层规则互相矛盾 | 对照功能块逐条排 |
| 秘密进提示词 | 提示词里有口令密钥 | 挪环境变量 |
| 知识塞提示词 | 几百行事实数据常驻 | 挪检索库 |
| 无版本裸奔 | 改提示词不留记录 | 上注册表 |
反模式之间的升级关系值得单独说:万能提示词是根因,其余四条是它的下游症状。一份提示词服务所有任务,规则自然越堆越多(规则打架),知识自然往里塞(知识塞提示词),改动牵一发动全身没人敢留版本(无版本裸奔)。从第一条开刀,后面四条自动减轻;反过来一条条修,治标不治本。
8.2 上线检查流程
图:系统提示词上线检查流
图下解读:三道关卡对应前三章的能力:结构检查、行为检查、成本检查。任何一道不过就退回对应环节,全部通过才进发布,版本号随之落库。
发布记录里同时登记本次用的清单版本。哪一版提示词配哪一版检查项,事后审计才有据可查,清单自身的演进也成了可追溯的资产。
总结
系统提示词的工程化是一条完整流水线:四大块结构决定内容边界,可判定性决定粒度,注册表与回归用例决定迭代安全,预算表决定成本底线。行为进提示词、事实进检索库、秘密进环境变量,三句口诀划清了系统提示词与相邻组件的界。多智能体场景按管辖权拆元指令,泄露防御从"不写秘密"起步。把提示词当代码管理,是本篇唯一需要记住的立场。
外部引用
- OpenAI Chat Completions API 参考:https://platform.openai.com/docs/api-reference/chat/create
- vLLM 推理引擎文档(OpenAI 兼容服务):https://docs.vllm.ai/
- Hugging Face 对话模板文档:https://huggingface.co/docs/transformers/chat_templating
- OWASP LLM 应用十大风险(注入与泄露分类):https://owasp.org/www-project-top-10-for-large-language-model-applications/
- 指令层级论文《Instruction Hierarchies for Large Language Models》:https://arxiv.org/abs/2404.13208
- Anthropic 提示词工程官方指南:https://docs.claude.com/en/docs/build-with-claude/prompt-engineering/overview
- 《Lost in the Middle》长上下文位置效应论文:https://arxiv.org/abs/2307.03172
更多推荐

所有评论(0)