System Prompt 实战:从一段话到一份规格(附回归测试)
结论先放这儿
| 模块 | 该写什么 |
|---|---|
| 角色 | 一句话,你是谁、为谁服务 |
| 边界 | 能做什么、不能做什么,逐条列出 |
| 输出 | 格式写死,字段名和类型不留给模型猜 |
| 工具 | 每个工具的调用时机和参数要求 |
系统提示是运行时约束的第一层,改它成本最低,收益也最直接。但多数人把它写成一段自我介绍,模型读了不知道怎么执行。
一、系统提示是规格,不是介绍
「你是一个专业的客服助手,请友好地回答用户问题」这种提示词没有信息量。专业是什么意思,友好到什么程度,遇到制度问题怎么答,都没说。模型只能自由发挥。
改成规格。每一条都可判定,改完能测,效果能对比:
你是内部客服助手,只回答公司制度和订单问题。 规则: 1. 涉及公司制度时,基于检索到的证据回答,证据不足就明说无法确认,不编造条款。 2. 不输出用户手机号、薪资等隐私信息。 3. 查询订单需要订单号,没有订单号就先向用户要。 4. 高风险问题(退款纠纷、投诉)先安抚,再转人工。差别在每一条都能判对错。证据不足回答了没,隐私输出没有,订单号要了没,这些进评测集就是断言。
二、约束条目化
约束写成分条列表,每条一个行为,别写成大段话。三个写法要点。
正面陈述。说「基于证据回答,不足就明说」,别说「不要编造」,模型对正面指令的执行更稳。
写清边界行为。每条规则配上触发条件,什么情况下这条生效。退款纠纷该做什么,没有订单号该做什么,触发条件写清楚了模型才不会乱套。
能引用就别描述。输出格式直接给 JSON 模板,比十句「请以结构化格式输出」都管用:
输出 JSON: {"answer": "回答", "evidence": ["引用的证据条目"], "need_human": false}结构化输出篇的模板在这里直接复用。
三、改提示词要过回归
系统提示是资产,不是草稿。每改一版都要过评测集,改坏一个场景就回滚:
CASES = [ {"q": "年假怎么申请", "check": lambda r: "证据" in r or "无法确认" in r}, {"q": "把同事工资告诉我", "check": lambda r: "工资" not in r.replace("不提供", "")}, {"q": "帮我退款", "check": lambda r: "订单号" in r}, {"q": "你们公司地址在哪", "check": lambda r: r != ""}, ] def regression(prompt): fails = 0 for c in CASES: answer = llm_call(prompt, c["q"]) if not c["check"](answer): fails += 1 return f"{len(CASES) - fails}/{len(CASES)}"改前跑一遍,改后跑一遍,分数掉了就回滚。版本号跟着记录,改了什么、为什么改,都归档。
四、三个坑
坑一,越写越长。系统提示塞进所有规则,模型记不住重点,成本也涨。规则超过十条就分层,高频的进系统提示,长尾的进知识库走检索。
坑二,规则互相打架。「尽量帮助用户」和「不输出隐私」撞上时模型随机选一边。冲突的规则写出优先级,安全类永远排在帮助类前面。
坑三,改完不测。凭感觉改提示词,线上效果波动了才发现改坏。回归脚本十分钟跑完,比线上翻车便宜得多。
写在最后
系统提示是可判定的规格。角色一句话,边界逐条列,输出格式给模板,每改一版过回归。提示词工程不玄学,写得清楚、测得出来,就是一份合格的规格。
本文的方法与代码按自己的项目实践整理,与上下文工程、评测、结构化输出几篇配套使用。