-
Notifications
You must be signed in to change notification settings - Fork 11.1k
feat: Claude to gemini (适配claude格式调用gemini渠道模型) #1443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -130,7 +130,7 @@ func getRandomSatisfiedChannel(group string, model string, retry int) (*Channel, | |||||||||||||||||||||||||
| channels := group2model2channels[group][model] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if len(channels) == 0 { | ||||||||||||||||||||||||||
| return nil, errors.New("channel not found") | ||||||||||||||||||||||||||
| return nil, nil | ||||||||||||||||||||||||||
|
Comment on lines
132
to
+133
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainCritical: Breaking change in error handling semantics may cause nil channel returns without errors. This change modifies the function to return The caller } 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 The change in • File: model/channel_cache.go 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if len(channels) == 1 { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import ( | |
| "one-api/common" | ||
| "one-api/constant" | ||
| "one-api/dto" | ||
| "one-api/relay/channel/openai" | ||
| relaycommon "one-api/relay/common" | ||
| "one-api/relay/helper" | ||
| "one-api/service" | ||
|
|
@@ -736,7 +737,7 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) (*dto.C | |
| choice := dto.ChatCompletionsStreamResponseChoice{ | ||
| Index: int(candidate.Index), | ||
| Delta: dto.ChatCompletionsStreamResponseChoiceDelta{ | ||
| Role: "assistant", | ||
| //Role: "assistant", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify 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 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 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 |
||
| }, | ||
| } | ||
| var texts []string | ||
|
|
@@ -798,13 +799,36 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) (*dto.C | |
| return &response, isStop, hasImage | ||
| } | ||
|
|
||
| func handleStream(c *gin.Context, info *relaycommon.RelayInfo, resp *dto.ChatCompletionsStreamResponse) error { | ||
| streamData, err := common.Marshal(resp) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to marshal stream response: %w", err) | ||
| } | ||
| err = openai.HandleStreamFormat(c, info, string(streamData), info.ChannelSetting.ForceFormat, info.ChannelSetting.ThinkingToContent) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to handle stream format: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func handleFinalStream(c *gin.Context, info *relaycommon.RelayInfo, resp *dto.ChatCompletionsStreamResponse) error { | ||
| streamData, err := common.Marshal(resp) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to marshal stream response: %w", err) | ||
| } | ||
| openai.HandleFinalResponse(c, info, string(streamData), resp.Id, resp.Created, resp.Model, resp.GetSystemFingerprint(), resp.Usage, info.ShouldIncludeUsage) | ||
| return nil | ||
| } | ||
|
|
||
| func GeminiChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) { | ||
| // responseText := "" | ||
| id := helper.GetResponseID(c) | ||
| createAt := common.GetTimestamp() | ||
| var usage = &dto.Usage{} | ||
| var imageCount int | ||
|
|
||
| respCount := 0 | ||
|
|
||
| helper.StreamScannerHandler(c, resp, info, func(data string) bool { | ||
| var geminiResponse GeminiChatResponse | ||
| err := common.UnmarshalJsonStr(data, &geminiResponse) | ||
|
|
@@ -833,18 +857,31 @@ func GeminiChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp * | |
| } | ||
| } | ||
| } | ||
| err = helper.ObjectData(c, response) | ||
|
|
||
| if respCount == 0 { | ||
| // send first response | ||
| err = handleStream(c, info, helper.GenerateStartEmptyResponse(id, createAt, info.UpstreamModelName, nil)) | ||
| if err != nil { | ||
| common.LogError(c, err.Error()) | ||
| } | ||
| } | ||
|
|
||
| err = handleStream(c, info, response) | ||
| if err != nil { | ||
| common.LogError(c, err.Error()) | ||
| } | ||
| if isStop { | ||
| response := helper.GenerateStopResponse(id, createAt, info.UpstreamModelName, constant.FinishReasonStop) | ||
| helper.ObjectData(c, response) | ||
| _ = handleStream(c, info, helper.GenerateStopResponse(id, createAt, info.UpstreamModelName, constant.FinishReasonStop)) | ||
| } | ||
| respCount++ | ||
| return true | ||
| }) | ||
|
|
||
| var response *dto.ChatCompletionsStreamResponse | ||
| if respCount == 0 { | ||
| // 空补全,报错不计费 | ||
| // empty response, throw an error | ||
| return nil, types.NewOpenAIError(errors.New("no response received from Gemini API"), types.ErrorCodeEmptyResponse, http.StatusInternalServerError) | ||
| } | ||
|
|
||
| if imageCount != 0 { | ||
| if usage.CompletionTokens == 0 { | ||
|
|
@@ -855,14 +892,14 @@ func GeminiChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp * | |
| usage.PromptTokensDetails.TextTokens = usage.PromptTokens | ||
| usage.CompletionTokens = usage.TotalTokens - usage.PromptTokens | ||
|
|
||
| if info.ShouldIncludeUsage { | ||
| response = helper.GenerateFinalUsageResponse(id, createAt, info.UpstreamModelName, *usage) | ||
| err := helper.ObjectData(c, response) | ||
| if err != nil { | ||
| common.SysError("send final response failed: " + err.Error()) | ||
| } | ||
| response := helper.GenerateFinalUsageResponse(id, createAt, info.UpstreamModelName, *usage) | ||
| err := handleFinalStream(c, info, response) | ||
| if err != nil { | ||
| common.SysError("send final response failed: " + err.Error()) | ||
| } | ||
| helper.Done(c) | ||
| //if info.RelayFormat == relaycommon.RelayFormatOpenAI { | ||
| // helper.Done(c) | ||
| //} | ||
| //resp.Body.Close() | ||
| return usage, nil | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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 checkif err != nilto 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:
} else { - return nil, nil + return nil, errors.New("channel not found") }Let me verify all callers of this exported function:
🏁 Script executed:
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.MemoryCacheEnabledbranch, ensure the three‐value return matches the signature:• 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