From b3d8e3e9aea60b4c7183e584b7f1976095ada090 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Fri, 16 Aug 2024 14:59:32 +0800 Subject: [PATCH 01/10] fix: lobechat #430 --- web/src/components/TokensTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js index 6dbb31b..9cece1e 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/TokensTable.js @@ -425,7 +425,7 @@ const TokensTable = () => { url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`; break; case 'lobe': - url = `https://chat-preview.lobehub.com/?settings={"keyVaults":{"openai":{"apiKey":"sk-${key}","baseURL":"${encodedServerAddress}"}}}`; + url = `https://chat-preview.lobehub.com/?settings={"keyVaults":{"openai":{"apiKey":"sk-${key}","baseURL":"${encodedServerAddress}/v1"}}}`; break; case 'next-mj': url = From a5ec11e463edff47569df465a235ee49f1efb16e Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Fri, 16 Aug 2024 16:16:38 +0800 Subject: [PATCH 02/10] fix: add email missing Message-ID --- common/email.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/email.go b/common/email.go index 62c9048..b23bb23 100644 --- a/common/email.go +++ b/common/email.go @@ -9,6 +9,11 @@ import ( "time" ) +func generateMessageID() string { + domain := strings.Split(SMTPFrom, "@")[1] + return fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), GetRandomString(12), domain) +} + func SendEmail(subject string, receiver string, content string) error { if SMTPFrom == "" { // for compatibility SMTPFrom = SMTPAccount @@ -18,8 +23,9 @@ func SendEmail(subject string, receiver string, content string) error { "From: %s<%s>\r\n"+ "Subject: %s\r\n"+ "Date: %s\r\n"+ + "Message-ID: %s\r\n"+ // 添加 Message-ID 头 "Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n", - receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), content)) + receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), generateMessageID(), content)) auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer) addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort) to := strings.Split(receiver, ";") From d0f76a5c61ded22f4ebb31b8d053d4ad10a24318 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Fri, 16 Aug 2024 17:25:03 +0800 Subject: [PATCH 03/10] feat: support gpt-4o-gizmo-* (close #436) --- common/model-ratio.go | 24 ++++++++++++++++-------- model/cache.go | 3 +++ relay/relay-text.go | 4 ++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index 284fb80..946ee46 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -23,10 +23,11 @@ const ( var defaultModelRatio = map[string]float64{ //"midjourney": 50, - "gpt-4-gizmo-*": 15, - "gpt-4-all": 15, - "gpt-4o-all": 15, - "gpt-4": 15, + "gpt-4-gizmo-*": 15, + "gpt-4o-gizmo-*": 2.5, + "gpt-4-all": 15, + "gpt-4o-all": 15, + "gpt-4": 15, //"gpt-4-0314": 15, //deprecated "gpt-4-0613": 15, "gpt-4-32k": 30, @@ -187,8 +188,8 @@ var defaultModelPrice = map[string]float64{ } var ( - modelPriceMap = make(map[string]float64) - modelPriceMapMutex = sync.RWMutex{} + modelPriceMap map[string]float64 = nil + modelPriceMapMutex = sync.RWMutex{} ) var ( modelRatioMap map[string]float64 = nil @@ -197,8 +198,9 @@ var ( var CompletionRatio map[string]float64 = nil var defaultCompletionRatio = map[string]float64{ - "gpt-4-gizmo-*": 2, - "gpt-4-all": 2, + "gpt-4-gizmo-*": 2, + "gpt-4o-gizmo-*": 3, + "gpt-4-all": 2, } func GetModelPriceMap() map[string]float64 { @@ -232,6 +234,9 @@ func GetModelPrice(name string, printErr bool) (float64, bool) { if strings.HasPrefix(name, "gpt-4-gizmo") { name = "gpt-4-gizmo-*" } + if strings.HasPrefix(name, "gpt-4o-gizmo") { + name = "gpt-4o-gizmo-*" + } price, ok := modelPriceMap[name] if !ok { if printErr { @@ -312,6 +317,9 @@ func GetCompletionRatio(name string) float64 { if strings.HasPrefix(name, "gpt-4-gizmo") { name = "gpt-4-gizmo-*" } + if strings.HasPrefix(name, "gpt-4o-gizmo") { + name = "gpt-4o-gizmo-*" + } if strings.HasPrefix(name, "gpt-3.5") { if name == "gpt-3.5-turbo" || strings.HasSuffix(name, "0125") { // https://openai.com/blog/new-embedding-models-and-api-updates diff --git a/model/cache.go b/model/cache.go index 2977bb6..7f5411e 100644 --- a/model/cache.go +++ b/model/cache.go @@ -270,6 +270,9 @@ func CacheGetRandomSatisfiedChannel(group string, model string, retry int) (*Cha if strings.HasPrefix(model, "gpt-4-gizmo") { model = "gpt-4-gizmo-*" } + if strings.HasPrefix(model, "gpt-4o-gizmo") { + model = "gpt-4o-gizmo-*" + } // if memory cache is disabled, get channel directly from database if !common.MemoryCacheEnabled { diff --git a/relay/relay-text.go b/relay/relay-text.go index 93d202d..7c6840f 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -354,6 +354,10 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelN logModel = "gpt-4-gizmo-*" logContent += fmt.Sprintf(",模型 %s", modelName) } + if strings.HasPrefix(logModel, "gpt-4o-gizmo") { + logModel = "gpt-4o-gizmo-*" + logContent += fmt.Sprintf(",模型 %s", modelName) + } if extraContent != "" { logContent += ", " + extraContent } From 7c4d9d225ee505457400cc4edc401f9f3555df9b Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Fri, 16 Aug 2024 18:27:26 +0800 Subject: [PATCH 04/10] feat: support SiliconFlow (close #437, close #403) --- common/constants.go | 2 + dto/rerank.go | 13 +-- relay/channel/siliconflow/adaptor.go | 80 +++++++++++++++++++ relay/channel/siliconflow/constant.go | 51 ++++++++++++ relay/channel/siliconflow/dto.go | 17 ++++ .../channel/siliconflow/relay-siliconflow.go | 44 ++++++++++ relay/constant/api_type.go | 3 + relay/relay-text.go | 2 +- relay/relay_adaptor.go | 3 + web/src/constants/channel.constants.js | 35 ++++---- 10 files changed, 227 insertions(+), 23 deletions(-) create mode 100644 relay/channel/siliconflow/adaptor.go create mode 100644 relay/channel/siliconflow/constant.go create mode 100644 relay/channel/siliconflow/dto.go create mode 100644 relay/channel/siliconflow/relay-siliconflow.go diff --git a/common/constants.go b/common/constants.go index 97e8583..d63955e 100644 --- a/common/constants.go +++ b/common/constants.go @@ -213,6 +213,7 @@ const ( ChannelTypeDify = 37 ChannelTypeJina = 38 ChannelCloudflare = 39 + ChannelTypeSiliconFlow = 40 ChannelTypeDummy // this one is only for count, do not add any channel after this @@ -259,4 +260,5 @@ var ChannelBaseURLs = []string{ "", //37 "https://api.jina.ai", //38 "https://api.cloudflare.com", //39 + "https://api.siliconflow.cn", //40 } diff --git a/dto/rerank.go b/dto/rerank.go index 0ee44b1..dfa7963 100644 --- a/dto/rerank.go +++ b/dto/rerank.go @@ -1,14 +1,17 @@ package dto type RerankRequest struct { - Documents []any `json:"documents"` - Query string `json:"query"` - Model string `json:"model"` - TopN int `json:"top_n"` + Documents []any `json:"documents"` + Query string `json:"query"` + Model string `json:"model"` + TopN int `json:"top_n"` + ReturnDocuments bool `json:"return_documents,omitempty"` + MaxChunkPerDoc int `json:"max_chunk_per_doc,omitempty"` + OverLapTokens int `json:"overlap_tokens,omitempty"` } type RerankResponseDocument struct { - Document any `json:"document"` + Document any `json:"document,omitempty"` Index int `json:"index"` RelevanceScore float64 `json:"relevance_score"` } diff --git a/relay/channel/siliconflow/adaptor.go b/relay/channel/siliconflow/adaptor.go new file mode 100644 index 0000000..1614905 --- /dev/null +++ b/relay/channel/siliconflow/adaptor.go @@ -0,0 +1,80 @@ +package siliconflow + +import ( + "errors" + "fmt" + "github.com/gin-gonic/gin" + "io" + "net/http" + "one-api/dto" + "one-api/relay/channel" + "one-api/relay/channel/openai" + relaycommon "one-api/relay/common" + "one-api/relay/constant" +) + +type Adaptor struct { +} + +func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) { + //TODO implement me + return nil, errors.New("not implemented") +} + +func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) { + //TODO implement me + return nil, errors.New("not implemented") +} + +func (a *Adaptor) Init(info *relaycommon.RelayInfo) { +} + +func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { + if info.RelayMode == constant.RelayModeRerank { + return fmt.Sprintf("%s/v1/rerank", info.BaseUrl), nil + } else if info.RelayMode == constant.RelayModeEmbeddings { + return fmt.Sprintf("%s/v1/embeddings ", info.BaseUrl), nil + } else if info.RelayMode == constant.RelayModeChatCompletions { + return fmt.Sprintf("%s/v1/chat/completions", info.BaseUrl), nil + } + return "", errors.New("invalid relay mode") +} + +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error { + channel.SetupApiRequestHeader(info, c, req) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", info.ApiKey)) + return nil +} + +func (a *Adaptor) ConvertRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) { + return request, nil +} + +func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) { + return channel.DoApiRequest(a, c, info, requestBody) +} + +func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) { + return request, nil +} + +func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage *dto.Usage, err *dto.OpenAIErrorWithStatusCode) { + if info.RelayMode == constant.RelayModeRerank { + err, usage = siliconflowRerankHandler(c, resp) + } else if info.RelayMode == constant.RelayModeChatCompletions { + if info.IsStream { + err, usage = openai.OaiStreamHandler(c, resp, info) + } else { + err, usage = openai.OpenaiHandler(c, resp, info.PromptTokens, info.UpstreamModelName) + } + } + return +} + +func (a *Adaptor) GetModelList() []string { + return ModelList +} + +func (a *Adaptor) GetChannelName() string { + return ChannelName +} diff --git a/relay/channel/siliconflow/constant.go b/relay/channel/siliconflow/constant.go new file mode 100644 index 0000000..819f4aa --- /dev/null +++ b/relay/channel/siliconflow/constant.go @@ -0,0 +1,51 @@ +package siliconflow + +var ModelList = []string{ + "THUDM/glm-4-9b-chat", + //"stabilityai/stable-diffusion-xl-base-1.0", + //"TencentARC/PhotoMaker", + "InstantX/InstantID", + //"stabilityai/stable-diffusion-2-1", + //"stabilityai/sd-turbo", + //"stabilityai/sdxl-turbo", + "ByteDance/SDXL-Lightning", + "deepseek-ai/deepseek-llm-67b-chat", + "Qwen/Qwen1.5-14B-Chat", + "Qwen/Qwen1.5-7B-Chat", + "Qwen/Qwen1.5-110B-Chat", + "Qwen/Qwen1.5-32B-Chat", + "01-ai/Yi-1.5-6B-Chat", + "01-ai/Yi-1.5-9B-Chat-16K", + "01-ai/Yi-1.5-34B-Chat-16K", + "THUDM/chatglm3-6b", + "deepseek-ai/DeepSeek-V2-Chat", + "Qwen/Qwen2-72B-Instruct", + "Qwen/Qwen2-7B-Instruct", + "Qwen/Qwen2-57B-A14B-Instruct", + //"stabilityai/stable-diffusion-3-medium", + "deepseek-ai/DeepSeek-Coder-V2-Instruct", + "Qwen/Qwen2-1.5B-Instruct", + "internlm/internlm2_5-7b-chat", + "BAAI/bge-large-en-v1.5", + "BAAI/bge-large-zh-v1.5", + "Pro/Qwen/Qwen2-7B-Instruct", + "Pro/Qwen/Qwen2-1.5B-Instruct", + "Pro/Qwen/Qwen1.5-7B-Chat", + "Pro/THUDM/glm-4-9b-chat", + "Pro/THUDM/chatglm3-6b", + "Pro/01-ai/Yi-1.5-9B-Chat-16K", + "Pro/01-ai/Yi-1.5-6B-Chat", + "Pro/google/gemma-2-9b-it", + "Pro/internlm/internlm2_5-7b-chat", + "Pro/meta-llama/Meta-Llama-3-8B-Instruct", + "Pro/mistralai/Mistral-7B-Instruct-v0.2", + "black-forest-labs/FLUX.1-schnell", + "iic/SenseVoiceSmall", + "netease-youdao/bce-embedding-base_v1", + "BAAI/bge-m3", + "internlm/internlm2_5-20b-chat", + "Qwen/Qwen2-Math-72B-Instruct", + "netease-youdao/bce-reranker-base_v1", + "BAAI/bge-reranker-v2-m3", +} +var ChannelName = "siliconflow" diff --git a/relay/channel/siliconflow/dto.go b/relay/channel/siliconflow/dto.go new file mode 100644 index 0000000..58cf81c --- /dev/null +++ b/relay/channel/siliconflow/dto.go @@ -0,0 +1,17 @@ +package siliconflow + +import "one-api/dto" + +type SFTokens struct { + InputTokens int `json:"input_tokens"` + OutputTokens int `json:"output_tokens"` +} + +type SFMeta struct { + Tokens SFTokens `json:"tokens"` +} + +type SFRerankResponse struct { + Results []dto.RerankResponseDocument `json:"results"` + Meta SFMeta `json:"meta"` +} diff --git a/relay/channel/siliconflow/relay-siliconflow.go b/relay/channel/siliconflow/relay-siliconflow.go new file mode 100644 index 0000000..a01e745 --- /dev/null +++ b/relay/channel/siliconflow/relay-siliconflow.go @@ -0,0 +1,44 @@ +package siliconflow + +import ( + "encoding/json" + "github.com/gin-gonic/gin" + "io" + "net/http" + "one-api/dto" + "one-api/service" +) + +func siliconflowRerankHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) { + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return service.OpenAIErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil + } + err = resp.Body.Close() + if err != nil { + return service.OpenAIErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil + } + var siliconflowResp SFRerankResponse + err = json.Unmarshal(responseBody, &siliconflowResp) + if err != nil { + return service.OpenAIErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil + } + usage := &dto.Usage{ + PromptTokens: siliconflowResp.Meta.Tokens.InputTokens, + CompletionTokens: siliconflowResp.Meta.Tokens.OutputTokens, + TotalTokens: siliconflowResp.Meta.Tokens.InputTokens + siliconflowResp.Meta.Tokens.OutputTokens, + } + rerankResp := &dto.RerankResponse{ + Results: siliconflowResp.Results, + Usage: *usage, + } + + jsonResponse, err := json.Marshal(rerankResp) + if err != nil { + return service.OpenAIErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil + } + c.Writer.Header().Set("Content-Type", "application/json") + c.Writer.WriteHeader(resp.StatusCode) + _, err = c.Writer.Write(jsonResponse) + return nil, usage +} diff --git a/relay/constant/api_type.go b/relay/constant/api_type.go index 6bd93c4..36a6b6b 100644 --- a/relay/constant/api_type.go +++ b/relay/constant/api_type.go @@ -23,6 +23,7 @@ const ( APITypeDify APITypeJina APITypeCloudflare + APITypeSiliconFlow APITypeDummy // this one is only for count, do not add any channel after this ) @@ -66,6 +67,8 @@ func ChannelType2APIType(channelType int) (int, bool) { apiType = APITypeJina case common.ChannelCloudflare: apiType = APITypeCloudflare + case common.ChannelTypeSiliconFlow: + apiType = APITypeSiliconFlow } if apiType == -1 { return APITypeOpenAI, false diff --git a/relay/relay-text.go b/relay/relay-text.go index 7c6840f..3c5393a 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -317,7 +317,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelN totalTokens := promptTokens + completionTokens var logContent string if !usePrice { - logContent = fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f", modelRatio, groupRatio, completionRatio) + logContent = fmt.Sprintf("模型倍率 %.2f,补全倍率 %.2f,分组倍率 %.2f", modelRatio, completionRatio, groupRatio) } else { logContent = fmt.Sprintf("模型价格 %.2f,分组倍率 %.2f", modelPrice, groupRatio) } diff --git a/relay/relay_adaptor.go b/relay/relay_adaptor.go index 4c0aef1..96fb737 100644 --- a/relay/relay_adaptor.go +++ b/relay/relay_adaptor.go @@ -16,6 +16,7 @@ import ( "one-api/relay/channel/openai" "one-api/relay/channel/palm" "one-api/relay/channel/perplexity" + "one-api/relay/channel/siliconflow" "one-api/relay/channel/task/suno" "one-api/relay/channel/tencent" "one-api/relay/channel/xunfei" @@ -62,6 +63,8 @@ func GetAdaptor(apiType int) channel.Adaptor { return &jina.Adaptor{} case constant.APITypeCloudflare: return &cloudflare.Adaptor{} + case constant.APITypeSiliconFlow: + return &siliconflow.Adaptor{} } return nil } diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js index 88614b0..ee53f7b 100644 --- a/web/src/constants/channel.constants.js +++ b/web/src/constants/channel.constants.js @@ -5,21 +5,21 @@ export const CHANNEL_OPTIONS = [ text: 'Midjourney Proxy', value: 2, color: 'light-blue', - label: 'Midjourney Proxy', + label: 'Midjourney Proxy' }, { key: 5, text: 'Midjourney Proxy Plus', value: 5, color: 'blue', - label: 'Midjourney Proxy Plus', + label: 'Midjourney Proxy Plus' }, { key: 36, text: 'Suno API', value: 36, color: 'purple', - label: 'Suno API', + label: 'Suno API' }, { key: 4, text: 'Ollama', value: 4, color: 'grey', label: 'Ollama' }, { @@ -27,77 +27,77 @@ export const CHANNEL_OPTIONS = [ text: 'Anthropic Claude', value: 14, color: 'indigo', - label: 'Anthropic Claude', + label: 'Anthropic Claude' }, { key: 33, text: 'AWS Claude', value: 33, color: 'indigo', - label: 'AWS Claude', + label: 'AWS Claude' }, { key: 3, text: 'Azure OpenAI', value: 3, color: 'teal', - label: 'Azure OpenAI', + label: 'Azure OpenAI' }, { key: 24, text: 'Google Gemini', value: 24, color: 'orange', - label: 'Google Gemini', + label: 'Google Gemini' }, { key: 34, text: 'Cohere', value: 34, color: 'purple', - label: 'Cohere', + label: 'Cohere' }, { key: 15, text: '百度文心千帆', value: 15, color: 'blue', - label: '百度文心千帆', + label: '百度文心千帆' }, { key: 17, text: '阿里通义千问', value: 17, color: 'orange', - label: '阿里通义千问', + label: '阿里通义千问' }, { key: 18, text: '讯飞星火认知', value: 18, color: 'blue', - label: '讯飞星火认知', + label: '讯飞星火认知' }, { key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet', - label: '智谱 ChatGLM', + label: '智谱 ChatGLM' }, { key: 26, text: '智谱 GLM-4V', value: 26, color: 'purple', - label: '智谱 GLM-4V', + label: '智谱 GLM-4V' }, { key: 11, text: 'Google PaLM2', value: 11, color: 'orange', - label: 'Google PaLM2', + label: 'Google PaLM2' }, { key: 39, text: 'Cloudflare', value: 39, color: 'grey', label: 'Cloudflare' }, { key: 25, text: 'Moonshot', value: 25, color: 'green', label: 'Moonshot' }, @@ -107,19 +107,20 @@ export const CHANNEL_OPTIONS = [ { key: 35, text: 'MiniMax', value: 35, color: 'green', label: 'MiniMax' }, { key: 37, text: 'Dify', value: 37, color: 'teal', label: 'Dify' }, { key: 38, text: 'Jina', value: 38, color: 'blue', label: 'Jina' }, + { key: 40, text: 'SiliconCloud', value: 40, color: 'purple', label: 'SiliconCloud' }, { key: 8, text: '自定义渠道', value: 8, color: 'pink', label: '自定义渠道' }, { key: 22, text: '知识库:FastGPT', value: 22, color: 'blue', - label: '知识库:FastGPT', + label: '知识库:FastGPT' }, { key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple', - label: '知识库:AI Proxy', - }, + label: '知识库:AI Proxy' + } ]; From 484a8595e4558e4e5f2879367f2598e47e1e18a7 Mon Sep 17 00:00:00 2001 From: OswinWu Date: Fri, 23 Aug 2024 17:16:09 +0800 Subject: [PATCH 05/10] =?UTF-8?q?fix:=20=E5=A4=9A=E5=9C=B0=E5=8C=BAoutlook?= =?UTF-8?q?=E9=82=AE=E7=AE=B1=E5=92=8Cofb=E9=82=AE=E7=AE=B1Auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/email-outlook-auth.go | 8 ++++++++ common/email.go | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/email-outlook-auth.go b/common/email-outlook-auth.go index 723a10b..f6a71b8 100644 --- a/common/email-outlook-auth.go +++ b/common/email-outlook-auth.go @@ -3,6 +3,7 @@ package common import ( "errors" "net/smtp" + "strings" ) type outlookAuth struct { @@ -30,3 +31,10 @@ func (a *outlookAuth) Next(fromServer []byte, more bool) ([]byte, error) { } return nil, nil } + +func isOutlookServer(server string) bool { + // 兼容多地区的outlook邮箱和ofb邮箱 + // 其实应该加一个Option来区分是否用LOGIN的方式登录 + // 先临时兼容一下 + return strings.Contains(server, "outlook") || strings.Contains(server, "onmicrosoft") +} diff --git a/common/email.go b/common/email.go index b23bb23..905f385 100644 --- a/common/email.go +++ b/common/email.go @@ -68,7 +68,7 @@ func SendEmail(subject string, receiver string, content string) error { if err != nil { return err } - } else if strings.HasSuffix(SMTPAccount, "outlook.com") { + } else if isOutlookServer(SMTPAccount) { auth = LoginAuth(SMTPAccount, SMTPToken) err = smtp.SendMail(addr, auth, SMTPAccount, to, mail) } else { From 144513f1d841fd178403b1ec6b117f06b053b442 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Fri, 23 Aug 2024 23:21:37 +0800 Subject: [PATCH 06/10] feat: rerank model mapping (close #444) --- relay/relay_rerank.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/relay/relay_rerank.go b/relay/relay_rerank.go index 9885fd3..4242155 100644 --- a/relay/relay_rerank.go +++ b/relay/relay_rerank.go @@ -38,6 +38,23 @@ func RerankHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode if len(rerankRequest.Documents) == 0 { return service.OpenAIErrorWrapperLocal(fmt.Errorf("documents is empty"), "invalid_documents", http.StatusBadRequest) } + + // map model name + modelMapping := c.GetString("model_mapping") + //isModelMapped := false + if modelMapping != "" && modelMapping != "{}" { + modelMap := make(map[string]string) + err := json.Unmarshal([]byte(modelMapping), &modelMap) + if err != nil { + return service.OpenAIErrorWrapperLocal(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError) + } + if modelMap[rerankRequest.Model] != "" { + rerankRequest.Model = modelMap[rerankRequest.Model] + // set upstream model name + //isModelMapped = true + } + } + relayInfo.UpstreamModelName = rerankRequest.Model modelPrice, success := common.GetModelPrice(rerankRequest.Model, false) groupRatio := common.GetGroupRatio(relayInfo.Group) From 967ccabb56b3fb896fd1e71af5b54e81152a2fe1 Mon Sep 17 00:00:00 2001 From: Xyfacai Date: Sat, 24 Aug 2024 13:36:41 +0800 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20dall-e-2=20?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/relay-image.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/relay/relay-image.go b/relay/relay-image.go index 74d6c30..411a2c3 100644 --- a/relay/relay-image.go +++ b/relay/relay-image.go @@ -38,9 +38,7 @@ func getAndValidImageRequest(c *gin.Context, info *relaycommon.RelayInfo) (*dto. if imageRequest.Model == "" { imageRequest.Model = "dall-e-2" } - if imageRequest.Quality == "" { - imageRequest.Quality = "standard" - } + // Not "256x256", "512x512", or "1024x1024" if imageRequest.Model == "dall-e-2" || imageRequest.Model == "dall-e" { if imageRequest.Size != "" && imageRequest.Size != "256x256" && imageRequest.Size != "512x512" && imageRequest.Size != "1024x1024" { @@ -50,6 +48,9 @@ func getAndValidImageRequest(c *gin.Context, info *relaycommon.RelayInfo) (*dto. if imageRequest.Size != "" && imageRequest.Size != "1024x1024" && imageRequest.Size != "1024x1792" && imageRequest.Size != "1792x1024" { return nil, errors.New("size must be one of 256x256, 512x512, or 1024x1024, dall-e-3 1024x1792 or 1792x1024") } + if imageRequest.Quality == "" { + imageRequest.Quality = "standard" + } //if imageRequest.N != 1 { // return nil, errors.New("n must be 1") //} From a8ac8a25d522dceda91112b3ca92f2559a808316 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Sat, 24 Aug 2024 17:15:55 +0800 Subject: [PATCH 08/10] feat: format claude messages when first role is not user --- relay/channel/claude/relay-claude.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/relay/channel/claude/relay-claude.go b/relay/channel/claude/relay-claude.go index 47abe31..1c9d4e6 100644 --- a/relay/channel/claude/relay-claude.go +++ b/relay/channel/claude/relay-claude.go @@ -139,6 +139,7 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR } claudeMessages := make([]ClaudeMessage, 0) + isFirstMessage := true for _, message := range formatMessages { if message.Role == "system" { if message.IsStringContent() { @@ -154,6 +155,22 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR claudeRequest.System = content } } else { + if isFirstMessage { + isFirstMessage = false + if message.Role != "user" { + // fix: first message is assistant, add user message + claudeMessage := ClaudeMessage{ + Role: "user", + Content: []ClaudeMediaMessage{ + { + Type: "text", + Text: "...", + }, + }, + } + claudeMessages = append(claudeMessages, claudeMessage) + } + } claudeMessage := ClaudeMessage{ Role: message.Role, } From ff0985f06e7df6e49dd0ea4c24c4a3dcf4f59035 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Sat, 24 Aug 2024 17:23:24 +0800 Subject: [PATCH 09/10] fix: channel auto ban #443 --- service/channel.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/service/channel.go b/service/channel.go index 5716a6d..b370a43 100644 --- a/service/channel.go +++ b/service/channel.go @@ -54,6 +54,8 @@ func ShouldDisableChannel(channelType int, err *relaymodel.OpenAIErrorWithStatus switch err.Error.Type { case "insufficient_quota": return true + case "insufficient_user_quota": + return true // https://docs.anthropic.com/claude/reference/errors case "authentication_error": return true From 46e03683ce668082f10ad914a0cf0e67bf8d43e1 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Sat, 24 Aug 2024 17:27:14 +0800 Subject: [PATCH 10/10] fix: channel auto ban --- controller/relay.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index 13fbde0..875222d 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -121,6 +121,9 @@ func shouldRetry(c *gin.Context, openaiErr *dto.OpenAIErrorWithStatusCode, retry if openaiErr == nil { return false } + if openaiErr.LocalError { + return false + } if retryTimes <= 0 { return false } @@ -151,9 +154,6 @@ func shouldRetry(c *gin.Context, openaiErr *dto.OpenAIErrorWithStatusCode, retry // azure处理超时不重试 return false } - if openaiErr.LocalError { - return false - } if openaiErr.StatusCode/100 == 2 { return false }