mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-10-31 22:03:41 +08:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			v0.5.10-al
			...
			v0.5.11-al
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b7fcb319da | ||
|  | 67c64e71c8 | ||
|  | 97030e27f8 | ||
|  | 461f5dab56 | ||
|  | af378c59af | ||
|  | bc6769826b | ||
|  | 0fe26cc4bd | ||
|  | 7d6a169669 | ||
|  | 66f06e5d6f | ||
|  | 6acb9537a9 | ||
|  | 7069c49bdf | ||
|  | 58dee76bf7 | ||
|  | 5cf23d8698 | 
| @@ -60,7 +60,7 @@ _✨ Access all LLM through the standard OpenAI API format, easy to deploy & use | |||||||
| 1. Support for multiple large models: | 1. Support for multiple large models: | ||||||
|    + [x] [OpenAI ChatGPT Series Models](https://platform.openai.com/docs/guides/gpt/chat-completions-api) (Supports [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference)) |    + [x] [OpenAI ChatGPT Series Models](https://platform.openai.com/docs/guides/gpt/chat-completions-api) (Supports [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference)) | ||||||
|    + [x] [Anthropic Claude Series Models](https://anthropic.com) |    + [x] [Anthropic Claude Series Models](https://anthropic.com) | ||||||
|    + [x] [Google PaLM2 Series Models](https://developers.generativeai.google) |    + [x] [Google PaLM2 and Gemini Series Models](https://developers.generativeai.google) | ||||||
|    + [x] [Baidu Wenxin Yiyuan Series Models](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) |    + [x] [Baidu Wenxin Yiyuan Series Models](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) | ||||||
|    + [x] [Alibaba Tongyi Qianwen Series Models](https://help.aliyun.com/document_detail/2400395.html) |    + [x] [Alibaba Tongyi Qianwen Series Models](https://help.aliyun.com/document_detail/2400395.html) | ||||||
|    + [x] [Zhipu ChatGLM Series Models](https://bigmodel.cn) |    + [x] [Zhipu ChatGLM Series Models](https://bigmodel.cn) | ||||||
|   | |||||||
| @@ -60,7 +60,7 @@ _✨ 標準的な OpenAI API フォーマットを通じてすべての LLM に | |||||||
| 1. 複数の大型モデルをサポート: | 1. 複数の大型モデルをサポート: | ||||||
|    + [x] [OpenAI ChatGPT シリーズモデル](https://platform.openai.com/docs/guides/gpt/chat-completions-api) ([Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference) をサポート) |    + [x] [OpenAI ChatGPT シリーズモデル](https://platform.openai.com/docs/guides/gpt/chat-completions-api) ([Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference) をサポート) | ||||||
|    + [x] [Anthropic Claude シリーズモデル](https://anthropic.com) |    + [x] [Anthropic Claude シリーズモデル](https://anthropic.com) | ||||||
|    + [x] [Google PaLM2 シリーズモデル](https://developers.generativeai.google) |    + [x] [Google PaLM2/Gemini シリーズモデル](https://developers.generativeai.google) | ||||||
|    + [x] [Baidu Wenxin Yiyuan シリーズモデル](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) |    + [x] [Baidu Wenxin Yiyuan シリーズモデル](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) | ||||||
|    + [x] [Alibaba Tongyi Qianwen シリーズモデル](https://help.aliyun.com/document_detail/2400395.html) |    + [x] [Alibaba Tongyi Qianwen シリーズモデル](https://help.aliyun.com/document_detail/2400395.html) | ||||||
|    + [x] [Zhipu ChatGLM シリーズモデル](https://bigmodel.cn) |    + [x] [Zhipu ChatGLM シリーズモデル](https://bigmodel.cn) | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @@ -66,20 +66,14 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用  | |||||||
| 1. 支持多种大模型: | 1. 支持多种大模型: | ||||||
|    + [x] [OpenAI ChatGPT 系列模型](https://platform.openai.com/docs/guides/gpt/chat-completions-api)(支持 [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference)) |    + [x] [OpenAI ChatGPT 系列模型](https://platform.openai.com/docs/guides/gpt/chat-completions-api)(支持 [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference)) | ||||||
|    + [x] [Anthropic Claude 系列模型](https://anthropic.com) |    + [x] [Anthropic Claude 系列模型](https://anthropic.com) | ||||||
|    + [x] [Google PaLM2 系列模型](https://developers.generativeai.google) |    + [x] [Google PaLM2/Gemini 系列模型](https://developers.generativeai.google) | ||||||
|    + [x] [百度文心一言系列模型](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) |    + [x] [百度文心一言系列模型](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) | ||||||
|    + [x] [阿里通义千问系列模型](https://help.aliyun.com/document_detail/2400395.html) |    + [x] [阿里通义千问系列模型](https://help.aliyun.com/document_detail/2400395.html) | ||||||
|    + [x] [讯飞星火认知大模型](https://www.xfyun.cn/doc/spark/Web.html) |    + [x] [讯飞星火认知大模型](https://www.xfyun.cn/doc/spark/Web.html) | ||||||
|    + [x] [智谱 ChatGLM 系列模型](https://bigmodel.cn) |    + [x] [智谱 ChatGLM 系列模型](https://bigmodel.cn) | ||||||
|    + [x] [360 智脑](https://ai.360.cn) |    + [x] [360 智脑](https://ai.360.cn) | ||||||
|    + [x] [腾讯混元大模型](https://cloud.tencent.com/document/product/1729) |    + [x] [腾讯混元大模型](https://cloud.tencent.com/document/product/1729) | ||||||
| 2. 支持配置镜像以及众多第三方代理服务: | 2. 支持配置镜像以及众多[第三方代理服务](https://iamazing.cn/page/openai-api-third-party-services)。 | ||||||
|    + [x] [OpenAI-SB](https://openai-sb.com) |  | ||||||
|    + [x] [CloseAI](https://referer.shadowai.xyz/r/2412) |  | ||||||
|    + [x] [API2D](https://api2d.com/r/197971) |  | ||||||
|    + [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf) |  | ||||||
|    + [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (邀请码:`OneAPI`) |  | ||||||
|    + [x] 自定义渠道:例如各种未收录的第三方代理服务 |  | ||||||
| 3. 支持通过**负载均衡**的方式访问多个渠道。 | 3. 支持通过**负载均衡**的方式访问多个渠道。 | ||||||
| 4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。 | 4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。 | ||||||
| 5. 支持**多机部署**,[详见此处](#多机部署)。 | 5. 支持**多机部署**,[详见此处](#多机部署)。 | ||||||
| @@ -371,6 +365,7 @@ graph LR | |||||||
|     + `TIKTOKEN_CACHE_DIR`:默认程序启动时会联网下载一些通用的词元的编码,如:`gpt-3.5-turbo`,在一些网络环境不稳定,或者离线情况,可能会导致启动有问题,可以配置此目录缓存数据,可迁移到离线环境。 |     + `TIKTOKEN_CACHE_DIR`:默认程序启动时会联网下载一些通用的词元的编码,如:`gpt-3.5-turbo`,在一些网络环境不稳定,或者离线情况,可能会导致启动有问题,可以配置此目录缓存数据,可迁移到离线环境。 | ||||||
|     + `DATA_GYM_CACHE_DIR`:目前该配置作用与 `TIKTOKEN_CACHE_DIR` 一致,但是优先级没有它高。 |     + `DATA_GYM_CACHE_DIR`:目前该配置作用与 `TIKTOKEN_CACHE_DIR` 一致,但是优先级没有它高。 | ||||||
| 15. `RELAY_TIMEOUT`:中继超时设置,单位为秒,默认不设置超时时间。 | 15. `RELAY_TIMEOUT`:中继超时设置,单位为秒,默认不设置超时时间。 | ||||||
|  | 16. `SQLITE_BUSY_TIMEOUT`:SQLite 锁等待超时设置,单位为毫秒,默认 `3000`。 | ||||||
|  |  | ||||||
| ### 命令行参数 | ### 命令行参数 | ||||||
| 1. `--port <port_number>`: 指定服务器监听的端口号,默认为 `3000`。 | 1. `--port <port_number>`: 指定服务器监听的端口号,默认为 `3000`。 | ||||||
|   | |||||||
| @@ -187,6 +187,7 @@ const ( | |||||||
| 	ChannelTypeAIProxyLibrary = 21 | 	ChannelTypeAIProxyLibrary = 21 | ||||||
| 	ChannelTypeFastGPT        = 22 | 	ChannelTypeFastGPT        = 22 | ||||||
| 	ChannelTypeTencent        = 23 | 	ChannelTypeTencent        = 23 | ||||||
|  | 	ChannelTypeGemini         = 24 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ChannelBaseURLs = []string{ | var ChannelBaseURLs = []string{ | ||||||
| @@ -214,4 +215,5 @@ var ChannelBaseURLs = []string{ | |||||||
| 	"https://api.aiproxy.io",            // 21 | 	"https://api.aiproxy.io",            // 21 | ||||||
| 	"https://fastgpt.run/api/openapi",   // 22 | 	"https://fastgpt.run/api/openapi",   // 22 | ||||||
| 	"https://hunyuan.cloud.tencent.com", //23 | 	"https://hunyuan.cloud.tencent.com", //23 | ||||||
|  | 	"",                                  //24 | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,3 +4,4 @@ var UsingSQLite = false | |||||||
| var UsingPostgreSQL = false | var UsingPostgreSQL = false | ||||||
|  |  | ||||||
| var SQLitePath = "one-api.db" | var SQLitePath = "one-api.db" | ||||||
|  | var SQLiteBusyTimeout = GetOrDefault("SQLITE_BUSY_TIMEOUT", 3000) | ||||||
|   | |||||||
| @@ -36,7 +36,11 @@ func init() { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if os.Getenv("SESSION_SECRET") != "" { | 	if os.Getenv("SESSION_SECRET") != "" { | ||||||
| 		SessionSecret = os.Getenv("SESSION_SECRET") | 		if os.Getenv("SESSION_SECRET") == "random_string" { | ||||||
|  | 			SysError("SESSION_SECRET is set to an example value, please change it to a random string.") | ||||||
|  | 		} else { | ||||||
|  | 			SessionSecret = os.Getenv("SESSION_SECRET") | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	if os.Getenv("SQLITE_PATH") != "" { | 	if os.Getenv("SQLITE_PATH") != "" { | ||||||
| 		SQLitePath = os.Getenv("SQLITE_PATH") | 		SQLitePath = os.Getenv("SQLITE_PATH") | ||||||
|   | |||||||
| @@ -83,12 +83,15 @@ var ModelRatio = map[string]float64{ | |||||||
| 	"ERNIE-Bot-4":               8.572,  // ¥0.12 / 1k tokens | 	"ERNIE-Bot-4":               8.572,  // ¥0.12 / 1k tokens | ||||||
| 	"Embedding-V1":              0.1429, // ¥0.002 / 1k tokens | 	"Embedding-V1":              0.1429, // ¥0.002 / 1k tokens | ||||||
| 	"PaLM-2":                    1, | 	"PaLM-2":                    1, | ||||||
|  | 	"gemini-pro":                1,      // $0.00025 / 1k characters -> $0.001 / 1k tokens | ||||||
| 	"chatglm_turbo":             0.3572, // ¥0.005 / 1k tokens | 	"chatglm_turbo":             0.3572, // ¥0.005 / 1k tokens | ||||||
| 	"chatglm_pro":               0.7143, // ¥0.01 / 1k tokens | 	"chatglm_pro":               0.7143, // ¥0.01 / 1k tokens | ||||||
| 	"chatglm_std":               0.3572, // ¥0.005 / 1k tokens | 	"chatglm_std":               0.3572, // ¥0.005 / 1k tokens | ||||||
| 	"chatglm_lite":              0.1429, // ¥0.002 / 1k tokens | 	"chatglm_lite":              0.1429, // ¥0.002 / 1k tokens | ||||||
| 	"qwen-turbo":                0.8572, // ¥0.012 / 1k tokens | 	"qwen-turbo":                0.5715, // ¥0.008 / 1k tokens  // https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-thousand-questions-metering-and-billing | ||||||
| 	"qwen-plus":                 10,     // ¥0.14 / 1k tokens | 	"qwen-plus":                 1.4286, // ¥0.02 / 1k tokens | ||||||
|  | 	"qwen-max":                  1.4286, // ¥0.02 / 1k tokens | ||||||
|  | 	"qwen-max-longcontext":      1.4286, // ¥0.02 / 1k tokens | ||||||
| 	"text-embedding-v1":         0.05,   // ¥0.0007 / 1k tokens | 	"text-embedding-v1":         0.05,   // ¥0.0007 / 1k tokens | ||||||
| 	"SparkDesk":                 1.2858, // ¥0.018 / 1k tokens | 	"SparkDesk":                 1.2858, // ¥0.018 / 1k tokens | ||||||
| 	"360GPT_S2_V9":              0.8572, // ¥0.012 / 1k tokens | 	"360GPT_S2_V9":              0.8572, // ¥0.012 / 1k tokens | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ func testChannel(channel *model.Channel, request ChatRequest) (err error, openai | |||||||
| 	switch channel.Type { | 	switch channel.Type { | ||||||
| 	case common.ChannelTypePaLM: | 	case common.ChannelTypePaLM: | ||||||
| 		fallthrough | 		fallthrough | ||||||
|  | 	case common.ChannelTypeGemini: | ||||||
|  | 		fallthrough | ||||||
| 	case common.ChannelTypeAnthropic: | 	case common.ChannelTypeAnthropic: | ||||||
| 		fallthrough | 		fallthrough | ||||||
| 	case common.ChannelTypeBaidu: | 	case common.ChannelTypeBaidu: | ||||||
|   | |||||||
| @@ -423,6 +423,15 @@ func init() { | |||||||
| 			Root:       "PaLM-2", | 			Root:       "PaLM-2", | ||||||
| 			Parent:     nil, | 			Parent:     nil, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Id:         "gemini-pro", | ||||||
|  | 			Object:     "model", | ||||||
|  | 			Created:    1677649963, | ||||||
|  | 			OwnedBy:    "google", | ||||||
|  | 			Permission: permission, | ||||||
|  | 			Root:       "gemini-pro", | ||||||
|  | 			Parent:     nil, | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Id:         "chatglm_turbo", | 			Id:         "chatglm_turbo", | ||||||
| 			Object:     "model", | 			Object:     "model", | ||||||
| @@ -477,6 +486,24 @@ func init() { | |||||||
| 			Root:       "qwen-plus", | 			Root:       "qwen-plus", | ||||||
| 			Parent:     nil, | 			Parent:     nil, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Id:         "qwen-max", | ||||||
|  | 			Object:     "model", | ||||||
|  | 			Created:    1677649963, | ||||||
|  | 			OwnedBy:    "ali", | ||||||
|  | 			Permission: permission, | ||||||
|  | 			Root:       "qwen-max", | ||||||
|  | 			Parent:     nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Id:         "qwen-max-longcontext", | ||||||
|  | 			Object:     "model", | ||||||
|  | 			Created:    1677649963, | ||||||
|  | 			OwnedBy:    "ali", | ||||||
|  | 			Permission: permission, | ||||||
|  | 			Root:       "qwen-max-longcontext", | ||||||
|  | 			Parent:     nil, | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Id:         "text-embedding-v1", | 			Id:         "text-embedding-v1", | ||||||
| 			Object:     "model", | 			Object:     "model", | ||||||
|   | |||||||
| @@ -13,13 +13,13 @@ import ( | |||||||
| // https://help.aliyun.com/document_detail/613695.html?spm=a2c4g.2399480.0.0.1adb778fAdzP9w#341800c0f8w0r | // https://help.aliyun.com/document_detail/613695.html?spm=a2c4g.2399480.0.0.1adb778fAdzP9w#341800c0f8w0r | ||||||
|  |  | ||||||
| type AliMessage struct { | type AliMessage struct { | ||||||
| 	User string `json:"user"` | 	Content string `json:"content"` | ||||||
| 	Bot  string `json:"bot"` | 	Role    string `json:"role"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AliInput struct { | type AliInput struct { | ||||||
| 	Prompt  string       `json:"prompt"` | 	//Prompt   string       `json:"prompt"` | ||||||
| 	History []AliMessage `json:"history"` | 	Messages []AliMessage `json:"messages"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AliParameters struct { | type AliParameters struct { | ||||||
| @@ -83,32 +83,17 @@ type AliChatResponse struct { | |||||||
|  |  | ||||||
| func requestOpenAI2Ali(request GeneralOpenAIRequest) *AliChatRequest { | func requestOpenAI2Ali(request GeneralOpenAIRequest) *AliChatRequest { | ||||||
| 	messages := make([]AliMessage, 0, len(request.Messages)) | 	messages := make([]AliMessage, 0, len(request.Messages)) | ||||||
| 	prompt := "" |  | ||||||
| 	for i := 0; i < len(request.Messages); i++ { | 	for i := 0; i < len(request.Messages); i++ { | ||||||
| 		message := request.Messages[i] | 		message := request.Messages[i] | ||||||
| 		if message.Role == "system" { | 		messages = append(messages, AliMessage{ | ||||||
| 			messages = append(messages, AliMessage{ | 			Content: message.StringContent(), | ||||||
| 				User: message.StringContent(), | 			Role:    strings.ToLower(message.Role), | ||||||
| 				Bot:  "Okay", | 		}) | ||||||
| 			}) |  | ||||||
| 			continue |  | ||||||
| 		} else { |  | ||||||
| 			if i == len(request.Messages)-1 { |  | ||||||
| 				prompt = message.StringContent() |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 			messages = append(messages, AliMessage{ |  | ||||||
| 				User: message.StringContent(), |  | ||||||
| 				Bot:  request.Messages[i+1].StringContent(), |  | ||||||
| 			}) |  | ||||||
| 			i++ |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return &AliChatRequest{ | 	return &AliChatRequest{ | ||||||
| 		Model: request.Model, | 		Model: request.Model, | ||||||
| 		Input: AliInput{ | 		Input: AliInput{ | ||||||
| 			Prompt:  prompt, | 			Messages: messages, | ||||||
| 			History: messages, |  | ||||||
| 		}, | 		}, | ||||||
| 		//Parameters: AliParameters{  // ChatGPT's parameters are not compatible with Ali's | 		//Parameters: AliParameters{  // ChatGPT's parameters are not compatible with Ali's | ||||||
| 		//	TopP: request.TopP, | 		//	TopP: request.TopP, | ||||||
|   | |||||||
							
								
								
									
										305
									
								
								controller/relay-gemini.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								controller/relay-gemini.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,305 @@ | |||||||
|  | package controller | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"one-api/common" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type GeminiChatRequest struct { | ||||||
|  | 	Contents         []GeminiChatContent        `json:"contents"` | ||||||
|  | 	SafetySettings   []GeminiChatSafetySettings `json:"safety_settings,omitempty"` | ||||||
|  | 	GenerationConfig GeminiChatGenerationConfig `json:"generation_config,omitempty"` | ||||||
|  | 	Tools            []GeminiChatTools          `json:"tools,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiInlineData struct { | ||||||
|  | 	MimeType string `json:"mimeType"` | ||||||
|  | 	Data     string `json:"data"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiPart struct { | ||||||
|  | 	Text       string            `json:"text,omitempty"` | ||||||
|  | 	InlineData *GeminiInlineData `json:"inlineData,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatContent struct { | ||||||
|  | 	Role  string       `json:"role,omitempty"` | ||||||
|  | 	Parts []GeminiPart `json:"parts"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatSafetySettings struct { | ||||||
|  | 	Category  string `json:"category"` | ||||||
|  | 	Threshold string `json:"threshold"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatTools struct { | ||||||
|  | 	FunctionDeclarations any `json:"functionDeclarations,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatGenerationConfig struct { | ||||||
|  | 	Temperature     float64  `json:"temperature,omitempty"` | ||||||
|  | 	TopP            float64  `json:"topP,omitempty"` | ||||||
|  | 	TopK            float64  `json:"topK,omitempty"` | ||||||
|  | 	MaxOutputTokens int      `json:"maxOutputTokens,omitempty"` | ||||||
|  | 	CandidateCount  int      `json:"candidateCount,omitempty"` | ||||||
|  | 	StopSequences   []string `json:"stopSequences,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Setting safety to the lowest possible values since Gemini is already powerless enough | ||||||
|  | func requestOpenAI2Gemini(textRequest GeneralOpenAIRequest) *GeminiChatRequest { | ||||||
|  | 	geminiRequest := GeminiChatRequest{ | ||||||
|  | 		Contents: make([]GeminiChatContent, 0, len(textRequest.Messages)), | ||||||
|  | 		//SafetySettings: []GeminiChatSafetySettings{ | ||||||
|  | 		//	{ | ||||||
|  | 		//		Category:  "HARM_CATEGORY_HARASSMENT", | ||||||
|  | 		//		Threshold: "BLOCK_ONLY_HIGH", | ||||||
|  | 		//	}, | ||||||
|  | 		//	{ | ||||||
|  | 		//		Category:  "HARM_CATEGORY_HATE_SPEECH", | ||||||
|  | 		//		Threshold: "BLOCK_ONLY_HIGH", | ||||||
|  | 		//	}, | ||||||
|  | 		//	{ | ||||||
|  | 		//		Category:  "HARM_CATEGORY_SEXUALLY_EXPLICIT", | ||||||
|  | 		//		Threshold: "BLOCK_ONLY_HIGH", | ||||||
|  | 		//	}, | ||||||
|  | 		//	{ | ||||||
|  | 		//		Category:  "HARM_CATEGORY_DANGEROUS_CONTENT", | ||||||
|  | 		//		Threshold: "BLOCK_ONLY_HIGH", | ||||||
|  | 		//	}, | ||||||
|  | 		//}, | ||||||
|  | 		GenerationConfig: GeminiChatGenerationConfig{ | ||||||
|  | 			Temperature:     textRequest.Temperature, | ||||||
|  | 			TopP:            textRequest.TopP, | ||||||
|  | 			MaxOutputTokens: textRequest.MaxTokens, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	if textRequest.Functions != nil { | ||||||
|  | 		geminiRequest.Tools = []GeminiChatTools{ | ||||||
|  | 			{ | ||||||
|  | 				FunctionDeclarations: textRequest.Functions, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	shouldAddDummyModelMessage := false | ||||||
|  | 	for _, message := range textRequest.Messages { | ||||||
|  | 		content := GeminiChatContent{ | ||||||
|  | 			Role: message.Role, | ||||||
|  | 			Parts: []GeminiPart{ | ||||||
|  | 				{ | ||||||
|  | 					Text: message.StringContent(), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		// there's no assistant role in gemini and API shall vomit if Role is not user or model | ||||||
|  | 		if content.Role == "assistant" { | ||||||
|  | 			content.Role = "model" | ||||||
|  | 		} | ||||||
|  | 		// Converting system prompt to prompt from user for the same reason | ||||||
|  | 		if content.Role == "system" { | ||||||
|  | 			content.Role = "user" | ||||||
|  | 			shouldAddDummyModelMessage = true | ||||||
|  | 		} | ||||||
|  | 		geminiRequest.Contents = append(geminiRequest.Contents, content) | ||||||
|  |  | ||||||
|  | 		// If a system message is the last message, we need to add a dummy model message to make gemini happy | ||||||
|  | 		if shouldAddDummyModelMessage { | ||||||
|  | 			geminiRequest.Contents = append(geminiRequest.Contents, GeminiChatContent{ | ||||||
|  | 				Role: "model", | ||||||
|  | 				Parts: []GeminiPart{ | ||||||
|  | 					{ | ||||||
|  | 						Text: "Okay", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 			shouldAddDummyModelMessage = false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &geminiRequest | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatResponse struct { | ||||||
|  | 	Candidates     []GeminiChatCandidate    `json:"candidates"` | ||||||
|  | 	PromptFeedback GeminiChatPromptFeedback `json:"promptFeedback"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *GeminiChatResponse) GetResponseText() string { | ||||||
|  | 	if g == nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	if len(g.Candidates) > 0 && len(g.Candidates[0].Content.Parts) > 0 { | ||||||
|  | 		return g.Candidates[0].Content.Parts[0].Text | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatCandidate struct { | ||||||
|  | 	Content       GeminiChatContent        `json:"content"` | ||||||
|  | 	FinishReason  string                   `json:"finishReason"` | ||||||
|  | 	Index         int64                    `json:"index"` | ||||||
|  | 	SafetyRatings []GeminiChatSafetyRating `json:"safetyRatings"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatSafetyRating struct { | ||||||
|  | 	Category    string `json:"category"` | ||||||
|  | 	Probability string `json:"probability"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GeminiChatPromptFeedback struct { | ||||||
|  | 	SafetyRatings []GeminiChatSafetyRating `json:"safetyRatings"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func responseGeminiChat2OpenAI(response *GeminiChatResponse) *OpenAITextResponse { | ||||||
|  | 	fullTextResponse := OpenAITextResponse{ | ||||||
|  | 		Id:      fmt.Sprintf("chatcmpl-%s", common.GetUUID()), | ||||||
|  | 		Object:  "chat.completion", | ||||||
|  | 		Created: common.GetTimestamp(), | ||||||
|  | 		Choices: make([]OpenAITextResponseChoice, 0, len(response.Candidates)), | ||||||
|  | 	} | ||||||
|  | 	for i, candidate := range response.Candidates { | ||||||
|  | 		choice := OpenAITextResponseChoice{ | ||||||
|  | 			Index: i, | ||||||
|  | 			Message: Message{ | ||||||
|  | 				Role:    "assistant", | ||||||
|  | 				Content: "", | ||||||
|  | 			}, | ||||||
|  | 			FinishReason: stopFinishReason, | ||||||
|  | 		} | ||||||
|  | 		if len(candidate.Content.Parts) > 0 { | ||||||
|  | 			choice.Message.Content = candidate.Content.Parts[0].Text | ||||||
|  | 		} | ||||||
|  | 		fullTextResponse.Choices = append(fullTextResponse.Choices, choice) | ||||||
|  | 	} | ||||||
|  | 	return &fullTextResponse | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) *ChatCompletionsStreamResponse { | ||||||
|  | 	var choice ChatCompletionsStreamResponseChoice | ||||||
|  | 	choice.Delta.Content = geminiResponse.GetResponseText() | ||||||
|  | 	choice.FinishReason = &stopFinishReason | ||||||
|  | 	var response ChatCompletionsStreamResponse | ||||||
|  | 	response.Object = "chat.completion.chunk" | ||||||
|  | 	response.Model = "gemini" | ||||||
|  | 	response.Choices = []ChatCompletionsStreamResponseChoice{choice} | ||||||
|  | 	return &response | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func geminiChatStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, string) { | ||||||
|  | 	responseText := "" | ||||||
|  | 	dataChan := make(chan string) | ||||||
|  | 	stopChan := make(chan bool) | ||||||
|  | 	scanner := bufio.NewScanner(resp.Body) | ||||||
|  | 	scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { | ||||||
|  | 		if atEOF && len(data) == 0 { | ||||||
|  | 			return 0, nil, nil | ||||||
|  | 		} | ||||||
|  | 		if i := strings.Index(string(data), "\n"); i >= 0 { | ||||||
|  | 			return i + 1, data[0:i], nil | ||||||
|  | 		} | ||||||
|  | 		if atEOF { | ||||||
|  | 			return len(data), data, nil | ||||||
|  | 		} | ||||||
|  | 		return 0, nil, nil | ||||||
|  | 	}) | ||||||
|  | 	go func() { | ||||||
|  | 		for scanner.Scan() { | ||||||
|  | 			data := scanner.Text() | ||||||
|  | 			data = strings.TrimSpace(data) | ||||||
|  | 			if !strings.HasPrefix(data, "\"text\": \"") { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			data = strings.TrimPrefix(data, "\"text\": \"") | ||||||
|  | 			data = strings.TrimSuffix(data, "\"") | ||||||
|  | 			dataChan <- data | ||||||
|  | 		} | ||||||
|  | 		stopChan <- true | ||||||
|  | 	}() | ||||||
|  | 	setEventStreamHeaders(c) | ||||||
|  | 	c.Stream(func(w io.Writer) bool { | ||||||
|  | 		select { | ||||||
|  | 		case data := <-dataChan: | ||||||
|  | 			// this is used to prevent annoying \ related format bug | ||||||
|  | 			data = fmt.Sprintf("{\"content\": \"%s\"}", data) | ||||||
|  | 			type dummyStruct struct { | ||||||
|  | 				Content string `json:"content"` | ||||||
|  | 			} | ||||||
|  | 			var dummy dummyStruct | ||||||
|  | 			err := json.Unmarshal([]byte(data), &dummy) | ||||||
|  | 			responseText += dummy.Content | ||||||
|  | 			var choice ChatCompletionsStreamResponseChoice | ||||||
|  | 			choice.Delta.Content = dummy.Content | ||||||
|  | 			response := ChatCompletionsStreamResponse{ | ||||||
|  | 				Id:      fmt.Sprintf("chatcmpl-%s", common.GetUUID()), | ||||||
|  | 				Object:  "chat.completion.chunk", | ||||||
|  | 				Created: common.GetTimestamp(), | ||||||
|  | 				Model:   "gemini-pro", | ||||||
|  | 				Choices: []ChatCompletionsStreamResponseChoice{choice}, | ||||||
|  | 			} | ||||||
|  | 			jsonResponse, err := json.Marshal(response) | ||||||
|  | 			if err != nil { | ||||||
|  | 				common.SysError("error marshalling stream response: " + err.Error()) | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 			c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonResponse)}) | ||||||
|  | 			return true | ||||||
|  | 		case <-stopChan: | ||||||
|  | 			c.Render(-1, common.CustomEvent{Data: "data: [DONE]"}) | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 	err := resp.Body.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), "" | ||||||
|  | 	} | ||||||
|  | 	return nil, responseText | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func geminiChatHandler(c *gin.Context, resp *http.Response, promptTokens int, model string) (*OpenAIErrorWithStatusCode, *Usage) { | ||||||
|  | 	responseBody, err := io.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil | ||||||
|  | 	} | ||||||
|  | 	err = resp.Body.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil | ||||||
|  | 	} | ||||||
|  | 	var geminiResponse GeminiChatResponse | ||||||
|  | 	err = json.Unmarshal(responseBody, &geminiResponse) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil | ||||||
|  | 	} | ||||||
|  | 	if len(geminiResponse.Candidates) == 0 { | ||||||
|  | 		return &OpenAIErrorWithStatusCode{ | ||||||
|  | 			OpenAIError: OpenAIError{ | ||||||
|  | 				Message: "No candidates returned", | ||||||
|  | 				Type:    "server_error", | ||||||
|  | 				Param:   "", | ||||||
|  | 				Code:    500, | ||||||
|  | 			}, | ||||||
|  | 			StatusCode: resp.StatusCode, | ||||||
|  | 		}, nil | ||||||
|  | 	} | ||||||
|  | 	fullTextResponse := responseGeminiChat2OpenAI(&geminiResponse) | ||||||
|  | 	completionTokens := countTokenText(geminiResponse.GetResponseText(), model) | ||||||
|  | 	usage := Usage{ | ||||||
|  | 		PromptTokens:     promptTokens, | ||||||
|  | 		CompletionTokens: completionTokens, | ||||||
|  | 		TotalTokens:      promptTokens + completionTokens, | ||||||
|  | 	} | ||||||
|  | 	fullTextResponse.Usage = usage | ||||||
|  | 	jsonResponse, err := json.Marshal(fullTextResponse) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errorWrapper(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 | ||||||
|  | } | ||||||
| @@ -19,7 +19,6 @@ func isWithinRange(element string, value int) bool { | |||||||
| 	if _, ok := common.DalleGenerationImageAmounts[element]; !ok { | 	if _, ok := common.DalleGenerationImageAmounts[element]; !ok { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	min := common.DalleGenerationImageAmounts[element][0] | 	min := common.DalleGenerationImageAmounts[element][0] | ||||||
| 	max := common.DalleGenerationImageAmounts[element][1] | 	max := common.DalleGenerationImageAmounts[element][1] | ||||||
|  |  | ||||||
| @@ -42,6 +41,10 @@ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode | |||||||
| 		return errorWrapper(err, "bind_request_body_failed", http.StatusBadRequest) | 		return errorWrapper(err, "bind_request_body_failed", http.StatusBadRequest) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if imageRequest.N == 0 { | ||||||
|  | 		imageRequest.N = 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Size validation | 	// Size validation | ||||||
| 	if imageRequest.Size != "" { | 	if imageRequest.Size != "" { | ||||||
| 		imageSize = imageRequest.Size | 		imageSize = imageRequest.Size | ||||||
| @@ -79,7 +82,10 @@ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode | |||||||
|  |  | ||||||
| 	// Number of generated images validation | 	// Number of generated images validation | ||||||
| 	if isWithinRange(imageModel, imageRequest.N) == false { | 	if isWithinRange(imageModel, imageRequest.N) == false { | ||||||
| 		return errorWrapper(errors.New("invalid value of n"), "n_not_within_range", http.StatusBadRequest) | 		// channel not azure | ||||||
|  | 		if channelType != common.ChannelTypeAzure { | ||||||
|  | 			return errorWrapper(errors.New("invalid value of n"), "n_not_within_range", http.StatusBadRequest) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// map model name | 	// map model name | ||||||
| @@ -102,7 +108,7 @@ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode | |||||||
| 		baseURL = c.GetString("base_url") | 		baseURL = c.GetString("base_url") | ||||||
| 	} | 	} | ||||||
| 	fullRequestURL := getFullRequestURL(baseURL, requestURL, channelType) | 	fullRequestURL := getFullRequestURL(baseURL, requestURL, channelType) | ||||||
| 	if channelType == common.ChannelTypeAzure && relayMode == RelayModeImagesGenerations { | 	if channelType == common.ChannelTypeAzure { | ||||||
| 		// https://learn.microsoft.com/en-us/azure/ai-services/openai/dall-e-quickstart?tabs=dalle3%2Ccommand-line&pivots=rest-api | 		// https://learn.microsoft.com/en-us/azure/ai-services/openai/dall-e-quickstart?tabs=dalle3%2Ccommand-line&pivots=rest-api | ||||||
| 		apiVersion := GetAPIVersion(c) | 		apiVersion := GetAPIVersion(c) | ||||||
| 		// https://{resource_name}.openai.azure.com/openai/deployments/dall-e-3/images/generations?api-version=2023-06-01-preview | 		// https://{resource_name}.openai.azure.com/openai/deployments/dall-e-3/images/generations?api-version=2023-06-01-preview | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ const ( | |||||||
| 	APITypeXunfei | 	APITypeXunfei | ||||||
| 	APITypeAIProxyLibrary | 	APITypeAIProxyLibrary | ||||||
| 	APITypeTencent | 	APITypeTencent | ||||||
|  | 	APITypeGemini | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var httpClient *http.Client | var httpClient *http.Client | ||||||
| @@ -57,6 +58,9 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errorWrapper(err, "bind_request_body_failed", http.StatusBadRequest) | 		return errorWrapper(err, "bind_request_body_failed", http.StatusBadRequest) | ||||||
| 	} | 	} | ||||||
|  | 	if textRequest.MaxTokens < 0 || textRequest.MaxTokens > math.MaxInt32/2 { | ||||||
|  | 		return errorWrapper(errors.New("max_tokens is invalid"), "invalid_max_tokens", http.StatusBadRequest) | ||||||
|  | 	} | ||||||
| 	if relayMode == RelayModeModerations && textRequest.Model == "" { | 	if relayMode == RelayModeModerations && textRequest.Model == "" { | ||||||
| 		textRequest.Model = "text-moderation-latest" | 		textRequest.Model = "text-moderation-latest" | ||||||
| 	} | 	} | ||||||
| @@ -118,6 +122,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 		apiType = APITypeAIProxyLibrary | 		apiType = APITypeAIProxyLibrary | ||||||
| 	case common.ChannelTypeTencent: | 	case common.ChannelTypeTencent: | ||||||
| 		apiType = APITypeTencent | 		apiType = APITypeTencent | ||||||
|  | 	case common.ChannelTypeGemini: | ||||||
|  | 		apiType = APITypeGemini | ||||||
| 	} | 	} | ||||||
| 	baseURL := common.ChannelBaseURLs[channelType] | 	baseURL := common.ChannelBaseURLs[channelType] | ||||||
| 	requestURL := c.Request.URL.String() | 	requestURL := c.Request.URL.String() | ||||||
| @@ -177,6 +183,23 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 		apiKey := c.Request.Header.Get("Authorization") | 		apiKey := c.Request.Header.Get("Authorization") | ||||||
| 		apiKey = strings.TrimPrefix(apiKey, "Bearer ") | 		apiKey = strings.TrimPrefix(apiKey, "Bearer ") | ||||||
| 		fullRequestURL += "?key=" + apiKey | 		fullRequestURL += "?key=" + apiKey | ||||||
|  | 	case APITypeGemini: | ||||||
|  | 		requestBaseURL := "https://generativelanguage.googleapis.com" | ||||||
|  | 		if baseURL != "" { | ||||||
|  | 			requestBaseURL = baseURL | ||||||
|  | 		} | ||||||
|  | 		version := "v1" | ||||||
|  | 		if c.GetString("api_version") != "" { | ||||||
|  | 			version = c.GetString("api_version") | ||||||
|  | 		} | ||||||
|  | 		action := "generateContent" | ||||||
|  | 		if textRequest.Stream { | ||||||
|  | 			action = "streamGenerateContent" | ||||||
|  | 		} | ||||||
|  | 		fullRequestURL = fmt.Sprintf("%s/%s/models/%s:%s", requestBaseURL, version, textRequest.Model, action) | ||||||
|  | 		apiKey := c.Request.Header.Get("Authorization") | ||||||
|  | 		apiKey = strings.TrimPrefix(apiKey, "Bearer ") | ||||||
|  | 		fullRequestURL += "?key=" + apiKey | ||||||
| 	case APITypeZhipu: | 	case APITypeZhipu: | ||||||
| 		method := "invoke" | 		method := "invoke" | ||||||
| 		if textRequest.Stream { | 		if textRequest.Stream { | ||||||
| @@ -274,6 +297,13 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) | 			return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) | ||||||
| 		} | 		} | ||||||
| 		requestBody = bytes.NewBuffer(jsonStr) | 		requestBody = bytes.NewBuffer(jsonStr) | ||||||
|  | 	case APITypeGemini: | ||||||
|  | 		geminiChatRequest := requestOpenAI2Gemini(textRequest) | ||||||
|  | 		jsonStr, err := json.Marshal(geminiChatRequest) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) | ||||||
|  | 		} | ||||||
|  | 		requestBody = bytes.NewBuffer(jsonStr) | ||||||
| 	case APITypeZhipu: | 	case APITypeZhipu: | ||||||
| 		zhipuRequest := requestOpenAI2Zhipu(textRequest) | 		zhipuRequest := requestOpenAI2Zhipu(textRequest) | ||||||
| 		jsonStr, err := json.Marshal(zhipuRequest) | 		jsonStr, err := json.Marshal(zhipuRequest) | ||||||
| @@ -367,6 +397,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			req.Header.Set("Authorization", apiKey) | 			req.Header.Set("Authorization", apiKey) | ||||||
| 		case APITypePaLM: | 		case APITypePaLM: | ||||||
| 			// do not set Authorization header | 			// do not set Authorization header | ||||||
|  | 		case APITypeGemini: | ||||||
|  | 			// do not set Authorization header | ||||||
| 		default: | 		default: | ||||||
| 			req.Header.Set("Authorization", "Bearer "+apiKey) | 			req.Header.Set("Authorization", "Bearer "+apiKey) | ||||||
| 		} | 		} | ||||||
| @@ -527,6 +559,25 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
|  | 	case APITypeGemini: | ||||||
|  | 		if textRequest.Stream { | ||||||
|  | 			err, responseText := geminiChatStreamHandler(c, resp) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			textResponse.Usage.PromptTokens = promptTokens | ||||||
|  | 			textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) | ||||||
|  | 			return nil | ||||||
|  | 		} else { | ||||||
|  | 			err, usage := geminiChatHandler(c, resp, promptTokens, textRequest.Model) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if usage != nil { | ||||||
|  | 				textResponse.Usage = *usage | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
| 	case APITypeZhipu: | 	case APITypeZhipu: | ||||||
| 		if isStream { | 		if isStream { | ||||||
| 			err, usage := zhipuStreamHandler(c, resp) | 			err, usage := zhipuStreamHandler(c, resp) | ||||||
|   | |||||||
| @@ -263,11 +263,52 @@ func setEventStreamHeaders(c *gin.Context) { | |||||||
| 	c.Writer.Header().Set("X-Accel-Buffering", "no") | 	c.Writer.Header().Set("X-Accel-Buffering", "no") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type GeneralErrorResponse struct { | ||||||
|  | 	Error    OpenAIError `json:"error"` | ||||||
|  | 	Message  string      `json:"message"` | ||||||
|  | 	Msg      string      `json:"msg"` | ||||||
|  | 	Err      string      `json:"err"` | ||||||
|  | 	ErrorMsg string      `json:"error_msg"` | ||||||
|  | 	Header   struct { | ||||||
|  | 		Message string `json:"message"` | ||||||
|  | 	} `json:"header"` | ||||||
|  | 	Response struct { | ||||||
|  | 		Error struct { | ||||||
|  | 			Message string `json:"message"` | ||||||
|  | 		} `json:"error"` | ||||||
|  | 	} `json:"response"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (e GeneralErrorResponse) ToMessage() string { | ||||||
|  | 	if e.Error.Message != "" { | ||||||
|  | 		return e.Error.Message | ||||||
|  | 	} | ||||||
|  | 	if e.Message != "" { | ||||||
|  | 		return e.Message | ||||||
|  | 	} | ||||||
|  | 	if e.Msg != "" { | ||||||
|  | 		return e.Msg | ||||||
|  | 	} | ||||||
|  | 	if e.Err != "" { | ||||||
|  | 		return e.Err | ||||||
|  | 	} | ||||||
|  | 	if e.ErrorMsg != "" { | ||||||
|  | 		return e.ErrorMsg | ||||||
|  | 	} | ||||||
|  | 	if e.Header.Message != "" { | ||||||
|  | 		return e.Header.Message | ||||||
|  | 	} | ||||||
|  | 	if e.Response.Error.Message != "" { | ||||||
|  | 		return e.Response.Error.Message | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
| func relayErrorHandler(resp *http.Response) (openAIErrorWithStatusCode *OpenAIErrorWithStatusCode) { | func relayErrorHandler(resp *http.Response) (openAIErrorWithStatusCode *OpenAIErrorWithStatusCode) { | ||||||
| 	openAIErrorWithStatusCode = &OpenAIErrorWithStatusCode{ | 	openAIErrorWithStatusCode = &OpenAIErrorWithStatusCode{ | ||||||
| 		StatusCode: resp.StatusCode, | 		StatusCode: resp.StatusCode, | ||||||
| 		OpenAIError: OpenAIError{ | 		OpenAIError: OpenAIError{ | ||||||
| 			Message: fmt.Sprintf("bad response status code %d", resp.StatusCode), | 			Message: "", | ||||||
| 			Type:    "upstream_error", | 			Type:    "upstream_error", | ||||||
| 			Code:    "bad_response_status_code", | 			Code:    "bad_response_status_code", | ||||||
| 			Param:   strconv.Itoa(resp.StatusCode), | 			Param:   strconv.Itoa(resp.StatusCode), | ||||||
| @@ -281,12 +322,20 @@ func relayErrorHandler(resp *http.Response) (openAIErrorWithStatusCode *OpenAIEr | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	var textResponse TextResponse | 	var errResponse GeneralErrorResponse | ||||||
| 	err = json.Unmarshal(responseBody, &textResponse) | 	err = json.Unmarshal(responseBody, &errResponse) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	openAIErrorWithStatusCode.OpenAIError = textResponse.Error | 	if errResponse.Error.Message != "" { | ||||||
|  | 		// OpenAI format error, so we override the default one | ||||||
|  | 		openAIErrorWithStatusCode.OpenAIError = errResponse.Error | ||||||
|  | 	} else { | ||||||
|  | 		openAIErrorWithStatusCode.OpenAIError.Message = errResponse.ToMessage() | ||||||
|  | 	} | ||||||
|  | 	if openAIErrorWithStatusCode.OpenAIError.Message == "" { | ||||||
|  | 		openAIErrorWithStatusCode.OpenAIError.Message = fmt.Sprintf("bad response status code %d", resp.StatusCode) | ||||||
|  | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -230,7 +230,13 @@ func xunfeiHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId strin | |||||||
| 		case stop = <-stopChan: | 		case stop = <-stopChan: | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	if len(xunfeiResponse.Payload.Choices.Text) == 0 { | ||||||
|  | 		xunfeiResponse.Payload.Choices.Text = []XunfeiChatResponseTextItem{ | ||||||
|  | 			{ | ||||||
|  | 				Content: "", | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	xunfeiResponse.Payload.Choices.Text[0].Content = content | 	xunfeiResponse.Payload.Choices.Text[0].Content = content | ||||||
|  |  | ||||||
| 	response := responseXunfei2OpenAI(&xunfeiResponse) | 	response := responseXunfei2OpenAI(&xunfeiResponse) | ||||||
|   | |||||||
| @@ -236,7 +236,7 @@ type ChatCompletionsStreamResponseChoice struct { | |||||||
| 	Delta struct { | 	Delta struct { | ||||||
| 		Content string `json:"content"` | 		Content string `json:"content"` | ||||||
| 	} `json:"delta"` | 	} `json:"delta"` | ||||||
| 	FinishReason *string `json:"finish_reason"` | 	FinishReason *string `json:"finish_reason,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ChatCompletionsStreamResponse struct { | type ChatCompletionsStreamResponse struct { | ||||||
|   | |||||||
| @@ -87,6 +87,8 @@ func Distribute() func(c *gin.Context) { | |||||||
| 			c.Set("api_version", channel.Other) | 			c.Set("api_version", channel.Other) | ||||||
| 		case common.ChannelTypeXunfei: | 		case common.ChannelTypeXunfei: | ||||||
| 			c.Set("api_version", channel.Other) | 			c.Set("api_version", channel.Other) | ||||||
|  | 		case common.ChannelTypeGemini: | ||||||
|  | 			c.Set("api_version", channel.Other) | ||||||
| 		case common.ChannelTypeAIProxyLibrary: | 		case common.ChannelTypeAIProxyLibrary: | ||||||
| 			c.Set("library_id", channel.Other) | 			c.Set("library_id", channel.Other) | ||||||
| 		case common.ChannelTypeAli: | 		case common.ChannelTypeAli: | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package model | package model | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"gorm.io/driver/mysql" | 	"gorm.io/driver/mysql" | ||||||
| 	"gorm.io/driver/postgres" | 	"gorm.io/driver/postgres" | ||||||
| 	"gorm.io/driver/sqlite" | 	"gorm.io/driver/sqlite" | ||||||
| @@ -59,7 +60,8 @@ func chooseDB() (*gorm.DB, error) { | |||||||
| 	// Use SQLite | 	// Use SQLite | ||||||
| 	common.SysLog("SQL_DSN not set, using SQLite as database") | 	common.SysLog("SQL_DSN not set, using SQLite as database") | ||||||
| 	common.UsingSQLite = true | 	common.UsingSQLite = true | ||||||
| 	return gorm.Open(sqlite.Open(common.SQLitePath), &gorm.Config{ | 	config := fmt.Sprintf("?_busy_timeout=%d", common.SQLiteBusyTimeout) | ||||||
|  | 	return gorm.Open(sqlite.Open(common.SQLitePath+config), &gorm.Config{ | ||||||
| 		PrepareStmt: true, // precompile SQL | 		PrepareStmt: true, // precompile SQL | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ export const CHANNEL_OPTIONS = [ | |||||||
|   { key: 14, text: 'Anthropic Claude', value: 14, color: 'black' }, |   { key: 14, text: 'Anthropic Claude', value: 14, color: 'black' }, | ||||||
|   { key: 3, text: 'Azure OpenAI', value: 3, color: 'olive' }, |   { key: 3, text: 'Azure OpenAI', value: 3, color: 'olive' }, | ||||||
|   { key: 11, text: 'Google PaLM2', value: 11, color: 'orange' }, |   { key: 11, text: 'Google PaLM2', value: 11, color: 'orange' }, | ||||||
|  |   { key: 24, text: 'Google Gemini', value: 24, color: 'orange' }, | ||||||
|   { key: 15, text: '百度文心千帆', value: 15, color: 'blue' }, |   { key: 15, text: '百度文心千帆', value: 15, color: 'blue' }, | ||||||
|   { key: 17, text: '阿里通义千问', value: 17, color: 'orange' }, |   { key: 17, text: '阿里通义千问', value: 17, color: 'orange' }, | ||||||
|   { key: 18, text: '讯飞星火认知', value: 18, color: 'blue' }, |   { key: 18, text: '讯飞星火认知', value: 18, color: 'blue' }, | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ const EditChannel = () => { | |||||||
|           localModels = ['ERNIE-Bot', 'ERNIE-Bot-turbo', 'ERNIE-Bot-4', 'Embedding-V1']; |           localModels = ['ERNIE-Bot', 'ERNIE-Bot-turbo', 'ERNIE-Bot-4', 'Embedding-V1']; | ||||||
|           break; |           break; | ||||||
|         case 17: |         case 17: | ||||||
|           localModels = ['qwen-turbo', 'qwen-plus', 'text-embedding-v1']; |           localModels = ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-longcontext', 'text-embedding-v1']; | ||||||
|           break; |           break; | ||||||
|         case 16: |         case 16: | ||||||
|           localModels = ['chatglm_turbo', 'chatglm_pro', 'chatglm_std', 'chatglm_lite']; |           localModels = ['chatglm_turbo', 'chatglm_pro', 'chatglm_std', 'chatglm_lite']; | ||||||
| @@ -83,6 +83,9 @@ const EditChannel = () => { | |||||||
|         case 23: |         case 23: | ||||||
|           localModels = ['hunyuan']; |           localModels = ['hunyuan']; | ||||||
|           break; |           break; | ||||||
|  |         case 24: | ||||||
|  |           localModels = ['gemini-pro']; | ||||||
|  |           break; | ||||||
|       } |       } | ||||||
|       setInputs((inputs) => ({ ...inputs, models: localModels })); |       setInputs((inputs) => ({ ...inputs, models: localModels })); | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user