feat: Claude to gemini (适配claude格式调用gemini渠道模型) - #1443
Conversation
WalkthroughThis 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
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
ErrorCodeEmptyResponseconstant 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
handleStreamFormattoHandleStreamFormatproperly 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
handleFinalResponsetoHandleFinalResponseproperly 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
GenerateStartEmptyResponsefunction 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 optionalsystemFingerprintparameter.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 SafeThe 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 toinfo.ChannelSetting.ForceFormatandinfo.ChannelSetting.ThinkingToContentsimplifies the code by removing unnecessary local variables.
151-151: LGTM! Simplified inline parameter access.Direct access to
info.ChannelSetting.ForceFormatandinfo.ChannelSetting.ThinkingToContentremoves 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
handleStreamand appropriately delegates toHandleFinalResponsewith 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
ErrorCodeEmptyResponsewithout billing, which aligns with the expected behavior.
895-902: Confirmed:helper.Done(c)Is Handled byhandleFinalStreamThe call to
helper.Done(c)is already performed insideopenai.HandleFinalResponse, which is invoked byhandleFinalStreaminrelay/channel/gemini/relay-gemini.go. The standalone, commented-outhelper.Done(c)is therefore redundant and can safely remain removed (or be deleted entirely).
| } else { | ||
| return nil, errors.New("channel not found") | ||
| return nil, nil |
There was a problem hiding this comment.
💡 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:
- Maintain the error return (recommended):
} else {
- return nil, nil
+ return nil, errors.New("channel not found")
}- 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.
| if len(channels) == 0 { | ||
| return nil, errors.New("channel not found") | ||
| return nil, nil |
There was a problem hiding this comment.
💡 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.
| 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", |
There was a problem hiding this comment.
💡 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 goLength 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.
feat: Claude to gemini (适配claude格式调用gemini渠道模型)
Summary by CodeRabbit
New Features
Bug Fixes
Refactor