package controller import ( "fmt" "net/http" "one-api/common" "strconv" "strings" "github.com/gin-gonic/gin" ) type Message struct { Role string `json:"role"` Content any `json:"content"` Name *string `json:"name,omitempty"` } type ImageURL struct { Url string `json:"url,omitempty"` Detail string `json:"detail,omitempty"` } type TextContent struct { Type string `json:"type,omitempty"` Text string `json:"text,omitempty"` } type ImageContent struct { Type string `json:"type,omitempty"` ImageURL *ImageURL `json:"image_url,omitempty"` } func (m Message) StringContent() string { content, ok := m.Content.(string) if ok { return content } contentList, ok := m.Content.([]any) if ok { var contentStr string for _, contentItem := range contentList { contentMap, ok := contentItem.(map[string]any) if !ok { continue } if contentMap["type"] == "text" { if subStr, ok := contentMap["text"].(string); ok { contentStr += subStr } } } return contentStr } return "" } const ( RelayModeUnknown = iota RelayModeChatCompletions RelayModeCompletions RelayModeEmbeddings RelayModeModerations RelayModeImagesGenerations RelayModeEdits RelayModeAudioSpeech RelayModeAudioTranscription RelayModeAudioTranslation ) // https://platform.openai.com/docs/api-reference/chat type ResponseFormat struct { Type string `json:"type,omitempty"` } type GeneralOpenAIRequest struct { Model string `json:"model,omitempty"` Messages []Message `json:"messages,omitempty"` Prompt any `json:"prompt,omitempty"` Stream bool `json:"stream,omitempty"` MaxTokens int `json:"max_tokens,omitempty"` Temperature float64 `json:"temperature,omitempty"` TopP float64 `json:"top_p,omitempty"` N int `json:"n,omitempty"` Input any `json:"input,omitempty"` Instruction string `json:"instruction,omitempty"` Size string `json:"size,omitempty"` Functions any `json:"functions,omitempty"` FrequencyPenalty float64 `json:"frequency_penalty,omitempty"` PresencePenalty float64 `json:"presence_penalty,omitempty"` ResponseFormat *ResponseFormat `json:"response_format,omitempty"` Seed float64 `json:"seed,omitempty"` Tools any `json:"tools,omitempty"` ToolChoice any `json:"tool_choice,omitempty"` User string `json:"user,omitempty"` } func (r GeneralOpenAIRequest) ParseInput() []string { if r.Input == nil { return nil } var input []string switch r.Input.(type) { case string: input = []string{r.Input.(string)} case []any: input = make([]string, 0, len(r.Input.([]any))) for _, item := range r.Input.([]any) { if str, ok := item.(string); ok { input = append(input, str) } } } return input } type ChatRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` MaxTokens int `json:"max_tokens"` } type TextRequest struct { Model string `json:"model"` Messages []Message `json:"messages"` Prompt string `json:"prompt"` MaxTokens int `json:"max_tokens"` //Stream bool `json:"stream"` } // ImageRequest docs: https://platform.openai.com/docs/api-reference/images/create type ImageRequest struct { Model string `json:"model"` Prompt string `json:"prompt" binding:"required"` N int `json:"n,omitempty"` Size string `json:"size,omitempty"` Quality string `json:"quality,omitempty"` ResponseFormat string `json:"response_format,omitempty"` Style string `json:"style,omitempty"` User string `json:"user,omitempty"` } type WhisperJSONResponse struct { Text string `json:"text,omitempty"` } type WhisperVerboseJSONResponse struct { Task string `json:"task,omitempty"` Language string `json:"language,omitempty"` Duration float64 `json:"duration,omitempty"` Text string `json:"text,omitempty"` Segments []Segment `json:"segments,omitempty"` } type Segment struct { Id int `json:"id"` Seek int `json:"seek"` Start float64 `json:"start"` End float64 `json:"end"` Text string `json:"text"` Tokens []int `json:"tokens"` Temperature float64 `json:"temperature"` AvgLogprob float64 `json:"avg_logprob"` CompressionRatio float64 `json:"compression_ratio"` NoSpeechProb float64 `json:"no_speech_prob"` } type TextToSpeechRequest struct { Model string `json:"model" binding:"required"` Input string `json:"input" binding:"required"` Voice string `json:"voice" binding:"required"` Speed float64 `json:"speed"` ResponseFormat string `json:"response_format"` } type Usage struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` } type OpenAIError struct { Message string `json:"message"` Type string `json:"type"` Param string `json:"param"` Code any `json:"code"` } type OpenAIErrorWithStatusCode struct { OpenAIError StatusCode int `json:"status_code"` } type TextResponse struct { Choices []OpenAITextResponseChoice `json:"choices"` Usage `json:"usage"` Error OpenAIError `json:"error"` } type OpenAITextResponseChoice struct { Index int `json:"index"` Message `json:"message"` FinishReason string `json:"finish_reason"` } type OpenAITextResponse struct { Id string `json:"id"` Object string `json:"object"` Created int64 `json:"created"` Choices []OpenAITextResponseChoice `json:"choices"` Usage `json:"usage"` } type OpenAIEmbeddingResponseItem struct { Object string `json:"object"` Index int `json:"index"` Embedding []float64 `json:"embedding"` } type OpenAIEmbeddingResponse struct { Object string `json:"object"` Data []OpenAIEmbeddingResponseItem `json:"data"` Model string `json:"model"` Usage `json:"usage"` } type ImageResponse struct { Created int `json:"created"` Data []struct { Url string `json:"url"` } } type ChatCompletionsStreamResponseChoice struct { Delta struct { Content string `json:"content"` } `json:"delta"` FinishReason *string `json:"finish_reason,omitempty"` } type ChatCompletionsStreamResponse struct { Id string `json:"id"` Object string `json:"object"` Created int64 `json:"created"` Model string `json:"model"` Choices []ChatCompletionsStreamResponseChoice `json:"choices"` } type CompletionsStreamResponse struct { Choices []struct { Text string `json:"text"` FinishReason string `json:"finish_reason"` } `json:"choices"` } func Relay(c *gin.Context) { relayMode := RelayModeUnknown if strings.HasPrefix(c.Request.URL.Path, "/v1/chat/completions") { relayMode = RelayModeChatCompletions } else if strings.HasPrefix(c.Request.URL.Path, "/v1/completions") { relayMode = RelayModeCompletions } else if strings.HasPrefix(c.Request.URL.Path, "/v1/embeddings") { relayMode = RelayModeEmbeddings } else if strings.HasSuffix(c.Request.URL.Path, "embeddings") { relayMode = RelayModeEmbeddings } else if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") { relayMode = RelayModeModerations } else if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") { relayMode = RelayModeImagesGenerations } else if strings.HasPrefix(c.Request.URL.Path, "/v1/edits") { relayMode = RelayModeEdits } else if strings.HasPrefix(c.Request.URL.Path, "/v1/audio/speech") { relayMode = RelayModeAudioSpeech } else if strings.HasPrefix(c.Request.URL.Path, "/v1/audio/transcriptions") { relayMode = RelayModeAudioTranscription } else if strings.HasPrefix(c.Request.URL.Path, "/v1/audio/translations") { relayMode = RelayModeAudioTranslation } var err *OpenAIErrorWithStatusCode switch relayMode { case RelayModeImagesGenerations: err = relayImageHelper(c, relayMode) case RelayModeAudioSpeech: fallthrough case RelayModeAudioTranslation: fallthrough case RelayModeAudioTranscription: err = relayAudioHelper(c, relayMode) default: err = relayTextHelper(c, relayMode) } if err != nil { requestId := c.GetString(common.RequestIdKey) retryTimesStr := c.Query("retry") retryTimes, _ := strconv.Atoi(retryTimesStr) if retryTimesStr == "" { retryTimes = common.RetryTimes } if retryTimes > 0 { c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s?retry=%d", c.Request.URL.Path, retryTimes-1)) } else { if err.StatusCode == http.StatusTooManyRequests { err.OpenAIError.Message = "当前分组上游负载已饱和,请稍后再试" } err.OpenAIError.Message = common.MessageWithRequestId(err.OpenAIError.Message, requestId) c.JSON(err.StatusCode, gin.H{ "error": err.OpenAIError, }) } channelId := c.GetInt("channel_id") common.LogError(c.Request.Context(), fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Message)) // https://platform.openai.com/docs/guides/error-codes/api-errors if shouldDisableChannel(&err.OpenAIError, err.StatusCode) { channelId := c.GetInt("channel_id") channelName := c.GetString("channel_name") disableChannel(channelId, channelName, err.Message) } } } func RelayNotImplemented(c *gin.Context) { err := OpenAIError{ Message: "API not implemented", Type: "one_api_error", Param: "", Code: "api_not_implemented", } c.JSON(http.StatusNotImplemented, gin.H{ "error": err, }) } func RelayNotFound(c *gin.Context) { err := OpenAIError{ Message: fmt.Sprintf("Invalid URL (%s %s)", c.Request.Method, c.Request.URL.Path), Type: "invalid_request_error", Param: "", Code: "", } c.JSON(http.StatusNotFound, gin.H{ "error": err, }) }