把一批文本一次性全量交给 Agnes-2.5-Flash 做摘要或改写,返工最多的原因不是内容质量,而是输出没法用程序接着处理:同一批里有的返回纯文本,有的返回 JSON,有的在 JSON 前后多说一句话,字段名还会换。可行的处理顺序是:先从真实的待处理清单里挑三条代表性样本,把输入模板和输出结构固定下来,再用二十条左右的小批跑验证,确认结构稳定后才放量。下面给的是能直接落到表格和脚本里的记录方式。
批量摘要和批量改写的稳定性,主要取决于输入是否同构、输出字段是否被显式约束,单条结果写得好不好并不是关键。建议先抽三条样本,覆盖长文、短句、含表格或特殊符号的情况,用同一套输入骨架提交,逐字段记录多出、缺失和格式被改写的情况;再用二十条左右小批统计字段完整率和异常类型。字段缺失时先改模板,不要先改解析代码;结构不稳就不放量。
从待处理文本里挑出三条样本
挑选标准只有一个:这三条要能代表整批文本在长度和结构上的差异。通常至少覆盖三类——一条明显偏长的正文、一条只有一两句的短文本、一条含表格或大量特殊符号(编号、代码片段、全角括号、竖线分隔)的文本。样本必须从真实待处理清单里抽,不要自己临时编,编出来的样本往往干净得不真实,验不出问题。
每条样本登记两项信息:字符长度和特殊结构说明。长度决定模型会不会截断或压缩过度,特殊结构决定模型会不会自作主张改写排版。建议先用下面的字段记一张小表,放在批处理脚本同一目录,后续对照时直接引用样本编号。
样本编号 doc_id 字符数 特殊结构说明
S1 doc_1024 约 3200 长文,含 3 级小标题,无表格
S2 doc_2077 约 60 单句,无标点结尾
S3 doc_3311 约 900 含 4 行 3 列表格,单元格内有竖线
字符数用 len(text) 之类的方式实际量出来再填,不要估。特殊结构那栏写具体一点:是表格、是列表、是引号嵌套,还是混了英文和数字编号,后面判断“输出结构为什么变了”时,这栏是最直接的线索。
把输入格式固定成同一套模板
输入差异是输出波动的第一来源。同一批任务里,如果有的把任务描述写在前面、有的写在后面,有的说“总结一下”、有的说“输出 JSON”,模型就会按各自的语气自由发挥。做法是把输入拆成三个固定位置:文本位置、任务描述位置、输出要求位置,三段的分隔符和顺序在整批里都不变,只让待处理文本和 doc_id 变化。
[任务]
对下面【待处理文本】做摘要,并严格按【输出要求】的 JSON 返回。
[输出要求]
只输出一个 JSON 对象,不要输出解释、前后缀文字或代码块围栏;
不要新增下面未列出的字段。字段固定为:
{
"doc_id": "原样回填输入中的 doc_id",
"summary": "中文摘要,不超过 120 字",
"keywords": ["3-5 个关键词"],
"contains_table": true 或 false,
"notes": "原文含无法归一的特殊结构时在此说明,否则填空字符串"
}
[待处理文本]
doc_id: {{DOC_ID}}
<<<TEXT>>>
{{RAW_TEXT}}
<<<END>>>
可替换的只有 {{DOC_ID}} 和 {{RAW_TEXT}} 两处;[任务] 和 [输出要求] 两段是常量,整批复用同一个字符串。这样做的目的是排除输入差异带来的输出波动,之后如果输出还变,问题就落在模型行为上,而不是模板本身。
几个细节建议一并写进常量段:用成对分隔符包住原文,避免原文里的冒号、方括号被误认为指令;明确“只输出 JSON”,比事后清理代码块围栏省事;doc_id 要求原样回填,是为了下游能把结果和输入对上号,缺了它整批结果就得靠顺序猜。改写的场景同理,把“保留原意、不改动数字和专有名词”这类约束放进[任务]段,而不是每次手写。
对照三条样本的输出结构
三条样本提交后,不要只看摘要写得像不像,逐字段比对结构。重点记四类情况:多出的字段、缺失的字段、字段顺序变化、模型自行改写格式(例如把 keywords 从数组写成逗号分隔字符串,把布尔值写成“是/否”,或者在 JSON 外面加说明文字)。
字段 S1 长文 S2 短句 S3 含表格
doc_id 回填一致 回填一致 回填一致
summary 有 有 有
keywords 5 个(数组) 3 个(数组) 4 个(数组)
contains_table false false true
notes 空字符串 空字符串 说明了表格行列关系
额外字段 title(多出) 无 无
格式改写 无 结尾多一句说明 无
这张表的读法:title 是模型自己加的字段,说明“不要新增字段”这句话约束力有限,通常需要在模板里补一句反例说明,重新提交同一条样本确认;S2 结尾多一句“以上是摘要”,说明短文本上模型更容易顺着语气加话,可以在[输出要求]里把“不要输出任何解释性文字”提到第一条。顺序变化在 JSON 场景下一般不影响下游解析,但如果下游是按字符串切分处理,就必须改成走 JSON 解析,不要靠位置取字段。
缺失字段的处理要看缺失方式:如果某字段在三条样本里都稳定出现,只是某条空值,通常填空字符串或空数组即可;如果字段本身在部分样本里直接不出现,说明模型认为它“不适用”,这时要么在模板里写明“字段必须出现,无内容时填空”,要么把这个字段从必填改成可选,并在下游做默认值。这两种选择要在放量前定下来,不能边跑边改。
放量前先跑一小批并设校验
三条样本只证明结构可能对齐,不证明整批都稳。放量前先跑二十条左右,从真实清单里按长度和结构分层抽,覆盖前面那三类情况。跑完不看总量,看两个指标:字段完整率(按字段统计,不是按条)和异常样本清单。字段完整率能暴露某个字段是不是系统性缺失;异常清单要按类型归类——解析失败、缺字段、多字段、格式被改写、doc_id 对不上。
import json, re
REQUIRED = ["doc_id", "summary", "keywords", "contains_table", "notes"]
def check(raw, expected_doc_id):
text = re.sub(r"^```(json)?|```$", "", raw.strip(), flags=re.M).strip()
try:
obj = json.loads(text)
except json.JSONDecodeError as e:
return {"ok": False, "type": "parse_error", "detail": str(e)}
missing = [k for k in REQUIRED if k not in obj]
extra = [k for k in obj if k not in REQUIRED]
return {
"ok": not missing and obj.get("doc_id") == expected_doc_id,
"type": "field_missing" if missing else "id_mismatch",
"missing": missing,
"extra": extra,
"kw_is_list": isinstance(obj.get("keywords"), list),
}
脚本里的 REQUIRED 要跟模板里的字段一一对应,改模板就得同步改这里,否则校验形同虚设。kw_is_list 这类类型检查建议保留,格式被改写往往先出现在类型上。
不合格时的处理顺序建议是:先改模板(补反例、把硬约束前置、明确空值写法),用同样的二十条重跑;模板改过两轮仍然在同一位置出问题,再考虑在下游加容错解析,或者把该字段降级为可选。不建议一上来就写正则兼容各种输出形态,那会把问题锁死在代码里。
放量门槛按可验证项定,通常包含:二十条里没有解析失败;必填字段全部出现;doc_id 全部能对上;额外字段为零,或已明确加入白名单;布尔和数组类型没有出现字符串替代。这几项都过了再扩到全量,扩量过程中保留同一套校验脚本,对异常样本抽样回看。需要结合具体文本类型和下游使用方确认的部分——比如摘要长度上限、是否接受模型补充说明——应该在写模板时定好,不建议留到放量之后再协商。