mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-10-24 10:23:41 +08:00
Compare commits
16 Commits
v0.6.6-alp
...
v0.6.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91b80ae879 | ||
|
|
2720e1a358 | ||
|
|
71f4403fd5 | ||
|
|
1f76c80553 | ||
|
|
7e027d2bd0 | ||
|
|
30f373b623 | ||
|
|
1c2654320e | ||
|
|
6cffb116b7 | ||
|
|
a84c7b38b7 | ||
|
|
1bd14af47b | ||
|
|
6170b91d1c | ||
|
|
04b49aa0ec | ||
|
|
ef88497f25 | ||
|
|
007906216d | ||
|
|
e64e7707a0 | ||
|
|
ea210b6ed7 |
@@ -86,6 +86,8 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
|
||||
+ [x] [Cohere](https://cohere.com/)
|
||||
+ [x] [DeepSeek](https://www.deepseek.com/)
|
||||
+ [x] [Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai/)
|
||||
+ [x] [DeepL](https://www.deepl.com/)
|
||||
+ [x] [together.ai](https://www.together.ai/)
|
||||
2. 支持配置镜像以及众多[第三方代理服务](https://iamazing.cn/page/openai-api-third-party-services)。
|
||||
3. 支持通过**负载均衡**的方式访问多个渠道。
|
||||
4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
|
||||
|
||||
@@ -5,6 +5,15 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/songquanpeng/one-api/common/config"
|
||||
"github.com/songquanpeng/one-api/common/ctxkey"
|
||||
"github.com/songquanpeng/one-api/common/logger"
|
||||
@@ -18,14 +27,6 @@ import (
|
||||
"github.com/songquanpeng/one-api/relay/meta"
|
||||
relaymodel "github.com/songquanpeng/one-api/relay/model"
|
||||
"github.com/songquanpeng/one-api/relay/relaymode"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -69,6 +70,7 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error
|
||||
adaptor.Init(meta)
|
||||
var modelName string
|
||||
modelList := adaptor.GetModelList()
|
||||
modelMap := channel.GetModelMapping()
|
||||
if len(modelList) != 0 {
|
||||
modelName = modelList[0]
|
||||
}
|
||||
@@ -77,6 +79,9 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error
|
||||
if len(modelNames) > 0 {
|
||||
modelName = modelNames[0]
|
||||
}
|
||||
if modelMap != nil && modelMap[modelName] != "" {
|
||||
modelName = modelMap[modelName]
|
||||
}
|
||||
}
|
||||
request := buildTestRequest()
|
||||
request.Model = modelName
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/cloudflare"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/cohere"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/coze"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/deepl"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/gemini"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/ollama"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
||||
@@ -52,6 +53,8 @@ func GetAdaptor(apiType int) adaptor.Adaptor {
|
||||
return &cohere.Adaptor{}
|
||||
case apitype.Cloudflare:
|
||||
return &cloudflare.Adaptor{}
|
||||
case apitype.DeepL:
|
||||
return &deepl.Adaptor{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/songquanpeng/one-api/common"
|
||||
"github.com/songquanpeng/one-api/common/helper"
|
||||
@@ -11,9 +15,6 @@ import (
|
||||
"github.com/songquanpeng/one-api/common/logger"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
||||
"github.com/songquanpeng/one-api/relay/model"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func stopReasonClaude2OpenAI(reason *string) string {
|
||||
@@ -176,10 +177,10 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
|
||||
if len(data) < 6 {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(data, "data: ") {
|
||||
if !strings.HasPrefix(data, "data:") {
|
||||
continue
|
||||
}
|
||||
data = strings.TrimPrefix(data, "data: ")
|
||||
data = strings.TrimPrefix(data, "data:")
|
||||
dataChan <- data
|
||||
}
|
||||
stopChan <- true
|
||||
@@ -192,7 +193,7 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
|
||||
select {
|
||||
case data := <-dataChan:
|
||||
// some implementations may add \r at the end of data
|
||||
data = strings.TrimSuffix(data, "\r")
|
||||
data = strings.TrimSpace(data)
|
||||
var claudeResponse StreamResponse
|
||||
err := json.Unmarshal([]byte(data), &claudeResponse)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,3 +5,10 @@ var ModelList = []string{
|
||||
"command-light", "command-light-nightly",
|
||||
"command-r", "command-r-plus",
|
||||
}
|
||||
|
||||
func init() {
|
||||
num := len(ModelList)
|
||||
for i := 0; i < num; i++ {
|
||||
ModelList = append(ModelList, ModelList[i]+"-internet")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ import (
|
||||
"github.com/songquanpeng/one-api/relay/model"
|
||||
)
|
||||
|
||||
var (
|
||||
WebSearchConnector = Connector{ID: "web-search"}
|
||||
)
|
||||
|
||||
func stopReasonCohere2OpenAI(reason *string) string {
|
||||
if reason == nil {
|
||||
return ""
|
||||
@@ -45,6 +49,10 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
|
||||
if cohereRequest.Model == "" {
|
||||
cohereRequest.Model = "command-r"
|
||||
}
|
||||
if strings.HasSuffix(cohereRequest.Model, "-internet") {
|
||||
cohereRequest.Model = strings.TrimSuffix(cohereRequest.Model, "-internet")
|
||||
cohereRequest.Connectors = append(cohereRequest.Connectors, WebSearchConnector)
|
||||
}
|
||||
for _, message := range textRequest.Messages {
|
||||
if message.Role == "user" {
|
||||
cohereRequest.Message = message.Content.(string)
|
||||
|
||||
73
relay/adaptor/deepl/adaptor.go
Normal file
73
relay/adaptor/deepl/adaptor.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package deepl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor"
|
||||
"github.com/songquanpeng/one-api/relay/meta"
|
||||
"github.com/songquanpeng/one-api/relay/model"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Adaptor struct {
|
||||
meta *meta.Meta
|
||||
promptText string
|
||||
}
|
||||
|
||||
func (a *Adaptor) Init(meta *meta.Meta) {
|
||||
a.meta = meta
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
|
||||
return fmt.Sprintf("%s/v2/translate", meta.BaseURL), nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error {
|
||||
adaptor.SetupCommonRequestHeader(c, req, meta)
|
||||
req.Header.Set("Authorization", "DeepL-Auth-Key "+meta.APIKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.GeneralOpenAIRequest) (any, error) {
|
||||
if request == nil {
|
||||
return nil, errors.New("request is nil")
|
||||
}
|
||||
convertedRequest, text := ConvertRequest(*request)
|
||||
a.promptText = text
|
||||
return convertedRequest, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (any, error) {
|
||||
if request == nil {
|
||||
return nil, errors.New("request is nil")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBody io.Reader) (*http.Response, error) {
|
||||
return adaptor.DoRequestHelper(a, c, meta, requestBody)
|
||||
}
|
||||
|
||||
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Meta) (usage *model.Usage, err *model.ErrorWithStatusCode) {
|
||||
if meta.IsStream {
|
||||
err = StreamHandler(c, resp, meta.ActualModelName)
|
||||
} else {
|
||||
err = Handler(c, resp, meta.ActualModelName)
|
||||
}
|
||||
promptTokens := len(a.promptText)
|
||||
usage = &model.Usage{
|
||||
PromptTokens: promptTokens,
|
||||
TotalTokens: promptTokens,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetModelList() []string {
|
||||
return ModelList
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetChannelName() string {
|
||||
return "deepl"
|
||||
}
|
||||
9
relay/adaptor/deepl/constants.go
Normal file
9
relay/adaptor/deepl/constants.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package deepl
|
||||
|
||||
// https://developers.deepl.com/docs/api-reference/glossaries
|
||||
|
||||
var ModelList = []string{
|
||||
"deepl-zh",
|
||||
"deepl-en",
|
||||
"deepl-ja",
|
||||
}
|
||||
11
relay/adaptor/deepl/helper.go
Normal file
11
relay/adaptor/deepl/helper.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package deepl
|
||||
|
||||
import "strings"
|
||||
|
||||
func parseLangFromModelName(modelName string) string {
|
||||
parts := strings.Split(modelName, "-")
|
||||
if len(parts) == 1 {
|
||||
return "ZH"
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
137
relay/adaptor/deepl/main.go
Normal file
137
relay/adaptor/deepl/main.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package deepl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/songquanpeng/one-api/common"
|
||||
"github.com/songquanpeng/one-api/common/helper"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
||||
"github.com/songquanpeng/one-api/relay/constant"
|
||||
"github.com/songquanpeng/one-api/relay/constant/finishreason"
|
||||
"github.com/songquanpeng/one-api/relay/constant/role"
|
||||
"github.com/songquanpeng/one-api/relay/model"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// https://developers.deepl.com/docs/getting-started/your-first-api-request
|
||||
|
||||
func ConvertRequest(textRequest model.GeneralOpenAIRequest) (*Request, string) {
|
||||
var text string
|
||||
if len(textRequest.Messages) != 0 {
|
||||
text = textRequest.Messages[len(textRequest.Messages)-1].StringContent()
|
||||
}
|
||||
deeplRequest := Request{
|
||||
TargetLang: parseLangFromModelName(textRequest.Model),
|
||||
Text: []string{text},
|
||||
}
|
||||
return &deeplRequest, text
|
||||
}
|
||||
|
||||
func StreamResponseDeepL2OpenAI(deeplResponse *Response) *openai.ChatCompletionsStreamResponse {
|
||||
var choice openai.ChatCompletionsStreamResponseChoice
|
||||
if len(deeplResponse.Translations) != 0 {
|
||||
choice.Delta.Content = deeplResponse.Translations[0].Text
|
||||
}
|
||||
choice.Delta.Role = role.Assistant
|
||||
choice.FinishReason = &constant.StopFinishReason
|
||||
openaiResponse := openai.ChatCompletionsStreamResponse{
|
||||
Object: constant.StreamObject,
|
||||
Created: helper.GetTimestamp(),
|
||||
Choices: []openai.ChatCompletionsStreamResponseChoice{choice},
|
||||
}
|
||||
return &openaiResponse
|
||||
}
|
||||
|
||||
func ResponseDeepL2OpenAI(deeplResponse *Response) *openai.TextResponse {
|
||||
var responseText string
|
||||
if len(deeplResponse.Translations) != 0 {
|
||||
responseText = deeplResponse.Translations[0].Text
|
||||
}
|
||||
choice := openai.TextResponseChoice{
|
||||
Index: 0,
|
||||
Message: model.Message{
|
||||
Role: role.Assistant,
|
||||
Content: responseText,
|
||||
Name: nil,
|
||||
},
|
||||
FinishReason: finishreason.Stop,
|
||||
}
|
||||
fullTextResponse := openai.TextResponse{
|
||||
Object: constant.NonStreamObject,
|
||||
Created: helper.GetTimestamp(),
|
||||
Choices: []openai.TextResponseChoice{choice},
|
||||
}
|
||||
return &fullTextResponse
|
||||
}
|
||||
|
||||
func StreamHandler(c *gin.Context, resp *http.Response, modelName string) *model.ErrorWithStatusCode {
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
var deeplResponse Response
|
||||
err = json.Unmarshal(responseBody, &deeplResponse)
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
fullTextResponse := StreamResponseDeepL2OpenAI(&deeplResponse)
|
||||
fullTextResponse.Model = modelName
|
||||
fullTextResponse.Id = helper.GetResponseID(c)
|
||||
jsonData, err := json.Marshal(fullTextResponse)
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
common.SetEventStreamHeaders(c)
|
||||
c.Stream(func(w io.Writer) bool {
|
||||
if jsonData != nil {
|
||||
c.Render(-1, common.CustomEvent{Data: "data: " + string(jsonData)})
|
||||
jsonData = nil
|
||||
return true
|
||||
}
|
||||
c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
|
||||
return false
|
||||
})
|
||||
_ = resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func Handler(c *gin.Context, resp *http.Response, modelName string) *model.ErrorWithStatusCode {
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
var deeplResponse Response
|
||||
err = json.Unmarshal(responseBody, &deeplResponse)
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
if deeplResponse.Message != "" {
|
||||
return &model.ErrorWithStatusCode{
|
||||
Error: model.Error{
|
||||
Message: deeplResponse.Message,
|
||||
Code: "deepl_error",
|
||||
},
|
||||
StatusCode: resp.StatusCode,
|
||||
}
|
||||
}
|
||||
fullTextResponse := ResponseDeepL2OpenAI(&deeplResponse)
|
||||
fullTextResponse.Model = modelName
|
||||
fullTextResponse.Id = helper.GetResponseID(c)
|
||||
jsonResponse, err := json.Marshal(fullTextResponse)
|
||||
if err != nil {
|
||||
return openai.ErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError)
|
||||
}
|
||||
c.Writer.Header().Set("Content-Type", "application/json")
|
||||
c.Writer.WriteHeader(resp.StatusCode)
|
||||
_, err = c.Writer.Write(jsonResponse)
|
||||
return nil
|
||||
}
|
||||
16
relay/adaptor/deepl/model.go
Normal file
16
relay/adaptor/deepl/model.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package deepl
|
||||
|
||||
type Request struct {
|
||||
Text []string `json:"text"`
|
||||
TargetLang string `json:"target_lang"`
|
||||
}
|
||||
|
||||
type Translation struct {
|
||||
DetectedSourceLanguage string `json:"detected_source_language,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Translations []Translation `json:"translations,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package gemini
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/songquanpeng/one-api/common/config"
|
||||
"github.com/songquanpeng/one-api/common/helper"
|
||||
@@ -10,8 +13,6 @@ import (
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
||||
"github.com/songquanpeng/one-api/relay/meta"
|
||||
"github.com/songquanpeng/one-api/relay/model"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Adaptor struct {
|
||||
@@ -25,7 +26,7 @@ func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
|
||||
version := helper.AssignOrDefault(meta.Config.APIVersion, config.GeminiVersion)
|
||||
action := "generateContent"
|
||||
if meta.IsStream {
|
||||
action = "streamGenerateContent"
|
||||
action = "streamGenerateContent?alt=sse"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/models/%s:%s", meta.BaseURL, version, meta.ActualModelName, action), nil
|
||||
}
|
||||
|
||||
@@ -232,8 +232,6 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *ChatResponse) *openai.ChatC
|
||||
|
||||
func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, 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 {
|
||||
@@ -247,14 +245,16 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
|
||||
}
|
||||
return 0, nil, nil
|
||||
})
|
||||
dataChan := make(chan string)
|
||||
stopChan := make(chan bool)
|
||||
go func() {
|
||||
for scanner.Scan() {
|
||||
data := scanner.Text()
|
||||
data = strings.TrimSpace(data)
|
||||
if !strings.HasPrefix(data, "\"text\": \"") {
|
||||
if !strings.HasPrefix(data, "data: ") {
|
||||
continue
|
||||
}
|
||||
data = strings.TrimPrefix(data, "\"text\": \"")
|
||||
data = strings.TrimPrefix(data, "data: ")
|
||||
data = strings.TrimSuffix(data, "\"")
|
||||
dataChan <- data
|
||||
}
|
||||
@@ -264,23 +264,17 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
|
||||
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 geminiResponse ChatResponse
|
||||
err := json.Unmarshal([]byte(data), &geminiResponse)
|
||||
if err != nil {
|
||||
logger.SysError("error unmarshalling stream response: " + err.Error())
|
||||
return true
|
||||
}
|
||||
var dummy dummyStruct
|
||||
err := json.Unmarshal([]byte(data), &dummy)
|
||||
responseText += dummy.Content
|
||||
var choice openai.ChatCompletionsStreamResponseChoice
|
||||
choice.Delta.Content = dummy.Content
|
||||
response := openai.ChatCompletionsStreamResponse{
|
||||
Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()),
|
||||
Object: "chat.completion.chunk",
|
||||
Created: helper.GetTimestamp(),
|
||||
Model: "gemini-pro",
|
||||
Choices: []openai.ChatCompletionsStreamResponseChoice{choice},
|
||||
response := streamResponseGeminiChat2OpenAI(&geminiResponse)
|
||||
if response == nil {
|
||||
return true
|
||||
}
|
||||
responseText += response.Choices[0].Delta.StringContent()
|
||||
jsonResponse, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
logger.SysError("error marshalling stream response: " + err.Error())
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package minimax
|
||||
|
||||
// https://www.minimaxi.com/document/guides/chat-model/V2?id=65e0736ab2845de20908e2dd
|
||||
|
||||
var ModelList = []string{
|
||||
"abab5.5s-chat",
|
||||
"abab5.5-chat",
|
||||
"abab6.5-chat",
|
||||
"abab6.5s-chat",
|
||||
"abab6-chat",
|
||||
"abab5.5-chat",
|
||||
"abab5.5s-chat",
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package ollama
|
||||
|
||||
var ModelList = []string{
|
||||
"codellama:7b-instruct",
|
||||
"llama2:7b",
|
||||
"llama2:latest",
|
||||
"llama3:latest",
|
||||
"phi3:latest",
|
||||
"qwen:0.5b-chat",
|
||||
"qwen:7b",
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/songquanpeng/one-api/common"
|
||||
"github.com/songquanpeng/one-api/common/image"
|
||||
"github.com/songquanpeng/one-api/common/logger"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
||||
"github.com/songquanpeng/one-api/relay/constant"
|
||||
@@ -32,9 +33,22 @@ func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
|
||||
Stream: request.Stream,
|
||||
}
|
||||
for _, message := range request.Messages {
|
||||
openaiContent := message.ParseContent()
|
||||
var imageUrls []string
|
||||
var contentText string
|
||||
for _, part := range openaiContent {
|
||||
switch part.Type {
|
||||
case model.ContentTypeText:
|
||||
contentText = part.Text
|
||||
case model.ContentTypeImageURL:
|
||||
_, data, _ := image.GetImageFromUrl(part.ImageURL.Url)
|
||||
imageUrls = append(imageUrls, data)
|
||||
}
|
||||
}
|
||||
ollamaRequest.Messages = append(ollamaRequest.Messages, Message{
|
||||
Role: message.Role,
|
||||
Content: message.StringContent(),
|
||||
Content: contentText,
|
||||
Images: imageUrls,
|
||||
})
|
||||
}
|
||||
return &ollamaRequest
|
||||
|
||||
@@ -86,9 +86,13 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Met
|
||||
if meta.IsStream {
|
||||
var responseText string
|
||||
err, responseText, usage = StreamHandler(c, resp, meta.Mode)
|
||||
if usage == nil {
|
||||
if usage == nil || usage.TotalTokens == 0 {
|
||||
usage = ResponseText2Usage(responseText, meta.ActualModelName, meta.PromptTokens)
|
||||
}
|
||||
if usage.TotalTokens != 0 && usage.PromptTokens == 0 { // some channels don't return prompt tokens & completion tokens
|
||||
usage.PromptTokens = meta.PromptTokens
|
||||
usage.CompletionTokens = usage.TotalTokens - meta.PromptTokens
|
||||
}
|
||||
} else {
|
||||
switch meta.Mode {
|
||||
case relaymode.ImagesGenerations:
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/mistral"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/moonshot"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/stepfun"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/togetherai"
|
||||
"github.com/songquanpeng/one-api/relay/channeltype"
|
||||
)
|
||||
|
||||
@@ -24,6 +25,7 @@ var CompatibleChannels = []int{
|
||||
channeltype.LingYiWanWu,
|
||||
channeltype.StepFun,
|
||||
channeltype.DeepSeek,
|
||||
channeltype.TogetherAI,
|
||||
}
|
||||
|
||||
func GetCompatibleChannelMeta(channelType int) (string, []string) {
|
||||
@@ -48,6 +50,8 @@ func GetCompatibleChannelMeta(channelType int) (string, []string) {
|
||||
return "stepfun", stepfun.ModelList
|
||||
case channeltype.DeepSeek:
|
||||
return "deepseek", deepseek.ModelList
|
||||
case channeltype.TogetherAI:
|
||||
return "together.ai", togetherai.ModelList
|
||||
default:
|
||||
return "openai", ModelList
|
||||
}
|
||||
|
||||
@@ -206,3 +206,7 @@ func CountTokenText(text string, model string) int {
|
||||
tokenEncoder := getTokenEncoder(model)
|
||||
return getTokenNum(tokenEncoder, text)
|
||||
}
|
||||
|
||||
func CountToken(text string) int {
|
||||
return CountTokenInput(text, "gpt-3.5-turbo")
|
||||
}
|
||||
|
||||
10
relay/adaptor/togetherai/constants.go
Normal file
10
relay/adaptor/togetherai/constants.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package togetherai
|
||||
|
||||
// https://docs.together.ai/docs/inference-models
|
||||
|
||||
var ModelList = []string{
|
||||
"meta-llama/Llama-3-70b-chat-hf",
|
||||
"deepseek-ai/deepseek-coder-33b-instruct",
|
||||
"mistralai/Mixtral-8x22B-Instruct-v0.1",
|
||||
"Qwen/Qwen1.5-72B-Chat",
|
||||
}
|
||||
@@ -62,8 +62,8 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G
|
||||
}
|
||||
switch relayMode {
|
||||
case relaymode.Embeddings:
|
||||
baiduEmbeddingRequest := ConvertEmbeddingRequest(*request)
|
||||
return baiduEmbeddingRequest, nil
|
||||
baiduEmbeddingRequest, err := ConvertEmbeddingRequest(*request)
|
||||
return baiduEmbeddingRequest, err
|
||||
default:
|
||||
// TopP (0.0, 1.0)
|
||||
request.TopP = math.Min(0.99, request.TopP)
|
||||
@@ -129,11 +129,15 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Met
|
||||
return
|
||||
}
|
||||
|
||||
func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *EmbeddingRequest {
|
||||
return &EmbeddingRequest{
|
||||
Model: "embedding-2",
|
||||
Input: request.Input.(string),
|
||||
func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) (*EmbeddingRequest, error) {
|
||||
inputs := request.ParseInput()
|
||||
if len(inputs) != 1 {
|
||||
return nil, errors.New("invalid input length, zhipu only support one input")
|
||||
}
|
||||
return &EmbeddingRequest{
|
||||
Model: request.Model,
|
||||
Input: inputs[0],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetModelList() []string {
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
Coze
|
||||
Cohere
|
||||
Cloudflare
|
||||
DeepL
|
||||
|
||||
Dummy // this one is only for count, do not add any channel after this
|
||||
)
|
||||
|
||||
@@ -138,6 +138,8 @@ var ModelRatio = map[string]float64{
|
||||
"Baichuan2-Turbo-192k": 0.016 * RMB,
|
||||
"Baichuan2-53B": 0.02 * RMB,
|
||||
// https://api.minimax.chat/document/price
|
||||
"abab6.5-chat": 0.03 * RMB,
|
||||
"abab6.5s-chat": 0.01 * RMB,
|
||||
"abab6-chat": 0.1 * RMB,
|
||||
"abab5.5-chat": 0.015 * RMB,
|
||||
"abab5.5s-chat": 0.005 * RMB,
|
||||
@@ -169,10 +171,14 @@ var ModelRatio = map[string]float64{
|
||||
"command-light": 0.5,
|
||||
"command-light-nightly": 0.5,
|
||||
"command-r": 0.5 / 1000 * USD,
|
||||
"command-r-plus ": 3.0 / 1000 * USD,
|
||||
"command-r-plus": 3.0 / 1000 * USD,
|
||||
// https://platform.deepseek.com/api-docs/pricing/
|
||||
"deepseek-chat": 1.0 / 1000 * RMB,
|
||||
"deepseek-coder": 1.0 / 1000 * RMB,
|
||||
// https://www.deepl.com/pro?cta=header-prices
|
||||
"deepl-zh": 25.0 / 1000 * USD,
|
||||
"deepl-en": 25.0 / 1000 * USD,
|
||||
"deepl-ja": 25.0 / 1000 * USD,
|
||||
}
|
||||
|
||||
var CompletionRatio = map[string]float64{}
|
||||
@@ -228,6 +234,9 @@ func GetModelRatio(name string) float64 {
|
||||
if strings.HasPrefix(name, "qwen-") && strings.HasSuffix(name, "-internet") {
|
||||
name = strings.TrimSuffix(name, "-internet")
|
||||
}
|
||||
if strings.HasPrefix(name, "command-") && strings.HasSuffix(name, "-internet") {
|
||||
name = strings.TrimSuffix(name, "-internet")
|
||||
}
|
||||
ratio, ok := ModelRatio[name]
|
||||
if !ok {
|
||||
ratio, ok = DefaultModelRatio[name]
|
||||
|
||||
@@ -39,6 +39,8 @@ const (
|
||||
Cohere
|
||||
DeepSeek
|
||||
Cloudflare
|
||||
DeepL
|
||||
TogetherAI
|
||||
|
||||
Dummy
|
||||
)
|
||||
|
||||
@@ -33,6 +33,8 @@ func ToAPIType(channelType int) int {
|
||||
apiType = apitype.Cohere
|
||||
case Cloudflare:
|
||||
apiType = apitype.Cloudflare
|
||||
case DeepL:
|
||||
apiType = apitype.DeepL
|
||||
}
|
||||
|
||||
return apiType
|
||||
|
||||
@@ -39,6 +39,8 @@ var ChannelBaseURLs = []string{
|
||||
"https://api.cohere.ai", // 35
|
||||
"https://api.deepseek.com", // 36
|
||||
"https://api.cloudflare.com", // 37
|
||||
"https://api-free.deepl.com", // 38
|
||||
"https://api.together.xyz", // 39
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
package constant
|
||||
|
||||
var StopFinishReason = "stop"
|
||||
var StreamObject = "chat.completion.chunk"
|
||||
var NonStreamObject = "chat.completion"
|
||||
|
||||
5
relay/constant/finishreason/define.go
Normal file
5
relay/constant/finishreason/define.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package finishreason
|
||||
|
||||
const (
|
||||
Stop = "stop"
|
||||
)
|
||||
5
relay/constant/role/define.go
Normal file
5
relay/constant/role/define.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package role
|
||||
|
||||
const (
|
||||
Assistant = "assistant"
|
||||
)
|
||||
@@ -53,6 +53,16 @@ func (e GeneralErrorResponse) ToMessage() string {
|
||||
}
|
||||
|
||||
func RelayErrorHandler(resp *http.Response) (ErrorWithStatusCode *model.ErrorWithStatusCode) {
|
||||
if resp == nil {
|
||||
return &model.ErrorWithStatusCode{
|
||||
StatusCode: 500,
|
||||
Error: model.Error{
|
||||
Message: "resp is nil",
|
||||
Type: "upstream_error",
|
||||
Code: "bad_response",
|
||||
},
|
||||
}
|
||||
}
|
||||
ErrorWithStatusCode = &model.ErrorWithStatusCode{
|
||||
StatusCode: resp.StatusCode,
|
||||
Error: model.Error{
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/songquanpeng/one-api/relay/relaymode"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getAndValidateTextRequest(c *gin.Context, relayMode int) (*relaymodel.GeneralOpenAIRequest, error) {
|
||||
@@ -124,9 +125,9 @@ func getPromptTokens(textRequest *relaymodel.GeneralOpenAIRequest, relayMode int
|
||||
}
|
||||
|
||||
func getPreConsumedQuota(textRequest *relaymodel.GeneralOpenAIRequest, promptTokens int, ratio float64) int64 {
|
||||
preConsumedTokens := config.PreConsumedQuota
|
||||
preConsumedTokens := config.PreConsumedQuota + int64(promptTokens)
|
||||
if textRequest.MaxTokens != 0 {
|
||||
preConsumedTokens = int64(promptTokens) + int64(textRequest.MaxTokens)
|
||||
preConsumedTokens += int64(textRequest.MaxTokens)
|
||||
}
|
||||
return int64(float64(preConsumedTokens) * ratio)
|
||||
}
|
||||
@@ -204,3 +205,23 @@ func getMappedModelName(modelName string, mapping map[string]string) (string, bo
|
||||
}
|
||||
return modelName, false
|
||||
}
|
||||
|
||||
func isErrorHappened(meta *meta.Meta, resp *http.Response) bool {
|
||||
if resp == nil {
|
||||
if meta.ChannelType == channeltype.AwsClaude {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return true
|
||||
}
|
||||
if meta.ChannelType == channeltype.DeepL {
|
||||
// skip stream check for deepl
|
||||
return false
|
||||
}
|
||||
if meta.IsStream && strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/songquanpeng/one-api/common/logger"
|
||||
"github.com/songquanpeng/one-api/relay"
|
||||
@@ -18,6 +14,8 @@ import (
|
||||
"github.com/songquanpeng/one-api/relay/channeltype"
|
||||
"github.com/songquanpeng/one-api/relay/meta"
|
||||
"github.com/songquanpeng/one-api/relay/model"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
|
||||
@@ -88,12 +86,9 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
|
||||
logger.Errorf(ctx, "DoRequest failed: %s", err.Error())
|
||||
return openai.ErrorWrapper(err, "do_request_failed", http.StatusInternalServerError)
|
||||
}
|
||||
if resp != nil {
|
||||
errorHappened := (resp.StatusCode != http.StatusOK) || (meta.IsStream && strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json"))
|
||||
if errorHappened {
|
||||
billing.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId)
|
||||
return RelayErrorHandler(resp)
|
||||
}
|
||||
if isErrorHappened(meta, resp) {
|
||||
billing.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId)
|
||||
return RelayErrorHandler(resp)
|
||||
}
|
||||
|
||||
// do response
|
||||
|
||||
@@ -137,6 +137,18 @@ export const CHANNEL_OPTIONS = {
|
||||
value: 36,
|
||||
color: 'primary'
|
||||
},
|
||||
38: {
|
||||
key: 38,
|
||||
text: 'DeepL',
|
||||
value: 38,
|
||||
color: 'primary'
|
||||
},
|
||||
39: {
|
||||
key: 39,
|
||||
text: 'together.ai',
|
||||
value: 39,
|
||||
color: 'primary'
|
||||
},
|
||||
8: {
|
||||
key: 8,
|
||||
text: '自定义渠道',
|
||||
|
||||
@@ -23,6 +23,8 @@ export const CHANNEL_OPTIONS = [
|
||||
{key: 35, text: 'Cohere', value: 35, color: 'blue'},
|
||||
{key: 36, text: 'DeepSeek', value: 36, color: 'black'},
|
||||
{key: 37, text: 'Cloudflare', value: 37, color: 'orange'},
|
||||
{key: 38, text: 'DeepL', value: 38, color: 'black'},
|
||||
{key: 39, text: 'together.ai', value: 39, color: 'blue'},
|
||||
{key: 8, text: '自定义渠道', value: 8, color: 'pink'},
|
||||
{key: 22, text: '知识库:FastGPT', value: 22, color: 'blue'},
|
||||
{key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple'},
|
||||
|
||||
Reference in New Issue
Block a user