Skip to content

Fix reasoning adaptor for openrouter - #1577

Merged
Calcium-Ion merged 2 commits into
QuantumNous:alphafrom
nekohy:feats-better-adaptor-for-openrouter
Aug 15, 2025
Merged

Fix reasoning adaptor for openrouter#1577
Calcium-Ion merged 2 commits into
QuantumNous:alphafrom
nekohy:feats-better-adaptor-for-openrouter

Conversation

@nekohy

@nekohy nekohy commented Aug 12, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added extended "thinking" support for one provider, allowing requests to include a controlled token budget for reasoning.
  • Bug Fixes

    • Prevented internal reasoning fields from being forwarded, avoiding inconsistent behavior between "thinking" and standard modes.
    • Improved error handling to stop malformed or incomplete reasoning payloads from being sent.
  • Notes

    • No changes to user workflows; integrations remain compatible.

@coderabbitai

coderabbitai Bot commented Aug 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds clearing of internal reasoning fields and extends thinking support: ConvertOpenAIRequest now clears ReasoningEffort after conversion, adds Anthropic-specific handling that converts THINKING into OpenRouter Reasoning (with required BudgetTokensMaxTokens), and returns errors on JSON unmarshal/marshal or missing budget tokens.

Changes

Cohort / File(s) Summary
OpenAI adaptor — request conversion
relay/channel/openai/adaptor.go
- Clears request.ReasoningEffort in both the thinking-suffix and non-thinking branches after converting/marshalling reasoning.
- Adds Anthropic-specific handling: when request.THINKING exists and info.UpstreamModelName starts with "anthropic", unmarshal THINKING into dto.Thinking; if thinking.Type == "enabled" require BudgetTokens and map it to openrouter.RequestReasoning{MaxTokens: BudgetTokens} and marshal into request.Reasoning; then clear request.THINKING.
- Improves error handling: returns errors for JSON unmarshal/marshal failures and missing BudgetTokens when thinking is enabled.

Sequence Diagram(s)

sequenceDiagram
  participant Relay as Relay
  participant OpenRouter as OpenRouterRequest

  Relay->>Relay: Receive ConvertOpenAIRequest(request, info)
  alt thinking_suffix path
    Relay->>Relay: Handle thinking suffix
    Relay->>Relay: Convert ReasoningEffort -> Reasoning
    Relay->>Relay: request.ReasoningEffort = ""
  else non-thinking path
    Relay->>Relay: Possibly marshal ReasoningEffort -> Reasoning
    Relay->>Relay: request.ReasoningEffort = ""
  end
  alt Anthropic upstream & THINKING present
    Relay->>Relay: Unmarshal request.THINKING -> dto.Thinking
    alt thinking.Type == "enabled"
      Relay->>Relay: require BudgetTokens present
      Relay->>Relay: Create RequestReasoning{MaxTokens: BudgetTokens}
      Relay->>Relay: Marshal -> request.Reasoning
    end
    Relay->>Relay: request.THINKING = nil
  end
  Relay->>OpenRouter: Send converted request (with Reasoning cleared/updated)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Possibly related PRs

Poem

I nibble bytes and tidy tracks,
I pack the thinking, then unpack.
Budget tokens set just right,
I clear the crumbs and hop from sight.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
relay/channel/openai/adaptor.go (1)

259-259: Same concern as above: this clear can be re-introduced later. Consider relying on a single final guard.

This clear is good, but it can still be undone by the later o*/gpt-5 model-suffix parsing block. If you adopt the final guard suggested above, these earlier per-branch clears become optional noise. Either keep both (belt-and-suspenders) or remove the earlier ones for simplicity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5abbeb and 33925dd.

📒 Files selected for processing (1)
  • relay/channel/openai/adaptor.go (2 hunks)

