Skip to content

fix(frontend): return JSON errors on the remaining OpenAI routes - #13685

Open
ayaangazali wants to merge 1 commit into
ai-dynamo:mainfrom
ayaangazali:fix/frontend-json-errors-remaining-routes
Open

fix(frontend): return JSON errors on the remaining OpenAI routes#13685
ayaangazali wants to merge 1 commit into
ai-dynamo:mainfrom
ayaangazali:fix/frontend-json-errors-remaining-routes

Conversation

@ayaangazali

@ayaangazali ayaangazali commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

#13102 gave /v1/responses, /v1/chat/completions and /v1/completions a JSON error envelope for a bad Content-Type, an oversized body and malformed JSON, by reading the body through read_json_request_body and parse_json_request.

Eight handlers were left on the plain Json<T> extractor and still return Axum's text/plain rejection. The envelope therefore depends on which endpoint the client happened to call. Measured on main, same mistake, same status code:

POST /v1/responses  + Content-Type: text/plain
  415  content-type: application/json
       {"message":"Expected request with Content-Type application/json","type":...,"code":415}

POST /v1/embeddings + Content-Type: text/plain
  415  content-type: text/plain; charset=utf-8
       Expected request with `Content-Type: application/json`

A client that parses the error body succeeds against one endpoint and throws against the other. That is the defect: not the status, which is already right, but the shape.

This routes the remaining handlers through the same two helpers: embeddings, classify, pooling, images, image edits, videos, video stream, audio speech. They already took HeaderMap and already returned Result<Response, ErrorResponse>, so each one is a signature swap plus the two lines #13102 established. No new abstraction and no new error text.

One handler needed more than the swap. images_edits validates input_reference and then delegates to images, which now consumes a Body it has already read. The part of images after parsing moves into images_with_request, which both call. Two real callers, so it is not a wrapper for its own sake.

Overlap worth flagging

@chanh has #13268 open against ensure_json_content_type, changing an absent Content-Type to be treated as JSON. This PR adds callers of that helper rather than touching it, so the changes are complementary and should not conflict textually. If #13268 lands first, these eight endpoints inherit the new semantics automatically, which is the point of routing them through the shared helper. Happy to rebase behind it.

Validation

Behaviour checked by driving a real server through the existing spawn_default_service harness, before and after:

endpoint before after
/v1/responses 415 application/json unchanged
/v1/embeddings 415 text/plain 415 application/json, identical envelope

Added test_embeddings_non_json_content_type_returns_json_error, mirroring the /v1/responses test from #13102 and additionally asserting the response content-type, since the status was already correct before this change and only the envelope was wrong. Confirmed red first by restoring openai.rs from upstream/main and re-running:

assertion `left == right` failed: the 415 must be JSON, not Axum's text/plain rejection
  left: Some(false)
 right: Some(true)
  • cargo test -p dynamo-llm --lib --no-default-features: 2042 passed, 0 failed on a clean run.
  • cargo clippy clean, cargo fmt --all -- --check clean.

Disclosing one flake rather than reporting only the clean run: test_oversized_body_returns_json_413 fails intermittently, 3 of 6 runs in isolation on this branch. That is the same rate I measured on clean main and reported on #13624; it is a pre-existing race between the 413 response and the client still sending the body, not something this change introduces. The two full-suite runs that failed failed only on that test.

Not verified here: I have no GPU, so the success paths of these eight endpoints were not exercised end to end. The change does not touch them, only how the request body is read and how the read is reported when it fails.


Open in Devin Review

Summary by CodeRabbit

  • Bug Fixes
    • Improved validation for API request bodies, including JSON content types and configured size limits.
    • Standardized request parsing across embeddings, classification, pooling, video, image, and audio endpoints.
    • Non-JSON embedding requests now return a clear 415 Unsupported Media Type response with structured error details.

ai-dynamo#13102 gave `/v1/responses`, `/v1/chat/completions` and `/v1/completions` a JSON
error envelope for a bad `Content-Type`, an oversized body and malformed JSON,
by reading the body through `read_json_request_body` and `parse_json_request`.

Eight handlers were left on the plain `Json<T>` extractor and still return
Axum's `text/plain` rejection, so the envelope depends on which endpoint the
client happened to call:

    POST /v1/responses  + Content-Type: text/plain
      415  application/json  {"message":"Expected request with Content-Type application/json",...}

    POST /v1/embeddings + Content-Type: text/plain
      415  text/plain        Expected request with `Content-Type: application/json`

A client that parses the error body works against one endpoint and fails
against the other, for the same mistake and the same status code.

Route the remaining handlers through the same two helpers: embeddings,
classify, pooling, images, image edits, videos, video stream and audio speech.
They already took `HeaderMap` and already returned
`Result<Response, ErrorResponse>`, so each is a signature swap plus the two
lines ai-dynamo#13102 established.

`images_edits` validates `input_reference` and then delegates to `images`,
which now consumes a `Body` it has already read. The part of `images` after
parsing moves into `images_with_request`, called by both.

No change to any success path, and no change to the error text; only the
envelope and content type on the eight endpoints that were inconsistent.

Signed-off-by: ayaangazali <ayaangazali.work@gmail.com>
@ayaangazali
ayaangazali requested a review from a team as a code owner August 23, 2026 01:21
Copilot AI lite review requested due to automatic review settings August 23, 2026 01:21
@copy-pr-bot

copy-pr-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ayaangazali
ayaangazali deployed to external_collaborator August 23, 2026 01:21 — with GitHub Actions Active
@ayaangazali
ayaangazali deployed to external_collaborator August 23, 2026 01:21 — with GitHub Actions Active

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Hi ayaangazali! Thank you for contributing to ai-dynamo/dynamo.

Just a reminder: The NVIDIA Test Github Validation CI runs an essential subset of the testing framework to quickly catch errors.Your PR reviewers may elect to test the changes comprehensively before approving your changes.

🚀

@github-actions github-actions Bot added fix external-contribution Pull request is from an external contributor frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` labels Aug 23, 2026

@devin-ai-integration devin-ai-integration 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9197b94c-633c-4084-844f-3a7a974b165c

📥 Commits

Reviewing files that changed from the base of the PR and between dbecec8 and 2834a94.

📒 Files selected for processing (2)
  • lib/llm/src/http/service/openai.rs
  • lib/llm/src/http/service/service_v2.rs

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


Walkthrough

OpenAI service handlers now read bounded request bodies, validate JSON content types, and parse requests through the tolerant JSON parser. Image generation and editing share request processing. An embeddings integration test verifies the JSON-formatted unsupported-media-type response.

Changes

OpenAI JSON request handling

Layer / File(s) Summary
Bounded JSON parsing for service endpoints
lib/llm/src/http/service/openai.rs, lib/llm/src/http/service/service_v2.rs
Embeddings, classify, pooling, videos, MJPEG video streaming, and audio speech now use explicit bounded JSON body parsing. The embeddings test verifies the JSON error envelope for unsupported content types.
Shared image request processing
lib/llm/src/http/service/openai.rs
Image generation and image edits parse request bodies explicitly and pass parsed requests to images_with_request.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 2834a

The change standardizes malformed-request errors across the remaining OpenAI routes without changing successful request handling; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides detailed scope and validation, but it omits the required Related Issues section and several template headings. Add the required Related Issues section and confirm either the linked issue or that no related issue exists.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: returning JSON errors from the remaining OpenAI routes.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
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.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contribution Pull request is from an external contributor fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants