Repository navigation
Conversation
多模态听写原先直接复用文本润色的 system prompt:安全约定描述的是请求里
并不存在的 `<raw_transcript>` 信封,user 消息只有音频,传统模式中的
「只输出整理后的文本正文」也被丢掉。OpenAI gpt-audio-1.5 因此把每次回复
都包成自拟的 JSON(`{"final_text": …}`、`{"raw_transcript": …}` 等),
并随流式上屏逐字打进前台 app。翻译只是在「最终输出语言偏好:简体中文」
之后追加的一行,多数时候被忽略。
- 安全约定改为描述录音;音频旁附上文字指令,system prompt 以标记格式收尾
(无翻译为 `[[OPENLESS_TEXT]]`,翻译复用传统模式的源文 / 译文标记,并按
Auto 处理输出语言偏好)。
- 录音里没有语音(沿用静音自动停止的电平规则)时不调用模型,否则模型会把
文字指令当作口述内容整理,或编造内容。
- 流式上屏去掉标记,以 JSON 或代码围栏开头的回复先不上屏;已上屏的文本即
最终文本,与传统流式一致。
- 未上屏的回复按传统润色清洗,并还原整段 JSON 外壳(只认已知字段);翻译
格式缺少译文时不插入任何内容,不把源文当译文。
- 多模态语音唤醒时同样清理 `polish_source` 中的唤醒 / 结束指令。
真实 gpt-audio-1.5,同一批合成语音(修复前 → 修复后):
- 润色:15/15 被包成 JSON → 0/10,全部流式上屏
- 翻译为 English:0/9 输出英文 → 10/10,源文全部保留
- 静音 / 底噪:不再调用模型,不插入任何内容
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cce70eec72
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if !is_live | ||
| && !transcription_only | ||
| && !crate::silence_auto_stop::pcm_contains_speech(&pcm) |
There was a problem hiding this comment.
Let short utterances reach the multimodal provider
On batch multimodal sessions, pcm_contains_speech only returns true after three 100-ms chunks exceed the threshold. Therefore every clip of 200 ms or less—and longer clips containing only a short or quiet spoken token—takes this branch and is reported as "no speech" without invoking the provider, even though it can contain valid single-word dictation. This hard gate should not reject audio solely because it cannot satisfy that duration/level heuristic.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
已修复(8e90167d):门限改为 10 ms 一块、连续 3 块(约 30 ms),与静音自动停止按录音回调计算电平的粒度一致,短词不再被拒,只拦截几乎无声的录音。
另外,真实使用中「嗯」「啊」、呼气这类能通过门限却没有词句的声音,会让模型复述提示词;同一提交加了复述检测:回复与本次请求的提示词高度重合时不插入。实测 gpt-audio-1.5:「好」「可以」正常输出;25 段无词句的声音 0 次插入提示词。
| prefer_translation | ||
| .then(|| field(ENVELOPE_TRANSLATION_KEYS)) | ||
| .flatten() | ||
| .or_else(|| field(ENVELOPE_TEXT_KEYS)) |
There was a problem hiding this comment.
Require a target before accepting a translation envelope
When translation is active and the model returns JSON such as {"raw_transcript":"你好"} without a translated field, prefer_translation finds no translation and this fallback accepts raw_transcript from the text keys. finalize_omni_output then treats that source value as successful output, so untranslated text is inserted despite the translation request; a missing-target envelope should yield empty/failure rather than use a source-only field.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
已修复(8e90167d):JSON 形式的翻译回复改用专门的解析,只从译文字段取结果;没有译文字段时听写以失败结束、不插入任何内容,不会把 raw_transcript 等源文字段当作译文。
| let key = key.trim_matches(['[', ']']).to_ascii_lowercase(); | ||
| key == *wanted || key.ends_with(&format!("_{wanted}")) | ||
| }) |
There was a problem hiding this comment.
Match only explicit JSON envelope keys
In a marked dictation whose spoken content is JSON such as {"display_text":"hello"}, strict cleanup reaches this predicate with lenient=false, but display_text matches the wanted text key through the suffix test. The cleaner then replaces the dictated JSON with hello, defeating the strict path's purpose of preserving user JSON; limit suffix aliases to explicit supported keys (for example final_translation) instead of every *_text or *_final key.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
已修复(8e90167d):去掉后缀匹配,只认明确列出的键名(含 final_translation);按要求格式输出的内容里只认 final_text、translated_text 这类典型的外壳键,{"display_text": …}、{"text": …} 等口述的 JSON 原样保留,已补测试。
| if let Some(text) = unwrap_json_text_envelope(&cleaned, translation_active, true) { | ||
| return (strip_omni_markers(&text), None); |
There was a problem hiding this comment.
Preserve source text from JSON translation envelopes
For a translation reply such as {"final_text":"你好","translated_text":"Hello"}, the helper returns only Hello and this immediate return forces the source to None. finish consequently uses Hello as raw_text and stores no polish_source, so history loses the original Chinese source (and cannot reuse it in later context), unlike the marker-form response; parse and retain the source field alongside the selected translation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
已修复(8e90167d):JSON 形式的翻译回复现在同时取出源文,写入 polish_source 和 raw_text,与标记格式的回复一致。
真实使用中,录音只有「嗯」「啊」、呼气等没有词句的声音时,能量门限会放行, gpt-audio-1.5 则把音频旁的文字指令或风格模板里的示例句当作结果输出, 直接插入前台 app。 - 记录本次请求实际发送的提示词;回复中字母与数字的三字片段有八成以上出现在 提示词里(不计标点、空白,过短的回复不判断)即视为复述,不插入任何内容。 流式上屏先缓冲正文开头 12 个字(翻译时为整段源文),确认不是复述再上屏。 - 语音门限改为 10 ms 一块,与静音自动停止的实际粒度一致,短词不再被拒。 - JSON 外壳只认明确的键名,不再做后缀匹配;按格式输出的内容里只认典型的 外壳键,口述的 JSON 原样保留。 - 翻译回复被包成 JSON 时保留源文;没有译文字段时不插入任何内容,不把源文 当译文;嵌在 JSON 字符串里的带标记回复也能还原。 真实 gpt-audio-1.5(经完整听写引擎): - 「嗯」「啊」、哼声、呼气等 25 段:0 次插入提示词或示例句 - 正常语音、短词、翻译共 12 段:全部正确,且流式上屏 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
摘要
Fixes #1304
Fixes #1306
让多模态听写(OpenAI 兼容通道,实测
gpt-audio-1.5)稳定输出可直接插入的纯文本,开启翻译时只插入目标语言译文。两个 issue 改的是同一组函数(多模态提示词组装、流式上屏过滤、最终文本整理),拆开会互相冲突,因此合在一个 PR。修复 / 新增 / 改进
omni_audio_injection_defense),不再提请求里并不存在的<raw_transcript>信封;omni_audio_user_prompt),system prompt 以标记格式收尾:无翻译为[[OPENLESS_TEXT]],翻译复用传统模式的源文 / 译文标记;格式里写明录音没有说话内容时标记后留空;compose_translate_prompts一致);不再附带请求里并不存在的对话历史说明。gpt-audio-1.5开启翻译会产出工具调用样的 JSON;只加文字指令、不加标记格式时,仍有约 40%–75% 的回复包 JSON。no speech in the recording,不插入任何内容。Gemini 专用转写与 Live 不受影响。OmniDictationStream):去掉回复中任意位置的标记;以{或代码围栏开头的回复整段不上屏;已上屏的文本就是最终文本,与传统流式一致,不会触发剪贴板兜底。finalize_omni_output):未上屏的回复按传统润色清洗;整段 JSON 外壳按明确列出的键名还原(final_text、translated_text、final_translation等,与键的顺序无关),嵌在 JSON 字符串里的带标记回复也能还原;按格式输出的内容里只认典型外壳键,用户口述的 JSON 原样保留;JSON 形式的翻译同时保留源文;缺少译文时不插入任何内容,不把源文当译文。polish_source中的唤醒 / 结束指令。兼容
gpt-audio-*)在多模态模式下的纯文本功能(打字问答、历史重新润色)仍会被接口拒绝;complete_omni_instruction_from_wav)未加同样的格式约定;测试计划
命令:
cargo test --locked -p openless-core;cargo clippy --locked -p openless-core --all-targets -- -D warnings结果:全部通过(1,664 个测试,其中库测试 1,428 个)。新增测试覆盖提示词组成、语音门限、流式过滤(标记跨 delta 切分、结束标记、JSON / 代码围栏开头、翻译只上屏译文)、最终文本整理与 JSON 外壳解包,以及 5 个端到端测试:4 个对接本地 SSE 服务(JSON 回复、标记回复、翻译、翻译的 JSON 回复),1 个确认没有语音时不发请求。
真实模型对照(
gpt-audio-1.5,同一批 5 段合成中文语音及静音 / 底噪,经SharedOmniDictationEngine完整链路):85b243ab)审查后的修改(
8e90167d)没有改动提示词;改动后经同一链路重跑:轻度润色 7/7(含两个短词)流式上屏且与最终文本一致,翻译 5/5 只输出英文、源文全部保留。本地打包实测(macOS,第一个提交
cce70eec):多模态听写输出纯文本;开启翻译只插入英文译文,源文写入历史记录。审查后的修改尚未重新打包实测。代码审查:按审查意见修正了标记位置、流式与最终文本不一致、JSON 外壳解包、翻译缺译文、静音等问题。
证据路径:对照脚本与原始结果在本地,未提交。
审查后的修改
第二个提交
8e90167d处理了 Codex 的 4 条意见(门限过严、JSON 翻译缺译文时误用源文、键名后缀匹配误伤口述 JSON、JSON 翻译丢失源文),并修复本地实测发现的问题:没有词句的声音会让模型复述提示词。各条意见下已逐条回复。🤖 Generated with Claude Code