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

未关闭
#1,304 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@Breadswim 已经在做这个了。

开始于 2026年10月31日。

  • #1310 来自 @Breadswim —— 未关闭

评估

难度
4/5
预计耗时
3-5 天
新手友好度
52/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
rust

调研方向

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.

由索引模型根据 Issue 内容生成。

描述

现象
  • 触发条件:

    • 识别管线:多模态模式;供应商 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。
主要语言
Rust
星标
3.7k
派生
349
平均合并
2 天 14 小时
30 天内合并 PR
97

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

Open-Less/openless 的其他 Issue

查看 Open-Less/openless 的全部 Issue

相似的 Issue

更多 Rust Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。