mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-10-31 13:53:41 +08:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			v0.5.0-alp
			...
			v0.5.2-alp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3e81d8af45 | ||
|  | b8cb86c2c1 | ||
|  | f45d586400 | ||
|  | 50dec03ff3 | ||
|  | f31d400b6f | ||
|  | 130e6bfd83 | ||
|  | d1335ebc01 | ||
|  | e92da7928b | ||
|  | d1b6f492b6 | ||
|  | b9f6461dd4 | ||
|  | 0a39521a3d | ||
|  | c134604cee | ||
|  | 929e43ef81 | ||
|  | dce8bbe1ca | ||
|  | bc2f48b1f2 | 
							
								
								
									
										16
									
								
								README.en.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.en.md
									
									
									
									
									
								
							| @@ -57,15 +57,13 @@ _✨ Access all LLM through the standard OpenAI API format, easy to deploy & use | |||||||
| > **Note**: The latest image pulled from Docker may be an `alpha` release. Specify the version manually if you require stability. | > **Note**: The latest image pulled from Docker may be an `alpha` release. Specify the version manually if you require stability. | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
| 1. Supports multiple API access channels: | 1. Support for multiple large models: | ||||||
|     + [x] Official OpenAI channel (support proxy configuration) |    + [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] **Azure OpenAI API** |    + [x] [Anthropic Claude Series Models](https://anthropic.com) | ||||||
|     + [x] [API Distribute](https://api.gptjk.top/register?aff=QGxj) |    + [x] [Google PaLM2 Series Models](https://developers.generativeai.google) | ||||||
|     + [x] [OpenAI-SB](https://openai-sb.com) |    + [x] [Baidu Wenxin Yiyuan Series Models](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) | ||||||
|     + [x] [API2D](https://api2d.com/r/197971) |    + [x] [Alibaba Tongyi Qianwen Series Models](https://help.aliyun.com/document_detail/2400395.html) | ||||||
|     + [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf) |    + [x] [Zhipu ChatGLM Series Models](https://bigmodel.cn) | ||||||
|     + [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (invitation code: `OneAPI`) |  | ||||||
|     + [x] Custom channel: Various third-party proxy services not included in the list |  | ||||||
| 2. Supports access to multiple channels through **load balancing**. | 2. Supports access to multiple channels through **load balancing**. | ||||||
| 3. Supports **stream mode** that enables typewriter-like effect through stream transmission. | 3. Supports **stream mode** that enables typewriter-like effect through stream transmission. | ||||||
| 4. Supports **multi-machine deployment**. [See here](#multi-machine-deployment) for more details. | 4. Supports **multi-machine deployment**. [See here](#multi-machine-deployment) for more details. | ||||||
|   | |||||||
| @@ -63,9 +63,9 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用  | |||||||
|    + [x] [Anthropic Claude 系列模型](https://anthropic.com) |    + [x] [Anthropic Claude 系列模型](https://anthropic.com) | ||||||
|    + [x] [Google PaLM2 系列模型](https://developers.generativeai.google) |    + [x] [Google PaLM2 系列模型](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] [智谱 ChatGLM 系列模型](https://bigmodel.cn) |    + [x] [智谱 ChatGLM 系列模型](https://bigmodel.cn) | ||||||
| 2. 支持配置镜像以及众多第三方代理服务: | 2. 支持配置镜像以及众多第三方代理服务: | ||||||
|    + [x] [API Distribute](https://api.gptjk.top/register?aff=QGxj) |  | ||||||
|    + [x] [OpenAI-SB](https://openai-sb.com) |    + [x] [OpenAI-SB](https://openai-sb.com) | ||||||
|    + [x] [API2D](https://api2d.com/r/197971) |    + [x] [API2D](https://api2d.com/r/197971) | ||||||
|    + [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf) |    + [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf) | ||||||
|   | |||||||
| @@ -156,24 +156,26 @@ const ( | |||||||
| 	ChannelTypeAnthropic = 14 | 	ChannelTypeAnthropic = 14 | ||||||
| 	ChannelTypeBaidu     = 15 | 	ChannelTypeBaidu     = 15 | ||||||
| 	ChannelTypeZhipu     = 16 | 	ChannelTypeZhipu     = 16 | ||||||
|  | 	ChannelTypeAli       = 17 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ChannelBaseURLs = []string{ | var ChannelBaseURLs = []string{ | ||||||
| 	"",                              // 0 | 	"",                               // 0 | ||||||
| 	"https://api.openai.com",        // 1 | 	"https://api.openai.com",         // 1 | ||||||
| 	"https://oa.api2d.net",          // 2 | 	"https://oa.api2d.net",           // 2 | ||||||
| 	"",                              // 3 | 	"",                               // 3 | ||||||
| 	"https://api.closeai-proxy.xyz", // 4 | 	"https://api.closeai-proxy.xyz",  // 4 | ||||||
| 	"https://api.openai-sb.com",     // 5 | 	"https://api.openai-sb.com",      // 5 | ||||||
| 	"https://api.openaimax.com",     // 6 | 	"https://api.openaimax.com",      // 6 | ||||||
| 	"https://api.ohmygpt.com",       // 7 | 	"https://api.ohmygpt.com",        // 7 | ||||||
| 	"",                              // 8 | 	"",                               // 8 | ||||||
| 	"https://api.caipacity.com",     // 9 | 	"https://api.caipacity.com",      // 9 | ||||||
| 	"https://api.aiproxy.io",        // 10 | 	"https://api.aiproxy.io",         // 10 | ||||||
| 	"",                              // 11 | 	"",                               // 11 | ||||||
| 	"https://api.api2gpt.com",       // 12 | 	"https://api.api2gpt.com",        // 12 | ||||||
| 	"https://api.aigc2d.com",        // 13 | 	"https://api.aigc2d.com",         // 13 | ||||||
| 	"https://api.anthropic.com",     // 14 | 	"https://api.anthropic.com",      // 14 | ||||||
| 	"https://aip.baidubce.com",      // 15 | 	"https://aip.baidubce.com",       // 15 | ||||||
| 	"https://open.bigmodel.cn",      // 16 | 	"https://open.bigmodel.cn",       // 16 | ||||||
|  | 	"https://dashscope.aliyuncs.com", // 17 | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,10 +42,13 @@ var ModelRatio = map[string]float64{ | |||||||
| 	"claude-2":                30, | 	"claude-2":                30, | ||||||
| 	"ERNIE-Bot":               0.8572, // ¥0.012 / 1k tokens | 	"ERNIE-Bot":               0.8572, // ¥0.012 / 1k tokens | ||||||
| 	"ERNIE-Bot-turbo":         0.5715, // ¥0.008 / 1k tokens | 	"ERNIE-Bot-turbo":         0.5715, // ¥0.008 / 1k tokens | ||||||
|  | 	"Embedding-V1":            0.1429, // ¥0.002 / 1k tokens | ||||||
| 	"PaLM-2":                  1, | 	"PaLM-2":                  1, | ||||||
| 	"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-v1":                 0.8572, // TBD: https://help.aliyun.com/document_detail/2399482.html?spm=a2c4g.2399482.0.0.1ad347feilAgag | ||||||
|  | 	"qwen-plus-v1":            0.5715, // Same as above | ||||||
| } | } | ||||||
|  |  | ||||||
| func ModelRatio2JSONString() string { | func ModelRatio2JSONString() string { | ||||||
|   | |||||||
| @@ -16,6 +16,14 @@ import ( | |||||||
|  |  | ||||||
| func testChannel(channel *model.Channel, request ChatRequest) (error, *OpenAIError) { | func testChannel(channel *model.Channel, request ChatRequest) (error, *OpenAIError) { | ||||||
| 	switch channel.Type { | 	switch channel.Type { | ||||||
|  | 	case common.ChannelTypePaLM: | ||||||
|  | 		fallthrough | ||||||
|  | 	case common.ChannelTypeAnthropic: | ||||||
|  | 		fallthrough | ||||||
|  | 	case common.ChannelTypeBaidu: | ||||||
|  | 		fallthrough | ||||||
|  | 	case common.ChannelTypeZhipu: | ||||||
|  | 		return errors.New("该渠道类型当前版本不支持测试,请手动测试"), nil | ||||||
| 	case common.ChannelTypeAzure: | 	case common.ChannelTypeAzure: | ||||||
| 		request.Model = "gpt-35-turbo" | 		request.Model = "gpt-35-turbo" | ||||||
| 	default: | 	default: | ||||||
|   | |||||||
| @@ -288,6 +288,15 @@ func init() { | |||||||
| 			Root:       "ERNIE-Bot-turbo", | 			Root:       "ERNIE-Bot-turbo", | ||||||
| 			Parent:     nil, | 			Parent:     nil, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Id:         "Embedding-V1", | ||||||
|  | 			Object:     "model", | ||||||
|  | 			Created:    1677649963, | ||||||
|  | 			OwnedBy:    "baidu", | ||||||
|  | 			Permission: permission, | ||||||
|  | 			Root:       "Embedding-V1", | ||||||
|  | 			Parent:     nil, | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Id:         "PaLM-2", | 			Id:         "PaLM-2", | ||||||
| 			Object:     "model", | 			Object:     "model", | ||||||
| @@ -324,6 +333,24 @@ func init() { | |||||||
| 			Root:       "chatglm_lite", | 			Root:       "chatglm_lite", | ||||||
| 			Parent:     nil, | 			Parent:     nil, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Id:         "qwen-v1", | ||||||
|  | 			Object:     "model", | ||||||
|  | 			Created:    1677649963, | ||||||
|  | 			OwnedBy:    "ali", | ||||||
|  | 			Permission: permission, | ||||||
|  | 			Root:       "qwen-v1", | ||||||
|  | 			Parent:     nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Id:         "qwen-plus-v1", | ||||||
|  | 			Object:     "model", | ||||||
|  | 			Created:    1677649963, | ||||||
|  | 			OwnedBy:    "ali", | ||||||
|  | 			Permission: permission, | ||||||
|  | 			Root:       "qwen-plus-v1", | ||||||
|  | 			Parent:     nil, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	openAIModelsMap = make(map[string]OpenAIModels) | 	openAIModelsMap = make(map[string]OpenAIModels) | ||||||
| 	for _, model := range openAIModels { | 	for _, model := range openAIModels { | ||||||
|   | |||||||
							
								
								
									
										240
									
								
								controller/relay-ali.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								controller/relay-ali.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | |||||||
|  | package controller | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  | 	"one-api/common" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // https://help.aliyun.com/document_detail/613695.html?spm=a2c4g.2399480.0.0.1adb778fAdzP9w#341800c0f8w0r | ||||||
|  |  | ||||||
|  | type AliMessage struct { | ||||||
|  | 	User string `json:"user"` | ||||||
|  | 	Bot  string `json:"bot"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AliInput struct { | ||||||
|  | 	Prompt  string       `json:"prompt"` | ||||||
|  | 	History []AliMessage `json:"history"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AliParameters struct { | ||||||
|  | 	TopP         float64 `json:"top_p,omitempty"` | ||||||
|  | 	TopK         int     `json:"top_k,omitempty"` | ||||||
|  | 	Seed         uint64  `json:"seed,omitempty"` | ||||||
|  | 	EnableSearch bool    `json:"enable_search,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AliChatRequest struct { | ||||||
|  | 	Model      string        `json:"model"` | ||||||
|  | 	Input      AliInput      `json:"input"` | ||||||
|  | 	Parameters AliParameters `json:"parameters,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AliError struct { | ||||||
|  | 	Code      string `json:"code"` | ||||||
|  | 	Message   string `json:"message"` | ||||||
|  | 	RequestId string `json:"request_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AliUsage struct { | ||||||
|  | 	InputTokens  int `json:"input_tokens"` | ||||||
|  | 	OutputTokens int `json:"output_tokens"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AliOutput struct { | ||||||
|  | 	Text         string `json:"text"` | ||||||
|  | 	FinishReason string `json:"finish_reason"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AliChatResponse struct { | ||||||
|  | 	Output AliOutput `json:"output"` | ||||||
|  | 	Usage  AliUsage  `json:"usage"` | ||||||
|  | 	AliError | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func requestOpenAI2Ali(request GeneralOpenAIRequest) *AliChatRequest { | ||||||
|  | 	messages := make([]AliMessage, 0, len(request.Messages)) | ||||||
|  | 	prompt := "" | ||||||
|  | 	for i := 0; i < len(request.Messages); i++ { | ||||||
|  | 		message := request.Messages[i] | ||||||
|  | 		if message.Role == "system" { | ||||||
|  | 			messages = append(messages, AliMessage{ | ||||||
|  | 				User: message.Content, | ||||||
|  | 				Bot:  "Okay", | ||||||
|  | 			}) | ||||||
|  | 			continue | ||||||
|  | 		} else { | ||||||
|  | 			if i == len(request.Messages)-1 { | ||||||
|  | 				prompt = message.Content | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			messages = append(messages, AliMessage{ | ||||||
|  | 				User: message.Content, | ||||||
|  | 				Bot:  request.Messages[i+1].Content, | ||||||
|  | 			}) | ||||||
|  | 			i++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return &AliChatRequest{ | ||||||
|  | 		Model: request.Model, | ||||||
|  | 		Input: AliInput{ | ||||||
|  | 			Prompt:  prompt, | ||||||
|  | 			History: messages, | ||||||
|  | 		}, | ||||||
|  | 		//Parameters: AliParameters{  // ChatGPT's parameters are not compatible with Ali's | ||||||
|  | 		//	TopP: request.TopP, | ||||||
|  | 		//	TopK: 50, | ||||||
|  | 		//	//Seed:         0, | ||||||
|  | 		//	//EnableSearch: false, | ||||||
|  | 		//}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func responseAli2OpenAI(response *AliChatResponse) *OpenAITextResponse { | ||||||
|  | 	choice := OpenAITextResponseChoice{ | ||||||
|  | 		Index: 0, | ||||||
|  | 		Message: Message{ | ||||||
|  | 			Role:    "assistant", | ||||||
|  | 			Content: response.Output.Text, | ||||||
|  | 		}, | ||||||
|  | 		FinishReason: response.Output.FinishReason, | ||||||
|  | 	} | ||||||
|  | 	fullTextResponse := OpenAITextResponse{ | ||||||
|  | 		Id:      response.RequestId, | ||||||
|  | 		Object:  "chat.completion", | ||||||
|  | 		Created: common.GetTimestamp(), | ||||||
|  | 		Choices: []OpenAITextResponseChoice{choice}, | ||||||
|  | 		Usage: Usage{ | ||||||
|  | 			PromptTokens:     response.Usage.InputTokens, | ||||||
|  | 			CompletionTokens: response.Usage.OutputTokens, | ||||||
|  | 			TotalTokens:      response.Usage.InputTokens + response.Usage.OutputTokens, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	return &fullTextResponse | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *ChatCompletionsStreamResponse { | ||||||
|  | 	var choice ChatCompletionsStreamResponseChoice | ||||||
|  | 	choice.Delta.Content = aliResponse.Output.Text | ||||||
|  | 	choice.FinishReason = aliResponse.Output.FinishReason | ||||||
|  | 	response := ChatCompletionsStreamResponse{ | ||||||
|  | 		Id:      aliResponse.RequestId, | ||||||
|  | 		Object:  "chat.completion.chunk", | ||||||
|  | 		Created: common.GetTimestamp(), | ||||||
|  | 		Model:   "ernie-bot", | ||||||
|  | 		Choices: []ChatCompletionsStreamResponseChoice{choice}, | ||||||
|  | 	} | ||||||
|  | 	return &response | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func aliStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { | ||||||
|  | 	var usage Usage | ||||||
|  | 	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 | ||||||
|  | 	}) | ||||||
|  | 	dataChan := make(chan string) | ||||||
|  | 	stopChan := make(chan bool) | ||||||
|  | 	go func() { | ||||||
|  | 		for scanner.Scan() { | ||||||
|  | 			data := scanner.Text() | ||||||
|  | 			if len(data) < 5 { // ignore blank line or wrong format | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			if data[:5] != "data:" { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			data = data[5:] | ||||||
|  | 			dataChan <- data | ||||||
|  | 		} | ||||||
|  | 		stopChan <- true | ||||||
|  | 	}() | ||||||
|  | 	c.Writer.Header().Set("Content-Type", "text/event-stream") | ||||||
|  | 	c.Writer.Header().Set("Cache-Control", "no-cache") | ||||||
|  | 	c.Writer.Header().Set("Connection", "keep-alive") | ||||||
|  | 	c.Writer.Header().Set("Transfer-Encoding", "chunked") | ||||||
|  | 	c.Writer.Header().Set("X-Accel-Buffering", "no") | ||||||
|  | 	lastResponseText := "" | ||||||
|  | 	c.Stream(func(w io.Writer) bool { | ||||||
|  | 		select { | ||||||
|  | 		case data := <-dataChan: | ||||||
|  | 			var aliResponse AliChatResponse | ||||||
|  | 			err := json.Unmarshal([]byte(data), &aliResponse) | ||||||
|  | 			if err != nil { | ||||||
|  | 				common.SysError("error unmarshalling stream response: " + err.Error()) | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 			usage.PromptTokens += aliResponse.Usage.InputTokens | ||||||
|  | 			usage.CompletionTokens += aliResponse.Usage.OutputTokens | ||||||
|  | 			usage.TotalTokens += aliResponse.Usage.InputTokens + aliResponse.Usage.OutputTokens | ||||||
|  | 			response := streamResponseAli2OpenAI(&aliResponse) | ||||||
|  | 			response.Choices[0].Delta.Content = strings.TrimPrefix(response.Choices[0].Delta.Content, lastResponseText) | ||||||
|  | 			lastResponseText = aliResponse.Output.Text | ||||||
|  | 			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), nil | ||||||
|  | 	} | ||||||
|  | 	return nil, &usage | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func aliHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { | ||||||
|  | 	var aliResponse AliChatResponse | ||||||
|  | 	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 | ||||||
|  | 	} | ||||||
|  | 	err = json.Unmarshal(responseBody, &aliResponse) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil | ||||||
|  | 	} | ||||||
|  | 	if aliResponse.Code != "" { | ||||||
|  | 		return &OpenAIErrorWithStatusCode{ | ||||||
|  | 			OpenAIError: OpenAIError{ | ||||||
|  | 				Message: aliResponse.Message, | ||||||
|  | 				Type:    aliResponse.Code, | ||||||
|  | 				Param:   aliResponse.RequestId, | ||||||
|  | 				Code:    aliResponse.Code, | ||||||
|  | 			}, | ||||||
|  | 			StatusCode: resp.StatusCode, | ||||||
|  | 		}, nil | ||||||
|  | 	} | ||||||
|  | 	fullTextResponse := responseAli2OpenAI(&aliResponse) | ||||||
|  | 	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, &fullTextResponse.Usage | ||||||
|  | } | ||||||
| @@ -54,13 +54,43 @@ type BaiduChatStreamResponse struct { | |||||||
| 	IsEnd      bool `json:"is_end"` | 	IsEnd      bool `json:"is_end"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type BaiduEmbeddingRequest struct { | ||||||
|  | 	Input []string `json:"input"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BaiduEmbeddingData struct { | ||||||
|  | 	Object    string    `json:"object"` | ||||||
|  | 	Embedding []float64 `json:"embedding"` | ||||||
|  | 	Index     int       `json:"index"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type BaiduEmbeddingResponse struct { | ||||||
|  | 	Id      string               `json:"id"` | ||||||
|  | 	Object  string               `json:"object"` | ||||||
|  | 	Created int64                `json:"created"` | ||||||
|  | 	Data    []BaiduEmbeddingData `json:"data"` | ||||||
|  | 	Usage   Usage                `json:"usage"` | ||||||
|  | 	BaiduError | ||||||
|  | } | ||||||
|  |  | ||||||
| func requestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduChatRequest { | func requestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduChatRequest { | ||||||
| 	messages := make([]BaiduMessage, 0, len(request.Messages)) | 	messages := make([]BaiduMessage, 0, len(request.Messages)) | ||||||
| 	for _, message := range request.Messages { | 	for _, message := range request.Messages { | ||||||
| 		messages = append(messages, BaiduMessage{ | 		if message.Role == "system" { | ||||||
| 			Role:    message.Role, | 			messages = append(messages, BaiduMessage{ | ||||||
| 			Content: message.Content, | 				Role:    "user", | ||||||
| 		}) | 				Content: message.Content, | ||||||
|  | 			}) | ||||||
|  | 			messages = append(messages, BaiduMessage{ | ||||||
|  | 				Role:    "assistant", | ||||||
|  | 				Content: "Okay", | ||||||
|  | 			}) | ||||||
|  | 		} else { | ||||||
|  | 			messages = append(messages, BaiduMessage{ | ||||||
|  | 				Role:    message.Role, | ||||||
|  | 				Content: message.Content, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return &BaiduChatRequest{ | 	return &BaiduChatRequest{ | ||||||
| 		Messages: messages, | 		Messages: messages, | ||||||
| @@ -101,6 +131,36 @@ func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *ChatCom | |||||||
| 	return &response | 	return &response | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func embeddingRequestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduEmbeddingRequest { | ||||||
|  | 	baiduEmbeddingRequest := BaiduEmbeddingRequest{ | ||||||
|  | 		Input: nil, | ||||||
|  | 	} | ||||||
|  | 	switch request.Input.(type) { | ||||||
|  | 	case string: | ||||||
|  | 		baiduEmbeddingRequest.Input = []string{request.Input.(string)} | ||||||
|  | 	case []string: | ||||||
|  | 		baiduEmbeddingRequest.Input = request.Input.([]string) | ||||||
|  | 	} | ||||||
|  | 	return &baiduEmbeddingRequest | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func embeddingResponseBaidu2OpenAI(response *BaiduEmbeddingResponse) *OpenAIEmbeddingResponse { | ||||||
|  | 	openAIEmbeddingResponse := OpenAIEmbeddingResponse{ | ||||||
|  | 		Object: "list", | ||||||
|  | 		Data:   make([]OpenAIEmbeddingResponseItem, 0, len(response.Data)), | ||||||
|  | 		Model:  "baidu-embedding", | ||||||
|  | 		Usage:  response.Usage, | ||||||
|  | 	} | ||||||
|  | 	for _, item := range response.Data { | ||||||
|  | 		openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, OpenAIEmbeddingResponseItem{ | ||||||
|  | 			Object:    item.Object, | ||||||
|  | 			Index:     item.Index, | ||||||
|  | 			Embedding: item.Embedding, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return &openAIEmbeddingResponse | ||||||
|  | } | ||||||
|  |  | ||||||
| func baiduStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { | func baiduStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { | ||||||
| 	var usage Usage | 	var usage Usage | ||||||
| 	scanner := bufio.NewScanner(resp.Body) | 	scanner := bufio.NewScanner(resp.Body) | ||||||
| @@ -201,3 +261,39 @@ func baiduHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCo | |||||||
| 	_, err = c.Writer.Write(jsonResponse) | 	_, err = c.Writer.Write(jsonResponse) | ||||||
| 	return nil, &fullTextResponse.Usage | 	return nil, &fullTextResponse.Usage | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func baiduEmbeddingHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { | ||||||
|  | 	var baiduResponse BaiduEmbeddingResponse | ||||||
|  | 	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 | ||||||
|  | 	} | ||||||
|  | 	err = json.Unmarshal(responseBody, &baiduResponse) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil | ||||||
|  | 	} | ||||||
|  | 	if baiduResponse.ErrorMsg != "" { | ||||||
|  | 		return &OpenAIErrorWithStatusCode{ | ||||||
|  | 			OpenAIError: OpenAIError{ | ||||||
|  | 				Message: baiduResponse.ErrorMsg, | ||||||
|  | 				Type:    "baidu_error", | ||||||
|  | 				Param:   "", | ||||||
|  | 				Code:    baiduResponse.ErrorCode, | ||||||
|  | 			}, | ||||||
|  | 			StatusCode: resp.StatusCode, | ||||||
|  | 		}, nil | ||||||
|  | 	} | ||||||
|  | 	fullTextResponse := embeddingResponseBaidu2OpenAI(&baiduResponse) | ||||||
|  | 	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, &fullTextResponse.Usage | ||||||
|  | } | ||||||
|   | |||||||
| @@ -69,11 +69,11 @@ func requestOpenAI2Claude(textRequest GeneralOpenAIRequest) *ClaudeRequest { | |||||||
| 			prompt += fmt.Sprintf("\n\nHuman: %s", message.Content) | 			prompt += fmt.Sprintf("\n\nHuman: %s", message.Content) | ||||||
| 		} else if message.Role == "assistant" { | 		} else if message.Role == "assistant" { | ||||||
| 			prompt += fmt.Sprintf("\n\nAssistant: %s", message.Content) | 			prompt += fmt.Sprintf("\n\nAssistant: %s", message.Content) | ||||||
| 		} else { | 		} else if message.Role == "system" { | ||||||
| 			// ignore other roles | 			prompt += fmt.Sprintf("\n\nSystem: %s", message.Content) | ||||||
| 		} | 		} | ||||||
| 		prompt += "\n\nAssistant:" |  | ||||||
| 	} | 	} | ||||||
|  | 	prompt += "\n\nAssistant:" | ||||||
| 	claudeRequest.Prompt = prompt | 	claudeRequest.Prompt = prompt | ||||||
| 	return &claudeRequest | 	return &claudeRequest | ||||||
| } | } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ const ( | |||||||
| 	APITypePaLM | 	APITypePaLM | ||||||
| 	APITypeBaidu | 	APITypeBaidu | ||||||
| 	APITypeZhipu | 	APITypeZhipu | ||||||
|  | 	APITypeAli | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var httpClient *http.Client | var httpClient *http.Client | ||||||
| @@ -73,7 +74,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 	// map model name | 	// map model name | ||||||
| 	modelMapping := c.GetString("model_mapping") | 	modelMapping := c.GetString("model_mapping") | ||||||
| 	isModelMapped := false | 	isModelMapped := false | ||||||
| 	if modelMapping != "" { | 	if modelMapping != "" && modelMapping != "{}" { | ||||||
| 		modelMap := make(map[string]string) | 		modelMap := make(map[string]string) | ||||||
| 		err := json.Unmarshal([]byte(modelMapping), &modelMap) | 		err := json.Unmarshal([]byte(modelMapping), &modelMap) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -85,14 +86,18 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	apiType := APITypeOpenAI | 	apiType := APITypeOpenAI | ||||||
| 	if strings.HasPrefix(textRequest.Model, "claude") { | 	switch channelType { | ||||||
|  | 	case common.ChannelTypeAnthropic: | ||||||
| 		apiType = APITypeClaude | 		apiType = APITypeClaude | ||||||
| 	} else if strings.HasPrefix(textRequest.Model, "ERNIE") { | 	case common.ChannelTypeBaidu: | ||||||
| 		apiType = APITypeBaidu | 		apiType = APITypeBaidu | ||||||
| 	} else if strings.HasPrefix(textRequest.Model, "PaLM") { | 	case common.ChannelTypePaLM: | ||||||
| 		apiType = APITypePaLM | 		apiType = APITypePaLM | ||||||
| 	} else if strings.HasPrefix(textRequest.Model, "chatglm_") { | 	case common.ChannelTypeZhipu: | ||||||
| 		apiType = APITypeZhipu | 		apiType = APITypeZhipu | ||||||
|  | 	case common.ChannelTypeAli: | ||||||
|  | 		apiType = APITypeAli | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| 	baseURL := common.ChannelBaseURLs[channelType] | 	baseURL := common.ChannelBaseURLs[channelType] | ||||||
| 	requestURL := c.Request.URL.String() | 	requestURL := c.Request.URL.String() | ||||||
| @@ -134,12 +139,17 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant" | 			fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant" | ||||||
| 		case "BLOOMZ-7B": | 		case "BLOOMZ-7B": | ||||||
| 			fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/bloomz_7b1" | 			fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/bloomz_7b1" | ||||||
|  | 		case "Embedding-V1": | ||||||
|  | 			fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/embedding-v1" | ||||||
| 		} | 		} | ||||||
| 		apiKey := c.Request.Header.Get("Authorization") | 		apiKey := c.Request.Header.Get("Authorization") | ||||||
| 		apiKey = strings.TrimPrefix(apiKey, "Bearer ") | 		apiKey = strings.TrimPrefix(apiKey, "Bearer ") | ||||||
| 		fullRequestURL += "?access_token=" + apiKey // TODO: access token expire in 30 days | 		fullRequestURL += "?access_token=" + apiKey // TODO: access token expire in 30 days | ||||||
| 	case APITypePaLM: | 	case APITypePaLM: | ||||||
| 		fullRequestURL = "https://generativelanguage.googleapis.com/v1beta2/models/chat-bison-001:generateMessage" | 		fullRequestURL = "https://generativelanguage.googleapis.com/v1beta2/models/chat-bison-001:generateMessage" | ||||||
|  | 		if baseURL != "" { | ||||||
|  | 			fullRequestURL = fmt.Sprintf("%s/v1beta2/models/chat-bison-001:generateMessage", baseURL) | ||||||
|  | 		} | ||||||
| 		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 | ||||||
| @@ -149,6 +159,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			method = "sse-invoke" | 			method = "sse-invoke" | ||||||
| 		} | 		} | ||||||
| 		fullRequestURL = fmt.Sprintf("https://open.bigmodel.cn/api/paas/v3/model-api/%s/%s", textRequest.Model, method) | 		fullRequestURL = fmt.Sprintf("https://open.bigmodel.cn/api/paas/v3/model-api/%s/%s", textRequest.Model, method) | ||||||
|  | 	case APITypeAli: | ||||||
|  | 		fullRequestURL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation" | ||||||
| 	} | 	} | ||||||
| 	var promptTokens int | 	var promptTokens int | ||||||
| 	var completionTokens int | 	var completionTokens int | ||||||
| @@ -202,12 +214,20 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 		} | 		} | ||||||
| 		requestBody = bytes.NewBuffer(jsonStr) | 		requestBody = bytes.NewBuffer(jsonStr) | ||||||
| 	case APITypeBaidu: | 	case APITypeBaidu: | ||||||
| 		baiduRequest := requestOpenAI2Baidu(textRequest) | 		var jsonData []byte | ||||||
| 		jsonStr, err := json.Marshal(baiduRequest) | 		var err error | ||||||
|  | 		switch relayMode { | ||||||
|  | 		case RelayModeEmbeddings: | ||||||
|  | 			baiduEmbeddingRequest := embeddingRequestOpenAI2Baidu(textRequest) | ||||||
|  | 			jsonData, err = json.Marshal(baiduEmbeddingRequest) | ||||||
|  | 		default: | ||||||
|  | 			baiduRequest := requestOpenAI2Baidu(textRequest) | ||||||
|  | 			jsonData, err = json.Marshal(baiduRequest) | ||||||
|  | 		} | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			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(jsonData) | ||||||
| 	case APITypePaLM: | 	case APITypePaLM: | ||||||
| 		palmRequest := requestOpenAI2PaLM(textRequest) | 		palmRequest := requestOpenAI2PaLM(textRequest) | ||||||
| 		jsonStr, err := json.Marshal(palmRequest) | 		jsonStr, err := json.Marshal(palmRequest) | ||||||
| @@ -222,6 +242,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 APITypeAli: | ||||||
|  | 		aliRequest := requestOpenAI2Ali(textRequest) | ||||||
|  | 		jsonStr, err := json.Marshal(aliRequest) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) | ||||||
|  | 		} | ||||||
|  | 		requestBody = bytes.NewBuffer(jsonStr) | ||||||
| 	} | 	} | ||||||
| 	req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody) | 	req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -246,6 +273,11 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 	case APITypeZhipu: | 	case APITypeZhipu: | ||||||
| 		token := getZhipuToken(apiKey) | 		token := getZhipuToken(apiKey) | ||||||
| 		req.Header.Set("Authorization", token) | 		req.Header.Set("Authorization", token) | ||||||
|  | 	case APITypeAli: | ||||||
|  | 		req.Header.Set("Authorization", "Bearer "+apiKey) | ||||||
|  | 		if textRequest.Stream { | ||||||
|  | 			req.Header.Set("X-DashScope-SSE", "enable") | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) | 	req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) | ||||||
| 	req.Header.Set("Accept", c.Request.Header.Get("Accept")) | 	req.Header.Set("Accept", c.Request.Header.Get("Accept")) | ||||||
| @@ -276,7 +308,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			if strings.HasPrefix(textRequest.Model, "gpt-4") { | 			if strings.HasPrefix(textRequest.Model, "gpt-4") { | ||||||
| 				completionRatio = 2 | 				completionRatio = 2 | ||||||
| 			} | 			} | ||||||
| 			if isStream && apiType != APITypeBaidu && apiType != APITypeZhipu { | 			if isStream && apiType != APITypeBaidu && apiType != APITypeZhipu && apiType != APITypeAli { | ||||||
| 				completionTokens = countTokenText(streamResponseText, textRequest.Model) | 				completionTokens = countTokenText(streamResponseText, textRequest.Model) | ||||||
| 			} else { | 			} else { | ||||||
| 				promptTokens = textResponse.Usage.PromptTokens | 				promptTokens = textResponse.Usage.PromptTokens | ||||||
| @@ -364,7 +396,14 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
| 		} else { | 		} else { | ||||||
| 			err, usage := baiduHandler(c, resp) | 			var err *OpenAIErrorWithStatusCode | ||||||
|  | 			var usage *Usage | ||||||
|  | 			switch relayMode { | ||||||
|  | 			case RelayModeEmbeddings: | ||||||
|  | 				err, usage = baiduEmbeddingHandler(c, resp) | ||||||
|  | 			default: | ||||||
|  | 				err, usage = baiduHandler(c, resp) | ||||||
|  | 			} | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @@ -411,6 +450,26 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { | |||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
|  | 	case APITypeAli: | ||||||
|  | 		if isStream { | ||||||
|  | 			err, usage := aliStreamHandler(c, resp) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if usage != nil { | ||||||
|  | 				textResponse.Usage = *usage | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} else { | ||||||
|  | 			err, usage := aliHandler(c, resp) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if usage != nil { | ||||||
|  | 				textResponse.Usage = *usage | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
| 	default: | 	default: | ||||||
| 		return errorWrapper(errors.New("unknown api type"), "unknown_api_type", http.StatusInternalServerError) | 		return errorWrapper(errors.New("unknown api type"), "unknown_api_type", http.StatusInternalServerError) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -111,10 +111,21 @@ func getZhipuToken(apikey string) string { | |||||||
| func requestOpenAI2Zhipu(request GeneralOpenAIRequest) *ZhipuRequest { | func requestOpenAI2Zhipu(request GeneralOpenAIRequest) *ZhipuRequest { | ||||||
| 	messages := make([]ZhipuMessage, 0, len(request.Messages)) | 	messages := make([]ZhipuMessage, 0, len(request.Messages)) | ||||||
| 	for _, message := range request.Messages { | 	for _, message := range request.Messages { | ||||||
| 		messages = append(messages, ZhipuMessage{ | 		if message.Role == "system" { | ||||||
| 			Role:    message.Role, | 			messages = append(messages, ZhipuMessage{ | ||||||
| 			Content: message.Content, | 				Role:    "system", | ||||||
| 		}) | 				Content: message.Content, | ||||||
|  | 			}) | ||||||
|  | 			messages = append(messages, ZhipuMessage{ | ||||||
|  | 				Role:    "user", | ||||||
|  | 				Content: "Okay", | ||||||
|  | 			}) | ||||||
|  | 		} else { | ||||||
|  | 			messages = append(messages, ZhipuMessage{ | ||||||
|  | 				Role:    message.Role, | ||||||
|  | 				Content: message.Content, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return &ZhipuRequest{ | 	return &ZhipuRequest{ | ||||||
| 		Prompt:      messages, | 		Prompt:      messages, | ||||||
|   | |||||||
| @@ -99,6 +99,19 @@ type OpenAITextResponse struct { | |||||||
| 	Usage   `json:"usage"` | 	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 { | type ImageResponse struct { | ||||||
| 	Created int `json:"created"` | 	Created int `json:"created"` | ||||||
| 	Data    []struct { | 	Data    []struct { | ||||||
|   | |||||||
| @@ -503,5 +503,12 @@ | |||||||
|   "请输入 AZURE_OPENAI_ENDPOINT": "Please enter AZURE_OPENAI_ENDPOINT", |   "请输入 AZURE_OPENAI_ENDPOINT": "Please enter AZURE_OPENAI_ENDPOINT", | ||||||
|   "请输入自定义渠道的 Base URL": "Please enter the Base URL of the custom channel", |   "请输入自定义渠道的 Base URL": "Please enter the Base URL of the custom channel", | ||||||
|   "Homepage URL 填": "Fill in the Homepage URL", |   "Homepage URL 填": "Fill in the Homepage URL", | ||||||
|   "Authorization callback URL 填": "Fill in the Authorization callback URL" |   "Authorization callback URL 填": "Fill in the Authorization callback URL", | ||||||
|  |   "请为通道命名": "Please name the channel", | ||||||
|  |   "此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:": "This is optional, used to modify the model name in the request body, it's a JSON string, the key is the model name in the request, and the value is the model name to be replaced, for example:", | ||||||
|  |   "模型重定向": "Model redirection", | ||||||
|  |   "请输入渠道对应的鉴权密钥": "Please enter the authentication key corresponding to the channel", | ||||||
|  |   "注意,": "Note that, ", | ||||||
|  |   ",图片演示。": "related image demo.", | ||||||
|  |   "令牌创建成功,请在列表页面点击复制获取令牌!": "Token created successfully, please click copy on the list page to get the token!" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ func SetRelayRouter(router *gin.Engine) { | |||||||
| 	modelsRouter := router.Group("/v1/models") | 	modelsRouter := router.Group("/v1/models") | ||||||
| 	modelsRouter.Use(middleware.TokenAuth()) | 	modelsRouter.Use(middleware.TokenAuth()) | ||||||
| 	{ | 	{ | ||||||
| 		modelsRouter.GET("/", controller.ListModels) | 		modelsRouter.GET("", controller.ListModels) | ||||||
| 		modelsRouter.GET("/:model", controller.RetrieveModel) | 		modelsRouter.GET("/:model", controller.RetrieveModel) | ||||||
| 	} | 	} | ||||||
| 	relayV1Router := router.Group("/v1") | 	relayV1Router := router.Group("/v1") | ||||||
|   | |||||||
| @@ -363,9 +363,12 @@ const ChannelsTable = () => { | |||||||
|                   </Table.Cell> |                   </Table.Cell> | ||||||
|                   <Table.Cell> |                   <Table.Cell> | ||||||
|                     <Popup |                     <Popup | ||||||
|                       content={channel.balance_updated_time ? renderTimestamp(channel.balance_updated_time) : '未更新'} |                       trigger={<span onClick={() => { | ||||||
|                       key={channel.id} |                         updateChannelBalance(channel.id, channel.name, idx); | ||||||
|                       trigger={renderBalance(channel.type, channel.balance)} |                       }} style={{ cursor: 'pointer' }}> | ||||||
|  |                       {renderBalance(channel.type, channel.balance)} | ||||||
|  |                     </span>} | ||||||
|  |                       content="点击更新" | ||||||
|                       basic |                       basic | ||||||
|                     /> |                     /> | ||||||
|                   </Table.Cell> |                   </Table.Cell> | ||||||
| @@ -380,16 +383,16 @@ const ChannelsTable = () => { | |||||||
|                       > |                       > | ||||||
|                         测试 |                         测试 | ||||||
|                       </Button> |                       </Button> | ||||||
|                       <Button |                       {/*<Button*/} | ||||||
|                         size={'small'} |                       {/*  size={'small'}*/} | ||||||
|                         positive |                       {/*  positive*/} | ||||||
|                         loading={updatingBalance} |                       {/*  loading={updatingBalance}*/} | ||||||
|                         onClick={() => { |                       {/*  onClick={() => {*/} | ||||||
|                           updateChannelBalance(channel.id, channel.name, idx); |                       {/*    updateChannelBalance(channel.id, channel.name, idx);*/} | ||||||
|                         }} |                       {/*  }}*/} | ||||||
|                       > |                       {/*>*/} | ||||||
|                         更新余额 |                       {/*  更新余额*/} | ||||||
|                       </Button> |                       {/*</Button>*/} | ||||||
|                       <Popup |                       <Popup | ||||||
|                         trigger={ |                         trigger={ | ||||||
|                           <Button size='small' negative> |                           <Button size='small' negative> | ||||||
|   | |||||||
| @@ -227,7 +227,7 @@ const UsersTable = () => { | |||||||
|                       content={user.email ? user.email : '未绑定邮箱地址'} |                       content={user.email ? user.email : '未绑定邮箱地址'} | ||||||
|                       key={user.username} |                       key={user.username} | ||||||
|                       header={user.display_name ? user.display_name : user.username} |                       header={user.display_name ? user.display_name : user.username} | ||||||
|                       trigger={<span>{renderText(user.username, 10)}</span>} |                       trigger={<span>{renderText(user.username, 15)}</span>} | ||||||
|                       hoverable |                       hoverable | ||||||
|                     /> |                     /> | ||||||
|                   </Table.Cell> |                   </Table.Cell> | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ export const CHANNEL_OPTIONS = [ | |||||||
|   { 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: 15, text: '百度文心千帆', value: 15, color: 'blue' }, |   { key: 15, text: '百度文心千帆', value: 15, color: 'blue' }, | ||||||
|  |   { key: 17, text: '阿里通义千问', value: 17, color: 'orange' }, | ||||||
|   { key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet' }, |   { key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet' }, | ||||||
|   { key: 8, text: '自定义渠道', value: 8, color: 'pink' }, |   { key: 8, text: '自定义渠道', value: 8, color: 'pink' }, | ||||||
|   { key: 2, text: '代理:API2D', value: 2, color: 'blue' }, |   { key: 2, text: '代理:API2D', value: 2, color: 'blue' }, | ||||||
| @@ -14,5 +15,5 @@ export const CHANNEL_OPTIONS = [ | |||||||
|   { key: 6, text: '代理:OpenAI Max', value: 6, color: 'violet' }, |   { key: 6, text: '代理:OpenAI Max', value: 6, color: 'violet' }, | ||||||
|   { key: 9, text: '代理:AI.LS', value: 9, color: 'yellow' }, |   { key: 9, text: '代理:AI.LS', value: 9, color: 'yellow' }, | ||||||
|   { key: 12, text: '代理:API2GPT', value: 12, color: 'blue' }, |   { key: 12, text: '代理:API2GPT', value: 12, color: 'blue' }, | ||||||
|   { key: 13, text: '代理:AIGC2D', value: 13, color: 'purple' } |   { key: 13, text: '代理:AIGC2D', value: 13, color: 'purple' }, | ||||||
| ]; | ]; | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| export const toastConstants = { | export const toastConstants = { | ||||||
|   SUCCESS_TIMEOUT: 500, |   SUCCESS_TIMEOUT: 1500, | ||||||
|   INFO_TIMEOUT: 3000, |   INFO_TIMEOUT: 3000, | ||||||
|   ERROR_TIMEOUT: 5000, |   ERROR_TIMEOUT: 5000, | ||||||
|   WARNING_TIMEOUT: 10000, |   WARNING_TIMEOUT: 10000, | ||||||
|   | |||||||
| @@ -35,6 +35,27 @@ const EditChannel = () => { | |||||||
|   const [customModel, setCustomModel] = useState(''); |   const [customModel, setCustomModel] = useState(''); | ||||||
|   const handleInputChange = (e, { name, value }) => { |   const handleInputChange = (e, { name, value }) => { | ||||||
|     setInputs((inputs) => ({ ...inputs, [name]: value })); |     setInputs((inputs) => ({ ...inputs, [name]: value })); | ||||||
|  |     if (name === 'type' && inputs.models.length === 0) { | ||||||
|  |       let localModels = []; | ||||||
|  |       switch (value) { | ||||||
|  |         case 14: | ||||||
|  |           localModels = ['claude-instant-1', 'claude-2']; | ||||||
|  |           break; | ||||||
|  |         case 11: | ||||||
|  |           localModels = ['PaLM-2']; | ||||||
|  |           break; | ||||||
|  |         case 15: | ||||||
|  |           localModels = ['ERNIE-Bot', 'ERNIE-Bot-turbo', 'Embedding-V1']; | ||||||
|  |           break; | ||||||
|  |         case 17: | ||||||
|  |           localModels = ['qwen-v1', 'qwen-plus-v1']; | ||||||
|  |           break; | ||||||
|  |         case 16: | ||||||
|  |           localModels = ['chatglm_pro', 'chatglm_std', 'chatglm_lite']; | ||||||
|  |           break; | ||||||
|  |       } | ||||||
|  |       setInputs((inputs) => ({ ...inputs, models: localModels })); | ||||||
|  |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const loadChannel = async () => { |   const loadChannel = async () => { | ||||||
| @@ -132,7 +153,10 @@ const EditChannel = () => { | |||||||
|       localInputs.base_url = localInputs.base_url.slice(0, localInputs.base_url.length - 1); |       localInputs.base_url = localInputs.base_url.slice(0, localInputs.base_url.length - 1); | ||||||
|     } |     } | ||||||
|     if (localInputs.type === 3 && localInputs.other === '') { |     if (localInputs.type === 3 && localInputs.other === '') { | ||||||
|       localInputs.other = '2023-03-15-preview'; |       localInputs.other = '2023-06-01-preview'; | ||||||
|  |     } | ||||||
|  |     if (localInputs.model_mapping === '') { | ||||||
|  |       localInputs.model_mapping = '{}'; | ||||||
|     } |     } | ||||||
|     let res; |     let res; | ||||||
|     localInputs.models = localInputs.models.join(','); |     localInputs.models = localInputs.models.join(','); | ||||||
| @@ -192,7 +216,7 @@ const EditChannel = () => { | |||||||
|                   <Form.Input |                   <Form.Input | ||||||
|                     label='默认 API 版本' |                     label='默认 API 版本' | ||||||
|                     name='other' |                     name='other' | ||||||
|                     placeholder={'请输入默认 API 版本,例如:2023-03-15-preview,该配置可以被实际的请求查询参数所覆盖'} |                     placeholder={'请输入默认 API 版本,例如:2023-06-01-preview,该配置可以被实际的请求查询参数所覆盖'} | ||||||
|                     onChange={handleInputChange} |                     onChange={handleInputChange} | ||||||
|                     value={inputs.other} |                     value={inputs.other} | ||||||
|                     autoComplete='new-password' |                     autoComplete='new-password' | ||||||
| @@ -270,8 +294,8 @@ const EditChannel = () => { | |||||||
|             }}>清除所有模型</Button> |             }}>清除所有模型</Button> | ||||||
|             <Input |             <Input | ||||||
|               action={ |               action={ | ||||||
|                 <Button type={'button'} onClick={()=>{ |                 <Button type={'button'} onClick={() => { | ||||||
|                   if (customModel.trim() === "") return; |                   if (customModel.trim() === '') return; | ||||||
|                   if (inputs.models.includes(customModel)) return; |                   if (inputs.models.includes(customModel)) return; | ||||||
|                   let localModels = [...inputs.models]; |                   let localModels = [...inputs.models]; | ||||||
|                   localModels.push(customModel); |                   localModels.push(customModel); | ||||||
| @@ -279,9 +303,9 @@ const EditChannel = () => { | |||||||
|                   localModelOptions.push({ |                   localModelOptions.push({ | ||||||
|                     key: customModel, |                     key: customModel, | ||||||
|                     text: customModel, |                     text: customModel, | ||||||
|                     value: customModel, |                     value: customModel | ||||||
|                   }); |                   }); | ||||||
|                   setModelOptions(modelOptions=>{ |                   setModelOptions(modelOptions => { | ||||||
|                     return [...modelOptions, ...localModelOptions]; |                     return [...modelOptions, ...localModelOptions]; | ||||||
|                   }); |                   }); | ||||||
|                   setCustomModel(''); |                   setCustomModel(''); | ||||||
| @@ -297,7 +321,7 @@ const EditChannel = () => { | |||||||
|           </div> |           </div> | ||||||
|           <Form.Field> |           <Form.Field> | ||||||
|             <Form.TextArea |             <Form.TextArea | ||||||
|               label='模型映射' |               label='模型重定向' | ||||||
|               placeholder={`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`} |               placeholder={`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n${JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2)}`} | ||||||
|               name='model_mapping' |               name='model_mapping' | ||||||
|               onChange={handleInputChange} |               onChange={handleInputChange} | ||||||
| @@ -323,7 +347,7 @@ const EditChannel = () => { | |||||||
|                 label='密钥' |                 label='密钥' | ||||||
|                 name='key' |                 name='key' | ||||||
|                 required |                 required | ||||||
|                 placeholder={inputs.type === 15 ? "请输入 access token,当前版本暂不支持自动刷新,请每 30 天更新一次" : '请输入渠道对应的鉴权密钥'} |                 placeholder={inputs.type === 15 ? '请输入 access token,当前版本暂不支持自动刷新,请每 30 天更新一次' : '请输入渠道对应的鉴权密钥'} | ||||||
|                 onChange={handleInputChange} |                 onChange={handleInputChange} | ||||||
|                 value={inputs.key} |                 value={inputs.key} | ||||||
|                 autoComplete='new-password' |                 autoComplete='new-password' | ||||||
| @@ -354,7 +378,7 @@ const EditChannel = () => { | |||||||
|               </Form.Field> |               </Form.Field> | ||||||
|             ) |             ) | ||||||
|           } |           } | ||||||
|           <Button type={isEdit ? "button" : "submit"} positive onClick={submit}>提交</Button> |           <Button type={isEdit ? 'button' : 'submit'} positive onClick={submit}>提交</Button> | ||||||
|         </Form> |         </Form> | ||||||
|       </Segment> |       </Segment> | ||||||
|     </> |     </> | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ const EditToken = () => { | |||||||
|       if (isEdit) { |       if (isEdit) { | ||||||
|         showSuccess('令牌更新成功!'); |         showSuccess('令牌更新成功!'); | ||||||
|       } else { |       } else { | ||||||
|         showSuccess('令牌创建成功!'); |         showSuccess('令牌创建成功,请在列表页面点击复制获取令牌!'); | ||||||
|         setInputs(originInputs); |         setInputs(originInputs); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user