第一次用 WeLM 做中文改写,建议先把两个变量冻结:单次输入的原文长度,和输出要用的字段结构。风格、术语白名单先不动,跑顺了再逐个放开。多数「每次结果都不一样」的观感,来自提示里没给边界。
先把提示拆成指令、原文、输出约束三段,再用同一段素材跑 200 字、500 字、1000 字三档输入,对比输出完整性、漏改和自行扩写。输出侧用固定字段名约束结构,逐条核对字段有没有被守住,守不住的样本单独存档,记下输入长度、任务类型、失败表现和复现次数。提示约束只能降低格式漂移的概率,不能保证每次命中,需要靠记录判断哪些长度必须拆开提交。
把一条中文改写需求拆成指令、原文、约束三段
把整段需求挤成一句话丢进去,模型很难分清哪部分是待改写的正文、哪部分是要求。三段式的做法是让每一段只承担一件事,出问题时也能定位到是哪一段引起的。
【指令】
把【原文】改写成更通顺的中文,不改变事实、数字、观点和人名,只调整句式、语序和重复用词。
风格:书面、简洁。
【原文】
<<<
(此处粘贴待改写的正文)
>>>
【输出约束】
只输出一个 JSON 对象,键名固定为 rewritten、changed、kept、uncertain,顺序不变,
不要输出 JSON 以外的任何文字,不要用代码块包裹。
rewritten:字符串,改写后的正文。
changed:字符串数组,每条不超过 30 字,最多 3 条。
kept:字符串数组,原样保留的术语、数字、专有名词,没有就填空数组。
uncertain:字符串数组,含义不明、无法确定改法的地方,没有就填空数组。
三段各自放什么、不放什么:
- 指令段放角色、任务动作、改写范围和风格,不放开头的原文,避免模型在读到任务说明前先按猜测生成半句。
- 原文段只放待改写文本,并用定界符包住;不放字数要求和字段说明,否则这些要求本身可能被当成待改写的句子一起改掉。
- 输出约束段只放字段名、字段顺序、值类型、长度上限和禁止项;风格描述留在指令段,两处都写容易互相冲突。
风格、任务类型(改口语、改公文、压缩成摘要)和术语白名单属于可替换项,一次只动一处,否则结果变化时说不清是哪个变量造成的。
用同一段原文跑 200 字、500 字、1000 字三档输入
输入长度是第二个容易出问题的变量。准备同一主题的素材,截成 200 字、500 字、1000 字左右三份,用同一份提示模板跑,除长度以外不改任何东西。字数用 wc -m 粗略核对:
wc -m draft_200.txt draft_500.txt draft_1000.txt
用 -m 而不是 -c,是因为 -c 统计的是字节数,一个中文字符在 UTF-8 下通常占 3 个字节,看字节数容易判断错。截取时保持段落完整,别从句子中间切断,否则后面分不清漏改是长度造成的还是残句造成的。
三档按下表逐档记录,每格写具体现象和位置,不要只写「正常」「异常」:
| 输入档位 | 输出完整性 | 是否漏改 | 是否自行扩写 |
|---|---|---|---|
| 200 字 | rewritten 是否覆盖原文全部句子 | 指出未改动的句子在原第几段 | 输出字数明显多于原文即记一次 |
| 500 字 | 另看段落顺序有没有被打乱 | 同上 | 注意有没有新增的解释性句子 |
| 1000 字 | 另看结尾段落是否被截断 | 同上 | 注意有没有补出原文没有的信息 |
三档连起来看,目的是找拐点:200 字和 500 字都完整、1000 字开始漏改或自行扩写,那么分界就在这两档之间,处理方式是拆成两段分别提交,或者把改写要求进一步收窄。具体从哪一档开始走样,取决于原文结构、段落长度和当时的模型行为,需要自己跑一遍看。
在输出里固定字段名,逐条核对格式有没有被守住
命名约定越死,核对越省力。建议固定成几条:一律小写英文加下划线,不用驼峰;顺序为先正文、后改动说明、再保留项和存疑项;值类型固定,正文用字符串,说明类一律用数组;空值用空数组,不用 null,也不省略键。改模板时只改字段的值和说明,要改字段名就整体升一个版本号,比如在文件名里标 v2,别在同一套记录里混用两种命名。
跑完一轮按清单逐条核对:
- 输出里是否只有约定的四个键,开头有没有多出「以下是改写结果」这类说明。
- 键名是否逐字一致,有没有出现 rewritten_text、Rewrite、rewriteResult 之类的变体。
- 键的顺序是否和约定一致,部分解析逻辑对顺序敏感。
- 数组字段是否真的返回数组,有没有把三条改动压成一句话。
- 没有内容时是否返回空数组,而不是返回 null 或直接省略键。
- 整段输出有没有被代码块围栏包住,导致解析失败。
机械核对可以交给 jq。把模型原始回复存成 reply.json:
cat reply.json | jq -e 'has("rewritten") and has("changed") and has("kept") and has("uncertain")'
cat reply.json | jq -e '(.rewritten|type=="string") and (.changed|type=="array")'
cat reply.json | jq -e 'keys'
第一条只验证字段在不在,第二条验证值类型,第三条把实际键名打印出来对照。这三条都不检查语义,改写质量仍然要人看。格式被破坏时,把原始回复、当次提示词、输入字数和运行时间存在同一个文件里,例如 logs/format_break_001.md,不要只截一段贴到聊天记录,后面检索不到上下文。
把守不住格式的用例单独存档并标出输入特征
成功用例记一遍就够了,守不住格式的样本才值得单独留档。每条失败样本按固定列记录,表头不要中途改,攒起来才能横向比较。
| 列名 | 记录内容 |
|---|---|
| 输入长度 | wc -m 得到的字符数,以及是否做了分段提交 |
| 任务类型 | 改写、缩写、改成口语、改成公文等,一次只写一种 |
| 失败表现 | 字段缺失、字段改名、多出说明文字、漏改、自行扩写,尽量抄一段原文片段 |
| 复现次数 | 同一提示词、同一输入、不改参数重跑 N 次里出现几次,分母写清楚 |
复现次数这一列最容易记糊。分母是同一输入不改参数重跑的次数,比如 3 次里出现 2 次就写 2/3;中途调了温度、长度上限或提示词,那属于另一条记录,不能算进同一分母。
存档时再补一段输入特征,写明这段原文是否含列表、是否含表格、数字是否密集、是否含专有名词、段落数是多少,这几项常和格式被破坏同时出现。文件名带上长度和任务类型,例如 fail_input500_list_note.md,之后按文件名就能筛出同类样本。记录表一开始只有几条不必急着归纳,跑过一段时间再回看,更容易判断哪些长度需要拆开提交、哪些字段名总被模型改写。