[omni] OpenAI gpt-audio-1.5 多模态听写输出被包成 JSON 对象(final_text 等字段)后直接上屏

Aperta
#1,304 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@Breadswim ci sta già lavorando.

Dal 31/10/2026.

  • #1310 di @Breadswim — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
52/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
rust

Direzione di ricerca

Start by reading openless-core/src/cloud_providers.rs around the cited build_messages and OpenAICompatibleOmni paths, then compare the Gemini handling in openless-core/src/llm_gemini.rs and its clean_polish_output call. Check how streaming deltas are handled and identify suitable tests; the issue does not name existing test files. Done means the listed acceptance criteria are met, including ordinary-text passthrough, JSON-shaped output coverage, and validation with real gpt-audio-1.5.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

现象
  • 触发条件:

    • 识别管线:多模态模式;供应商 OpenAI(支持音频);模型 gpt-audio-1.5;接口地址 https://api.openai.com/v1
    • 风格:内置「清晰结构」「轻度润色」均复现;流式上屏开启
    • 平台 / 版本:macOS;2.0.1+build.20261016 与 2.0.2-Beta.3+build.20261026-c9f49c60 均复现。代码定位基于 beta 最新提交 85b243ab,多模态相关代码自 c9f49c60 起没有改动
  • 当前表现:插入的不是纯文本,而是模型自行组织的 JSON 风格包裹:键名不固定,有时连合法 JSON 都不是,甚至会改写正文。已观察到的形态:

    形态 次数
    {"final_text": "…"} 29
    {"final_text": "…", "translated_text": "…"}(开启翻译时,见 #1306) 2
    {"original_text": "…", "final_text": "…"}(两个字段内容相同) 1
    {"raw_transcript": "…"}(内容像未整理的原始转写,句末没有标点) 1
    {"…"}:有大括号和引号,但没有键名,不是合法 JSON 1
    修改说明被包在大括号里输出,后面才是正文,开头为 {"这里面"平板因为使用"这部分不太通顺,可能是口误,去掉后整理如下:} 1
    正文被改写:口述的 .env 变成了 {"env"},例如 {"env"} 文件就是刚才提到的环境配置文件。 1

    示例:

    {"final_text": "现在我已经换成轻度润色了。"}
    {"raw_transcript": "你可以把我现在发的信息和包括上一条的信息使用轻度润色"}
    

    多行结果里的换行以字面 \n 出现(JSON 转义没有还原)。流式上屏开启时,JSON 字符随 SSE delta 逐段打进目标 app。

  • 证据:

    • 本机全部 36 条非空多模态记录(清晰结构 14 条、轻度润色 22 条)都带有 JSON 风格的包裹:33 条是 JSON 对象,3 条是只有大括号、不是合法 JSON 的输出(其中 1 条改写了正文),可稳定复现。

    • 日志示例:

      [omni] POST https://api.openai.com/v1/chat/completions provider=openai model=gpt-audio-1.5 audio=true stream=true
      [omni] stream done; total chars=35
      
初步定位
  1. 请求没有要求 JSON。omni_body 只包含 model / stream / messages / temperature 和 thinking 控制参数,没有 response_format;提示词模板和请求构造代码里也没有这些键名。JSON 外壳来自模型输出本身。
  2. 多模态提示词没有针对音频输入适配。听写结束时以「传统润色 system prompt + 空 user 文本」调用模型,user 消息里只有 input_audio:

https://github.com/Open-Less/openless/blob/85b243abd83abb295cd99e0ec31ba483c3f5c6fe/openless-all/app/crates/openless-core/src/cloud_providers.rs#L2531-L2533

传统模式 user 消息末尾的「只输出整理后的文本正文。」(prompts::user_prompt)在这条路径上被丢掉;system prompt 末尾的安全约定(prompts::polish_injection_defense)仍在描述 <raw_transcript> 信封,但请求里没有这个信封。
3. OpenAI 兼容多模态通道没有输出清洗。传统润色和 Gemini 多模态(complete_omni)都会经过 clean_polish_output:

https://github.com/Open-Less/openless/blob/85b243abd83abb295cd99e0ec31ba483c3f5c6fe/openless-all/app/crates/openless-core/src/llm_gemini.rs#L266

而 OpenAICompatibleOmni 直接返回原始文本,听写结束时只做 trim()(补上 clean_polish_output 本身也解不开 JSON 外壳):

https://github.com/Open-Less/openless/blob/85b243abd83abb295cd99e0ec31ba483c3f5c6fe/openless-all/app/crates/openless-core/src/cloud_providers.rs#L2547

  1. 键名都取自提示词里的用词,而且出现了直接违反输出要求的结果,说明模型是在借用提示词词汇自行组织 JSON,而不是遵循输出要求;问题与具体风格无关:

    键名 提示词中的对应内容
    final_text 风格模板中的「直接输出最终文本正文」(清晰结构)/「直接输出最终正文」(轻度润色)
    raw_transcript 安全约定 polish_injection_defense 中的 <raw_transcript>;该词不出现在任何风格模板里,多模态请求里也没有这个信封
    original_text 轻度润色模板中的「不输出原文」
    translated_text 多模态翻译段中的「只输出译文,不要输出源文」

    OpenAI 将 gpt-audio-1.5 定位为 Chat Completions 中「音频输入、音频输出」的语音模型,模型页没有列出转写端点和结构化输出(模型页)。

影响
  • 用 OpenAI 音频模型的多模态听写基本不可用:每次都要手动删掉 JSON 外壳和字面 \n。
  • 流式上屏开启时,JSON 字符在模型返回过程中就已经插入目标 app,只在最后清洗最终文本不够。
  • 正文可能被改写:.env 变成 {"env"} 这类改动不显眼,容易混进命令、配置或代码;修改说明也会被当作正文插入。事后清洗只能剥掉套在整段外面的壳,修不好正文中间的改动,所以修复重点必须放在提示词侧。
  • OpenAI 兼容多模态通道也被其他供应商(如百炼 / Qwen Omni)共用,它们同样没有输出清洗和面向音频的提示词;是否出现同样现象未验证。
建议接受标准
  • OpenAI gpt-audio-1.5 多模态听写(流式上屏开 / 关;清晰结构、轻度润色风格)最终插入纯文本,不含 JSON 外壳、键名或字面 \n。
  • 多模态请求为音频输入提供专门的指令:例如在 input_audio 旁附一段文字(build_messages 已支持音频 + 文字),明确只输出纯文本正文,不要 JSON、字段名或代码块;安全约定改为描述音频输入,而不是 <raw_transcript>。
  • OpenAI 兼容多模态通道的输出与 Gemini 通道一样经过 clean_polish_output。
  • 如果模型仍返回 JSON 对象,最终文本和流式 delta 都能还原为纯文本:不依赖固定键名,至少覆盖上表中整段被包裹的五种形态(多字段时按明确规则选取字段);不是 JSON 对象的输出原样保留。
  • 正文中不出现模型自行添加的大括号、引号或修改说明(例如 .env 不会被改写成 {"env"})。这类问题无法靠事后清洗可靠修复,需要由提示词改造保证,并用真实模型验证。
  • 单元测试覆盖:JSON 外壳解包(含转义字符、跨 delta 切分、多种键名)、普通文本原样透传。
  • 用真实 gpt-audio-1.5 做修复前后对照。
TODO / 不确定项
  • 「正式表达」「原文」风格尚未实测;「清晰结构」「轻度润色」已确认复现。
  • 代码中 OpenAI 多模态的默认模型 gpt-4o-audio-preview,以及其他 OpenAI 兼容多模态供应商是否有同样现象,尚未验证。
  • 对 JSON 外壳做兜底解包需要取舍(用户确实口述 JSON 的情况);多字段时选取哪个字段的规则需维护者确认。
  • 翻译相关的问题(译文时有时无、以 translated_text 字段返回)另见 #1306。
  • 我可以负责修复,并用自己的 OpenAI Key 做真实模型验证,之后提 PR 关联本 issue。
Lingua principale
Rust
Stelle
3.7k
Fork
349
Merge medio
2g 14h
PR unite (30g)
97

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Open-Less/openless

Tutte le issue di Open-Less/openless

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.