Skip to content

fix(openrouter): preserve reasoning details#796

Merged
mikehostetler merged 1 commit into
agentjido:mainfrom
dannote:fix/openai-compatible-reasoning-details
Jul 6, 2026
Merged

fix(openrouter): preserve reasoning details#796
mikehostetler merged 1 commit into
agentjido:mainfrom
dannote:fix/openai-compatible-reasoning-details

Conversation

@dannote

@dannote dannote commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Description

This PR preserves OpenAI-compatible reasoning_details across ReqLLM's provider boundary.

OpenRouter can return reasoning_details as OpenAI-compatible wire maps with provider-specific metadata such as type, signatures, encrypted-signature markers, indexes, and other opaque fields. ReqLLM already has a normalized %ReqLLM.Message.ReasoningDetails{} struct with provider_data; this change makes that contract explicit and lossless across decode, context storage, and provider-boundary re-encoding.

This change:

  • Decodes OpenAI-compatible reasoning_details into %ReqLLM.Message.ReasoningDetails{}.
  • Preserves unknown/provider-specific fields in provider_data for lossless round-trips.
  • Decodes both non-streaming response messages and streaming delta metadata.
  • Keeps normalized reasoning_details as the internal representation by default.
  • Adds an explicit encode_context_to_openai_format/3 option, encode_reasoning_details?: true, for callers that intentionally need OpenAI-compatible wire maps.
  • Makes OpenRouter and MiniMax own their provider-specific reasoning-details re-encoding at the provider boundary.
  • Filters provider-specific reasoning details so OpenRouter does not accidentally emit MiniMax/Anthropic/Google details, and MiniMax preserves its own wire shape.

#791 has landed. This branch has been rebased onto current agentjido/main as a single reasoning-details commit.

Type of Change

  • Bug fix (non-breaking change fixing an issue)
  • New feature (non-breaking change adding functionality)
  • Breaking change (fix or feature causing existing functionality to change)
  • Documentation update

Breaking Changes

None expected.

The internal representation becomes stricter and more consistent: OpenAI-compatible reasoning_details decoded by ReqLLM.Provider.Defaults are normalized to %ReqLLM.Message.ReasoningDetails{}. Existing raw maps are still tolerated in provider-specific encoders where they were already accepted.

Testing

  • Tests pass (mix test)
  • Quality checks pass (mix quality)

Ran focused tests:

mix test test/providers/openrouter_test.exs test/req_llm/provider/defaults_test.exs test/providers/minimax_test.exs test/provider/openai/responses_api_unit_test.exs test/provider/openai_structured_output_test.exs
Result: 306 passed

Ran full tests:

mix test
Result: 3523 passed, 11 skipped, 144 excluded

Ran project quality gate:

mix quality
Result: passed

GitHub CI is green on commit a18e86c0e5d8359309ee9e632936bc50cf4f157e.

Model Compatibility

No live model compatibility fixtures were recorded for this PR.

The change is local decode/encode behavior for OpenAI-compatible reasoning metadata and provider-boundary request rendering. It is covered by unit/provider tests for:

  • default OpenAI-compatible response decoding
  • streaming reasoning-details metadata decoding
  • explicit OpenAI-compatible reasoning-details encoding
  • OpenRouter response decode and request re-encode round-trip
  • OpenRouter provider-data/signature preservation
  • MiniMax provider-specific reasoning-details wire encoding
  • provider filtering so unrelated providers' reasoning details are not emitted

Checklist

  • My code follows the project's style guidelines
  • I have updated the documentation accordingly
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass
  • My commits follow conventional commit format
  • I have NOT edited CHANGELOG.md (it is auto-generated by git_ops)

Docs note: no docs were updated because this is an internal provider-boundary correctness fix for existing reasoning_details data, not a new public user-facing API.

@dannote dannote force-pushed the fix/openai-compatible-reasoning-details branch 2 times, most recently from 9ac10fd to a05ac48 Compare July 2, 2026 08:31
@mikehostetler mikehostetler added the needs_work Changes requested before merge label Jul 6, 2026
@mikehostetler mikehostetler force-pushed the fix/openai-compatible-reasoning-details branch from 4b47895 to a18e86c Compare July 6, 2026 18:24
@mikehostetler mikehostetler marked this pull request as ready for review July 6, 2026 18:28
@mikehostetler mikehostetler added ready_to_merge and removed needs_work Changes requested before merge labels Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a18e86c0e5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ReqLLM.Message.ReasoningDetails.to_openai_compatible(detail)
end

defp encode_reasoning_detail_for_openai(_detail), do: nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve raw reasoning detail maps during encoding

When a caller reuses a legacy or persisted context whose reasoning_details are already raw OpenAI-compatible maps, the normal request path now enables encode_reasoning_details?: true and this catch-all filters every non-%ReasoningDetails{} entry out. That means those conversations silently stop sending the prior reasoning metadata on the next OpenAI-compatible request; before this change the raw maps were passed through unchanged.

Useful? React with 👍 / 👎.

Comment on lines +1207 to +1208
reasoning_details = decode_openai_reasoning_details(details, model.provider)
[ReqLLM.StreamChunk.meta(%{reasoning_details: reasoning_details})]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't pre-normalize MiniMax stream details

For MiniMax streaming responses that include delta.reasoning_details, this now converts the raw MiniMax wire maps into generic %ReasoningDetails{} before ReqLLM.Providers.Minimax.decode_stream_event/2 pipes the chunks through its MiniMax-specific normalizer. That normalizer expects raw maps with string keys like "id" and "text"; given a struct, it treats it as a map and loses the text/signature fields, corrupting streamed MiniMax reasoning continuity.

Useful? React with 👍 / 👎.

@mikehostetler mikehostetler merged commit 14940a4 into agentjido:main Jul 6, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants