本文同步自 MAW 原始仓库的 docs/engineering/LLM_POSTPROCESS_PROTOCOL.md。
这份协议定义 MAW Launcher 如何让 OpenAI-compatible LLM 修改字幕文字,同时保证模型不能写入字幕时间。它是工具箱实现契约,不是工程 schema 的替代品;工程结构仍以 JSON_SCHEMA.md 为准。
1. 信任边界
本地工程的 segments[*].start/end 是唯一时间真源,单位始终为整数毫秒。普通校对、改写和翻译请求不包含时间码、媒体路径、逐词时间或其他工程元数据。重新断句采用独立的断点协议;有完整词级时间的段使用 item 单元,缺少词级时间的段使用不可细分的整段单元:请求可以包含不透明 atom ID 和文字,但仍不包含时间、媒体路径或其他工程元数据。
发送给模型的用户消息是一个按原字幕顺序排列的数组:
[
{ "id": "c0001", "text": "第一条字幕" },
{ "id": "c0002", "text": "第二条字幕" }
]
id 是本次请求临时生成的不透明标识,只表达原字幕顺序。它不是时间、工程主键,也不会写入输出工程。
重新断句的 item-aware 请求会在 cue 中附带只读 atom:
{
"id": "c0001",
"text": "第一条字幕",
"items": [
{ "id": "c0001a0001", "text": "第一" },
{ "id": "c0001a0002", "text": "条字幕" }
]
}
2. 模型返回格式
普通校对、改写和非翻译任务返回一个 JSON 对象,顶层只有 groups 协议字段有意义:
{
"groups": [
{ "source_ids": ["c0001"], "text": "校对后的第一条字幕" },
{ "source_ids": ["c0002"], "text": "校对后的第二条字幕" }
]
}
为兼容只改文字的简单响应,单个 id 字段等同于只含一个元素的 source_ids。返回中的 start、end 或其他额外字段不会成为输出时间来源。
翻译任务使用严格的一对一格式,每条输入 cue 对应一个同 ID 的 group:
{
"groups": [
{ "id": "c0001", "text": "First subtitle" },
{ "id": "c0002", "text": "Second subtitle" }
]
}
翻译解析器为兼容旧模型,也接收只含一个元素的 source_ids,并允许它和规范 id 格式混合出现;含多个元素的 source_ids 在翻译任务中始终无效。
重新断句不返回文字或整组词项列表,只返回每条新字幕的最后一个单元 ID:
[c0001a0001]
[c0002a0002]
断点必须来自本批输入、唯一且递增,最后断点必须覆盖最后一个单元。固定脚本从原工程重建全部文字、标点、时间与逐词 items;断点不能切开完整词项、连续英文单词、数字比例或数字单位。批次附带只读前后文,不能将它们输出为本批字幕;禁用段、明确说话人变化和长停顿作为处理边界。
max_chars / Launcher 的 maxChars 默认 0(不限),允许 1–500。中文、英文与标点逐字符计数,空白不计。模型优先选择语义断点,脚本对超长组在合法边界补拆并避免极短尾条;完整词项或没有词级时间的整段超长时不猜测时间,保留并报告超限。网络失败保留原段,警告可能超限;全部批次失败或断点协议重试后仍无效,不生成产物。兼容旧 callback 的 atom 分组及 cue 文字分组,但原文须精确完整覆盖并落在合法单元边界;不因返回 cue 格式清掉正确词级时间。
3. ID 覆盖规则
本地解析器在写文件前强制检查以下规则:
- 所有输入 ID 必须完整覆盖,不能遗漏或添加未知 ID。
- ID 必须保持原顺序,不能重排。
- 合并只能合并连续字幕,例如
source_ids: ["c0001", "c0002"]。 - 拆分只能让相邻多个 group 重复同一个 ID,例如两个连续 group 都使用
c0001。 - 每个 group 的
text必须是非空字符串。
翻译任务额外要求每个输入 ID 恰好出现一次,禁止合并、拆分、重排、遗漏、重复或添加未知 ID。
本地会先对每个 group 做独立校验。普通任务中,结构化 JSON 缺少 source_ids、text 为空、ID 未知或顺序错误时,只跳过该 group 及其无法安全归属的源字幕;只要仍有合规字幕,其他字幕可以生成输出产物。翻译任务不会写出部分结果:解析器只对无效或遗漏的 cue 做拆分补救,每批最多追加 32 次请求;达到上限仍未补齐时终止、不创建产物,并报告全部未完成 cue ID。JSON 语法损坏无法安全定位 group,客户端会自动重试一次;重试后仍不是合法 JSON 时终止本次处理,不写出结果。重试前会先尝试一次尾部修复:响应在结尾处缺少闭合括号或多出尾逗号时自动补全后重新解析(字符串中途截断等无法安全定位的损坏不做修复,仍走重试)。
单次请求最多发送 40 条字幕,且输入字幕文字总长度约不超过 4,000 个字符;任意一项达到上限就会在本地按原顺序切分批次。更大的工程会保留全局 cue ID 后再统一校验和写出;为保持批次隔离,模型不能跨批次合并或拆分字幕。超过单批字符上限的单条字幕会单独成批,不会被本地强行拆分。
单批网络中断、超时或 HTTP 408 / 429 / 5xx 会降级为保留该批原字幕,再继续后续批次;翻译补齐请求瞬时失败也整批保留原文。警告记录失败批次和 cue ID 范围。失败批次不参与成功批次的重分句;普通输出和翻译回填保留其 ID、时间码与元数据,双语合一仍按该模式清理逐词时间码。全部请求失败、无可用结果、鉴权 / 配置错误或不合规协议仍中止,不写出产物。这与无效响应的校验/补救是两种不同处理。
翻译成中文会在发送前预过滤:已是中文的字幕不发送给模型,按原位置逐字节保留(含逐词时间码;空字幕同样跳过)。判断是纯字符脚本启发式:中文按 CJK 表意文字占「中文 + 拉丁字母」的比例 ≥ 70% 视为已是中文;出现假名、谚文或其他文字脚本时照常发送;中英混合句按同一占比判断,CJK 比例不足 70% 才发送。翻译成英文不会依据拉丁字母跳过字幕,以免将西语、法语等误判为英文;纯数字/标点字幕两个方向都会跳过。翻译成中文时,全部非空字幕都已是中文会直接报错,不写出产物。
普通任务若有 group 不合规,Launcher 会在处理完成提示中列出每条被跳过字幕的全局序号、cue ID、批次、模型组号、跳过原因和原文摘要;只要仍有合规字幕就可写出其余产物。翻译任务的错误报告同样包含未完成 ID 和原因,但不会写出缺行的部分译文。
4. 本地时间映射
- 一对一修改:复制原段
start/end。 - 合并连续字幕:使用首个源段的
start和最后源段的end。 - 拆分单个字幕:按照拆分数量在原段时间槽内本地等分;如果原时长不足以让每段保持正时长,则拒绝结果。
- 模型返回的任何时间字段都不参与计算。
固定替换会在等长改字时按原 item 边界更新文字;长度变化时只合并受影响 item,并继承首尾时间范围。普通 LLM 校对、改写和文稿匹配会复用未改写或有明确局部 diff 的 item;整段大幅改写、无法映射的合并/拆分会移除受影响 item。发生合并或拆分时,位置相关的贴纸和颜色引用也不会复制到新段;一致的安全标量(例如说话人标签)可在连续来源一致时保留。翻译结果不保留逐词 items。未勾选合并时,自动管线会将它作为无 items 的副字幕,同时保留翻译前主轨;与主字幕文本相同的句子(预过滤保留的句子)不会重复进入副轨。勾选「将双语字幕合并为单个字幕」时则先把独立翻译工程和 SRT 放入运行目录,再生成单轨双语工程和 SRT,行序默认翻译成中文时译文在上、翻译成英文时原文在上,可用「双语行序」强制译文在上或原文在上;空文本 cue 不会进入合并结果,翻译与原文相同的句子只保留一份。勾选「只翻译非中文(英文)的字幕」(与「将双语字幕合并为单个字幕」互斥)时,翻译结果直接替换原字幕对应句子,输出单条字幕:未发生翻译的句子逐字节保留(含逐词 items)。自动管线会在运行目录保留编号为 0 的原始快照,后续 .mosp / .srt 阶段共用递增序号;保留中间产物时,独立翻译文件可用于检查模型结果,但不会作为最终副轨发布。
5. 文件与链式处理
处理结果使用原输入目录和操作后缀生成,中文界面为 clip.校对文本.mosp / clip.校对文本.srt(英文界面为 clip.proofread.*)。固定替换的后缀按实际启用的部分细分:只有批量替换为 批量替换,只有简繁转换为 转简体 / 转繁体,两者都有时组合为 批量替换.转繁体(英文界面为 replace / simplified / traditional 及点连接组合);勾选了固定替换但既无替换规则也未选择转换方向时跳过该步骤,不产出文件。重新断句、自定义同理使用 重新断句 / 自定义。同名文件已存在时会追加递增编号。自动后处理运行目录另使用 后处理.<序号>.<阶段名> / postprocess.<序号>.<stage> 命名,并在 manifest.json 记录初始快照与各阶段路径。写入采用同目录临时文件加原子替换,且永不覆盖源工程或源 SRT。合并双语时,手动工具箱输出在中文界面为 clip.翻译为英文.双语合一.mosp / clip.翻译为英文.双语合一.srt(中文目标对应 .翻译为中文.双语合一;英文界面为 clip.translate-en-bilingual.mosp / clip.translate-en-bilingual.srt);自动后处理最终输出使用 clip.postprocess.bilingual.mosp / clip.postprocess.bilingual.srt,中文界面下本地化为 clip.后处理.双语合一.mosp / clip.后处理.双语合一.srt(操作后缀随界面语言本地化,翻译段 translate-* 与双语标记同样会本地化)。只有 SRT 输入时也使用相同的双语合一命名标记。回填单语使用对应的 -backfill 标记:中文界面为 clip.翻译为中文.回填.*(自动管线为 clip.后处理.回填.*),英文界面为 clip.translate-zh-backfill.* / clip.postprocess.backfill.*。
成功后 Launcher 会把生成路径设为下一次工具运行的输入,因此可以按“固定替换 → LLM 校对 → 翻译”等顺序链式处理。固定替换可先批量替换,再做简繁转换;只有 SRT 输入时,如选择工程输出,会创建 .mosp 工程。
翻译入口会检查工程和 SRT 输入的文件名:「双语合一 / bilingual」「回填 / backfill」会阻止再次翻译,.translate-en / .translate-zh(含中文界面的 .翻译为英文 / .翻译为中文)会阻止同一目标的明显重复翻译。这是显式产物标记保护,不是对被用户重命名文件的内容或语言进行识别。
6. OpenAI-compatible HTTP 契约
客户端向配置 URL 的 /chat/completions 发送 Bearer 认证请求;如果 URL 已以 /chat/completions 结尾则直接使用。出于凭据安全,HTTPS 可使用远端地址,明文 HTTP 只允许 localhost、127.0.0.1 或 IPv6 环回地址。请求包含:
model:当前供应商或自定义模型名;messages:系统协议和 cue 文本数组;response_format:JSON 输出约束,按端点能力自动降级——默认{"type": "json_object"};被端点拒绝(HTTP 400 / 422)时改发json_schema语法约束(字幕协议和剪后通读 reviews 各用对应结构;口播整理编辑稿直接使用类 Markdown,不发送 JSON 约束);两者都被拒绝或约束下返回空内容时不再携带该参数,仅靠提示词约束。端点能力按「端点 + 模型」在会话内缓存,当前工序的结构独立选择,不复用另一工序的 schema;temperature: 0.1。
预设供应商为 DeepSeek、智谱 Coding Plan 和阿里云 Qwen;Custom 可填写 HTTPS 或环回 HTTP 的 OpenAI-compatible URL。API Key、URL、模型和最近供应商只保存在本机配置中,完整 Key 不进入工程、SRT、前端配置响应或任务结果。字幕文字会发送给用户选择的供应商,用户应自行确认其隐私和数据保留政策。
7. 思考强度与流式显示
Launcher 使用统一的 reasoningMode 设置:auto、off、low、medium、high。默认值是 off,即主动关闭可用的思考模式;只有用户选择 auto 时,才省略 reasoning 参数并跟随当前模型的服务商默认行为。不同供应商和模型的参数并不完全相同,客户端会按供应商/模型映射为原生的 thinking、enable_thinking、thinking_budget 或 reasoning_effort;Custom 接口无法可靠探测能力,显式设置时只做 OpenAI-compatible 的最佳努力映射。
当前适配器的主要映射如下:
| 供应商 / 模型家族 | off |
low / medium / high |
|---|---|---|
| DeepSeek | thinking.type=disabled |
thinking.type=enabled;V4 额外使用 reasoning_effort=high |
| 智谱 | thinking.type=disabled |
thinking.type=enabled;GLM-5.2 额外传递统一级别 |
| Qwen3 / QwQ / QvQ | enable_thinking=false |
enable_thinking=true;低 / 中使用 4096 / 16384 thinking budget,高跟随模型上限 |
| Qwen3.8 | enable_thinking=false |
enable_thinking=true + reasoning_effort,高映射为 xhigh |
| 其他 Qwen | enable_thinking=false |
enable_thinking=true,跟随模型自己的思考上限 |
| Custom | 不发送推理参数,以保持兼容 | 显式尝试传递同名 reasoning_effort,由接口自行决定是否接受 |
LLM 工具在有前端回调时使用 SSE 流式请求。流式响应中的 reasoning 增量和正文增量分开传给 Launcher:前者只显示在临时的「思考」区域,后者显示为正在生成的 JSON。思考内容和未完成的 JSON 不写入工程、SRT 或日志。
流式传输不改变最终文件协议。客户端先拼接完整正文、解析 groups、按上述覆盖校验和失败规则处理各批,再一次性原子写出产物。中途断流不会把未完成 JSON 写入文件;瞬时请求失败时可保留该批原字幕,JSON 或协议问题则按校验/补救规则处理。不返回独立 reasoning 字段的模型只显示可用正文;不支持 SSE 的接口报告请求错误。
AI 口播整理编辑稿
口播整理使用独立的原文编辑协议:[c001] 原文、~~完整词项~~、行尾 %待确认:原因%。模型不能重写、重排或漏掉目标行,不输出时间。固定解析器校验完整 ID、顺序、原文一致性和词项边界,再映射回源 items 的整数毫秒时间;漏行或截断是协议错误,不表示删除。默认执行编辑、漏清扫描和稀疏信息损失审计。
sourceLine 是可直接编辑的底稿:原文中的 ~、%、反斜杠分别写成 \~、\%、\\,换行与回车写成 \n、\r。模型须原样保留转义;只有新插入的未转义 ~~ 和行尾注释表示处理操作。解析器解码后与 asrText 精确比较,词项索引和时间仍按源文字计算。重复折叠只是覆盖比较的辅助,不能改变数字、比例、否定或限定条件;快捷删除路径也先核验这些信息。
重新断句保留源 items,并按原 item 到新字幕的对应关系重绑可靠的 cleanup_scope。跨新字幕的删减范围拆成多个精确 scope,词项间停顿保留音频范围,原 cut 的时间并集及人工恢复覆盖不变。无词级时间与有词级时间的段分别处理;无法保持已有可靠局部删减绑定时中止写出,不能静默恢复已删内容。无 scope 的音频范围不授权剪字。
副字幕绑定仍遵守 MVP 的一对一契约。已绑定主字幕不送入断句请求,保留原 ID、文字、时间和 items,并隔开前后处理批次;绑定关系即使关闭显示仍保留。未绑定字幕正常处理,绑定段超过最大字数时单独提示。推荐先断句再翻译绑定,不能把已有绑定静默重绑为一对多。
MOSP 保留源 segments/items,生效删除写入 AI 来源空隙,待确认写为 pending 区段。再次整理读取当前生效文字并保留已有删除与手动恢复;导出文字由生效空隙派生。参考文稿可选,使用方法见 工具箱。