Skip to content

Fix verbose_json incompatibility with OpenAI transcribe models#218

Merged
marcbodea merged 2 commits into
zachlatta:mainfrom
ojhurst:fix/transcription-openai-model-compat
Jul 10, 2026
Merged

Fix verbose_json incompatibility with OpenAI transcribe models#218
marcbodea merged 2 commits into
zachlatta:mainfrom
ojhurst:fix/transcription-openai-model-compat

Conversation

@ojhurst

@ojhurst ojhurst commented May 29, 2026

Copy link
Copy Markdown
Contributor

What happened

When setting up FreeFlow with an OpenAI *-transcribe model (e.g. gpt-4o-mini-transcribe), the setup test fails immediately and the onboarding screen shows a raw API error body:

FreeFlow setup error showing Status 400 response_format incompatibility

Submission failed: Status 400: {
  "error": {
    "message": "response_format 'verbose_json' is not compatible with model 'gpt-4o-mini-transcribe-api-ev3'. Use 'json' or 'text' instead.",
    "type": "invalid_request_error",
    "param": "response_format",
    "code": "unsupported_value"
  }
}

Two separate problems compound here: the wrong response_format value is being sent, and HTTP 400 had no friendly case in the error handler so the raw body surfaced.

The diagnosis

TranscriptionService hard-codes response_format = "verbose_json". OpenAI's Whisper-based models (whisper-1) accept this. OpenAI's newer *-transcribe model family explicitly rejects it — their API only accepts "json" or "text".

verbose_json is needed for Groq/Whisper users because it returns per-segment no_speech_prob values, which the hallucination filter relies on. Switching to "json" globally would break that. But "json" is the right default for any model whose name contains "transcribe".

The hallucination filter already degrades gracefully when segments are absent (parseTranscript checks for json["segments"] and returns false — skipping the filter — when none are present), so "json" responses from transcribe models flow through cleanly.

The fix

1. Model-aware response_format — changed from a stored constant to a computed property:

private var transcriptionResponseFormat: String {
    transcriptionModel.lowercased().contains("transcribe") ? "json" : "verbose_json"
}

This preserves verbose_json for Groq and Whisper users while routing any *-transcribe model to the json format the provider expects.

2. Explicit HTTP 400 case — added to friendlyHTTPMessage:

case 400:
    return "Provider rejected the request (HTTP 400). Check your model name and Base URL in Settings."

Previously 400 fell through to the generic default. The new message points directly at the two most common 400 causes: wrong model name and wrong Base URL.

Files changed

  • Sources/TranscriptionService.swift — two changes: computed transcriptionResponseFormat, case 400 in friendlyHTTPMessage

Behavior unchanged

  • Groq users (whisper-large-v3, etc.): still receive verbose_json, hallucination filter works as before.
  • Any model not containing "transcribe" in its name: unchanged.
  • 401, 403, 404, 413, 429, 5xx: unchanged.

Verified

  • gpt-4o-mini-transcribe path: reasoned through — response_format becomes "json", API accepts the request, transcript returned; hallucination filter skips gracefully with no segments.
  • Groq path: "whisper-large-v3" does not contain "transcribe", format stays "verbose_json", no change.
  • 400 friendly message: confirmed the string is what the user sees in the setup error view.

Notes / Considered

  • Why not use "json" for all models? The hallucination filter needs no_speech_prob from verbose_json segments. Removing it for Whisper users would silently stop filtering common hallucinations on silence.
  • Naming-convention bet: the contains("transcribe") check assumes OpenAI keeps "transcribe" in future model names. If a provider uses a different naming scheme, this falls back to verbose_json and they would see the same 400 again. A settings override for response_format would be a more robust long-term fix, but that is a separate change.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error messaging for transcription service failures, now providing model name and configuration details when HTTP 400 errors occur.
  • Improvements

    • Enhanced transcription service compatibility by selecting the response format based on the configured transcription model (verbose output for supported whisper variants; JSON otherwise).

OpenAI's gpt-4o-transcribe and gpt-4o-mini-transcribe model family only
accepts "json" or "text" as response_format — sending "verbose_json"
returns a 400 unsupported_value error and the setup test fails.

Make transcriptionResponseFormat model-aware: models whose name contains
"transcribe" get "json"; all others (Groq whisper-large-v3, etc.) keep
"verbose_json" so the hallucination filter's no_speech_prob segments
remain available. The hallucination filter already degrades gracefully
when segments are absent, so there is no second change needed.

Also add an explicit 400 case to friendlyHTTPMessage so users see
"Check your model name and Base URL in Settings" rather than the
generic fallback, which is actionable for this exact failure mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

TranscriptionService.swift now selects "verbose_json" for allowlisted Whisper models and "json" for other models. HTTP 400 responses receive a dedicated message directing users to check the model name and Base URL.

Changes

Transcription Service Updates

Layer / File(s) Summary
Dynamic transcription response format selection
Sources/TranscriptionService.swift
Transcription requests derive their response format from the configured model, using "verbose_json" for allowlisted Whisper variants and "json" otherwise.
HTTP 400 provider rejection message
Sources/TranscriptionService.swift
friendlyHTTPMessage(status:host:) adds a dedicated HTTP 400 message referencing the model name and Base URL.

Estimated code review effort: 2 (Simple) | ~8 minutes

Possibly related PRs

  • zachlatta/freeflow#157: Both changes update TranscriptionService.friendlyHTTPMessage(status:host:) for user-facing HTTP error messages.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adjusting transcription response formats for OpenAI transcribe models.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@marcbodea

Copy link
Copy Markdown
Collaborator

"json" is the right default for any model whose name contains "transcribe".

this sounds a bit too wide

@github-actions github-actions Bot added size/s and removed size/xs labels Jul 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/TranscriptionService.swift (1)

219-220: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use \(provider) interpolation for consistency with other cases.

The HTTP 400 message uses the literal word "Provider" while every other case interpolates the actual host via \(provider). This loses provider-specific context that would help users distinguish between, e.g., OpenAI and Groq rejections.

Proposed fix
-            return "Provider rejected the request (HTTP 400). Check your model name and Base URL in Settings."
+            return "\(provider) rejected the request (HTTP 400). Check your model name and Base URL in Settings."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TranscriptionService.swift` around lines 219 - 220, Update the HTTP
400 branch in the relevant error-message switch to interpolate the existing
provider value as \(provider), matching the other cases while preserving the
guidance about the model name and Base URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/TranscriptionService.swift`:
- Around line 219-220: Update the HTTP 400 branch in the relevant error-message
switch to interpolate the existing provider value as \(provider), matching the
other cases while preserving the guidance about the model name and Base URL.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d150879b-7db8-4cdd-9233-253ba4094570

📥 Commits

Reviewing files that changed from the base of the PR and between b795da2 and 6bc9ad6.

📒 Files selected for processing (1)
  • Sources/TranscriptionService.swift

@marcbodea
marcbodea merged commit abfc058 into zachlatta:main Jul 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants