Skip to content

feat: Claude to gemini (适配claude格式调用gemini渠道模型) - #1443

Merged
Calcium-Ion merged 1 commit into
alphafrom
claude_to_gemini
Jul 26, 2025
Merged

feat: Claude to gemini (适配claude格式调用gemini渠道模型)#1443
Calcium-Ion merged 1 commit into
alphafrom
claude_to_gemini

Conversation

@Calcium-Ion

@Calcium-Ion Calcium-Ion commented Jul 26, 2025

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Improved handling of streaming chat responses, including standardized initial and final response formatting.
    • Added support for generating an initial empty assistant response in streaming chats.
    • Implemented conversion of Claude requests for Gemini channels.
  • Bug Fixes

    • Adjusted error handling when no channels are found, now returning a nil error instead of an error message.
    • Added a specific error code for empty responses.
  • Refactor

    • Streamlined and centralized response handling logic for chat streaming.
    • Renamed and exported certain helper functions for consistency and broader use.

@coderabbitai

coderabbitai Bot commented Jul 26, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This update modifies error handling and response streaming across several modules. It changes the behavior for cases where no channels or abilities are found, introduces standardized helper functions for streaming responses, implements request conversion logic in the Gemini adaptor, and exports previously unexported helper functions. Additionally, a new error code and a utility for generating empty assistant responses are added.

Changes

File(s) Change Summary
model/ability.go, model/channel_cache.go Modified error handling: functions now return (nil, nil) instead of (nil, error) when no channel/ability found.
relay/channel/gemini/adaptor.go Implemented ConvertClaudeRequest method, delegating conversion to OpenAI adaptor and further processing.
relay/channel/gemini/relay-gemini.go Added handleStream and handleFinalStream helpers; refactored streaming logic to use these functions.
relay/helper/common.go Added GenerateStartEmptyResponse to create an initial empty assistant response chunk.
relay/channel/openai/helper.go Renamed handleStreamFormat and handleFinalResponse to exported HandleStreamFormat and HandleFinalResponse.
relay/channel/openai/relay-openai.go Updated to use exported helper functions and inlined settings; removed local variables for channel settings.
types/error.go Added new error code constant: ErrorCodeEmptyResponse.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GeminiAdaptor
    participant OpenAIAdaptor

    Client->>GeminiAdaptor: ConvertClaudeRequest(ctx, info, req)
    GeminiAdaptor->>OpenAIAdaptor: ConvertClaudeRequest(ctx, info, req)
    OpenAIAdaptor-->>GeminiAdaptor: GeneralOpenAIRequest / error
    GeminiAdaptor->>GeminiAdaptor: ConvertOpenAIRequest(ctx, info, GeneralOpenAIRequest)
    GeminiAdaptor-->>Client: ConvertedRequest / error
Loading
sequenceDiagram
    participant GeminiStreamHandler
    participant HelperCommon
    participant Client

    GeminiStreamHandler->>HelperCommon: GenerateStartEmptyResponse(...)
    HelperCommon-->>GeminiStreamHandler: StartEmptyResponse
    GeminiStreamHandler->>Client: Send StartEmptyResponse

    loop For each streamed chunk
        GeminiStreamHandler->>Client: Send Streamed Chunk (handleStream)
    end

    alt No streamed responses
        GeminiStreamHandler-->>Client: Error (ErrorCodeEmptyResponse)
    else Final usage response
        GeminiStreamHandler->>Client: Send Final Response (handleFinalStream)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

In the warren’s code-lit night,
A bunny hops with streaming delight.
Errors now are gentler, nil and clear,
Helpers exported for all to revere.
New streams flow with a thoughtful start,
“Empty response” finds its place in the chart—
Oh, what joy in a rabbit’s heart! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

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

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 3

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c51ec31 and 8e3cf2e.

📒 Files selected for processing (8)
  • model/ability.go (1 hunks)
  • model/channel_cache.go (1 hunks)
  • relay/channel/gemini/adaptor.go (2 hunks)
  • relay/channel/gemini/relay-gemini.go (5 hunks)
  • relay/channel/openai/helper.go (2 hunks)
  • relay/channel/openai/relay-openai.go (3 hunks)
  • relay/helper/common.go (1 hunks)
  • types/error.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
relay/channel/gemini/relay-gemini.go (1)

Learnt from: 9Ninety
PR: #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.

🔇 Additional comments (15)
types/error.go (1)

66-66: LGTM! Well-placed error code addition.

The new ErrorCodeEmptyResponse constant follows the existing naming convention and is appropriately categorized within the response error codes section. This will improve error handling for empty response scenarios in the streaming handlers.

relay/channel/openai/helper.go (2)

17-17: LGTM! Function exported for cross-package reuse.

Renaming handleStreamFormat to HandleStreamFormat properly exports the function, enabling its use in other channel adaptors while maintaining the same functionality.


161-161: LGTM! Function exported for cross-package reuse.

Renaming handleFinalResponse to HandleFinalResponse properly exports the function, enabling standardized final response handling across different channel implementations.

relay/helper/common.go (1)

142-158: LGTM! Well-designed helper function.

The GenerateStartEmptyResponse function provides a standardized way to create initial empty assistant responses for streaming. The implementation follows the existing patterns in the file and properly handles the optional systemFingerprint parameter.

relay/channel/gemini/adaptor.go (2)

12-12: LGTM! Import added for delegation pattern.

The import of the OpenAI package is necessary for the delegation pattern implemented in ConvertClaudeRequest.


25-32: Verification Complete: Delegation Chain is Safe

The call to openai.Adaptor.ConvertClaudeRequest returns a *dto.GeneralOpenAIRequest as expected, and gemini.Adaptor.ConvertOpenAIRequest accepts that same type without issue. The type assertion is safe and error handling is correct. No further changes required.

relay/channel/openai/relay-openai.go (3)

130-130: LGTM! Updated to use exported helper function.

The call to HandleStreamFormat (now exported) with inline access to info.ChannelSetting.ForceFormat and info.ChannelSetting.ThinkingToContent simplifies the code by removing unnecessary local variables.


151-151: LGTM! Simplified inline parameter access.

Direct access to info.ChannelSetting.ForceFormat and info.ChannelSetting.ThinkingToContent removes unnecessary local variables and makes the code more direct.


170-170: LGTM! Updated to use exported helper function.

The call to HandleFinalResponse (now exported) completes the refactoring to use standardized helper functions across different channel implementations.

relay/channel/gemini/relay-gemini.go (6)

12-12: LGTM! Import necessary for OpenAI helper delegation.

The new import supports the architectural change to centralize streaming response handling through OpenAI helper functions.


802-812: Well-implemented helper function for stream handling.

The function provides a clean abstraction over the OpenAI helper with proper error handling and descriptive error messages.


814-821: Consistent implementation for final stream handling.

The function mirrors the structure of handleStream and appropriately delegates to HandleFinalResponse with all necessary parameters.


830-830: Response counter for empty response detection.

The counter variable is appropriately initialized and will be used to track streamed responses for empty response handling.


861-884: Improved streaming logic with empty response detection.

The changes enhance the streaming experience by:

  • Sending an initial empty response chunk for better client UX
  • Using centralized helper functions for consistency
  • Properly detecting and handling empty responses with appropriate error codes

The empty response handling correctly returns ErrorCodeEmptyResponse without billing, which aligns with the expected behavior.


895-902: Confirmed: helper.Done(c) Is Handled by handleFinalStream

The call to helper.Done(c) is already performed inside openai.HandleFinalResponse, which is invoked by handleFinalStream in relay/channel/gemini/relay-gemini.go. The standalone, commented-out helper.Done(c) is therefore redundant and can safely remain removed (or be deleted entirely).

Comment thread model/ability.go
Comment on lines 138 to +139
} else {
return nil, errors.New("channel not found")
return nil, nil

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.

💡 Verification agent

🧩 Analysis chain

Critical: Breaking change in exported function may affect external callers.

This exported function now returns (nil, nil) instead of (nil, error) when no abilities are found. This breaks the expected Go error handling contract where callers check if err != nil to detect failure conditions.

Since this is an exported function, verify all callers (both internal and potentially external) can handle the new semantics. The function should either:

  1. Maintain the error return (recommended):
	} else {
-		return nil, nil
+		return nil, errors.New("channel not found")
	}
  1. Or update the function documentation to clearly indicate the new behavior and ensure all callers are updated.

Let me verify all callers of this exported function:


🏁 Script executed:

#!/bin/bash
# Search for all callers of the exported GetRandomSatisfiedChannel function
rg -A 5 -B 2 "GetRandomSatisfiedChannel\("

Length of output: 2409


Action Required: Handle nil channel return in all callers of GetRandomSatisfiedChannel

The exported function GetRandomSatisfiedChannel now returns (nil, nil) when no channel is found. While controller/relay.go already checks for a nil channel, middleware/distributor.go does not—and will panic or exhibit undefined behavior when it receives a nil channel with no error.

Please update callers (or revert the change) as follows:

• middleware/distributor.go
After invoking CacheGetRandomSatisfiedChannel, add a nil‐channel check before using channel:

   channel, selectGroup, err = model.CacheGetRandomSatisfiedChannel(c, userGroup, modelRequest.Model, 0)
   if err != nil {
       // existing error handling…
   }
+    if channel == nil {
+        return /* appropriate error or fallback, e.g.: */
+            fmt.Errorf("no channel available for group %s, model %s", selectGroup, modelRequest.Model)
+    }
   // proceed safely with non‐nil channel…

• model/channel_cache.go
In the !common.MemoryCacheEnabled branch, ensure the three‐value return matches the signature:

if !common.MemoryCacheEnabled {
-    return GetRandomSatisfiedChannel(group, model, retry)
+    ch, err := GetRandomSatisfiedChannel(group, model, retry)
+    return ch, selectGroup, err
}

• model/ability.go
Either revert to returning an error (errors.New("channel not found")) for consistency with Go conventions, or update the function’s documentation to explicitly state that (nil, nil) signals “no channel found” and that callers must guard against a nil *Channel.

Fix these locations so that no caller continues past a nil channel without proper handling.

🤖 Prompt for AI Agents
In model/ability.go at lines 138-139, the function GetRandomSatisfiedChannel
returns (nil, nil) when no channel is found, which can cause panics in callers
that do not check for nil channels. To fix this, update all callers to check for
a nil channel before use: in middleware/distributor.go, add a nil check after
calling CacheGetRandomSatisfiedChannel; in model/channel_cache.go, modify the
!common.MemoryCacheEnabled branch to capture and return the three values
(channel, selectGroup, error) from GetRandomSatisfiedChannel; and in
model/ability.go, either revert to returning an error when no channel is found
or update the function documentation to clearly state that (nil, nil) means no
channel and callers must handle it. Ensure no caller proceeds with a nil channel
without proper handling.

Comment thread model/channel_cache.go
Comment on lines 132 to +133
if len(channels) == 0 {
return nil, errors.New("channel not found")
return nil, nil

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.

💡 Verification agent

🧩 Analysis chain

Critical: Breaking change in error handling semantics may cause nil channel returns without errors.

This change modifies the function to return (nil, nil) instead of (nil, error) when no channels are found. However, the caller on lines 107-110 expects an error when no channel is available and will now incorrectly return a nil channel without an error to its caller.

The caller CacheGetRandomSatisfiedChannel at lines 107-110 needs to handle the new semantics:

	} else {
		channel, err = getRandomSatisfiedChannel(group, model, retry)
-		if err != nil {
-			return nil, group, err
-		}
+		if err != nil {
+			return nil, group, err
+		}
+		if channel == nil {
+			return nil, group, errors.New("channel not found")
+		}
	}

Let me verify all callers of this function to ensure they handle the new semantics correctly:


🏁 Script executed:

#!/bin/bash
# Search for all callers of getRandomSatisfiedChannel to verify they handle nil channel with nil error
rg -A 10 -B 2 "getRandomSatisfiedChannel"

Length of output: 1701


Handle missing channel error in CacheGetRandomSatisfiedChannel

The change in getRandomSatisfiedChannel to return (nil, nil) when no channels are found breaks the error path in the else‐branch of CacheGetRandomSatisfiedChannel. You must explicitly detect a nil channel and return an error instead of propagating a silent success.

