openai / openai/openai-openapi

Audio transcription response oneOf variants overlap, misclassifying diarized_json

Open
#567 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug specification
Dominant language
No language data
Stars
2.5k
Forks
527
Avg merge
1h 46m
Merged PRs (30d)
2

Description

Summary

The non-streaming POST /audio/transcriptions JSON response union is not
structurally disjoint. A valid diarized_json response also satisfies
CreateTranscriptionResponseJson, because the basic JSON schema requires only
text and permits additional properties.

This makes the current oneOf invalid for real diarized payloads and causes
generated SDKs that select the first viable branch to deserialize diarized
responses as the basic transcription model, losing typed speaker segments.

This report is against openai/openai-openapi@690521b1753dce0c6d6b275f583d22537679cff9.

Affected schema

POST /audio/transcriptions declares:

application/json:
  schema:
    oneOf:
      - $ref: "#/components/schemas/CreateTranscriptionResponseJson"
      - $ref: "#/components/schemas/CreateTranscriptionResponseDiarizedJson"
      - $ref: "#/components/schemas/CreateTranscriptionResponseVerboseJson"

Source:

The basic schema's only required property is:

required:
  - text

Neither additionalProperties: false nor another exclusion prevents a richer
diarized or verbose response from also validating as the basic schema.

Minimal valid payload demonstrating the overlap

{
  "task": "transcribe",
  "duration": 1.5,
  "text": "Hello from Alice.",
  "segments": [
    {
      "id": "seg_0",
      "type": "transcript.text.segment",
      "start": 0.0,
      "end": 1.5,
      "text": "Hello from Alice.",
      "speaker": "Alice"
    }
  ]
}

This payload satisfies CreateTranscriptionResponseDiarizedJson, but it also
satisfies CreateTranscriptionResponseJson because it has text and object schemas
allow additional properties by default. Therefore it matches at least two
branches of oneOf, which should require exactly one.

The same structural problem applies to CreateTranscriptionResponseVerboseJson:
a valid verbose payload has text and therefore also satisfies the basic JSON
schema.

Observed generated SDK impact

In openai/openai-ruby@dd49e31134cf8dd1442f1b3fb428bab4dbc663d1, the generated
response union is ordered:

  1. Transcription
  2. TranscriptionDiarized
  3. TranscriptionVerbose

Source:
https://github.com/openai/openai-ruby/blob/dd49e31134cf8dd1442f1b3fb428bab4dbc663d1/lib/openai/models/audio/transcription_create_response.rb#L12-L25

The Ruby union is undiscriminated and selects the first viable variant. For the
payload above, it returns OpenAI::Models::Audio::Transcription, not
OpenAI::Models::Audio::TranscriptionDiarized. The segments value remains an
untyped array of hashes instead of
OpenAI::Models::Audio::TranscriptionDiarizedSegment instances.

Minimal Ruby reproduction:

stub_request(:post, "http://localhost/audio/transcriptions").to_return(
  status: 200,
  headers: {"content-type" => "application/json"},
  body: payload.to_json
)

response = OpenAI::Client.new(
  base_url: "http://localhost",
  api_key: "My API Key"
).audio.transcriptions.create(
  file: StringIO.new("synthetic audio"),
  model: :"gpt-4o-transcribe-diarize",
  response_format: :diarized_json
)

response.class
# actual:   OpenAI::Models::Audio::Transcription
# expected: OpenAI::Models::Audio::TranscriptionDiarized

This is not just a Ruby hand-written-code issue: the generated ordering mirrors
the response variant ordering in the specification, while the schemas provide no
reliable discriminator for response-shape selection.

Expected behavior

A valid non-streaming diarized_json response should be representable by exactly
one response variant and generated SDKs should select the documented diarized
model, including typed speaker segments.

Likewise, valid verbose_json responses should select the verbose model rather
than the basic text-only model.

Suggested spec-level direction

Please make the response alternatives unambiguous at the OpenAPI/generation
boundary. Possible approaches:

  1. Model the variants as genuinely mutually exclusive schemas, if the wire
    response provides enough distinguishing information.
  2. If the wire payload has no stable discriminator, carry the request's
    response_format through generation metadata/operation overloads so SDKs can
    select the declared response model without shape guessing.
  3. As a narrow interim generator-oriented mitigation, order the more specific
    variants before the permissive basic variant: DiarizedJson, VerboseJson, then
    Json. This would address first-match generators, but it does not by itself
    make the OpenAPI oneOf semantically valid.

A regression fixture for all three non-streaming JSON response formats would
help prevent this from recurring.

Related issues

  • #497 reported the invalid/missing task discriminator and was closed after
    that discriminator was removed.
  • #563 addressed whether task and duration are required in diarized responses.

This report is distinct: after those changes, the remaining response schemas
still overlap structurally, so valid diarized and verbose payloads can be
selected as the basic response model.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with openapi.yaml at the response union and the three transcription response schemas linked in the issue. Compare their required properties and validation behavior, then inspect the Ruby union ordering in transcription_create_response.rb and the existing issue context. Done means the three response formats are represented unambiguously and regression coverage demonstrates correct model selection for basic, diarized, and verbose JSON.

Written by the indexing model from the issue text.

Assessment

Tech stack
openapi, ruby
Domain
api, backend-api-design
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.