new-api/relay/channel/gemini/relay-gemini-native.go

177 lines
5.4 KiB
Go
Raw Normal View History

2025-05-26 13:34:41 +08:00
package gemini
import (
"io"
"net/http"
2025-06-21 00:54:40 +08:00
"strings"
2025-05-26 13:34:41 +08:00
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/logger"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relay/helper"
"github.com/QuantumNous/new-api/service"
"github.com/QuantumNous/new-api/types"
2025-08-09 00:27:33 +08:00
"github.com/pkg/errors"
2025-05-26 13:34:41 +08:00
"github.com/gin-gonic/gin"
)
func GeminiTextGenerationHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
defer service.CloseResponseBodyGracefully(resp)
2025-05-26 13:34:41 +08:00
// 读取响应体
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
2025-07-29 15:20:08 +08:00
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
2025-05-26 13:34:41 +08:00
}
if common.DebugEnabled {
println(string(responseBody))
}
// 解析为 Gemini 原生响应格式
var geminiResponse dto.GeminiChatResponse
err = common.Unmarshal(responseBody, &geminiResponse)
2025-05-26 13:34:41 +08:00
if err != nil {
2025-07-29 15:20:08 +08:00
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
2025-05-26 13:34:41 +08:00
}
// 计算使用量(基于 UsageMetadata
usage := dto.Usage{
PromptTokens: geminiResponse.UsageMetadata.PromptTokenCount,
2025-06-19 14:50:50 +08:00
CompletionTokens: geminiResponse.UsageMetadata.CandidatesTokenCount + geminiResponse.UsageMetadata.ThoughtsTokenCount,
2025-05-26 13:34:41 +08:00
TotalTokens: geminiResponse.UsageMetadata.TotalTokenCount,
}
2025-06-07 12:26:23 +08:00
usage.CompletionTokenDetails.ReasoningTokens = geminiResponse.UsageMetadata.ThoughtsTokenCount
for _, detail := range geminiResponse.UsageMetadata.PromptTokensDetails {
if detail.Modality == "AUDIO" {
usage.PromptTokensDetails.AudioTokens = detail.TokenCount
} else if detail.Modality == "TEXT" {
usage.PromptTokensDetails.TextTokens = detail.TokenCount
}
}
service.IOCopyBytesGracefully(c, resp, responseBody)
2025-05-26 13:34:41 +08:00
2025-05-26 14:50:50 +08:00
return &usage, nil
}
2025-08-09 00:27:33 +08:00
func NativeGeminiEmbeddingHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.Usage, *types.NewAPIError) {
defer service.CloseResponseBodyGracefully(resp)
2025-08-09 00:27:33 +08:00
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
}
if common.DebugEnabled {
println(string(responseBody))
}
usage := &dto.Usage{
PromptTokens: info.PromptTokens,
TotalTokens: info.PromptTokens,
}
if info.IsGeminiBatchEmbedding {
2025-08-09 00:27:33 +08:00
var geminiResponse dto.GeminiBatchEmbeddingResponse
err = common.Unmarshal(responseBody, &geminiResponse)
if err != nil {
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
}
} else {
var geminiResponse dto.GeminiEmbeddingResponse
err = common.Unmarshal(responseBody, &geminiResponse)
if err != nil {
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
}
}
service.IOCopyBytesGracefully(c, resp, responseBody)
2025-08-09 00:27:33 +08:00
return usage, nil
}
func GeminiTextGenerationStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
2025-05-26 14:50:50 +08:00
var usage = &dto.Usage{}
var imageCount int
helper.SetEventStreamHeaders(c)
2025-06-21 00:54:40 +08:00
responseText := strings.Builder{}
2025-05-26 14:50:50 +08:00
helper.StreamScannerHandler(c, resp, info, func(data string) bool {
var geminiResponse dto.GeminiChatResponse
err := common.UnmarshalJsonStr(data, &geminiResponse)
2025-05-26 14:50:50 +08:00
if err != nil {
logger.LogError(c, "error unmarshalling stream response: "+err.Error())
2025-05-26 14:50:50 +08:00
return false
}
// 统计图片数量
for _, candidate := range geminiResponse.Candidates {
for _, part := range candidate.Content.Parts {
if part.InlineData != nil && part.InlineData.MimeType != "" {
imageCount++
}
2025-06-21 00:54:40 +08:00
if part.Text != "" {
responseText.WriteString(part.Text)
}
2025-05-26 14:50:50 +08:00
}
}
// 更新使用量统计
if geminiResponse.UsageMetadata.TotalTokenCount != 0 {
usage.PromptTokens = geminiResponse.UsageMetadata.PromptTokenCount
2025-06-19 14:50:50 +08:00
usage.CompletionTokens = geminiResponse.UsageMetadata.CandidatesTokenCount + geminiResponse.UsageMetadata.ThoughtsTokenCount
2025-05-26 14:50:50 +08:00
usage.TotalTokens = geminiResponse.UsageMetadata.TotalTokenCount
2025-06-07 12:26:23 +08:00
usage.CompletionTokenDetails.ReasoningTokens = geminiResponse.UsageMetadata.ThoughtsTokenCount
for _, detail := range geminiResponse.UsageMetadata.PromptTokensDetails {
if detail.Modality == "AUDIO" {
usage.PromptTokensDetails.AudioTokens = detail.TokenCount
} else if detail.Modality == "TEXT" {
usage.PromptTokensDetails.TextTokens = detail.TokenCount
}
}
2025-05-26 14:50:50 +08:00
}
// 直接发送 GeminiChatResponse 响应
err = helper.StringData(c, data)
2025-05-26 14:50:50 +08:00
if err != nil {
logger.LogError(c, err.Error())
2025-05-26 14:50:50 +08:00
}
info.SendResponseCount++
2025-05-26 14:50:50 +08:00
return true
})
if info.SendResponseCount == 0 {
return nil, types.NewOpenAIError(errors.New("no response received from Gemini API"), types.ErrorCodeEmptyResponse, http.StatusInternalServerError)
}
2025-05-26 14:50:50 +08:00
if imageCount != 0 {
if usage.CompletionTokens == 0 {
usage.CompletionTokens = imageCount * 258
}
}
// 如果usage.CompletionTokens为0则使用本地统计的completion tokens
if usage.CompletionTokens == 0 {
str := responseText.String()
if len(str) > 0 {
usage = service.ResponseText2Usage(responseText.String(), info.UpstreamModelName, info.PromptTokens)
} else {
// 空补全,不需要使用量
usage = &dto.Usage{}
}
}
// 移除流式响应结尾的[Done]因为Gemini API没有发送Done的行为
//helper.Done(c)
2025-05-26 14:50:50 +08:00
return usage, nil
2025-05-26 13:34:41 +08:00
}