mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-14 00:01:53 +00:00
feat: enhance text protocol conversion and advanced custom routing (#5825)
* refactor: consolidate relay protocol converters * refactor relayconvert text converters * feat: refine relay converters and advanced custom routing * refactor: enhance logging and add thought signature handling for Gemini requests * refactor: enhance channel cache and pricing endpoint handling for advanced custom models * feat: preserve billing usage semantics * feat: add protocol-aware billing usage * Delete useless files * chore: update action versions in workflow files * chore: update Docker action versions in workflow files * fix: harden billing usage settlement and hot-path route matching - estimate Gemini completion tokens locally when billable usageMetadata is prompt-only but output content was received (e.g. client aborts the stream before the final chunk), and rebuild the attached billing_usage as estimated so settlement does not bill zero output tokens - guard NewClaudeMessagesBillingUsage against all-zero ClaudeUsage, matching the OpenAI/Gemini constructors, so a zero billing_usage cannot override a non-zero top-level usage during settlement - cache compiled advanced-custom route model regexes; they run on the request hot path and were recompiled per request - move the effectiveBillingUsage remap to PostTextConsumeQuota only, and document that calculateTextQuotaSummary expects remapped usage - document the updatePricingLock -> channelSyncLock lock ordering that InitChannelCache/CacheUpdateChannel rely on, and the aux-struct pitfall in GeminiChatResponse.UnmarshalJSON
This commit is contained in:
@@ -42,11 +42,14 @@ type Adaptor struct {
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertGeminiRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeminiChatRequest) (any, error) {
|
||||
// 使用 service.GeminiToOpenAIRequest 转换请求格式
|
||||
openaiRequest, err := service.GeminiToOpenAIRequest(request, info)
|
||||
result, err := service.ConvertRequest(c, info, types.RelayFormatOpenAI, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
openaiRequest, ok := result.Value.(*dto.GeneralOpenAIRequest)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected OpenAI chat completions request, got %T", result.Value)
|
||||
}
|
||||
return a.ConvertOpenAIRequest(c, info, openaiRequest)
|
||||
}
|
||||
|
||||
@@ -61,10 +64,14 @@ func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayIn
|
||||
// println(fmt.Sprintf("failed to save request body to file: %v", err))
|
||||
// }
|
||||
//}
|
||||
aiRequest, err := service.ClaudeToOpenAIRequest(*request, info)
|
||||
result, err := service.ConvertRequest(c, info, types.RelayFormatOpenAI, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aiRequest, ok := result.Value.(*dto.GeneralOpenAIRequest)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected OpenAI chat completions request, got %T", result.Value)
|
||||
}
|
||||
//if common.DebugEnabled {
|
||||
// println(fmt.Sprintf("convert claude to openai request result: %s", common.GetJsonString(aiRequest)))
|
||||
// // Save request body to file for debugging
|
||||
|
||||
@@ -41,11 +41,18 @@ func OaiResponsesToChatHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
|
||||
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
|
||||
}
|
||||
|
||||
chatId := helper.GetResponseID(c)
|
||||
chatResp, usage, err := service.ResponsesResponseToChatCompletionsResponse(&responsesResp, chatId)
|
||||
chatResult, err := relayconvert.ConvertResponse(c, info, types.RelayFormatOpenAI, &responsesResp)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
chatResp, ok := chatResult.Value.(*dto.OpenAITextResponse)
|
||||
if !ok {
|
||||
return nil, types.NewOpenAIError(fmt.Errorf("expected OpenAI chat response, got %T", chatResult.Value), types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
if chatID := helper.GetResponseID(c); chatID != "" {
|
||||
chatResp.Id = chatID
|
||||
}
|
||||
usage := chatResult.Usage
|
||||
|
||||
if usage == nil || usage.TotalTokens == 0 {
|
||||
text := service.ExtractOutputTextFromResponses(&responsesResp)
|
||||
@@ -53,17 +60,15 @@ func OaiResponsesToChatHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
|
||||
chatResp.Usage = *usage
|
||||
}
|
||||
|
||||
var responseBody []byte
|
||||
switch info.RelayFormat {
|
||||
case types.RelayFormatClaude:
|
||||
claudeResp := service.ResponseOpenAI2Claude(chatResp, info)
|
||||
responseBody, err = common.Marshal(claudeResp)
|
||||
case types.RelayFormatGemini:
|
||||
geminiResp := service.ResponseOpenAI2Gemini(chatResp, info)
|
||||
responseBody, err = common.Marshal(geminiResp)
|
||||
default:
|
||||
responseBody, err = common.Marshal(chatResp)
|
||||
responseValue := any(chatResp)
|
||||
if info.RelayFormat != types.RelayFormatOpenAI {
|
||||
targetResult, err := relayconvert.ConvertResponse(c, info, info.RelayFormat, chatResp)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
responseValue = targetResult.Value
|
||||
}
|
||||
responseBody, err := common.Marshal(responseValue)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeJsonMarshalFailed, http.StatusInternalServerError)
|
||||
}
|
||||
@@ -145,28 +150,33 @@ func OaiResponsesToChatBufferedStreamHandler(c *gin.Context, info *relaycommon.R
|
||||
}
|
||||
accumulator.SupplementResponseOutput(finalResponse)
|
||||
|
||||
chatId := helper.GetResponseID(c)
|
||||
chatResp, usage, err := service.ResponsesResponseToChatCompletionsResponse(finalResponse, chatId)
|
||||
chatResult, err := relayconvert.ConvertResponse(c, info, types.RelayFormatOpenAI, finalResponse)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
chatResp, ok := chatResult.Value.(*dto.OpenAITextResponse)
|
||||
if !ok {
|
||||
return nil, types.NewOpenAIError(fmt.Errorf("expected OpenAI chat response, got %T", chatResult.Value), types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
if chatID := helper.GetResponseID(c); chatID != "" {
|
||||
chatResp.Id = chatID
|
||||
}
|
||||
usage := chatResult.Usage
|
||||
if usage == nil || usage.TotalTokens == 0 {
|
||||
text := service.ExtractOutputTextFromResponses(finalResponse)
|
||||
usage = service.ResponseText2Usage(c, text, info.UpstreamModelName, info.GetEstimatePromptTokens())
|
||||
chatResp.Usage = *usage
|
||||
}
|
||||
|
||||
var responseBody []byte
|
||||
switch info.RelayFormat {
|
||||
case types.RelayFormatClaude:
|
||||
claudeResp := service.ResponseOpenAI2Claude(chatResp, info)
|
||||
responseBody, err = common.Marshal(claudeResp)
|
||||
case types.RelayFormatGemini:
|
||||
geminiResp := service.ResponseOpenAI2Gemini(chatResp, info)
|
||||
responseBody, err = common.Marshal(geminiResp)
|
||||
default:
|
||||
responseBody, err = common.Marshal(chatResp)
|
||||
responseValue := any(chatResp)
|
||||
if info.RelayFormat != types.RelayFormatOpenAI {
|
||||
targetResult, err := relayconvert.ConvertResponse(c, info, info.RelayFormat, chatResp)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
responseValue = targetResult.Value
|
||||
}
|
||||
responseBody, err := common.Marshal(responseValue)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeJsonMarshalFailed, http.StatusInternalServerError)
|
||||
}
|
||||
@@ -184,37 +194,77 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
|
||||
|
||||
responseId := helper.GetResponseID(c)
|
||||
createAt := time.Now().Unix()
|
||||
state := relayconvert.NewResponsesToChatStreamState(info.UpstreamModelName, false)
|
||||
state.ID = responseId
|
||||
state.Created = createAt
|
||||
state, err := relayconvert.NewResponseStreamState(types.RelayFormatOpenAIResponses, info.RelayFormat, relayconvert.ResponseStreamOptions{
|
||||
ID: responseId,
|
||||
Model: info.UpstreamModelName,
|
||||
Created: createAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
streamErr := (*types.NewAPIError)(nil)
|
||||
|
||||
if info.RelayFormat == types.RelayFormatClaude && info.ClaudeConvertInfo == nil {
|
||||
info.ClaudeConvertInfo = &relaycommon.ClaudeConvertInfo{LastMessagesType: relaycommon.LastMessageTypeNone}
|
||||
}
|
||||
|
||||
sendChatChunk := func(chunk dto.ChatCompletionsStreamResponse) bool {
|
||||
if len(chunk.Choices) == 0 && chunk.Usage == nil {
|
||||
sendGeminiResponse := func(geminiResponse *dto.GeminiChatResponse) bool {
|
||||
if geminiResponse == nil {
|
||||
return true
|
||||
}
|
||||
if info.RelayFormat == types.RelayFormatOpenAI {
|
||||
if err := helper.ObjectData(c, &chunk); err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
chunkData, err := common.Marshal(&chunk)
|
||||
geminiResponseStr, err := common.Marshal(geminiResponse)
|
||||
if err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeJsonMarshalFailed, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
if err := HandleStreamFormat(c, info, string(chunkData), false, false); err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
c.Render(-1, common.CustomEvent{Data: "data: " + string(geminiResponseStr)})
|
||||
_ = helper.FlushWriter(c)
|
||||
return true
|
||||
}
|
||||
|
||||
sendStreamResult := func(result relayconvert.ResponseResult) bool {
|
||||
switch value := result.Value.(type) {
|
||||
case dto.ChatCompletionsStreamResponse:
|
||||
if len(value.Choices) == 0 && value.Usage == nil {
|
||||
return true
|
||||
}
|
||||
if err := helper.ObjectData(c, &value); err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case *dto.ChatCompletionsStreamResponse:
|
||||
if value == nil || (len(value.Choices) == 0 && value.Usage == nil) {
|
||||
return true
|
||||
}
|
||||
if err := helper.ObjectData(c, value); err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case dto.ClaudeResponse:
|
||||
if err := helper.ClaudeData(c, value); err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case *dto.ClaudeResponse:
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
if err := helper.ClaudeData(c, *value); err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case dto.GeminiChatResponse:
|
||||
return sendGeminiResponse(&value)
|
||||
case *dto.GeminiChatResponse:
|
||||
return sendGeminiResponse(value)
|
||||
default:
|
||||
streamErr = types.NewOpenAIError(fmt.Errorf("unsupported converted stream response type %T", result.Value), types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
helper.StreamScannerHandler(c, resp, info, func(data string, sr *helper.StreamResult) {
|
||||
@@ -243,14 +293,14 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
|
||||
return
|
||||
}
|
||||
|
||||
chunks, err := relayconvert.ResponsesStreamEventToChatChunks(&streamResp, state)
|
||||
results, err := relayconvert.ConvertStreamResponseChunk(c, info, state, &streamResp)
|
||||
if err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
}
|
||||
for _, chunk := range chunks {
|
||||
if !sendChatChunk(chunk) {
|
||||
for _, result := range results {
|
||||
if !sendStreamResult(result) {
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
}
|
||||
@@ -261,22 +311,26 @@ func OaiResponsesToChatStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
|
||||
return nil, streamErr
|
||||
}
|
||||
|
||||
usage := state.Usage
|
||||
if usage.TotalTokens == 0 {
|
||||
usage := state.Usage()
|
||||
if usage == nil || usage.TotalTokens == 0 {
|
||||
usage = service.ResponseText2Usage(c, state.UsageText(), info.UpstreamModelName, info.GetEstimatePromptTokens())
|
||||
state.Usage = usage
|
||||
state.SetUsage(usage)
|
||||
}
|
||||
|
||||
if info.RelayFormat == types.RelayFormatClaude && info.ClaudeConvertInfo != nil {
|
||||
info.ClaudeConvertInfo.Usage = usage
|
||||
}
|
||||
for _, chunk := range relayconvert.FinalizeResponsesToChatStream(state) {
|
||||
if !sendChatChunk(chunk) {
|
||||
finalResults, err := relayconvert.FinalizeStreamResponse(c, info, state)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
for _, result := range finalResults {
|
||||
if !sendStreamResult(result) {
|
||||
return nil, streamErr
|
||||
}
|
||||
}
|
||||
if info.RelayFormat == types.RelayFormatOpenAI && info.ShouldIncludeUsage && usage != nil {
|
||||
if err := helper.ObjectData(c, helper.GenerateFinalUsageResponse(responseId, state.Created, state.Model, *usage)); err != nil {
|
||||
if err := helper.ObjectData(c, helper.GenerateFinalUsageResponse(responseId, createAt, info.UpstreamModelName, *usage)); err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
relayconstant "github.com/QuantumNous/new-api/relay/constant"
|
||||
"github.com/QuantumNous/new-api/relay/helper"
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
"github.com/QuantumNous/new-api/service/relayconvert"
|
||||
"github.com/QuantumNous/new-api/types"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -41,7 +43,14 @@ func handleClaudeFormat(c *gin.Context, data string, info *relaycommon.RelayInfo
|
||||
if streamResponse.Usage != nil {
|
||||
info.ClaudeConvertInfo.Usage = streamResponse.Usage
|
||||
}
|
||||
claudeResponses := service.StreamResponseOpenAI2Claude(&streamResponse, info)
|
||||
result, err := relayconvert.ConvertStreamResponse(c, info, types.RelayFormatClaude, &streamResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
claudeResponses, ok := result.Value.([]*dto.ClaudeResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected Claude stream responses, got %T", result.Value)
|
||||
}
|
||||
for _, resp := range claudeResponses {
|
||||
helper.ClaudeData(c, *resp)
|
||||
}
|
||||
@@ -55,7 +64,14 @@ func handleGeminiFormat(c *gin.Context, data string, info *relaycommon.RelayInfo
|
||||
return err
|
||||
}
|
||||
|
||||
geminiResponse := service.StreamResponseOpenAI2Gemini(&streamResponse, info)
|
||||
result, err := relayconvert.ConvertStreamResponse(c, info, types.RelayFormatGemini, &streamResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
geminiResponse, ok := result.Value.(*dto.GeminiChatResponse)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected Gemini stream response, got %T", result.Value)
|
||||
}
|
||||
|
||||
// 如果返回 nil,表示没有实际内容,跳过发送
|
||||
if geminiResponse == nil {
|
||||
@@ -165,7 +181,16 @@ func HandleFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, lastStream
|
||||
|
||||
info.ClaudeConvertInfo.Usage = usage
|
||||
|
||||
claudeResponses := service.StreamResponseOpenAI2Claude(&streamResponse, info)
|
||||
result, err := relayconvert.ConvertStreamResponse(c, info, types.RelayFormatClaude, &streamResponse)
|
||||
if err != nil {
|
||||
common.SysLog("error converting Claude stream response: " + err.Error())
|
||||
return
|
||||
}
|
||||
claudeResponses, ok := result.Value.([]*dto.ClaudeResponse)
|
||||
if !ok {
|
||||
common.SysLog(fmt.Sprintf("expected Claude stream responses, got %T", result.Value))
|
||||
return
|
||||
}
|
||||
for _, resp := range claudeResponses {
|
||||
_ = helper.ClaudeData(c, *resp)
|
||||
}
|
||||
@@ -183,7 +208,16 @@ func HandleFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, lastStream
|
||||
// 而包含最后一段文本输出的响应(倒数第二个)的 finishReason 为 null
|
||||
// 暂不知是否有程序会不兼容。
|
||||
|
||||
geminiResponse := service.StreamResponseOpenAI2Gemini(&streamResponse, info)
|
||||
result, err := relayconvert.ConvertStreamResponse(c, info, types.RelayFormatGemini, &streamResponse)
|
||||
if err != nil {
|
||||
common.SysLog("error converting Gemini stream response: " + err.Error())
|
||||
return
|
||||
}
|
||||
geminiResponse, ok := result.Value.(*dto.GeminiChatResponse)
|
||||
if !ok {
|
||||
common.SysLog(fmt.Sprintf("expected Gemini stream response, got %T", result.Value))
|
||||
return
|
||||
}
|
||||
|
||||
// openai 流响应开头的空数据
|
||||
if geminiResponse == nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
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/service/relayconvert"
|
||||
"github.com/QuantumNous/new-api/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -271,15 +272,21 @@ func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Respo
|
||||
break
|
||||
}
|
||||
case types.RelayFormatClaude:
|
||||
claudeResp := service.ResponseOpenAI2Claude(&simpleResponse, info)
|
||||
claudeRespStr, err := common.Marshal(claudeResp)
|
||||
convertResult, err := relayconvert.ConvertResponse(c, info, types.RelayFormatClaude, &simpleResponse)
|
||||
if err != nil {
|
||||
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
|
||||
}
|
||||
claudeRespStr, err := common.Marshal(convertResult.Value)
|
||||
if err != nil {
|
||||
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
|
||||
}
|
||||
responseBody = claudeRespStr
|
||||
case types.RelayFormatGemini:
|
||||
geminiResp := service.ResponseOpenAI2Gemini(&simpleResponse, info)
|
||||
geminiRespStr, err := common.Marshal(geminiResp)
|
||||
convertResult, err := relayconvert.ConvertResponse(c, info, types.RelayFormatGemini, &simpleResponse)
|
||||
if err != nil {
|
||||
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
|
||||
}
|
||||
geminiRespStr, err := common.Marshal(convertResult.Value)
|
||||
if err != nil {
|
||||
return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
|
||||
}
|
||||
|
||||
@@ -35,11 +35,18 @@ func OaiChatToResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
|
||||
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
|
||||
}
|
||||
|
||||
responseID := helper.GetResponseID(c)
|
||||
responsesResp, usage, err := service.ChatCompletionsResponseToResponsesResponse(&chatResp, responseID)
|
||||
if responseID := helper.GetResponseID(c); responseID != "" {
|
||||
chatResp.Id = responseID
|
||||
}
|
||||
convertResult, err := relayconvert.ConvertResponse(c, info, types.RelayFormatOpenAIResponses, &chatResp)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
responsesResp, ok := convertResult.Value.(*dto.OpenAIResponsesResponse)
|
||||
if !ok {
|
||||
return nil, types.NewOpenAIError(fmt.Errorf("expected OpenAI responses response, got %T", convertResult.Value), types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
usage := convertResult.Usage
|
||||
if usage == nil || usage.TotalTokens == 0 {
|
||||
text := service.ExtractOutputTextFromResponses(responsesResp)
|
||||
usage = service.ResponseText2Usage(c, text, info.UpstreamModelName, info.GetEstimatePromptTokens())
|
||||
@@ -62,7 +69,13 @@ func OaiChatToResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
|
||||
defer service.CloseResponseBodyGracefully(resp)
|
||||
|
||||
responseID := helper.GetResponseID(c)
|
||||
state := relayconvert.NewChatToResponsesStreamState(responseID, info.UpstreamModelName)
|
||||
state, err := relayconvert.NewResponseStreamState(types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses, relayconvert.ResponseStreamOptions{
|
||||
ID: responseID,
|
||||
Model: info.UpstreamModelName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
streamErr := (*types.NewAPIError)(nil)
|
||||
|
||||
sendEvent := func(event relayconvert.ChatToResponsesStreamEvent) bool {
|
||||
@@ -97,13 +110,19 @@ func OaiChatToResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
|
||||
return
|
||||
}
|
||||
|
||||
events, err := relayconvert.ChatCompletionsStreamChunkToResponsesEvents(&chunk, state)
|
||||
results, err := relayconvert.ConvertStreamResponseChunk(c, info, state, &chunk)
|
||||
if err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
}
|
||||
for _, event := range events {
|
||||
for _, result := range results {
|
||||
event, ok := result.Value.(relayconvert.ChatToResponsesStreamEvent)
|
||||
if !ok {
|
||||
streamErr = types.NewOpenAIError(fmt.Errorf("expected OAI responses stream event, got %T", result.Value), types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
}
|
||||
if !sendEvent(event) {
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
@@ -115,13 +134,21 @@ func OaiChatToResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo
|
||||
return nil, streamErr
|
||||
}
|
||||
|
||||
usage := state.Usage
|
||||
usage := state.Usage()
|
||||
if usage == nil || usage.TotalTokens == 0 {
|
||||
usage = service.ResponseText2Usage(c, state.UsageText(), info.UpstreamModelName, info.GetEstimatePromptTokens())
|
||||
state.Usage = relayconvert.UsageFromChatUsage(usage)
|
||||
state.SetUsage(usage)
|
||||
}
|
||||
|
||||
for _, event := range relayconvert.FinalizeChatCompletionsStreamToResponses(state) {
|
||||
finalResults, err := relayconvert.FinalizeStreamResponse(c, info, state)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
for _, result := range finalResults {
|
||||
event, ok := result.Value.(relayconvert.ChatToResponsesStreamEvent)
|
||||
if !ok {
|
||||
return nil, types.NewOpenAIError(fmt.Errorf("expected OAI responses stream event, got %T", result.Value), types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
if !sendEvent(event) {
|
||||
return nil, streamErr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user