• File: model/channel_cache.go
Location: inside else { … } starting around line 107

Suggested diff:

     } else {
-        channel, err = getRandomSatisfiedChannel(group, model, retry)
-        if err != nil {
-            return nil, group, err
-        }
+        channel, err = getRandomSatisfiedChannel(group, model, retry)
+        if err != nil {
+            return nil, group, err
+        }
+        if channel == nil {
+            return nil, group, errors.New("channel not found")
+        }
     }

This ensures callers continue to receive an error when no channel is available.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(channels) == 0 {
return nil, errors.New("channel not found")
return nil, nil
} else {
channel, err = getRandomSatisfiedChannel(group, model, retry)
if err != nil {
return nil, group, err
}
if channel == nil {
return nil, group, errors.New("channel not found")
}
}
🤖 Prompt for AI Agents
In model/channel_cache.go around lines 107 and 132-133, the function
getRandomSatisfiedChannel returns (nil, nil) when no channels are found, which
causes CacheGetRandomSatisfiedChannel to silently succeed with a nil channel. To
fix this, modify CacheGetRandomSatisfiedChannel to explicitly check if the
returned channel is nil and return an appropriate error instead of nil, nil.
This ensures that callers receive an error when no channel is available,
preserving correct error handling.

Index: int(candidate.Index),
Delta: dto.ChatCompletionsStreamResponseChoiceDelta{
Role: "assistant",
//Role: "assistant",

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.

💡 Verification agent

🧩 Analysis chain

Verify impact of commented out assistant role assignment.

The commented out role assignment in the streaming response delta might affect API compatibility. Clients may expect the role field to be present in streaming responses.


🏁 Script executed:

#!/bin/bash
# Description: Check if role is set elsewhere in streaming responses and verify OpenAI compatibility

# Search for role assignments in streaming response handling
rg -A 5 -B 5 "Role.*assistant" --type go

# Look for Delta role assignments in other parts of the codebase
rg -A 3 -B 3 "Delta.*Role" --type go

# Check OpenAI documentation references or comments about role requirements
rg -i "role.*required\|required.*role" --type go

Length of output: 19609


Confirm explicit assistant role initialization in streaming deltas

We need to ensure that the first streamed chunk still declares "assistant" so clients parsing OpenAI‐style streams know when the assistant reply begins. In all other adapters (Cloudflare, Claude, Cohere, etc.), the very first ChatCompletionsStreamResponseChoiceDelta includes Role: "assistant". In the Gemini adapter this line is commented out:

choice := dto.ChatCompletionsStreamResponseChoice{
    Index: int(candidate.Index),
    Delta: dto.ChatCompletionsStreamResponseChoiceDelta{
        //Role: "assistant",
    },
}

Without restoring it, downstream consumers may never see the assistant role header and could ignore or misinterpret subsequent content deltas.

Please add back:

 choice := dto.ChatCompletionsStreamResponseChoice{
     Index: int(candidate.Index),
     Delta: dto.ChatCompletionsStreamResponseChoiceDelta{
-        //Role: "assistant",
+        Role: "assistant",
     },
 }

at relay/channel/gemini/relay-gemini.go around line 740 to align with OpenAI’s streaming protocol.

🤖 Prompt for AI Agents
In relay/channel/gemini/relay-gemini.go at line 740, the assistant role
initialization in the first streamed delta is commented out, which can cause
downstream clients to miss the assistant role header. Uncomment the line setting
Role: "assistant" inside the ChatCompletionsStreamResponseChoiceDelta struct to
explicitly declare the assistant role in the initial streamed chunk, ensuring
compatibility with OpenAI-style streaming protocols.

@Calcium-Ion
Calcium-Ion merged commit e7524c8 into alpha Jul 26, 2025
3 checks passed
@Calcium-Ion
Calcium-Ion deleted the claude_to_gemini branch July 26, 2025 06:07
@coderabbitai coderabbitai Bot mentioned this pull request Jul 30, 2025
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
feat: Claude to gemini (适配claude格式调用gemini渠道模型)
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.

1 participant