做 prompt 调试这件事,最容易让人产生自我怀疑。同一段提示词,昨天跑得好好的,今天换了个版本、换了个参数、甚至只是把上下文的顺序调了一下,输出就开始飘。很多人一遇到这种情况,第一反应是"模型不行",第二反应是"再多加几句强调",结果提示词越写越长,从两百字膨胀到两千字,效果反而更难复现。我在调试通义千问大模型的过程中,踩过的最大的一个坑就是:把 prompt 当成"说服模型的咒语"来写,而不是当成"一份给新同事的工作说明书"来写。前者靠感觉,后者靠结构。这篇内容我想聊的就是后者——怎么把通义千问大模型的 prompt 调试,从一门看运气的玄学,变成一套能记录、能对比、能回滚的工程流程。不管你是刚接上 API 想跑通第一个 demo,还是已经在做线上业务、被格式错误和输出漂移折磨了很久,下面这些拆解应该都能对上你的场景。
1. 先摸清模型的"脾气",再动手写第一版 prompt
1.1 同一个版本号下,不同规格的模型完全是两种生物
很多人调 prompt 的时候忽略了一件事:模型是有"规格"的。同一代通义千问下面,往往同时存在不同参数规模的版本,还有 Instruct(指令微调)和 Chat(对话微调)的区分。这两类东西对 prompt 的反应差异非常大,不是"大号的和小号的一样只是聪明点"这么简单。
参数规模小的版本,指令跟随的"护栏"更薄。你写"从下面这段文本里抽取人名、时间和金额,输出 JSON",它大概率能听懂;但你要是写"帮我分析一下这段文本",它就会开始自由发挥,因为它无法判断你到底要什么粒度的分析。而参数规模大的版本,对模糊指令的容忍度更高,但同时"过度理解"的风险也更大——你只是让它润色,它可能顺手帮你改了事实。
我的做法是:先用大规格版本把 prompt 调通,确认逻辑没问题,再往下压到小规格版本看能不能扛住。如果在小规格上崩了,说明你的 prompt 里存在太多"隐含约定",需要显式化。反过来,如果你一开始就从小规格版本调,很容易把 prompt 写成一大坨补丁,最后迁移到大规格版本上反而啰嗦,模型会被冗余信息干扰。
| 模型规格 | 指令跟随强度 | 模糊指令容忍度 | 调试策略 |
|---|---|---|---|
| 小规格(轻量版) | 弱,需要显式拆解步骤 | 低,必须给判定标准 | 先在大规格上调通,再降级验证 |
| 中等规格 | 中,常规任务够用 | 中 | 主力调试对象,兼顾成本 |
| 大规格(旗舰版) | 强,能处理复合指令 | 高,但容易"多做一步" | 用它定天花板,注意约束输出范围 |
还有一点值得说:版本号是会变的。你今天调好的 prompt,下个月服务端做了模型更新,输出风格可能就轻微漂移了。这不是危言耸听,这是所有调用云端模型的团队都会遇到的事。所以我后面会专门讲怎么建立评测集,目的就是让这种漂移能在上线前被发现,而不是被用户投诉才发现。
1.2 中文指令的"动词"比"形容词"权重高得多
我在调试中文场景时发现一个规律:模型对动词的敏感度远高于对形容词的敏感度。你写"请非常认真地、仔细地、严谨地分析",这一串形容词带来的增益很小;但你写"逐条列出""先分类再排序""对每条标注出处",效果立刻不一样。
原因不难理解。形容词描述的是"态度",而态度无法被转化为具体的生成行为;动词描述的是"动作",动作可以被拆解成输出结构。所以我的第一条硬规则是:每一句形容词,都要翻译成一句动词。
举例,"请你专业地回答"这句,翻译过来应该是"回答中不要出现口语化表达,不要使用'我觉得''大概'这类模糊词,涉及数据时标注来源类型"。你看,翻译完之后 prompt 变长了,但模型的行为是可预期的。
顺带提一句中文标点的坑。中文全角逗号、句号、引号在模型输出里非常常见,如果你后面要用正则去解析,或者要用 JSON 解析器去 load,全角符号和 Markdown 代码围栏(```json)是两个高频崩溃点。这个我在第 6 节会展开讲具体的排查方式,这里先埋个钉子:凡是涉及机器解析的输出,prompt 里就必须明确要求"只输出纯 JSON,不要加代码块标记,不要加解释性文字",而且这句话要放在 prompt 的最后一段,而不是开头。
1.3 上下文长度不是"能塞多少塞多少"
通义千问支持的长上下文能力确实很能打,长文档摘要、整份合同审阅这类场景可以直接把原文丢进去。但"能塞"和"该塞"是两回事。我实测下来的体感是:当关键信息位于超长上下文的中间位置时,模型对它的利用率会明显下降,这跟很多长上下文模型的普遍表现是一致的。
所以我在做长文档类 prompt 时,会用一种很土但有效的办法:把最关键的那几段,在 prompt 开头摘一遍,在结尾再点一次。开头那次是给模型"定调",结尾那次是给模型"提醒别忘了"。中间才是完整原文。听起来很笨,但比反复调 temperature 有效得多。
另外,长上下文场景要特别注意"指令淹没"。如果你前面塞了八千字原文,最后才写一句"请输出 JSON",模型很可能就忽略掉这句了。解决办法是把输出格式要求做成固定模板,放在系统提示里,并且用明确的分隔符(比如三个等号或者 XML 风格的标签)把原文和指令区隔开。
[系统提示] 你是文本信息抽取助手。输出必须严格遵循以下 JSON 结构,不得增删字段: {"name": "", "time": "", "amount": 0, "confidence": "high|medium|low"} [用户消息] === 待处理文本开始 === (这里放原文) === 待处理文本结束 === 按上述 JSON 结构抽取。只输出 JSON。这套"分隔符 + 尾部复述"的组合,是我在长文本抽取任务里最稳的写法。
2. 把 prompt 拆成四层:角色、任务、约束、样例
2.1 角色设定的真实作用边界
网上很多教程一上来就是"你是一位拥有二十年经验的资深专家"。这句话有用吗?有,但作用被严重高估了。它的真实作用是调整输出的语域和详略程度,而不是提升模型的真实能力。你给模型安一个"资深律师"的角色,它不会真的多出二十年执业经验,但它会更倾向于使用法律语境下的术语、更倾向于分条款罗列、更少使用口语。
所以我在写角色时,会把它写成能力边界的描述,而不是头衔的堆砌。对比一下:
- 弱的写法:
你是一位非常专业、经验丰富的数据分析专家。 - 强的写法:
你负责把业务方的口语化需求转成可执行的查询条件。你只做转译,不做业务判断;遇到字段含义不明确时,输出需要澄清的问题,不要自行猜测。
后者的价值不在于"专家"两个字,而在于它明确了做什么和不做什么。"不做什么"往往比"做什么"更重要,因为模型的默认倾向是"尽量帮忙",你不划边界,它就会越界。
2.2 任务描述要写成"可验证"的形式
什么叫可验证?就是这个任务的完成标准,换一个人来看也能判断对错。帮我总结一下这篇文章是不可验证的,因为没人知道多长算合格、要不要保留数字、要不要保留观点。用不超过 120 字总结,保留所有金额和时间节点,不添加原文没有的信息就是可验证的。
我一般用三个要素来写任务:动作 + 对象 + 判定标准。
- 动作:抽取 / 分类 / 改写 / 对比 / 判断
- 对象:哪段文本、哪些字段、哪个维度
- 判定标准:长度、字段完整性、是否允许推断、格式要求
这里有个细节很容易被忽略:一次只让它做一件事。我见过太多 prompt 是这样的:"请阅读这段客服对话,判断用户情绪,提取问题类型,给出回复建议,并评估是否需要转人工。"这四件事里,前三件是抽取,第四件是决策,混在一起模型很容易顾此失彼,尤其在小规格模型上。拆成两次调用,成本可能还更低,因为每次的 prompt 更短、输出更可控。
2.3 约束条件的优先级要显式声明
约束写多了会互相打架。比如你既要"输出不超过 50 字",又要"完整列出所有问题点",而问题点有十二个——这就没法同时满足。模型这时候会怎么办?它会自己选一个牺牲掉,而且每次牺牲的可能不一样,这就是输出不稳定的来源之一。
我的处理方式是给约束排优先级,并且在 prompt 里明说:
约束优先级(从高到低): 1. 不得编造原文不存在的信息 2. 必须输出合法 JSON 3. 字段完整性优先于字数限制 4. 字数限制仅在信息量允许时遵守这句话看起来很"工程化",但它解决的是一个真实问题:模型在冲突时的决策依据。你给了优先级,它的输出就稳定得多;你不给,它就随机挑一个。
还有一个反直觉的经验:负向约束的效果弱于正向约束。不要写得太啰嗦,不如每段不超过三句话。不要用 Markdown,不如输出纯文本,不使用任何符号标记。原因还是那个——模型对"动作"的响应强于对"禁止"的响应。负向约束不是不能用,但它最好作为正向约束的补充,而不是替代。
2.4 few-shot 样例:宁少勿滥,边界样例比典型样例更值钱
关于 few-shot,最常见的误区是"样例越多越好"。实际上三到五个精心挑选的样例,效果往往好过十五个随机样例,因为样例太多会稀释指令本身的权重,尤其在小规格模型上。
我选样例的标准是这样的:
- 一个典型样例:最常见的情况,用来定基调
- 一个边界样例:最容易出错的那种情况,比如字段缺失、格式不规范、语义模糊
- 一个反例(可选):明确告诉模型"这样输出是错的",但要标注清楚原因
样例的顺序也有讲究。我习惯把最典型的放第一个,把最难的放最后一个——因为紧邻指令的样例,对模型的影响权重更高。这个规律不一定在所有场景都成立,但在我调试过的抽取类任务里,把边界样例放在最后,明显降低了边界情况的错误率。
样例的格式必须和期望输出完全一致。这一点听起来是废话,但我见过真实的翻车:样例里 JSON 用了双引号,指令里说"输出 JSON",结果模型在字段值里混进了单引号,解析直接挂掉。样例是模型最重要的模仿对象,你在样例里犯的错,模型会原样放大。
3. 结构化输出:从格式乱飞到字段可解析
3.1 为什么必须走结构化输出
只要你的输出要进下游程序,结构化就是绕不过去的。自然语言输出看着舒服,但解析起来是灾难——今天是"金额:1000 元",明天是"金额为 1000 元",后天是"共计一千元"。你写正则写到怀疑人生。
通义千问在指令里明确要求 JSON 时,遵循度是相当不错的,但"不错"不等于"100%"。我在真实业务里测下来,一个写得好的 JSON prompt,首次通过率大概能做到九成以上,剩下的靠重试和校验兜底。这个数字听起来可以接受,但你要想清楚一件事:如果每天调用一万次,一成的失败就是一千次。所以结构化输出必须配一套容错机制,不能指望 prompt 一次到位。
3.2 字段定义要"窄",不要"宽"
新手常犯的错误是字段设计得太自由:
{"result": "这里放分析结果"}这种 schema 等于没设计。正确的做法是把字段拆到原子级,每个字段都有明确的类型和取值域:
{ "sentiment": "positive|neutral|negative", "issues": [ {"type": "logistics|quality|service|other", "summary": "不超过20字"} ], "need_human": true, "confidence": "high|medium|low" }三个关键点:
- 枚举字段用竖线列出候选值,比说"选一个最合适的"有效得多
- 数组元素的结构也要写清楚,不能只说"输出数组"
- 布尔字段用 true/false,不要用"是/否",避免中文解析歧义
还有一个细节:缺失值怎么表示,必须提前约定。是输出null,还是输出空字符串,还是直接省略这个字段?三种做法在代码里的处理逻辑完全不同。我在 prompt 里会固定写一句:无法从原文确定的字段,值设为 null,不得猜测,不得省略字段。这一句话省掉了我大量的调试时间。
3.3 解析失败的处理链路
下面这段是我常用的"调用 + 校验 + 重试"骨架,用 Python 写,走的是 OpenAI 兼容风格接口(具体地址和密钥以你实际的接入方式为准):
import json import re from openai import OpenAI client = OpenAI( api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL", # 以实际接入地址为准 ) FENCE = re.compile(r"^```(?:json)?\s*|\s*```$", re.MULTILINE) def strip_fence(text: str) -> str: """去掉模型偶尔加上的代码块围栏""" return FENCE.sub("", text.strip()).strip() def call_once(prompt: str, temperature: float = 0.1) -> str: resp = client.chat.completions.create( model="YOUR_MODEL_NAME", messages=[ {"role": "system", "content": "你是结构化信息抽取助手,只输出 JSON。"}, {"role": "user", "content": prompt}, ], temperature=temperature, max_tokens=1024, ) return resp.choices[0].message.content def extract(prompt: str, retries: int = 2) -> dict: last_err = None for i in range(retries + 1): raw = call_once(prompt, temperature=0.1 + i * 0.1) try: return json.loads(strip_fence(raw)) except json.JSONDecodeError as e: last_err = e # 第二轮把模型自己的错误输出回灌,要求它只修正格式 prompt = ( f"{prompt}\n\n你上一次的输出无法被解析,错误信息:{e}\n" "请只输出合法 JSON,不要代码块标记,不要任何解释文字。" ) raise ValueError(f"多次重试仍无法解析:{last_err}")这段代码里有几个我踩过坑才加上的设计:
strip_fence是必须的。模型在输出 JSON 时经常会顺手加 ```json 围栏,尤其在它认为"这是一个代码块"的时候。- 重试时温度递增。第一轮用 0.1 求稳定,如果失败了,第二轮稍微提一点温度,相当于换一条生成路径,避免在同一个错误上反复撞墙。这个技巧在小概率格式错误上挺有效。
- 把错误信息回灌给模型。这招比单纯重复原 prompt 有效得多,因为模型能看到自己错在哪,修正起来目标更明确。
- 重试次数不要超过两三次。再多就是浪费调用成本了,该回头改 prompt 了。
注意:重试不是万能的。如果某个 prompt 的重试率超过 15%,那基本可以判定是 prompt 本身的问题,不是运气问题。这时候别加重试次数,去改字段定义和样例。
4. 参数和 prompt 是联动的,不能分开调
4.1 temperature 和 top_p 只动一个
这是老生常谈,但真正遵守的人不多。temperature 和 top_p 都是在调整概率分布的"锐度",同时调两个,你就无法判断效果变化是哪个参数带来的,调试就变成了掷骰子。
我的习惯是:固定 top_p 默认值不动,只调 temperature。抽取、分类、格式转换这类任务,temperature 放 0.1 到 0.3;改写、头脑风暴、文案生成,放 0.7 到 0.9。中间地带(0.4 到 0.6)我很少用,因为它既不够稳定也不够有创意,属于两头不讨好。
| 任务类型 | temperature | 说明 |
|---|---|---|
| 字段抽取 / 格式转换 | 0.1 - 0.2 | 越接近 0 越稳定,但别设成 0,偶发死循环 |
| 分类 / 判断 | 0.2 - 0.3 | 保留一点点灵活性,避免死板 |
| 摘要 / 改写 | 0.3 - 0.6 | 需要一点语言组织能力 |
| 创意文案 / 起标题 | 0.7 - 0.9 | 太高会跑题,建议配约束 |
有个坑要提醒:temperature 设成 0 并不等于完全确定性。受限于推理实现和硬件,即使 temperature 为 0,同一输入也可能偶发不同输出。所以任何依赖"完全可复现"的设计都是不牢靠的,该做校验还得做校验。
4.2 max_tokens 与截断:一个很隐蔽的失败源
max_tokens 设小了,输出会被硬截断。这时候你看到的不是报错,而是一段看起来正常但没写完的内容,JSON 少了个右括号,或者数组没闭合。如果你的代码里没有捕获这种情况,它就会变成一个诡异的解析错误,让你以为是模型不听话。
排查方法很简单:在调试阶段把 finish_reason 打出来。当它等于length时,说明就是被截断了,跟 prompt 没关系,把 max_tokens 调大或者让输出更短就行。这个字段我在调试期一定会打日志,上线后也保留,因为它是最便宜的异常发现手段。
另外一个相关经验:让模型"先说结论再说理由"的 prompt,在截断场景下更安全。因为即使后面被截掉,你最需要的部分已经出来了。反过来,如果 prompt 要求"先分析再给结论",一旦截断,你什么都拿不到。
4.3 流式输出对调试的双刃剑
流式输出在用户体验上是刚需,但对调试很不友好。因为你在开发工具里看到的是一段段拼接的碎片,格式错误很难一眼看出来。我的做法是调试期关流式,上线前再开。调试期用非流式拿到完整字符串,直接丢进解析器和断言里跑,效率高得多。
如果一定要在流式下调试,有个小技巧:把累计的完整文本在每一帧后都存一份,出问题时打印最后 200 个字符。因为格式错误九成发生在结尾——少个括号、多个逗号、混进一句"以上是全部内容"。看结尾比看全文快。
5. 把 prompt 当成代码来管:版本、评测、回归
5.1 建一个"小而狠"的评测集
这是我认为整个调试流程里投入产出比最高的一件事,但绝大多数人跳过它。做法很简单:从真实业务数据里挑 20 到 50 条,覆盖典型情况和边界情况,每条都标注期望输出,存成一个文件。
不用多,二十条就够开始。关键是这几类必须覆盖到:
- 最常见的主流情况(占一半以上)
- 字段缺失的情况
- 文本超长的情况
- 格式最不规范的情况(乱码、错别字、中英混杂)
- 语义模糊、模型容易猜测的情况
有了这个集合,你每次改 prompt 就跑一遍,看通过率是升了还是降了。这比"我感觉这次改得不错"靠谱一万倍。我经历过很多次"改完之后感觉更好了,但跑评测发现边界情况通过率掉了 20 个点"——如果没评测集,这个回归会直接上线。
评测脚本也很简单:
def evaluate(prompt_template: str, cases: list) -> dict: passed, details = 0, [] for case in cases: try: out = extract(prompt_template.format(input=case["input"])) ok = check_schema(out) and match_expect(out, case["expect"]) except Exception as e: ok, out = False, str(e) passed += ok details.append({"id": case["id"], "ok": ok, "out": out}) return {"pass_rate": passed / len(cases), "details": details}check_schema校验格式,match_expect校验内容。两个指标要分开看:格式通过率和内容准确率。很多人把这两个混在一起,结果格式问题被内容问题掩盖了,改了半天 prompt 没改到点上。
5.2 prompt 的版本管理要有 diff
prompt 一定要进版本库,不要存在聊天记录里、不要存在某个人的记事本里。我习惯把每个 prompt 存成独立文件,加上版本号和修改备注:
prompts/ extract_v1.txt extract_v2.txt # 修复:字段 confidence 改为枚举 extract_v3.txt # 新增:无原文依据时输出 null eval_cases.jsonl每次改完,跑评测,把通过率记在备注里。半年之后你回头看,能清楚知道哪次改动带来了提升、哪次改动其实是在倒退。这件事听起来很枯燥,但它是把 prompt 调试从"手艺"变成"工程"的分水岭。
还有一个实践:不要在同一个文件上连续改十次。改三次还没解决,说明你的思路可能有问题,应该退回去重新想任务拆解,而不是继续在措辞上打转。我自己定了个规矩:同一个 prompt 连续改超过五版还没达到目标,就停下来,把任务拆成两个更小的任务。
5.3 A/B 对比要在同一批数据上跑
对比两个 prompt 版本时,最常见的错误是"用不同数据测"。你在 A 版本上测了十条简单数据,在 B 版本上测了十条难数据,然后得出 B 更差的结论,这毫无意义。同一批数据、同一组参数、同一个模型版本,只改 prompt,这是唯一有效的对比方式。
如果条件允许,还要控制调用时机。模型服务端偶尔会有负载波动,虽然影响不大,但在做精细对比时,尽量把两个版本交替跑,而不是先跑完 A 再跑 B,避免时间因素混入。
6. 那些让人抓狂的故障,以及我的排查链路
6.1 模型"不听话":八成是指令被上下文淹没了
现象:prompt 里明明白白写了"输出 JSON",模型却回了一大段带 Markdown 标题的分析。
我的排查顺序是这样的:
第一步,看指令的位置。如果格式要求写在 prompt 开头,而后面跟了两千字原文,那基本就是被淹没了。把格式要求挪到最后,或者在系统提示里重述一遍。
第二步,看有没有互相冲突的指令。比如系统提示说"尽量详细解释",用户消息说"只输出 JSON",模型就会在两者之间摇摆。这种冲突特别隐蔽,因为两句话隔着很远。
第三步,看样例。如果 few-shot 样例里有一个输出带着解释文字,模型就会认为"带解释也是可以的"。样例的权重比指令还高。
第四步,看是不是任务本身太难。如果任务需要多步推理,模型可能会"边想边写",把思考过程也输出出来。这时候要么拆任务,要么在 prompt 里明确要求"先思考后输出时,把思考过程放在 标签内,最终答案放在标签外"——当然,这要求你的解析器能处理标签。
6.2 幻觉:给证据比说"不要编造"有用
现象:让它从合同里抽甲方名称,原文里没有明确写,它给你编了一个看起来很像的名字。
不要编造这句话的作用很有限,因为模型并不"知道"自己在编造。有效的做法是强制它给出依据:
对每个抽取出的字段,必须同时输出 source 字段,值为原文中的连续片段(不少于 5 个字)。 如果原文中没有可直接支撑该字段的片段,该字段值设为 null,source 设为 null。这个改动的效果非常明显。因为模型要"编一个原文片段"的难度,比"编一个字段值"高得多,它在找不到依据时更倾向于老实输出 null。我在好几个抽取任务上用过这招,幻觉率下降得很明显。
还有一个配套手段:允许它说"不知道"。很多 prompt 隐含了"你必须给出答案"的压力,模型就会硬凑。明确写一句信息不足时输出 null 是正确答案,不会被扣分,能显著改善这种情况。
6.3 长上下文里的"中间遗忘"
现象:给了一万字文档,问其中位于中间部分的某个细节,模型答不上来,或者答错。
这个不是通义千问独有的问题,长上下文模型普遍存在。我的应对策略有三条:
策略一:位置调整。把最关键的信息放在上下文的最前面或最后面。如果文档本身顺序不能改,就在开头加一段"重点摘录",把关键段落复制过去。
策略二:先检索后生成。如果文档特别长,先做一次"定位"调用——让模型找出与问题相关的段落编号,第二次调用只把这些段落喂进去。两次调用的总成本可能比一次超长上下文还低,准确率还高。
策略三:分段处理再汇总。对于摘要类任务,把长文切成若干段,每段单独摘要,最后把摘要汇总成总摘要。这个"map-reduce"式的做法很土,但在超长文档上非常稳。
6.4 格式解析失败的高频原因清单
我把实际遇到过的解析失败原因整理成了下面这张表,按出现频率排序。遇到解析报错时,我基本是照着这张表从上往下查:
| 现象 | 根因 | 处理方式 |
|---|---|---|
| 开头有 ```json 围栏 | 模型把输出当成代码块 | prompt 明确"不要代码块标记" + 代码里做围栏剥离 |
| 结尾多一句"以上是结果" | 模型的对话习惯 | prompt 要求"最后一个字符必须是 }" |
| 中文引号导致解析失败 | 全角符号混入 | 要求"使用英文半角双引号" |
| 数字字段带单位 | 如 "1000元" | 字段定义明确"纯数字,不带单位" |
| 数组只有一个元素时退化为对象 | 模型简化输出 | 样例里放一个单元素数组做示范 |
| 布尔值输出"是/否" | 中文习惯 | 字段定义写死 true/false |
| 字段名大小写不一致 | 无约束 | 样例 + 明确"字段名严格按给定大小写" |
这张表里,围栏问题和尾部多余文字加起来占了绝大多数。这两个都是纯格式问题,跟任务难度无关,只要 prompt 里说清楚、代码里兜住,就能基本消灭。
6.5 一个容易被忽略的排查动作:打印原始输出
最后说一个非常基础但极其有效的习惯:永远打印模型的原始输出,不要只打印解析后的结果。
我见过太多次这样的调试过程:代码里json.loads报错,开发者盯着自己的代码看了半小时,怀疑是解析逻辑有问题,实际上模型的输出里混了一个看不见的全角空格,或者干脆返回了空字符串。如果你在解析前先print(repr(raw)),一眼就能看出来。
repr()会把不可见字符显示出来,这是排查格式问题时最省时间的动作。我现在所有涉及模型输出的代码,第一版一定带这行打印,等稳定了再考虑去掉。
7. 我在实际项目里沉淀下来的几条习惯
调了这么久 prompt,有几个习惯是我不再动摇的。
第一,任何一个 prompt 写完之后,先自己读一遍,假装自己是第一次看到这段文字的实习生。如果你读完不确定要做什么,模型大概率也不确定。这个自检动作花不了一分钟,但能省掉好几轮调试。
第二,prompt 的长度和稳定性不成正比。我见过两千字的 prompt 效果不如三百字的。长 prompt 的问题在于约束太多、互相冲突的概率上升,而且你自己都记不住里面写了什么。真正有效的 prompt 是每一句都有明确作用的,删掉任何一句都会掉指标。
第三,先修任务拆解,再修措辞。当效果不好的时候,九十%的人第一反应是改措辞——加"请""务必""非常重要"。这些几乎没用。真正有用的是问自己:这个任务是不是一次做了太多事?是不是缺了判定标准?是不是没给样例?把这三件事解决了,措辞根本不用调。
第四,把调试过程中的"意外发现"记下来。我有个小本子,专门记那些"没想到会这样"的现象,比如某个词一加进去模型就开始输出 Markdown、某个字段名一改格式通过率就涨了。这些现象没法从文档里查到,只能靠积累。时间长了你会发现,这些零散的观察拼起来,就是你对这个模型最真实的理解。
最后一个实操建议:给你的 prompt 加一行版本注释。在文件开头写清楚版本号、修改日期、改了什么、评测通过率是多少。这个动作看起来很像形式主义,但当你三个月后回来改一个老项目,看到这行注释的时候,你会感谢当时的自己。