Comment on lines +240 to 242
// 清空多余的ReasoningEffort
request.ReasoningEffort = ""
} else {

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.

🛠️ Refactor suggestion

Clearing ReasoningEffort here is correct but can be undone later in this function. Add a final guard.

Nice catch clearing ReasoningEffort for the -thinking OpenRouter path. However, a few lines below (Lines 262-287), the code may re-populate request.ReasoningEffort when parsing model-suffix-based effort for o*/gpt-5 models. This can leak ReasoningEffort back into the final request for OpenRouter and contradict the PR’s goal.

Add a finalization step after the suffix-parsing block to:

  • If ChannelType is OpenRouter, move any residual ReasoningEffort into request.Reasoning (if still empty) and then clear ReasoningEffort again to guarantee no residue.

Apply this diff near the end of the o*/gpt-5 block (just before returning), while keeping info.ReasoningEffort intact:

@@
-        info.ReasoningEffort = request.ReasoningEffort
+        info.ReasoningEffort = request.ReasoningEffort
@@
-    }
+    }
+
+    // Ensure OpenRouter never carries ReasoningEffort; fold it into Reasoning if needed.
+    if info.ChannelType == constant.ChannelTypeOpenRouter {
+        if len(request.Reasoning) == 0 && request.ReasoningEffort != "" && request.ReasoningEffort != "none" {
+            reasoning := map[string]any{
+                "enabled": true,
+            }
+            if request.ReasoningEffort != "none" {
+                reasoning["effort"] = request.ReasoningEffort
+            }
+            marshal, err := common.Marshal(reasoning)
+            if err != nil {
+                return nil, fmt.Errorf("error marshalling reasoning: %w", err)
+            }
+            request.Reasoning = marshal
+        }
+        request.ReasoningEffort = ""
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In relay/channel/openai/adaptor.go around lines 240-242 (and the o*/gpt-5
suffix-parsing block at ~262-287), there's a risk that ReasoningEffort cleared
earlier gets re-populated during suffix parsing; add a final guard just before
returning from the o*/gpt-5 handling: if ChannelType == OpenRouter then if
request.Reasoning is empty and request.ReasoningEffort is non-empty move
request.ReasoningEffort into request.Reasoning, then set request.ReasoningEffort
= "" to ensure no residual ReasoningEffort is sent to OpenRouter; do not modify
info.ReasoningEffort.

@nekohy nekohy changed the title Fix Openai reasoning adaptor for openrouter Fix reasoning adaptor for openrouter Aug 12, 2025

@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.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
relay/channel/openai/adaptor.go (1)

311-318: Verify ReasoningEffort doesn't leak to OpenRouter after suffix parsing

After parsing the model suffix and setting request.ReasoningEffort = effort (line 313), this value is stored in info.ReasoningEffort (line 318) but request.ReasoningEffort remains populated. For OpenRouter requests, this could potentially send ReasoningEffort in the final request, which contradicts the earlier clearing logic.

Consider adding a final guard to ensure ReasoningEffort is cleared for OpenRouter:

 		info.ReasoningEffort = request.ReasoningEffort
+
+		// Ensure OpenRouter never carries ReasoningEffort
+		if info.ChannelType == constant.ChannelTypeOpenRouter {
+			request.ReasoningEffort = ""
+		}
♻️ Duplicate comments (1)
relay/channel/openai/adaptor.go (1)

240-242: Potential issue: ReasoningEffort may be re-populated later

While clearing ReasoningEffort here is correct for OpenRouter's -thinking suffix path, this can be undone later in the function. Lines 318 populate info.ReasoningEffort = request.ReasoningEffort when processing o*/gpt-5 models with suffix-based effort parsing (lines 311-316), which can re-populate the field.

To verify if this is still an issue, let me check if there's any code that might send ReasoningEffort to OpenRouter after this point:

#!/bin/bash
# Check if ReasoningEffort is used anywhere else in OpenRouter-related code
rg -A 5 "ReasoningEffort" --glob "**/*openrouter*"
🧹 Nitpick comments (1)
relay/channel/openai/adaptor.go (1)

273-275: Consider more specific error message for missing BudgetTokens

The error message could be more informative by including the model name for easier debugging.

-					return nil, fmt.Errorf("BudgetTokens is nil when thinking is enabled")
+					return nil, fmt.Errorf("BudgetTokens is required when thinking is enabled for model %s", info.UpstreamModelName)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33925dd and c1a545a.

📒 Files selected for processing (1)
  • relay/channel/openai/adaptor.go (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-21T03:37:41.726Z
Learnt from: 9Ninety
PR: QuantumNous/new-api#1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.

Applied to files:

  • relay/channel/openai/adaptor.go
🧬 Code Graph Analysis (1)
relay/channel/openai/adaptor.go (4)
dto/claude.go (1)
  • Thinking (267-270)
common/json.go (2)
  • Unmarshal (8-10)
  • Marshal (20-22)
relay/channel/openrouter/dto.go (1)
  • RequestReasoning (3-9)
dto/openai_request.go (1)
  • Reasoning (648-651)
🔇 Additional comments (2)
relay/channel/openai/adaptor.go (2)

259-259: Good: Properly clearing ReasoningEffort for non-thinking OpenRouter requests

This ensures that after converting ReasoningEffort to the OpenRouter-specific Reasoning format, the original field is cleared to prevent sending duplicate information.


262-291: Well-implemented Anthropic thinking support for OpenRouter

The implementation correctly:

  1. Checks for THINKING field and Anthropic model prefix
  2. Unmarshals to the proper dto.Thinking structure
  3. Validates that BudgetTokens is not nil when thinking is enabled
  4. Converts to OpenRouter's RequestReasoning format with MaxTokens
  5. Clears the THINKING field after conversion

The error handling is comprehensive with clear error messages for debugging.

@Calcium-Ion
Calcium-Ion merged commit ef1e380 into QuantumNous:alpha Aug 15, 2025
3 checks passed
@nekohy
nekohy deleted the feats-better-adaptor-for-openrouter branch August 18, 2025 04:50
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
…for-openrouter

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants