feat: 算力引擎 Go 后端核心(new-api)
- 统一 OpenAI 兼容 /v1 中继 + 渠道/额度/令牌/流水 - 各领域包:relay 模型网关、model 数据层、controller 管理 API
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"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/relaykit/dto"
|
||||
"github.com/QuantumNous/new-api/relaykit/relayconvert"
|
||||
"github.com/QuantumNous/new-api/relaykit/types"
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func OaiChatToResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
|
||||
if resp == nil || resp.Body == nil {
|
||||
return nil, types.NewOpenAIError(fmt.Errorf("invalid response"), types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
defer service.CloseResponseBodyGracefully(resp)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeReadResponseBodyFailed, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
var chatResp dto.OpenAITextResponse
|
||||
if err := common.Unmarshal(body, &chatResp); err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
if oaiError := chatResp.GetOpenAIError(); oaiError != nil && oaiError.Type != "" {
|
||||
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
|
||||
}
|
||||
|
||||
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())
|
||||
responsesResp.Usage = relayconvert.UsageFromChatUsage(usage)
|
||||
}
|
||||
|
||||
responseBody, err := common.Marshal(responsesResp)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeJsonMarshalFailed, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
service.IOCopyBytesGracefully(c, resp, responseBody)
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
func OaiChatToResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
|
||||
if resp == nil || resp.Body == nil {
|
||||
return nil, types.NewOpenAIError(fmt.Errorf("invalid response"), types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
}
|
||||
defer service.CloseResponseBodyGracefully(resp)
|
||||
|
||||
responseID := helper.GetResponseID(c)
|
||||
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 {
|
||||
data, err := common.Marshal(event.Payload)
|
||||
if err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeJsonMarshalFailed, http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
helper.ResponseChunkData(c, dto.ResponsesStreamResponse{Type: event.Type}, string(data))
|
||||
return true
|
||||
}
|
||||
|
||||
helper.StreamScannerHandler(c, resp, info, func(data string, sr *helper.StreamResult) {
|
||||
if streamErr != nil {
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
}
|
||||
|
||||
var errorResp dto.OpenAITextResponse
|
||||
if err := common.UnmarshalJsonStr(data, &errorResp); err == nil {
|
||||
if oaiError := errorResp.GetOpenAIError(); oaiError != nil && oaiError.Type != "" {
|
||||
streamErr = types.WithOpenAIError(*oaiError, resp.StatusCode)
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var chunk dto.ChatCompletionsStreamResponse
|
||||
if err := common.UnmarshalJsonStr(data, &chunk); err != nil {
|
||||
logger.LogError(c, "failed to unmarshal chat stream response: "+err.Error())
|
||||
sr.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
results, err := relayconvert.ConvertStreamResponseChunk(c, info, state, &chunk)
|
||||
if err != nil {
|
||||
streamErr = types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError)
|
||||
sr.Stop(streamErr)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if streamErr != nil {
|
||||
return nil, streamErr
|
||||
}
|
||||
|
||||
usage := state.Usage()
|
||||
if usage == nil || usage.TotalTokens == 0 {
|
||||
usage = service.ResponseText2Usage(c, state.UsageText(), info.UpstreamModelName, info.GetEstimatePromptTokens())
|
||||
state.SetUsage(usage)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
Reference in New Issue
Block a user