修正 SFX Chat completion 预算契约
将优化与翻译的 VectorEngine Chat wire 锁定为 max_completion_tokens。 拒绝旧 max_tokens 与不适用于 Chat 的 max_output_tokens 字段。 统一权威设计、共享计划与决策记录中的 completion tokens 语义。
This commit is contained in:
@@ -30,7 +30,7 @@ T0 只冻结设计、决策、任务归属、迁移 / 回滚门禁和安全记
|
||||
| T1–T6 计划、基线、测试和迁移门禁已通过 Git 共享 | 已完成 | 本文 |
|
||||
| External v1 `model` 完整矩阵已冻结 | 已完成 | 本文“请求与幂等口径” |
|
||||
| 英文化具有 LLM 语义判断和程序 Script 门禁 | 已完成 | 本文“Prompt 与 LLM 口径” |
|
||||
| 一键优化与翻译的 `max_output_tokens` completion 总预算和 `length` 行为已冻结 | 已完成 | 两类请求均为 `2048 × 4 = 8192`,预算包含 reasoning 与可见输出,见本文“Prompt 与 LLM 口径” |
|
||||
| 一键优化与翻译的 completion tokens 总预算和 `length` 行为已冻结 | 已完成 | 两类请求均为 `2048 × 4 = 8192`,预算包含 reasoning 与可见输出,见本文“Prompt 与 LLM 口径” |
|
||||
| 旧 Vidu 队列 drain、发布顺序和回滚门禁已冻结 | 已完成 | 本文“发布与回滚” |
|
||||
| 凭据安全边界已明确 | 已完成 | 秘密值只允许由服务端私密配置注入,不进入 Git、文档、日志或 fixture;凭据轮换不作为本次 T0 仓库门禁 |
|
||||
| T0 放行状态已确认 | 已完成 | `2026-08-06` 项目负责人明确确认 T0 通过,可以进入 T1 |
|
||||
@@ -88,7 +88,7 @@ T0 已通过,T1–T5 可以按本文依赖顺序进入实现;T0 通过不表
|
||||
|
||||
- `userPrompt` 和 `actualPrompt` 上限均为 2048 Unicode code points。
|
||||
- 只按 ECMAScript `String.trim()` 删除首尾空白和行终止符,包含 `U+FEFF`、保留首尾 `U+0085`;不做 NFC、内部空白折叠、换行转换、标点替换或静默截断。
|
||||
- 一键优化固定 `gpt-5.6-luna`、`reasoning_effort = medium`;Worker 翻译固定同模型、`reasoning_effort = low`。两类请求分别按各自 2048 Unicode code point 候选上限的 4 倍,固定 `max_output_tokens = 8192`;它是隐藏 reasoning token 与可见 JSON 输出 token 共享的 completion 总预算,不是可见正文保证,也不按实际输入长度缩小。两者均不发送 temperature 或 function tools。
|
||||
- 一键优化固定 `gpt-5.6-luna`、`reasoning_effort = medium`;Worker 翻译固定同模型、`reasoning_effort = low`。两类请求分别按各自 2048 Unicode code point 候选上限的 4 倍,固定 completion tokens 总预算 `8192`;该预算由隐藏 reasoning tokens 与可见 JSON 输出 tokens 共享,不包含输入 Prompt tokens,不是可见正文保证,也不按实际输入长度缩小。当前 VectorEngine OpenAI Chat wire 固定发送 `max_completion_tokens = 8192`;内部历史字段名 `max_output_tokens` 不是业务语义。两者均不发送 temperature 或 function tools。
|
||||
- 翻译 envelope 固定 `prompt / isEnglish / isFaithfulTranslation / isDirectGenerationFormat / hasAddedOrRemovedRequirement`,只接受完整 `response.text` 中的唯一 JSON object。
|
||||
- `isEnglish = true` 作为 LLM 语义判断,程序侧另外要求:候选至少含一个 Script=Latin 的 alphabetic code point,且所有 alphabetic code point 的 Script 均为 Latin;Common / Inherited 数字、标点、空白和符号允许。
|
||||
- 日文假名、韩文、西里尔、希腊、阿拉伯等非 Latin alphabetic Script 候选失败。测试必须覆盖中文、英文、中英混合输入,以及 actualPrompt 2048 / 2049 边界。
|
||||
@@ -143,11 +143,11 @@ External v1 `model` 先删除首尾 Unicode `White_Space`,再按大小写敏
|
||||
|
||||
### T2:一键优化 BFF 和 Worker 翻译 service
|
||||
|
||||
- 增加登录态 SFX Prompt 优化 BFF,固定 Luna + Medium + `max_output_tokens = 8192` completion 总预算,32 KiB body limit,严格唯一 JSON envelope,不调用音频 provider 或正式计费。
|
||||
- 增加仅 Worker 可调用的 Luna + Low 翻译 service,每次业务尝试固定 `max_output_tokens = 8192` completion 总预算,严格 `isEnglish` + Unicode Script 门禁、保真判断和最多一次业务重试。
|
||||
- 增加登录态 SFX Prompt 优化 BFF,固定 Luna + Medium + completion tokens 总预算 `8192`,32 KiB body limit,严格唯一 JSON envelope,不调用音频 provider 或正式计费。
|
||||
- 增加仅 Worker 可调用的 Luna + Low 翻译 service,每次业务尝试固定 completion tokens 总预算 `8192`,严格 `isEnglish` + Unicode Script 门禁、保真判断和最多一次业务重试。
|
||||
- 测试覆盖中文 / 英文 / 中英混合输入,日文 / 韩文 / 西里尔等非 Latin 字母候选,actualPrompt 2048 / 2049,请求体精确 token 上限,以及优化直接拒绝 `length`、翻译首轮 `length` 重试一次 / 第二轮 `length` 最终失败和 `content_filter / transport` 行为。
|
||||
|
||||
实施记录(`2026-08-06`):T2 已完成。登录态 `POST /api/editor/audios/sound-effects/prompts/optimizations` 已按 `32 KiB` body limit、Luna + Medium + OpenAI Chat + `max_tokens=8192` 接入,并注册 User-scope tracking;优化候选只接受完整唯一五字段 JSON object,拒绝 tool call、未完成响应、非 Han、生成参数内容、代码块、解释、额外 / 重复字段和超限结果,错误响应不暴露候选或内部 envelope。现有音频生成模块内已增加不注册 HTTP 路由的 Worker 翻译 service,固定 Luna + Low + `max_tokens=8192`,严格执行保真 / 直接生成格式 / Latin Script 门禁,首轮内容不合格或 `length` 只以原始 `userPrompt` 重试一次,`content_filter` 和 transport 最终失败不进入第二业务语义轮;typed failure 只暴露 `translation_invalid / translation_upstream_failed` 安全分类。T2 只交付可供 T5 调用的内部 service,尚未改变当前 Vidu 正式生成、队列、计费、持久化、External v1 或 OpenAPI,不是可发布切点。
|
||||
实施记录(`2026-08-06`,`2026-08-07` 同步 master token 契约):T2 已完成。登录态 `POST /api/editor/audios/sound-effects/prompts/optimizations` 已按 `32 KiB` body limit、Luna + Medium + OpenAI Chat + completion tokens 总预算 `8192` 接入,并注册 User-scope tracking;当前 VectorEngine Chat wire 只发送 `max_completion_tokens=8192`,不发送 `max_tokens` 或 `max_output_tokens`。优化候选只接受完整唯一五字段 JSON object,拒绝 tool call、未完成响应、非 Han、生成参数内容、代码块、解释、额外 / 重复字段和超限结果,错误响应不暴露候选或内部 envelope。现有音频生成模块内已增加不注册 HTTP 路由的 Worker 翻译 service,固定 Luna + Low + completion tokens 总预算 `8192`,使用同一 Chat wire 字段,严格执行保真 / 直接生成格式 / Latin Script 门禁,首轮内容不合格或 `length` 只以原始 `userPrompt` 重试一次,`content_filter` 和 transport 最终失败不进入第二业务语义轮;typed failure 只暴露 `translation_invalid / translation_upstream_failed` 安全分类。T2 只交付可供 T5 调用的内部 service,尚未改变当前 Vidu 正式生成、队列、计费、持久化、External v1 或 OpenAPI,不是可发布切点。
|
||||
|
||||
### T3:ElevenLabs adapter、配置、二进制与时长探测
|
||||
|
||||
@@ -211,8 +211,8 @@ T5 -> T6
|
||||
| --- | --- |
|
||||
| canonical | 空 / 全 ECMAScript trim 字符(含 U+FEFF)、首尾 U+0085 保留、U+200B、内部 U+FEFF、换行、组合字符、ZWJ emoji、2048 / 2049 |
|
||||
| 预设 | 40 + 12、ID / label 唯一、文案等值、逗号追加、重复、清快照、超限保文 |
|
||||
| 优化 | Luna + Medium + `max_output_tokens=8192` completion 总预算请求体、唯一 JSON、布尔门禁、length 直接失败、content_filter、无 tool call、无候选泄漏、dialog / scope 迟到响应 |
|
||||
| 翻译 | 每轮 Luna + Low + `max_output_tokens=8192` completion 总预算请求体,中文 / 英文 / 中英混合输入,日文 / 韩文 / 西里尔候选,isEnglish + Script 门禁,2048 / 2049,首轮 length 唯一重试、第二轮 length 最终失败且 provider 0 次 |
|
||||
| 优化 | Luna + Medium + completion tokens 总预算 `8192`,Chat wire 只含 `max_completion_tokens=8192`,唯一 JSON、布尔门禁、length 直接失败、content_filter、无 tool call、无候选泄漏、dialog / scope 迟到响应 |
|
||||
| 翻译 | 每轮 Luna + Low + completion tokens 总预算 `8192`,Chat wire 只含 `max_completion_tokens=8192`,中文 / 英文 / 中英混合输入,日文 / 韩文 / 西里尔候选,isEnglish + Script 门禁,2048 / 2049,首轮 length 唯一重试、第二轮 length 最终失败且 provider 0 次 |
|
||||
| External model | omitted / null / 空串 / 纯空白 / 包围空白新模型 / 显式新模型共用幂等 payload;`audio1.0` / 未知值为 400 + 零副作用 |
|
||||
| ElevenLabs | auto / manual × Loop false / true,固定 model / influence / format,Key 不泄漏,网络 / HTTP / body 失败均只有一次 POST |
|
||||
| 二进制与时长 | `40 MiB` 接受 / `40 MiB + 1 byte` 拒绝,Content-Length / chunked 超限、空 / HTML / JSON / 损坏 MP3、允许与 fallback MIME;有限正时长、30.5 / 60 / 600s 接受,>600s / NaN / 无穷拒绝 |
|
||||
|
||||
@@ -6742,7 +6742,7 @@
|
||||
- provider 迁移:新编辑器 SFX 任务固定使用 ElevenLabs `eleven_text_to_sound_v2`,不提供模型选择、Vidu fallback 或 `audio1.0` alias。Vidu builder / 轮询仅保留给历史素材和其它未迁移调用方;历史素材可读,重绘新任务使用 ElevenLabs。
|
||||
- Prompt 真相:`prompt` 表示用户可见且确认的 canonical `userPrompt`,`actual_prompt` 表示 Worker 严格验收后实际提交给 ElevenLabs 的英文 `actualPrompt`。两者均以 2048 Unicode code points 为上限,只删除首尾 Unicode `White_Space`,不做其它规范化、默认 Prompt 回退或静默截断。
|
||||
- 可见交互:SFX 固定 40 个事件预设 + 12 个补充要求。空 Prompt 直接写入;非空 Prompt 末尾已是 Unicode 标点时直接追加,否则使用中文逗号 `,` 分隔。允许重复,不保留选中态,不去重或截断;点击预设清除旧快照。
|
||||
- Prompt 助手:一键优化使用 `gpt-5.6-luna`、`reasoning_effort = medium`;Worker 正式英文化使用同模型、`reasoning_effort = low`。两类候选上限均为 2048 Unicode code points,因此分别固定 `max_output_tokens = 2048 × 4 = 8192`;真实 VectorEngine Chat 探测已确认该字段限制隐藏 reasoning token 与可见输出 token 共享的 completion 总预算,不包含输入 prompt token,也不是可见正文保证。预算不按实际输入长度动态缩小;两者均不发送 temperature 或 function tools,只接受完整 `response.text` 中唯一 JSON object 的严格 envelope。翻译除要求 `isEnglish = true` 外,程序侧还要求候选至少含一个 Latin alphabetic code point,且所有 alphabetic code point 都属于 Latin Script;日文假名、韩文、西里尔、希腊和阿拉伯等非 Latin 字母均失败。优化只有一个业务语义轮,`finish_reason = length` 直接失败;翻译首轮成功响应但候选不合格或 `length` 时使用同一 `userPrompt`、同一 `8192` 上限唯一重试,第二轮 `length` 最终失败,`content_filter` 和 transport 最终失败不开启第二业务语义轮。翻译最终失败时 ElevenLabs 请求数必须为 0。
|
||||
- Prompt 助手:一键优化使用 `gpt-5.6-luna`、`reasoning_effort = medium`;Worker 正式英文化使用同模型、`reasoning_effort = low`。两类候选上限均为 2048 Unicode code points,因此分别固定 completion tokens 总预算 `2048 × 4 = 8192`;该预算由隐藏 reasoning tokens 与可见输出 tokens 共享,不包含输入 Prompt tokens,也不是可见正文保证。当前 VectorEngine OpenAI Chat wire 固定发送 `max_completion_tokens = 8192`,内部历史字段名 `max_output_tokens` 不是业务语义。预算不按实际输入长度动态缩小;两者均不发送 temperature 或 function tools,只接受完整 `response.text` 中唯一 JSON object 的严格 envelope。翻译除要求 `isEnglish = true` 外,程序侧还要求候选至少含一个 Latin alphabetic code point,且所有 alphabetic code point 都属于 Latin Script;日文假名、韩文、西里尔、希腊和阿拉伯等非 Latin 字母均失败。优化只有一个业务语义轮,`finish_reason = length` 直接失败;翻译首轮成功响应但候选不合格或 `length` 时使用同一 `userPrompt`、同一 `8192` 上限唯一重试,第二轮 `length` 最终失败,`content_filter` 和 transport 最终失败不开启第二业务语义轮。翻译最终失败时 ElevenLabs 请求数必须为 0。
|
||||
- 撤销与锁:一键优化成功产生一层 canonical Prompt 交换快照;优化失败清除本次临时快照,不恢复更早快照。AI 操作和正式提交使用 dialog ID、账号 + 项目 scope、同步 operation ID 与 `AbortController`;只锁当前 SFX dialog。正式提交在第一个 `await` 前冻结 Prompt / duration / Loop;API 接受后结束 `submitting`、进入现有 `queued/generating` 占位,不把接受任务写成生成已完成。
|
||||
- 时长与 Loop:首次打开默认手动时长模式、`5s`、Loop false;手动范围 `0.5-30s`,UI 步进 `0.1s`。自动模式发送 `duration_seconds = null`,禁用 slider 但保留最近手动值。Loop 是独立 API 布尔参数;系统不根据 Prompt 推断、同步或校验 Loop,Prompt 文本与 Loop 开关不建立业务一致性门禁。
|
||||
- ElevenLabs 契约:`POST /v1/sound-generation`,body 固定 `text / model_id / duration_seconds / loop / prompt_influence=0.3`,query 固定 `output_format=mp3_44100_128`,`xi-api-key` 只在服务端 header 注入。provider POST 不自动重试,浏览器正式 POST 不 unsafe retry,队列 `max_attempts = 1`,一个平台 job 最多一次 ElevenLabs POST。成功响应复用现有 `MAX_GENERATED_AUDIO_BYTES = 40 MiB` 做 Content-Length 预检和 `limit + 1` 流式读取,验证 MIME 与真实 MP3,并探测有限正实际时长;实际时长仅受独立技术异常上限 `600s` 约束,不与请求最大 `30s` 比较,`30.5s-600s` 的合法结果可接受。平台使用 operation / queue job ID 作为 `taskId`,不伪造 provider task ID。
|
||||
@@ -6753,9 +6753,9 @@
|
||||
|
||||
## 2026-08-06 SFX 生成优化 V2.0 T2 助手与 Worker 翻译 service
|
||||
|
||||
- 一键优化:新增登录态内部路由 `POST /api/editor/audios/sound-effects/prompts/optimizations`,复用 T1 的最小请求 / 响应 DTO、现有编辑器 `LlmClient`、标准成功 / 错误 envelope 和 route tracking。请求固定 Luna、OpenAI Chat、Medium、`max_tokens=8192`,不发送 temperature 或 function tools;路由独立使用 `32 KiB` body limit,不增加功能级限流器,也不进入 External v1。
|
||||
- 一键优化:新增登录态内部路由 `POST /api/editor/audios/sound-effects/prompts/optimizations`,复用 T1 的最小请求 / 响应 DTO、现有编辑器 `LlmClient`、标准成功 / 错误 envelope 和 route tracking。请求固定 Luna、OpenAI Chat、Medium、completion tokens 总预算 `8192`,当前 VectorEngine Chat wire 只发送 `max_completion_tokens=8192`,不发送 temperature 或 function tools;路由独立使用 `32 KiB` body limit,不增加功能级限流器,也不进入 External v1。
|
||||
- 优化验收:内部五字段 envelope 必须从完整 `response.text` 直接反序列化,允许外围 JSON whitespace,拒绝代码块、前后解释、多个 JSON、额外 / 重复字段、错误类型、tool call 和未完成 finish reason。候选只删除首尾 Unicode `White_Space`,要求 `1-2048` code points、至少一个 Han code point,并严格执行 `true / true / false / false`;失败 HTTP 响应不携带候选、内部 envelope 或上游回显正文。
|
||||
- 翻译 service:在现有 `vector_engine_audio_generation` 内新增仅 crate 内部生成流水线可见、没有同步 HTTP 路由的翻译 service。每个业务语义轮固定 Luna、OpenAI Chat、Low、`max_tokens=8192`,不发送 temperature 或 tools;候选严格执行五字段结构、`true / true / true / false`、`1-2048` code points、至少一个 Latin alphabetic code point,且所有 alphabetic code point 都属于 Latin Script。Han、假名、韩文、西里尔、希腊和阿拉伯字母均失败,Common / Inherited 数字、标点、空白和符号允许。
|
||||
- 翻译 service:在现有 `vector_engine_audio_generation` 内新增仅 crate 内部生成流水线可见、没有同步 HTTP 路由的翻译 service。每个业务语义轮固定 Luna、OpenAI Chat、Low、completion tokens 总预算 `8192`,当前 VectorEngine Chat wire 只发送 `max_completion_tokens=8192`,不发送 temperature 或 tools;候选严格执行五字段结构、`true / true / true / false`、`1-2048` code points、至少一个 Latin alphabetic code point,且所有 alphabetic code point 都属于 Latin Script。Han、假名、韩文、西里尔、希腊和阿拉伯字母均失败,Common / Inherited 数字、标点、空白和符号允许。
|
||||
- 轮次与错误:首轮成功返回但结构、判断、Script、长度或 `finish_reason=length` 不合格时,只以同一 canonical `userPrompt` 开启唯一第二业务语义轮;不得读取或传递首轮候选。首轮 `content_filter` 和 transport / timeout / 上游最终失败直接结束;`LlmClient` 内部 transport retry 仍属于当前业务轮。第二轮任何失败均最终失败。service 错误展示只提供 `translation_invalid / translation_upstream_failed` 分类和安全中文消息,不保存或输出未通过候选。
|
||||
- 阶段边界:T2 没有调用 ElevenLabs、创建额外任务、扣费、修改队列 payload、持久化 `actual_prompt` 或变更 External v1 / OpenAPI / SpacetimeDB schema。T5 接入正式 Worker 时必须移除 T2 的 staged dead-code 豁免,并把翻译结果作为唯一 `actualPrompt` 进入 provider;T2 单独合入仍不是可发布切点。
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ SFX V2 已完成产品与技术口径冻结,T0 已通过,T1–T5 已完成
|
||||
### 一键优化、撤销与前端锁
|
||||
|
||||
- 一键优化只走登录态内部 BFF `POST /api/editor/audios/sound-effects/prompts/optimizations`,请求为 `{ currentPrompt: string }`,成功响应为 `{ prompt: string, charCount: number }`。该路由不向 External v1 开放,不调用 ElevenLabs、不创建正式任务、不触发 SFX 生成扣费。
|
||||
- 优化固定使用 `gpt-5.6-luna`、OpenAI Chat、`reasoning_effort = medium`、`max_output_tokens = 8192`,不发送 temperature 或 function tools;`8192` 按候选 Prompt 上限 `2048 Unicode code points × 4` 固定计算,是隐藏 reasoning token 与可见 JSON 输出 token 共享的 completion 总预算,不是 8192 个可见正文 token 的保证,也不按本次输入长度动态缩小。服务端固定 `32 KiB` body limit,canonical 字符范围为 `1-2048`。
|
||||
- 优化固定使用 `gpt-5.6-luna`、OpenAI Chat、`reasoning_effort = medium`,completion tokens 总预算固定为 `8192`,不发送 temperature 或 function tools;`8192` 按候选 Prompt 上限 `2048 Unicode code points × 4` 固定计算,由隐藏 reasoning tokens 与可见 JSON 输出 tokens 共享,不包含输入 Prompt tokens,不是 8192 个可见正文 token 的保证,也不按本次输入长度动态缩小。当前 VectorEngine OpenAI Chat wire 固定发送 `max_completion_tokens = 8192`;内部历史字段名 `max_output_tokens` 不是业务语义。服务端固定 `32 KiB` body limit,canonical 字符范围为 `1-2048`。
|
||||
- LLM 必须返回唯一 JSON object,内部 envelope 固定包含 `prompt: string`、`isDirectWritebackFormat: boolean`、`isContentComplete: boolean`、`hasObviousFragment: boolean`、`hasGenerationParameterContent: boolean`。完整 `response.text` 只允许 JSON whitespace 包围的单一 object;拒绝代码块、前后解释、多个 JSON 值、tool call、子串提取和自动修复。
|
||||
- 候选 canonicalize 后必须非空、不超过 2048、至少含一个 Han code point,且四个布尔值依次为 `true / true / false / false`。`finish_reason = length` 或 `content_filter` 均失败,不写回候选。失败响应不暴露未通过候选或内部 envelope。
|
||||
- AI 开始前 canonicalize 并写回 Prompt,以该值取代旧快照作为本次临时快照。成功后转为单层可撤销快照;失败保留请求前 Prompt、清除临时快照,不恢复更早快照。
|
||||
@@ -135,10 +135,10 @@ SFX V2 已完成产品与技术口径冻结,T0 已通过,T1–T5 已完成
|
||||
|
||||
### Worker 翻译、ElevenLabs 和结果真相
|
||||
|
||||
- API 只把 canonical `userPrompt` 入队,翻译必须在 Worker 内执行,不在入队前同步生成英文。翻译的每次业务尝试固定使用 `gpt-5.6-luna`、OpenAI Chat、`reasoning_effort = low`、`max_output_tokens = 8192`,不发送 temperature 或 function tools;`8192` 同样按 `actualPrompt` 上限 `2048 Unicode code points × 4` 固定计算,是隐藏 reasoning 与可见 JSON 输出共享的 completion 总预算,不因重试或本次输入长度改变,也不表示可见正文一定可使用 8192 tokens。
|
||||
- API 只把 canonical `userPrompt` 入队,翻译必须在 Worker 内执行,不在入队前同步生成英文。翻译的每次业务尝试固定使用 `gpt-5.6-luna`、OpenAI Chat、`reasoning_effort = low`,completion tokens 总预算固定为 `8192`,不发送 temperature 或 function tools;`8192` 同样按 `actualPrompt` 上限 `2048 Unicode code points × 4` 固定计算,由隐藏 reasoning tokens 与可见 JSON 输出 tokens 共享,不包含输入 Prompt tokens,不因重试或本次输入长度改变,也不表示可见正文一定可使用 8192 tokens。当前 Chat wire 同样只发送 `max_completion_tokens = 8192`。
|
||||
- 翻译内部 envelope 固定为 `prompt: string`、`isEnglish: boolean`、`isFaithfulTranslation: boolean`、`isDirectGenerationFormat: boolean`、`hasAddedOrRemovedRequirement: boolean`。每次必须对完整 `response.text` 做单一 JSON object 全量解析;候选 canonicalize 后必须非空、不超过 2048,四个布尔值必须为 `true / true / true / false`。
|
||||
- `isEnglish = true` 是 LLM 语义判断,程序侧还必须执行 Unicode Script 门禁:候选至少包含一个 Script=Latin 的 alphabetic code point,且所有 alphabetic code point 的 Script 都是 Latin;Common / Inherited 的数字、标点、空白和声音设计符号允许保留。Han、Hiragana、Katakana、Hangul、Cyrillic、Greek、Arabic 等非 Latin 字母都失败。
|
||||
- 第一次成功响应的候选为空、`isEnglish != true`、非英文 Script 门禁失败、结构非法、格式 / 保真判断失败、超过 2048 Unicode code points 或 `finish_reason = length` 时,使用同一份原始 `userPrompt` 进行唯一一次业务重试;不得把第一次候选当作新事实源。第二次仍使用 `max_output_tokens = 8192`,第二次 `finish_reason = length` 按最终翻译失败处理。首轮 `content_filter` 直接失败;transport、timeout 或上游最终失败只使用该轮 `LlmClient` 内部 transport retry,不额外开启第二业务语义轮。
|
||||
- 第一次成功响应的候选为空、`isEnglish != true`、非英文 Script 门禁失败、结构非法、格式 / 保真判断失败、超过 2048 Unicode code points 或 `finish_reason = length` 时,使用同一份原始 `userPrompt` 进行唯一一次业务重试;不得把第一次候选当作新事实源。第二次仍使用 `8192` completion tokens 总预算,第二次 `finish_reason = length` 按最终翻译失败处理。首轮 `content_filter` 直接失败;transport、timeout 或上游最终失败只使用该轮 `LlmClient` 内部 transport retry,不额外开启第二业务语义轮。
|
||||
- 第二次任何失败都是最终翻译失败;不返回候选,不调用 ElevenLabs,job 进入失败 / 退款链路。只有验收通过后才允许构造 `actualPrompt` 并调用 provider。
|
||||
- ElevenLabs endpoint 为 `POST /v1/sound-generation`,鉴权 `xi-api-key` 只在服务端 header 注入。body 固定包含 `text = actualPrompt`、`model_id = eleven_text_to_sound_v2`、`duration_seconds = null | frozen manual value`、`loop = frozen boolean`、`prompt_influence = 0.3`;query 固定 `output_format=mp3_44100_128`。调用方不能覆盖 model、influence 或 output format;`mp3_44100_128` 是请求格式,不把响应必须精确为 `128 kbps` 扩展成硬校验,合法结果不得只因探测码率不同被拒绝。
|
||||
- ElevenLabs 没有本链路可用的幂等键,所以 provider POST 不做自动 retry;浏览器正式生成 POST 也保持 0 次 unsafe retry,队列 `max_attempts = 1`。一个平台 job 最多调用一次 ElevenLabs。
|
||||
@@ -524,7 +524,7 @@ POST /api/editor/audios/background-music/prompts/simplifications
|
||||
- BGM Suno body 仍只包含 `mv`、`gpt_description_prompt`、`make_instrumental`,固定 `Suno` 胶囊和动态泥点价格不变;历史和其它未迁移 Vidu 调用方的 builder / 轮询能力保持可用,但新编辑器 SFX 任务只调用 ElevenLabs。
|
||||
- `audio-sound-effect` 与 `audio-background-music` 必须由同一个音频 composer 渲染,并在组件内通过 `isSoundEffect` 分支。SFX 不得渲染 BGM 的补全 / 简化、Suno 模型和 BGM 字符规则;BGM 不得渲染 SFX 的一键优化、Loop、ElevenLabs 模型和 SFX 时长控件。两个 mode 相互切换时,菜单、预设滚动、锁、快照和助手状态不得跨分支泄漏。
|
||||
- SFX V2 翻译失败时 ElevenLabs 请求数为 0;成功时每个平台 job 最多一次 ElevenLabs POST,`prompt / actual_prompt`、实际时长、Loop、model、provider 和 Task ID 在队列、素材、画布、响应、刷新和重绘后保持权威一致。
|
||||
- SFX 两类 LLM 请求契约测试分别断言:一键优化每次请求为 Luna + Medium + `max_output_tokens = 8192`,翻译每次业务尝试为 Luna + Low + `max_output_tokens = 8192`,两者都不含 temperature;测试命名和说明必须把该字段解释为包含 reasoning 的 completion 总预算。优化遇到 `finish_reason = length` 直接失败且不写回;翻译首轮 `length` 只重试一次,第二轮 `length` 最终失败且 ElevenLabs 请求数为 0。
|
||||
- SFX 两类 LLM 请求契约测试分别断言:一键优化每次请求为 Luna + Medium + `max_completion_tokens = 8192`,翻译每次业务尝试为 Luna + Low + `max_completion_tokens = 8192`,两者都不含 `max_tokens`、`max_output_tokens` 或 temperature;测试命名和说明必须把 `8192` 解释为包含 reasoning 的 completion tokens 总预算。优化遇到 `finish_reason = length` 直接失败且不写回;翻译首轮 `length` 只重试一次,第二轮 `length` 最终失败且 ElevenLabs 请求数为 0。
|
||||
- SFX V2 翻译测试覆盖中文、英文和中英混合 userPrompt 统一英文化,覆盖日文假名、韩文和西里尔字母候选被 Script 门禁拒绝,并锁定 actualPrompt 2048 / 2049 Unicode code point 边界。
|
||||
- SFX V2 二进制测试锁定 `40 MiB` / `40 MiB + 1 byte`、有 / 无 / 伪造 Content-Length、chunked 超限、允许 / fallback / 显式错误 MIME 和真实 MP3 探测;时长测试锁定有限正数、`30.5s`、`60s`、`600s` 均接受,`>600s`、NaN 和无穷值拒绝,不把请求 `30s` 当作响应硬上限。
|
||||
- External v1 的 nullable duration、Loop、固定模型、幂等重放和最终响应必须与 Rust 实现及 OpenAPI 逐字段一致。`model` 的 omitted / null / 空串 / 纯空白 / 包围空白新模型 / 显式新模型必须收敛为同一幂等 payload;`audio1.0` 与未知值必须返回 `400`、零入队、零预扣、零 LLM 和零 provider。
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
const SOUND_EFFECT_PROMPT_ASSIST_MAX_OUTPUT_TOKENS: u32 = 8192;
|
||||
const SOUND_EFFECT_PROMPT_ASSIST_COMPLETION_TOKEN_BUDGET: u32 = 8192;
|
||||
const SOUND_EFFECT_PROMPT_ASSIST_MODEL: &str = "gpt-5.6-luna";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
@@ -101,7 +101,7 @@ fn build_sound_effect_optimization_llm_request(current_prompt: &str) -> LlmRunRe
|
||||
.with_model(SOUND_EFFECT_PROMPT_ASSIST_MODEL)
|
||||
.with_openai_chat()
|
||||
.with_response_reasoning_effort(LlmResponseReasoningEffort::Medium)
|
||||
.with_max_output_tokens(SOUND_EFFECT_PROMPT_ASSIST_MAX_OUTPUT_TOKENS)
|
||||
.with_max_output_tokens(SOUND_EFFECT_PROMPT_ASSIST_COMPLETION_TOKEN_BUDGET)
|
||||
}
|
||||
|
||||
fn inspect_sound_effect_optimization_candidate(
|
||||
@@ -262,7 +262,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
request.max_output_tokens,
|
||||
Some(SOUND_EFFECT_PROMPT_ASSIST_MAX_OUTPUT_TOKENS)
|
||||
Some(SOUND_EFFECT_PROMPT_ASSIST_COMPLETION_TOKEN_BUDGET)
|
||||
);
|
||||
assert!(request.function_tools.is_empty());
|
||||
assert_eq!(request.tool_choice, None);
|
||||
@@ -456,7 +456,9 @@ mod tests {
|
||||
);
|
||||
assert_eq!(requests[0].body["model"], SOUND_EFFECT_PROMPT_ASSIST_MODEL);
|
||||
assert_eq!(requests[0].body["reasoning_effort"], "medium");
|
||||
assert_eq!(requests[0].body["max_tokens"], 8192);
|
||||
assert_eq!(requests[0].body["max_completion_tokens"], 8192);
|
||||
assert!(requests[0].body.get("max_tokens").is_none());
|
||||
assert!(requests[0].body.get("max_output_tokens").is_none());
|
||||
assert!(requests[0].body.get("temperature").is_none());
|
||||
assert!(requests[0].body.get("tools").is_none());
|
||||
assert!(requests[0].body.get("tool_choice").is_none());
|
||||
|
||||
+6
-4
@@ -9,7 +9,7 @@ use crate::prompt::sound_effect::{
|
||||
sound_effect_translation_system_prompt, sound_effect_translation_user_prompt,
|
||||
};
|
||||
|
||||
const SOUND_EFFECT_TRANSLATION_MAX_OUTPUT_TOKENS: u32 = 8192;
|
||||
const SOUND_EFFECT_TRANSLATION_COMPLETION_TOKEN_BUDGET: u32 = 8192;
|
||||
const SOUND_EFFECT_TRANSLATION_MODEL: &str = "gpt-5.6-luna";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
@@ -93,7 +93,7 @@ fn build_sound_effect_translation_llm_request(user_prompt: &str) -> LlmRunReques
|
||||
.with_model(SOUND_EFFECT_TRANSLATION_MODEL)
|
||||
.with_openai_chat()
|
||||
.with_response_reasoning_effort(LlmResponseReasoningEffort::Low)
|
||||
.with_max_output_tokens(SOUND_EFFECT_TRANSLATION_MAX_OUTPUT_TOKENS)
|
||||
.with_max_output_tokens(SOUND_EFFECT_TRANSLATION_COMPLETION_TOKEN_BUDGET)
|
||||
}
|
||||
|
||||
fn has_finish_reason(response: &LlmRunResponse, expected: &str) -> bool {
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
request.max_output_tokens,
|
||||
Some(SOUND_EFFECT_TRANSLATION_MAX_OUTPUT_TOKENS)
|
||||
Some(SOUND_EFFECT_TRANSLATION_COMPLETION_TOKEN_BUDGET)
|
||||
);
|
||||
assert!(request.function_tools.is_empty());
|
||||
assert_eq!(request.tool_choice, None);
|
||||
@@ -342,7 +342,9 @@ mod tests {
|
||||
);
|
||||
assert_eq!(request.body["model"], SOUND_EFFECT_TRANSLATION_MODEL);
|
||||
assert_eq!(request.body["reasoning_effort"], "low");
|
||||
assert_eq!(request.body["max_tokens"], 8192);
|
||||
assert_eq!(request.body["max_completion_tokens"], 8192);
|
||||
assert!(request.body.get("max_tokens").is_none());
|
||||
assert!(request.body.get("max_output_tokens").is_none());
|
||||
assert!(request.body.get("temperature").is_none());
|
||||
assert!(request.body.get("tools").is_none());
|
||||
assert!(request.body.get("tool_choice").is_none());
|
||||
|
||||
Reference in New Issue
Block a user