Skip to content

fix(responses): accept assistant output_text messages without id/status in input#6599

Merged
ishandhanani merged 6 commits into
mainfrom
worktree-fix-responses-deser
Mar 6, 2026
Merged

fix(responses): accept assistant output_text messages without id/status in input#6599
ishandhanani merged 6 commits into
mainfrom
worktree-fix-responses-deser

Conversation

@MatejKosec
Copy link
Copy Markdown
Contributor

@MatejKosec MatejKosec commented Feb 25, 2026

Summary

  • Make OutputMessage.id and OutputMessage.status optional (Option<> with #[serde(default)]) so clients can replay assistant turns in conversation history without including output metadata fields
  • Fixes 400 deserialization errors when clients send output_text content in assistant messages without id and status (these are output-only metadata, not relevant for input context)
  • All construction sites (stream converter, response builder) continue to set both fields explicitly — no change to output behavior
  • Backwards compatible: payloads that include id and status still deserialize correctly

Context

An external regression report identified that assistant messages with output_text content fail with 400 (InputParam deserialization error) on the nvidia_dynamo endpoint. The root cause is that OutputMessage requires id and status as mandatory fields, but clients replaying multi-turn history naturally omit these.

The serde untagged enum cascade (InputItemItem::MessageMessageItem::Output) fails at OutputMessage due to missing required fields, then fails all remaining variants, producing a generic deserialization error from axum's Json extractor before handler code even runs.

Test plan

  • Added 4 serde unit tests covering:
  • CI: cargo test -p dynamo-async-openai
  • CI: cargo test -p dynamo-llm (protocol conversion tests)

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced system flexibility by making message ID and status fields optional. The system now handles a broader range of message formats, including input replay scenarios and batch operations. This improvement strengthens integration capabilities with diverse external systems while maintaining full backward compatibility.

@MatejKosec MatejKosec requested a review from a team as a code owner February 25, 2026 22:38
@github-actions github-actions Bot added fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` labels Feb 25, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 25, 2026

Walkthrough

OutputMessage fields id and status are converted from required to optional types. The id field changes from String to Option, and status changes from OutputStatus to Option. Serde attributes enable deserialization when these fields are absent. All construction sites are updated to wrap values in Some(...).

Changes

Cohort / File(s) Summary
Core Type Definition
lib/async-openai/src/types/responses/response.rs
Made OutputMessage.id and OutputMessage.status optional with serde(default, skip_serializing_if = "Option::is_none"). Added test modules to validate deserialization with and without these fields.
Protocol Implementation
lib/llm/src/protocols/openai/responses/mod.rs, lib/llm/src/protocols/openai/responses/stream_converter.rs
Updated OutputMessage construction to wrap id and status values in Some(...) across multiple locations (initialization, completion, final assembly). Updated tests to align with Option-wrapped field patterns.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With Option's gentle grace we dance,
Fields now flexible, given their chance,
Some(...) wraps both id and state,
Deserialization's now first-rate!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: making OutputMessage id/status optional to accept assistant messages without these fields in input.
Description check ✅ Passed The description covers all required template sections with sufficient detail: Overview (summary of the fix), Details (what changed and why), Where to start (specific files affected), and Related Issues (references the regression report context).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
lib/async-openai/src/types/responses/response.rs (1)

1548-1566: ⚠️ Potential issue | 🟡 Minor

Stale doc comment on MessageItem should be updated to reflect optional fields.

The MessageItem doc comment on lines 184–185 still reads:

OutputMessage: role=assistant, required id & status fields

This is now incorrect — both id and status are optional. The comment drives incorrect expectations for future readers trying to understand the disambiguation strategy.

📝 Proposed fix
-/// - OutputMessage: role=assistant, required id & status fields
+/// - OutputMessage: role=assistant (optional id & status — required in model output, optional in input history)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/async-openai/src/types/responses/response.rs` around lines 1548 - 1566,
Update the stale doc comment that describes OutputMessage/MessageItem to reflect
that the id and status fields are optional: change any text stating "required id
& status fields" or similar to indicate that id: Option<String> and status:
Option<OutputStatus> are optional, and adjust phrasing around role
(AssistantRole) and content (Vec<OutputMessageContent>) if needed; locate the
comment near the OutputMessage struct / MessageItem description and ensure it
accurately documents the optionality of id and status so readers understand the
disambiguation strategy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@lib/async-openai/src/types/responses/response.rs`:
- Around line 1548-1566: Update the stale doc comment that describes
OutputMessage/MessageItem to reflect that the id and status fields are optional:
change any text stating "required id & status fields" or similar to indicate
that id: Option<String> and status: Option<OutputStatus> are optional, and
adjust phrasing around role (AssistantRole) and content
(Vec<OutputMessageContent>) if needed; locate the comment near the OutputMessage
struct / MessageItem description and ensure it accurately documents the
optionality of id and status so readers understand the disambiguation strategy.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c18b475 and 2e5634c.

📒 Files selected for processing (3)
  • lib/async-openai/src/types/responses/response.rs
  • lib/llm/src/protocols/openai/responses/mod.rs
  • lib/llm/src/protocols/openai/responses/stream_converter.rs

…eserialization

Clients replaying assistant turns in conversation history send output_text
messages without `id` and `status` fields, which are output metadata not
relevant when used as input context. The strict required fields caused 400
deserialization errors (axum Json extractor rejects before handler runs).

Make both fields Option<> with serde defaults so:
- Assistant messages with output_text content deserialize without id/status
- Existing payloads with id/status continue to work (backwards compatible)
- All construction sites (stream converter, response builder) still set both

Adds serde unit tests for the exact customer reproduction cases.

Signed-off-by: Marko Kosec <mkosec@nvidia.com>
Signed-off-by: Matej Kosec <mkosec@nvidia.com>
Signed-off-by: Marko Kosec <mkosec@nvidia.com>
Signed-off-by: Matej Kosec <mkosec@nvidia.com>
The `annotations` field on `OutputTextContent` is required but clients
replaying assistant output_text messages as input typically omit it.
Add `#[serde(default)]` so it defaults to an empty vec, allowing the
OutputMessage deserialization path to succeed.

Signed-off-by: Marko Kosec <mkosec@nvidia.com>
Signed-off-by: Matej Kosec <mkosec@nvidia.com>
…cation, text, service_tier) (#6626)

Signed-off-by: Vasilis Vagias <vvagias@nvidia.com>
@ishandhanani ishandhanani enabled auto-merge (squash) March 3, 2026 17:50
@ishandhanani ishandhanani merged commit 9b2b44e into main Mar 6, 2026
88 checks passed
@ishandhanani ishandhanani deleted the worktree-fix-responses-deser branch March 6, 2026 17:44
rmccorm4 pushed a commit that referenced this pull request Mar 7, 2026
…us in input (#6599)

Signed-off-by: Marko Kosec <mkosec@nvidia.com>
Signed-off-by: Matej Kosec <mkosec@nvidia.com>
Signed-off-by: Vasilis Vagias <vvagias@nvidia.com>
Co-authored-by: vvagias <vasilis.n.vagias@gmail.com>
Co-authored-by: ishandhanani <82981111+ishandhanani@users.noreply.github.com>
saturley-hall pushed a commit that referenced this pull request Mar 7, 2026
…us in input (#6599) (#7049)

Signed-off-by: Marko Kosec <mkosec@nvidia.com>
Signed-off-by: Matej Kosec <mkosec@nvidia.com>
Signed-off-by: Vasilis Vagias <vvagias@nvidia.com>
Co-authored-by: MatejKosec <mkosec@nvidia.com>
Co-authored-by: vvagias <vasilis.n.vagias@gmail.com>
Co-authored-by: ishandhanani <82981111+ishandhanani@users.noreply.github.com>
ishandhanani added a commit that referenced this pull request Apr 16, 2026
…t messages

Two regressions in the Responses API → Chat Completions bridge when a prior
turn contains a `function_call` + assistant text + `function_call_output`
sequence (emitted by OpenAI Agents SDK, Codex, etc.):

1. Tool-call chain broken by interstitial assistant text.
   `convert_input_items_to_messages` emitted three separate chat messages
   (assistant-with-tool-calls, assistant-with-text, tool). Jinja templates
   that require a tool message to directly follow its assistant tool_call
   (e.g. MiniMax) then see the interstitial assistant message reset
   `last_tool_call.name` and reject the payload with
   "Message has tool role, but there was no previous assistant message
   with a tool call!".

   Coalesce adjacent assistant-side items (OutputMessage, FunctionCall,
   assistant EasyMessage) into a single ChatCompletionRequestAssistantMessage
   carrying both `content` and `tool_calls`. This matches the Chat Completions
   spec and lets downstream templates pair tool calls with their outputs.

2. Strict upstream deserialization rejects bare assistant messages.
   After the async-openai 0.34 upgrade (#7625) the lenient fix from #6599
   was dropped: upstream `OutputMessage` requires `id` + `status`, and
   `OutputTextContent` requires `annotations`. Clients like Codex send
   `{"type":"message","role":"assistant","content":[{"type":"output_text",
   "text":"..."}]}` without these, failing with
   "data did not match any variant of untagged enum InputParam".

   Add a custom Deserialize on NvCreateResponse that patches these items
   with synthetic defaults before delegating to the upstream strict types.

Tested live against MiniMax-M2.7 on dynamo.frontend:
- Codex `codex exec "hello minimax"` round-trips successfully
- All three previously-failing repro shapes (simpler, structured w/ id+status,
  structured w/o id+status) now return 200

Covered by three new unit tests in responses/mod.rs.
yao531441 pushed a commit to yao531441/dynamo that referenced this pull request May 13, 2026
…us in input (ai-dynamo#6599)

Signed-off-by: Marko Kosec <mkosec@nvidia.com>
Signed-off-by: Matej Kosec <mkosec@nvidia.com>
Signed-off-by: Vasilis Vagias <vvagias@nvidia.com>
Co-authored-by: vvagias <vasilis.n.vagias@gmail.com>
Co-authored-by: ishandhanani <82981111+ishandhanani@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants