Skip to content

fix: OpenRouter 转写专用模型改走 /audio/transcriptions - #127

Open
sheepbox8646 wants to merge 1 commit into
mainfrom
fix/twi-14-openrouter-transcription-endpoint
Open

sheepbox8646 wants to merge 1 commit into
mainfrom
fix/twi-14-openrouter-transcription-endpoint

Conversation

@sheepbox8646

Copy link
Copy Markdown
Member

概要

provider/openrouter/transcription 把所有模型都按 input_audio 发到 /chat/completions。默认模型 openai/gpt-4o-mini-transcribe 是转写专用模型,OpenRouter 会拒绝:

400: openai/gpt-4o-mini-transcribe is a transcription model and cannot be used with the chat/completions endpoint. Use the /api/v1/audio/transcriptions endpoint instead.

所以 TranscriptionModel("") 必定失败。

  • 按模型选择端点:output_modalities 含 transcription 且不含 text 的模型,走 /audio/transcriptions(multipart)。其他带音频输入的对话模型(如 openai/gpt-audio-mini、qwen/qwen3.8-omni-flash)仍走 /chat/completions,行为不变。
  • 模型目录缓存:
    • 首次转写时拉取 /models?output_modalities=all,每个 Provider 缓存一小时。ListModels 的结果也会写入缓存。
    • 并发请求只拉一次。等待中的调用方在自己的 ctx 结束时可以放弃。
    • 单次拉取限时 15 秒,失败后 30 秒内不重试。刷新失败时继续使用旧目录。
    • 查找时先用完整 ID,查不到再去掉 :variant 后缀。
  • 回退规则:从未拉到目录时,按模型 ID 判断:包含 scribe 或 whisper,或某个名称片段正好是 asr、stt。
  • multipart 字段:按 OpenRouter STT 文档,发送 model、file,以及可选的 language、temperature、response_format、timestamp_granularities[]、keyterms、diarize。文件名缺扩展名时,按 content type 补上。
  • prompt 只用于对话模型:转写端点文档里没有这个字段,所以不发送。
  • 结果映射:text、language、duration 直接映射,没有 duration 时用 usage.seconds。words 映射到 Words,说话人填入 SpeakerID。
  • 默认模型不变:openai/gpt-4o-mini-transcribe 现在会走正确的端点。
  • 文档:docs/speech.md 新增 OpenRouter 转写一节,说明端点选择、各配置项适用的端点和结果映射。

关联

验证

  • 新增单元测试,覆盖以下情况:
    • 按目录分流、:free 变体、目录结果优先于名称判断。
    • 并发时只拉一次目录;ListModels 的结果会被复用。
    • 拉取失败后回退并限流重试,过期后能恢复。
    • multipart 的各字段与 Content-Disposition。
    • json 与 verbose_json 两种响应的映射。
    • 转写端点的 403 错误会变成 APIError。
  • 本地运行 go build ./...、go vet ./...、go vet -tags integration ./...、go test ./... -short -count=1 -race、golangci-lint run ./...,全部通过。go mod tidy 无变化。
  • 真实接口(OpenRouter):
模型 端点 结果
openai/whisper-large-v3-turbo 转写 成功:the quick brown fox jumps over the lazy dog.
mistralai/voxtral-mini-3b-2507 转写 成功。它的 ID 不满足回退规则,说明是目录判断在起作用
elevenlabs/scribe-v2 转写 成功
qwen/qwen3.8-omni-flash 对话 成功
默认模型(openai/gpt-4o-mini-transcribe) 转写 403,服务条款限制
openai/gpt-audio-mini 对话 403,服务条款限制
  • 实测中发现并修复了一个问题:用 mime.FormatMediaType 生成的 Content-Disposition 参数没加引号、顺序也被重排,OpenRouter 会返回 400 Invalid multipart/form-data body。现在改成与标准库 CreateFormFile 一致的写法,并加了回归测试。

未验证

  • 测试用的 Key 调用 OpenAI 托管的模型都返回 403(服务条款限制,与本改动无关),所以没看到默认模型实际转出的文字。可以确认它已经走了正确的端点:原来的 400 不再出现。而且带上 verbose_json 时,它返回 does not support response_format "verbose_json",这个报错只会来自 /audio/transcriptions。
  • keyterms、diarize 只做了单元测试。
  • 不在本 PR 范围内:ListModels 只要求输入含 audio,所以会把 heygen/* 等视频生成模型也列为可转写模型。

🤖 Generated with Claude Code

OpenRouter 转写 provider 原来把所有模型都发到 /chat/completions,默认模型 openai/gpt-4o-mini-transcribe 因此必定返回 400。现在按 OpenRouter 模型目录判断:输出只有 transcription 的模型走 /audio/transcriptions(multipart),其他带音频输入的对话模型仍走 /chat/completions。目录按 Provider 缓存一小时,拉取失败时回退到按模型 ID 判断。

Linear: TWI-14

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant