技术深度

stop sequence 的边界与坑

给模型设置停止词之后,接口正常返回,正文也能显示,但最后一项业务字段不见了。这类问题比显式报错更难发现:监控看到请求成功,使用者拿到的却是半份结果。stop sequence 的验收目标不能只有“模型停了”,还要回答“为什么停、停在哪里、剩余内容是否可用”。

本文聚焦生产接入的故障边界。停止词的基础概念可参考 LLM stop token 设计指南,这里不再展开训练时的结束标记和通用选词教程。

一、把生成结束和业务完成拆成两个状态

以 Claude Messages API 为例,官方用 stop_reason 区分自然结束、达到输出上限、命中自定义停止序列以及工具调用等状态。命中自定义序列时,还可以读取 stop_sequence 确认匹配项。Claude 官方停止原因说明

这些字段描述生成过程,不能替应用证明业务成功。即使命中了预期标记,前面的 JSON 仍可能缺字段、引用不存在的订单,或包含错误金额。因此建议保留两个独立状态:生成结束状态,以及解析和业务校验状态。

例如一份抽取结果只包含客户姓名,没有必填日期,生成可以正常结束,但业务状态必须是校验失败。不要为了降低错误率,自动填入当前日期让它通过。这会把可见的不完整输出变成难以追踪的数据污染。

二、最危险的停止词往往也是合法正文

把 } 当停止词,会与嵌套 JSON 的内部闭合括号碰撞;把 Markdown 分隔线当停止词,会与正文中的表格、示例或文档片段碰撞。更长、更奇特的标记只能降低偶然碰撞概率,无法证明用户材料永远不会包含它。

候选停止词应加入的反例主要检查点
换行多行地址或代码是否首行后就结束
右花括号嵌套对象、字符串中的括号是否损坏结构
END引用资料中的同名文本是否截断引用内容
自定义结束标签用户要求解释该标签是否把正文误当控制标记

不要把“上一次没碰撞”当作格式协议。对输入包含停止标记的情况,应在产品设计阶段明确是允许原样引用、转义展示,还是改用另一种输出协议;不能等数据被截断后再猜原因。

涉及 JSON 的任务,应采用目标接口实际支持的结构化输出能力,并继续进行本地解析和业务约束检查。格式约束与内容正确性不同,相关处理见 LLM 输出校验。

三、区分字符串转义和实际传输内容

排查停止词不命中时,要比较实际字符,而不是只看配置文件的外观。换行字符与反斜杠加字母 n 是两个不同的字符串;配置经过 YAML、环境变量、程序字符串和 JSON 编码后,人工看到的文本尤其容易产生误解。

# 仅检查本地字符串,不会调用模型 API
real_newline = "\n<END>"
literal_backslash = "\\n<END>"
assert real_newline != literal_backslash
print(repr(real_newline), len(real_newline))
print(repr(literal_backslash), len(literal_backslash))

建议在测试环境记录停止配置的转义表示、长度和配置版本。日志只记录固定控制标记,不把用户正文一并输出。发现大小写、空格、全角符号或换行差异时,应修正生成约定与配置,而不是随意加入更多宽泛停止词。

还要核对请求进入网关之后是否仍保留相同字段。应用层配置正确,只能说明第一段链路正确;适配器重命名参数、删除空值或改变字符串处理方式,都需要用实际序列化结果检查。

四、流式分片不是文本匹配边界

如果由供应商实现服务端停止,应优先读取其完成事件和结束原因,不能把客户端“暂时收不到下一片”视为正常结束。断网、超时、主动取消都可能让文本看起来停在一个完整句子上。

如果业务另外实现客户端截断,要明确这只是本地展示或处理逻辑。以标记 <END> 为例,分片可能是 <E 与 ND>;逐片查找完整标记会漏掉。稳妥的实现要缓存可能构成标记前缀的尾部,等待后续字符,确认不会参与匹配的部分才向下游输出。

客户端已经输出给用户的字符无法靠后续匹配自动收回。多标记共享前缀时,还要定义哪个匹配优先,并测试标记跨多个分片、流在半个标记处断开、连接结束但未收到完成事件的情况。详细的 SSE 帧解析应单独实现,参考 LLM 流式 SSE 解析。

本地停止展示也不等于服务端已停止计算或不再计费。取消操作的效果和账单统计要按实际接口观察,不能直接用页面少显示的字符数计算节省金额。

五、接口迁移必须重跑能力与终止测试

不能因为两个服务都接受类似的 JSON 请求,就假定停止参数、返回字段和支持的模型完全一致。迁移时分别核对端点、模型、SDK 序列化与网关映射,把“支持停止参数”作为能力项记录,而不是全局默认开启。

可以建立四类检查:不带停止参数的基线请求、携带有效标记的请求、携带可能碰撞标记的请求,以及故意使用不受支持配置的请求。最后一类应在测试环境进行,用来确认系统会明确报错,还是静默移除参数;不能在生产流量里试探。

对网关维护者,未知结束原因应保留原始值并进入可观察分支,避免默认映射为成功。对应用维护者,应把模型及端点变更与停止配置版本关联,后续回滚才知道该恢复哪组组合。

工具调用任务尤其需要独立验收:收到调用意图、完成工具执行、回传结果和最终业务确认是不同阶段。任何文本标记都不能替代授权、参数校验和幂等控制。

六、用反例矩阵决定是否上线

停止词测试不宜只检查一个漂亮示例。建议从真实业务中构造去标识化样本,覆盖正文包含标记、标记缺失、输出上限先到、嵌套结构、空输出和连接中断。每个样本同时断言结束原因、可解析性和业务字段完整性。

先运行不带自定义停止词的基线,再运行候选配置。对比有效结果比例、人工检查发现的误截断、重试次数和实际用量,不能只看平均输出更短。节省的输出如果被重试和人工修复抵消,就没有改善整体流程。

发布时保留关闭停止配置的开关,并把错误分成解析失败、业务校验失败和未确认完成。告警应能定位到配置版本及请求标识,无需保存全部原文。若某类合法输入持续碰撞,应回到输出协议设计,而不是继续往停止词列表添加补丁。

最后为每个可接受的结束状态写清后续动作:正常校验后交付、等待工具结果、明确显示不完整,或在预算内重试。只有当这些分支都可解释时,停止词才是一项可维护的工程配置。

七、相关阅读