From bc8ceca7428f5b39196883862177a4a65d2b8237 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 29 Sep 2023 12:46:26 +0000 Subject: [PATCH 001/170] feat: api can modify token's used and remains - Updated base image in Dockerfile from `node:16` to `node:18` - Added `tokenPatch` struct to handle updates to the `Token` model - Updated `UpdateToken` function to use `tokenPatch` struct for binding JSON - Improved error handling and validation for token updates - Refined checks for token status, expiration, and quota - Enhanced error handling and response for token updates - Updated JSON response to include `data` field --- Dockerfile | 4 +- controller/token.go | 96 ++++++++++++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/Dockerfile b/Dockerfile index ffb8c21b..b378b5bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16 as builder +FROM node:18 as builder WORKDIR /build COPY web/package.json . @@ -7,7 +7,7 @@ COPY ./web . COPY ./VERSION . RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build -FROM golang AS builder2 +FROM golang:1.21.1 AS builder2 ENV GO111MODULE=on \ CGO_ENABLED=1 \ diff --git a/controller/token.go b/controller/token.go index 8642122c..a452102b 100644 --- a/controller/token.go +++ b/controller/token.go @@ -1,11 +1,13 @@ package controller import ( - "github.com/gin-gonic/gin" + "fmt" "net/http" "one-api/common" "one-api/model" "strconv" + + "github.com/gin-gonic/gin" ) func GetAllTokens(c *gin.Context) { @@ -159,42 +161,62 @@ func DeleteToken(c *gin.Context) { return } +type updateTokenDto struct { + Id int `json:"id"` + Status int `json:"status" gorm:"default:1"` + Name *string `json:"name" gorm:"index" ` + ExpiredTime *int64 `json:"expired_time" gorm:"bigint;default:-1"` // -1 means never expired + RemainQuota *int `json:"remain_quota" gorm:"default:0"` + UnlimitedQuota *bool `json:"unlimited_quota" gorm:"default:false"` + // AddRemainQuota add or subtract remain quota + AddRemainQuota int `json:"add_remain_quota"` + // AddUsedQuota add or subtract used quota + AddUsedQuota int `json:"add_used_quota"` +} + func UpdateToken(c *gin.Context) { userId := c.GetInt("id") statusOnly := c.Query("status_only") - token := model.Token{} - err := c.ShouldBindJSON(&token) + tokenPatch := new(updateTokenDto) + if err := c.ShouldBindJSON(tokenPatch); err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "parse request join: " + err.Error(), + }) + return + } + + if tokenPatch.Name != nil && + (len(*tokenPatch.Name) > 30 || len(*tokenPatch.Name) == 0) { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "令牌名称错误,长度应在 1-30 之间", + }) + return + } + + cleanToken, err := model.GetTokenByIds(tokenPatch.Id, userId) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": err.Error(), + "message": fmt.Sprintf("get token by id %d: %s", tokenPatch.Id, err.Error()), }) return } - if len(token.Name) > 30 { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "令牌名称过长", - }) - return - } - cleanToken, err := model.GetTokenByIds(token.Id, userId) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) - return - } - if token.Status == common.TokenStatusEnabled { - if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() && cleanToken.ExpiredTime != -1 { + + if tokenPatch.Status == common.TokenStatusEnabled { + if cleanToken.Status == common.TokenStatusExpired && + cleanToken.ExpiredTime <= common.GetTimestamp() && + cleanToken.ExpiredTime != -1 { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期", }) return } - if cleanToken.Status == common.TokenStatusExhausted && cleanToken.RemainQuota <= 0 && !cleanToken.UnlimitedQuota { + if cleanToken.Status == common.TokenStatusExhausted && + cleanToken.RemainQuota <= 0 && + !cleanToken.UnlimitedQuota { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度", @@ -203,22 +225,34 @@ func UpdateToken(c *gin.Context) { } } if statusOnly != "" { - cleanToken.Status = token.Status + cleanToken.Status = tokenPatch.Status } else { - // If you add more fields, please also update token.Update() - cleanToken.Name = token.Name - cleanToken.ExpiredTime = token.ExpiredTime - cleanToken.RemainQuota = token.RemainQuota - cleanToken.UnlimitedQuota = token.UnlimitedQuota + // If you add more fields, please also update tokenPatch.Update() + if tokenPatch.Name != nil { + cleanToken.Name = *tokenPatch.Name + } + if tokenPatch.ExpiredTime != nil { + cleanToken.ExpiredTime = *tokenPatch.ExpiredTime + } + if tokenPatch.RemainQuota != nil { + cleanToken.RemainQuota = *tokenPatch.RemainQuota + } + if tokenPatch.UnlimitedQuota != nil { + cleanToken.UnlimitedQuota = *tokenPatch.UnlimitedQuota + } } - err = cleanToken.Update() - if err != nil { + + cleanToken.RemainQuota += tokenPatch.AddRemainQuota + cleanToken.UsedQuota += tokenPatch.AddUsedQuota + + if err = cleanToken.Update(); err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": err.Error(), + "message": "update token: " + err.Error(), }) return } + c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", From b3ebf069e2f70c1708fe2a69a4f44f7f01afb46d Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 19 Oct 2023 09:01:03 +0000 Subject: [PATCH 002/170] feat: change default token prefix to `laisky-` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove prefix "laisky-" from the variable "key" in [middleware/auth.go] - Modify error message from "用户已被封禁" to "用户已被禁用" in [middleware/auth.go] - Remove check for "consumeQuota" when request URL starts with "/v1/models" in [middleware/auth.go] - Add ability for admin users to set "channelId" in context in [middleware/auth.go] - Update URL and key for `ama` and `opencat` links, and key format for `url` in links in [web/src/components/TokensTable.js] - Update URL for `nextUrl` in [web/src/components/TokensTable.js] - Change variable names and improve search function for token names in [web/src/components/TokensTable.js] - Add delete confirmation popup, copy and open link buttons, sorting functionality for column header, and loading state in [web/src/components/TokensTable.js] - Update pagination to load more data in [web/src/components/TokensTable.js] - Update rendering of token information, including quota and timestamp in [web/src/components/TokensTable.js] --- middleware/auth.go | 2 +- web/src/components/TokensTable.js | 42 +++++++++++++++---------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/middleware/auth.go b/middleware/auth.go index b0803612..8731b3c8 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -86,7 +86,7 @@ func TokenAuth() func(c *gin.Context) { return func(c *gin.Context) { key := c.Request.Header.Get("Authorization") key = strings.TrimPrefix(key, "Bearer ") - key = strings.TrimPrefix(key, "sk-") + key = strings.TrimPrefix(strings.TrimPrefix(key, "sk-"), "laisky-") parts := strings.Split(key, "-") key = parts[0] token, err := model.ValidateUserToken(key) diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js index c7ec9b48..fdb303c9 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/TokensTable.js @@ -94,26 +94,26 @@ const TokensTable = () => { let encodedServerAddress = encodeURIComponent(serverAddress); const nextLink = localStorage.getItem('chat_link'); let nextUrl; - + if (nextLink) { - nextUrl = nextLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; + nextUrl = nextLink + `/#/?settings={"key":"laisky-${key}","url":"${serverAddress}"}`; } else { - nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; + nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"laisky-${key}","url":"${serverAddress}"}`; } let url; switch (type) { case 'ama': - url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`; + url = `ama://set-api-key?server=${encodedServerAddress}&key=laisky-${key}`; break; case 'opencat': - url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`; + url = `opencat://team/join?domain=${encodedServerAddress}&token=laisky-${key}`; break; case 'next': url = nextUrl; break; default: - url = `sk-${key}`; + url = `laisky-${key}`; } if (await copy(url)) { showSuccess('已复制到剪贴板!'); @@ -128,7 +128,7 @@ const TokensTable = () => { let serverAddress = ''; if (status) { status = JSON.parse(status); - serverAddress = status.server_address; + serverAddress = status.server_address; } if (serverAddress === '') { serverAddress = window.location.origin; @@ -136,26 +136,26 @@ const TokensTable = () => { let encodedServerAddress = encodeURIComponent(serverAddress); const chatLink = localStorage.getItem('chat_link'); let defaultUrl; - + if (chatLink) { - defaultUrl = chatLink + `/#/?settings={"key":"sk-${key}"}`; + defaultUrl = chatLink + `/#/?settings={"key":"laisky-${key}"}`; } else { - defaultUrl = `https://chat.oneapi.pro/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`; + defaultUrl = `https://chat.oneapi.pro/#/?settings={"key":"laisky-${key}","url":"${serverAddress}"}`; } let url; switch (type) { case 'ama': - url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`; + url = `ama://set-api-key?server=${encodedServerAddress}&key=laisky-${key}`; break; - + case 'opencat': - url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`; + url = `opencat://team/join?domain=${encodedServerAddress}&token=laisky-${key}`; break; - + default: url = defaultUrl; } - + window.open(url, '_blank'); } @@ -351,21 +351,21 @@ const TokensTable = () => { - ({ ...option, onClick: async () => { await onOpenLink(option.value, token.key); } - }))} - trigger={<>} + }))} + trigger={<>} /> {' '} From 1c46d030ab56f0f2249d8842d791f0d2ce1d5a02 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 19 Oct 2023 09:42:47 +0000 Subject: [PATCH 003/170] feat: Implement API endpoints for user management and information retrieval - Added new API endpoints for user information retrieval, registration, login, logout, self-management, administration, options management, channel management, token management, redemption management, log management, and group retrieval - Improved authentication flow with GitHub, WeChat, and email - Added a function `GetSelfByToken` for getting user information using an OpenAI API token --- controller/user.go | 10 ++++++++++ router/api-router.go | 1 + 2 files changed, 11 insertions(+) diff --git a/controller/user.go b/controller/user.go index 8fd10b82..9d069306 100644 --- a/controller/user.go +++ b/controller/user.go @@ -312,6 +312,16 @@ func GetAffCode(c *gin.Context) { return } +// GetSelfByToken get user by openai api token +func GetSelfByToken(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "uid": c.GetInt("id"), + "token_id": c.GetInt("token_id"), + "username": c.GetString("username"), + }) + return +} + func GetSelf(c *gin.Context) { id := c.GetInt("id") user, err := model.GetUserById(id, false) diff --git a/router/api-router.go b/router/api-router.go index da3f9e61..49cdff9c 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -19,6 +19,7 @@ func SetApiRouter(router *gin.Engine) { apiRouter.GET("/home_page_content", controller.GetHomePageContent) apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) + apiRouter.GET("/user/get-by-token", middleware.TokenAuth(), controller.GetSelfByToken) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth) apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode) From d847b2354e264ead1d1aaab6dcd55bd5fd9092e6 Mon Sep 17 00:00:00 2001 From: Laisky Date: Thu, 9 Nov 2023 03:17:40 +0000 Subject: [PATCH 004/170] chore: Update dependencies and Dockerfile --- Dockerfile | 2 +- go.mod | 26 ++++++++++++------------- go.sum | 57 ++++++++++++++++++++++++++---------------------------- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/Dockerfile b/Dockerfile index b378b5bc..12759e39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY ./web . COPY ./VERSION . RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build -FROM golang:1.21.1 AS builder2 +FROM golang:1.21.4 AS builder2 ENV GO111MODULE=on \ CGO_ENABLED=1 \ diff --git a/go.mod b/go.mod index 10b78d68..825ee570 100644 --- a/go.mod +++ b/go.mod @@ -9,17 +9,17 @@ require ( github.com/gin-contrib/sessions v0.0.5 github.com/gin-contrib/static v0.0.1 github.com/gin-gonic/gin v1.9.1 - github.com/go-playground/validator/v10 v10.14.0 + github.com/go-playground/validator/v10 v10.16.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/uuid v1.3.0 - github.com/gorilla/websocket v1.5.0 - github.com/pkoukk/tiktoken-go v0.1.5 - golang.org/x/crypto v0.14.0 - gorm.io/driver/mysql v1.4.3 - gorm.io/driver/postgres v1.5.2 - gorm.io/driver/sqlite v1.4.3 - gorm.io/gorm v1.25.0 + github.com/google/uuid v1.4.0 + github.com/gorilla/websocket v1.5.1 + github.com/pkoukk/tiktoken-go v0.1.6 + golang.org/x/crypto v0.15.0 + gorm.io/driver/mysql v1.5.2 + gorm.io/driver/postgres v1.5.4 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 ) require ( @@ -32,14 +32,14 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -54,8 +54,8 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4865bcaa..84208854 100644 --- a/go.sum +++ b/go.sum @@ -43,12 +43,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -59,25 +59,24 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= -github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -102,7 +101,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -118,8 +116,8 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkoukk/tiktoken-go v0.1.5 h1:hAlT4dCf6Uk50x8E7HQrddhH3EWMKUN+LArExQQsQx4= -github.com/pkoukk/tiktoken-go v0.1.5/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= +github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw= +github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -150,8 +148,8 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= @@ -162,14 +160,14 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -191,14 +189,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k= -gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= -gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= -gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= -gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= -gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU= -gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From a392390d08ea3b26f8c412738ac3714322faaab9 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Wed, 15 Nov 2023 13:47:08 +0000 Subject: [PATCH 005/170] chore: Remove unnecessary GitHub workflows and add new CI workflow --- .github/workflows/ci.yml | 62 +++++++++++++++++++++ .github/workflows/docker-image-amd64-en.yml | 49 ---------------- .github/workflows/docker-image-amd64.yml | 54 ------------------ .github/workflows/docker-image-arm64.yml | 62 --------------------- .github/workflows/linux-release.yml | 54 ------------------ .github/workflows/macos-release.yml | 45 --------------- .github/workflows/windows-release.yml | 48 ---------------- 7 files changed, 62 insertions(+), 312 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/docker-image-amd64-en.yml delete mode 100644 .github/workflows/docker-image-amd64.yml delete mode 100644 .github/workflows/docker-image-arm64.yml delete mode 100644 .github/workflows/linux-release.yml delete mode 100644 .github/workflows/macos-release.yml delete mode 100644 .github/workflows/windows-release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..1baae351 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: ci + +on: + push: + branches: + - 'master' + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + + # Get values for cache paths to be used in later steps + - id: go-cache-paths + run: | + echo "::set-output name=go-build::$(go env GOCACHE)" + echo "::set-output name=go-mod::$(go env GOMODCACHE)" + + # Cache go build cache, used to speedup go test + - name: Go Build Cache + uses: actions/cache@v2 + with: + path: ${{ steps.go-cache-paths.outputs.go-build }} + key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} + + # Cache go mod cache, used to speedup builds + - name: Go Mod Cache + uses: actions/cache@v2 + with: + path: ${{ steps.go-cache-paths.outputs.go-mod }} + key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + + - + name: Build and push latest + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ppcelery/one-api:latest + - + name: Build and push hash label + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ppcelery/one-api:${{ github.sha }} diff --git a/.github/workflows/docker-image-amd64-en.yml b/.github/workflows/docker-image-amd64-en.yml deleted file mode 100644 index 44dc0bc0..00000000 --- a/.github/workflows/docker-image-amd64-en.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Publish Docker image (amd64, English) - -on: - push: - tags: - - '*' - workflow_dispatch: - inputs: - name: - description: 'reason' - required: false -jobs: - push_to_registries: - name: Push Docker image to multiple registries - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Save version info - run: | - git describe --tags > VERSION - - - name: Translate - run: | - python ./i18n/translate.py --repository_path . --json_file_path ./i18n/en.json - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: | - justsong/one-api-en - - - name: Build and push Docker images - uses: docker/build-push-action@v3 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/docker-image-amd64.yml b/.github/workflows/docker-image-amd64.yml deleted file mode 100644 index e3b8439a..00000000 --- a/.github/workflows/docker-image-amd64.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Publish Docker image (amd64) - -on: - push: - tags: - - '*' - workflow_dispatch: - inputs: - name: - description: 'reason' - required: false -jobs: - push_to_registries: - name: Push Docker image to multiple registries - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Save version info - run: | - git describe --tags > VERSION - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Log in to the Container registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: | - justsong/one-api - ghcr.io/${{ github.repository }} - - - name: Build and push Docker images - uses: docker/build-push-action@v3 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/docker-image-arm64.yml b/.github/workflows/docker-image-arm64.yml deleted file mode 100644 index d6449eb8..00000000 --- a/.github/workflows/docker-image-arm64.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Publish Docker image (arm64) - -on: - push: - tags: - - '*' - - '!*-alpha*' - workflow_dispatch: - inputs: - name: - description: 'reason' - required: false -jobs: - push_to_registries: - name: Push Docker image to multiple registries - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Save version info - run: | - git describe --tags > VERSION - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Log in to the Container registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: | - justsong/one-api - ghcr.io/${{ github.repository }} - - - name: Build and push Docker images - uses: docker/build-push-action@v3 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/linux-release.yml b/.github/workflows/linux-release.yml deleted file mode 100644 index 364b83ae..00000000 --- a/.github/workflows/linux-release.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Linux Release -permissions: - contents: write - -on: - push: - tags: - - '*' - - '!*-alpha*' -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Build Frontend - env: - CI: "" - run: | - cd web - npm install - REACT_APP_VERSION=$(git describe --tags) npm run build - cd .. - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - name: Build Backend (amd64) - run: | - go mod download - go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api - - - name: Build Backend (arm64) - run: | - sudo apt-get update - sudo apt-get install gcc-aarch64-linux-gnu - CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api-arm64 - - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - one-api - one-api-arm64 - draft: true - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml deleted file mode 100644 index bdd0d208..00000000 --- a/.github/workflows/macos-release.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: macOS Release -permissions: - contents: write - -on: - push: - tags: - - '*' - - '!*-alpha*' -jobs: - release: - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Build Frontend - env: - CI: "" - run: | - cd web - npm install - REACT_APP_VERSION=$(git describe --tags) npm run build - cd .. - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - name: Build Backend - run: | - go mod download - go build -ldflags "-X 'one-api/common.Version=$(git describe --tags)'" -o one-api-macos - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: one-api-macos - draft: true - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml deleted file mode 100644 index 33193a89..00000000 --- a/.github/workflows/windows-release.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Windows Release -permissions: - contents: write - -on: - push: - tags: - - '*' - - '!*-alpha*' -jobs: - release: - runs-on: windows-latest - defaults: - run: - shell: bash - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Build Frontend - env: - CI: "" - run: | - cd web - npm install - REACT_APP_VERSION=$(git describe --tags) npm run build - cd .. - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - name: Build Backend - run: | - go mod download - go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)'" -o one-api.exe - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: one-api.exe - draft: true - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From a5a3ca2b815fb2a8ababc11ef92b38e4cfc347ff Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Wed, 15 Nov 2023 13:49:06 +0000 Subject: [PATCH 006/170] chore: Update branch name from master to main in ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1baae351..e5fd947e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: ci on: push: branches: - - 'master' + - 'main' jobs: docker: From f979181b5f72f28dafa9902a7a79d97f23370ffa Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 16 Nov 2023 02:46:19 +0000 Subject: [PATCH 007/170] fix: Remove InsecureSkipVerify option in TLS config for email sending. --- common/email.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/email.go b/common/email.go index 74f4cccd..d50cd48f 100644 --- a/common/email.go +++ b/common/email.go @@ -24,8 +24,8 @@ func SendEmail(subject string, receiver string, content string) error { var err error if SMTPPort == 465 { tlsConfig := &tls.Config{ - InsecureSkipVerify: true, - ServerName: SMTPServer, + // InsecureSkipVerify: true, + ServerName: SMTPServer, } conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", SMTPServer, SMTPPort), tlsConfig) if err != nil { From edacd13025e26c23fc41bce900f3826a188b2c69 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 16 Nov 2023 04:53:08 +0000 Subject: [PATCH 008/170] feat:Exclude fields from database schema and add additional field to struct - Add `gorm:"-"` tag to exclude certain fields from the database schema - Add `AddReason` field to the `updateTokenDto` struct - Record consumption log if `AddUsedQuota` is non-zero - Record token quota modification log if `AddRemainQuota` is non-zero - Update and save `cleanToken` with new values --- controller/token.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/controller/token.go b/controller/token.go index a452102b..db2b64fe 100644 --- a/controller/token.go +++ b/controller/token.go @@ -169,9 +169,10 @@ type updateTokenDto struct { RemainQuota *int `json:"remain_quota" gorm:"default:0"` UnlimitedQuota *bool `json:"unlimited_quota" gorm:"default:false"` // AddRemainQuota add or subtract remain quota - AddRemainQuota int `json:"add_remain_quota"` + AddRemainQuota int `json:"add_remain_quota" gorm:"-"` // AddUsedQuota add or subtract used quota - AddUsedQuota int `json:"add_used_quota"` + AddUsedQuota int `json:"add_used_quota" gorm:"-"` + AddReason string `json:"add_reason" gorm:"-"` } func UpdateToken(c *gin.Context) { @@ -245,6 +246,13 @@ func UpdateToken(c *gin.Context) { cleanToken.RemainQuota += tokenPatch.AddRemainQuota cleanToken.UsedQuota += tokenPatch.AddUsedQuota + if tokenPatch.AddUsedQuota != 0 { + model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("外部(%s)消耗 %s", tokenPatch.AddReason, common.LogQuota(tokenPatch.AddUsedQuota))) + } + if tokenPatch.AddRemainQuota != 0 { + model.RecordLog(userId, model.LogTypeManage, fmt.Sprintf("修改令牌可用额度(%s) %s", tokenPatch.AddReason, common.LogQuota(tokenPatch.AddRemainQuota))) + } + if err = cleanToken.Update(); err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, From a0dc7e6fd6e4059e0b35d721da9fe77afb3f73c3 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 16 Nov 2023 05:24:56 +0000 Subject: [PATCH 009/170] refactor: Refactor token handling in the controller - Remove the unnecessary `AddRemainQuota` field from `updateTokenDto` struct - Adjust `cleanToken.RemainQuota` by subtracting `tokenPatch.AddUsedQuota` - Increase `cleanToken.UsedQuota` by adding `tokenPatch.AddUsedQuota` - Update `model.RecordLog` call to include `tokenPatch.AddReason` and `tokenPatch.AddUsedQuota` values --- controller/token.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/controller/token.go b/controller/token.go index db2b64fe..e3b5f6f3 100644 --- a/controller/token.go +++ b/controller/token.go @@ -168,8 +168,6 @@ type updateTokenDto struct { ExpiredTime *int64 `json:"expired_time" gorm:"bigint;default:-1"` // -1 means never expired RemainQuota *int `json:"remain_quota" gorm:"default:0"` UnlimitedQuota *bool `json:"unlimited_quota" gorm:"default:false"` - // AddRemainQuota add or subtract remain quota - AddRemainQuota int `json:"add_remain_quota" gorm:"-"` // AddUsedQuota add or subtract used quota AddUsedQuota int `json:"add_used_quota" gorm:"-"` AddReason string `json:"add_reason" gorm:"-"` @@ -243,15 +241,12 @@ func UpdateToken(c *gin.Context) { } } - cleanToken.RemainQuota += tokenPatch.AddRemainQuota + cleanToken.RemainQuota -= tokenPatch.AddUsedQuota cleanToken.UsedQuota += tokenPatch.AddUsedQuota if tokenPatch.AddUsedQuota != 0 { model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("外部(%s)消耗 %s", tokenPatch.AddReason, common.LogQuota(tokenPatch.AddUsedQuota))) } - if tokenPatch.AddRemainQuota != 0 { - model.RecordLog(userId, model.LogTypeManage, fmt.Sprintf("修改令牌可用额度(%s) %s", tokenPatch.AddReason, common.LogQuota(tokenPatch.AddRemainQuota))) - } if err = cleanToken.Update(); err != nil { c.JSON(http.StatusOK, gin.H{ From b2e46a33acee4439efef8a64fd49932dfa8d4e81 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 01:24:48 +0000 Subject: [PATCH 010/170] feat: token copy web url - Update `COPY_OPTIONS` object in `TokensTable.js` - Modify the `onCopy` function to use a different url variable - Fix pagination logic in the `onPaginationChange` function - Remove unnecessary code and comments in `TokensTable.js` --- web/src/components/TokensTable.js | 43 +++++++------------------------ 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js index 29ba19a2..295996aa 100644 --- a/web/src/components/TokensTable.js +++ b/web/src/components/TokensTable.js @@ -7,8 +7,7 @@ import { ITEMS_PER_PAGE } from '../constants'; import { renderQuota } from '../helpers/render'; const COPY_OPTIONS = [ - { key: 'next', text: 'ChatGPT Next Web', value: 'next' }, - { key: 'ama', text: 'AMA 问天', value: 'ama' }, + { key: 'web', text: 'Web', value: 'web' }, { key: 'opencat', text: 'OpenCat', value: 'opencat' }, ]; @@ -92,14 +91,14 @@ const TokensTable = () => { serverAddress = window.location.origin; } let encodedServerAddress = encodeURIComponent(serverAddress); - const nextLink = localStorage.getItem('chat_link'); - let nextUrl; + // const nextLink = localStorage.getItem('chat_link'); + // let nextUrl; - if (nextLink) { - nextUrl = nextLink + `/#/?settings={"key":"laisky-${key}","url":"${serverAddress}"}`; - } else { - nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"laisky-${key}","url":"${serverAddress}"}`; - } + // if (nextLink) { + // nextUrl = nextLink + `/#/?settings={"key":"laisky-${key}","url":"${serverAddress}"}`; + // } else { + // nextUrl = `https://chat.oneapi.pro/#/?settings={"key":"laisky-${key}","url":"${serverAddress}"}`; + // } let url; switch (type) { @@ -109,8 +108,8 @@ const TokensTable = () => { case 'opencat': url = `opencat://team/join?domain=${encodedServerAddress}&token=laisky-${key}`; break; - case 'next': - url = nextUrl; + case 'web': + url = `https://chat.laisky.com?apikey=laisky-${key}`; break; default: url = `laisky-${key}`; @@ -353,28 +352,6 @@ const TokensTable = () => { /> {' '} - - - ({ - ...option, - onClick: async () => { - await onOpenLink(option.value, token.key); - } - }))} - trigger={<>} - /> - - {' '} From b58bd7e3ab1ad79696bc09c137e09527e3089568 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 01:59:11 +0000 Subject: [PATCH 011/170] feat: support vision - Added new dependencies: `github.com/fsnotify/fsnotify v1.4.9`, `github.com/go-playground/assert/v2 v2.2.0`, `github.com/nxadm/tail v1.4.8`, `github.com/onsi/ginkgo v1.16.5`, `github.com/onsi/gomega v1.18.1`, `golang.org/x/net v0.10.0`, `gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7` - Updated dependencies: `github.com/gin-gonic/gin` from v1.9.0 to v2.0.0, `golang.org/x/net` from v0.17.0 to v0.10.0 - Removed dependencies: `github.com/golang-jwt/jwt v3.2.2+incompatible`, `github.com/gorilla/websocket v1.5.1` - Updated Go version from `1.18` to `1.21` - Made various modifications and refactoring in the code: - Added new struct `VisionMessage` with fields `Role`, `Content`, and `Name` - Added constants for certain types - Added methods and error handling to handle different message types - Modified existing struct and methods to accommodate changes - Removed unused imports --- controller/relay-aiproxy.go | 26 +- controller/relay-ali.go | 606 ++++++++++++++++---------------- controller/relay-baidu.go | 668 ++++++++++++++++++------------------ controller/relay-claude.go | 12 +- controller/relay-palm.go | 382 ++++++++++----------- controller/relay-tencent.go | 538 ++++++++++++++--------------- controller/relay-text.go | 465 ++++++++++++------------- controller/relay-xunfei.go | 574 +++++++++++++++---------------- controller/relay-zhipu.go | 548 ++++++++++++++--------------- controller/relay.go | 121 ++++++- go.mod | 7 +- go.sum | 14 +- package-lock.json | 6 + 13 files changed, 2051 insertions(+), 1916 deletions(-) create mode 100644 package-lock.json diff --git a/controller/relay-aiproxy.go b/controller/relay-aiproxy.go index d0159ce8..ca0911ba 100644 --- a/controller/relay-aiproxy.go +++ b/controller/relay-aiproxy.go @@ -4,12 +4,14 @@ import ( "bufio" "encoding/json" "fmt" - "github.com/gin-gonic/gin" "io" + "log" "net/http" "one-api/common" "strconv" "strings" + + "github.com/gin-gonic/gin" ) // https://docs.aiproxy.io/dev/library#使用已经定制好的知识库进行对话问答 @@ -47,9 +49,27 @@ type AIProxyLibraryStreamResponse struct { func requestOpenAI2AIProxyLibrary(request GeneralOpenAIRequest) *AIProxyLibraryRequest { query := "" - if len(request.Messages) != 0 { - query = request.Messages[len(request.Messages)-1].Content + if request.MessagesLen() != 0 { + switch msgs := request.Messages.(type) { + case []Message: + query = msgs[len(msgs)-1].Content + case []VisionMessage: + query = msgs[len(msgs)-1].Content.Text + case []any: + msg := msgs[len(msgs)-1] + switch msg := msg.(type) { + case Message: + query = msg.Content + case VisionMessage: + query = msg.Content.Text + default: + log.Panicf("unknown message type: %T", msg) + } + default: + log.Panicf("unknown message type: %T", msgs) + } } + return &AIProxyLibraryRequest{ Model: request.Model, Stream: request.Stream, diff --git a/controller/relay-ali.go b/controller/relay-ali.go index 50dc743c..b022a9a3 100644 --- a/controller/relay-ali.go +++ b/controller/relay-ali.go @@ -1,329 +1,329 @@ package controller -import ( - "bufio" - "encoding/json" - "github.com/gin-gonic/gin" - "io" - "net/http" - "one-api/common" - "strings" -) +// 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 +// // 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 AliMessage struct { +// User string `json:"user"` +// Bot string `json:"bot"` +// } -type AliInput struct { - Prompt string `json:"prompt"` - History []AliMessage `json:"history"` -} +// 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 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 AliChatRequest struct { +// Model string `json:"model"` +// Input AliInput `json:"input"` +// Parameters AliParameters `json:"parameters,omitempty"` +// } -type AliEmbeddingRequest struct { - Model string `json:"model"` - Input struct { - Texts []string `json:"texts"` - } `json:"input"` - Parameters *struct { - TextType string `json:"text_type,omitempty"` - } `json:"parameters,omitempty"` -} +// type AliEmbeddingRequest struct { +// Model string `json:"model"` +// Input struct { +// Texts []string `json:"texts"` +// } `json:"input"` +// Parameters *struct { +// TextType string `json:"text_type,omitempty"` +// } `json:"parameters,omitempty"` +// } -type AliEmbedding struct { - Embedding []float64 `json:"embedding"` - TextIndex int `json:"text_index"` -} +// type AliEmbedding struct { +// Embedding []float64 `json:"embedding"` +// TextIndex int `json:"text_index"` +// } -type AliEmbeddingResponse struct { - Output struct { - Embeddings []AliEmbedding `json:"embeddings"` - } `json:"output"` - Usage AliUsage `json:"usage"` - AliError -} +// type AliEmbeddingResponse struct { +// Output struct { +// Embeddings []AliEmbedding `json:"embeddings"` +// } `json:"output"` +// Usage AliUsage `json:"usage"` +// AliError +// } -type AliError struct { - Code string `json:"code"` - Message string `json:"message"` - RequestId string `json:"request_id"` -} +// 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"` - TotalTokens int `json:"total_tokens"` -} +// type AliUsage struct { +// InputTokens int `json:"input_tokens"` +// OutputTokens int `json:"output_tokens"` +// TotalTokens int `json:"total_tokens"` +// } -type AliOutput struct { - Text string `json:"text"` - FinishReason string `json:"finish_reason"` -} +// type AliOutput struct { +// Text string `json:"text"` +// FinishReason string `json:"finish_reason"` +// } -type AliChatResponse struct { - Output AliOutput `json:"output"` - Usage AliUsage `json:"usage"` - AliError -} +// 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 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 embeddingRequestOpenAI2Ali(request GeneralOpenAIRequest) *AliEmbeddingRequest { - return &AliEmbeddingRequest{ - Model: "text-embedding-v1", - Input: struct { - Texts []string `json:"texts"` - }{ - Texts: request.ParseInput(), - }, - } -} +// func embeddingRequestOpenAI2Ali(request GeneralOpenAIRequest) *AliEmbeddingRequest { +// return &AliEmbeddingRequest{ +// Model: "text-embedding-v1", +// Input: struct { +// Texts []string `json:"texts"` +// }{ +// Texts: request.ParseInput(), +// }, +// } +// } -func aliEmbeddingHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { - var aliResponse AliEmbeddingResponse - err := json.NewDecoder(resp.Body).Decode(&aliResponse) - if err != nil { - return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil - } +// func aliEmbeddingHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { +// var aliResponse AliEmbeddingResponse +// err := json.NewDecoder(resp.Body).Decode(&aliResponse) +// if err != nil { +// return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil +// } - err = resp.Body.Close() - if err != nil { - return errorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil - } +// err = resp.Body.Close() +// if err != nil { +// return errorWrapper(err, "close_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 - } +// if aliResponse.Code != "" { +// return &OpenAIErrorWithStatusCode{ +// OpenAIError: OpenAIError{ +// Message: aliResponse.Message, +// Type: aliResponse.Code, +// Param: aliResponse.RequestId, +// Code: aliResponse.Code, +// }, +// StatusCode: resp.StatusCode, +// }, nil +// } - fullTextResponse := embeddingResponseAli2OpenAI(&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 -} +// fullTextResponse := embeddingResponseAli2OpenAI(&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 +// } -func embeddingResponseAli2OpenAI(response *AliEmbeddingResponse) *OpenAIEmbeddingResponse { - openAIEmbeddingResponse := OpenAIEmbeddingResponse{ - Object: "list", - Data: make([]OpenAIEmbeddingResponseItem, 0, len(response.Output.Embeddings)), - Model: "text-embedding-v1", - Usage: Usage{TotalTokens: response.Usage.TotalTokens}, - } +// func embeddingResponseAli2OpenAI(response *AliEmbeddingResponse) *OpenAIEmbeddingResponse { +// openAIEmbeddingResponse := OpenAIEmbeddingResponse{ +// Object: "list", +// Data: make([]OpenAIEmbeddingResponseItem, 0, len(response.Output.Embeddings)), +// Model: "text-embedding-v1", +// Usage: Usage{TotalTokens: response.Usage.TotalTokens}, +// } - for _, item := range response.Output.Embeddings { - openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, OpenAIEmbeddingResponseItem{ - Object: `embedding`, - Index: item.TextIndex, - Embedding: item.Embedding, - }) - } - return &openAIEmbeddingResponse -} +// for _, item := range response.Output.Embeddings { +// openAIEmbeddingResponse.Data = append(openAIEmbeddingResponse.Data, OpenAIEmbeddingResponseItem{ +// Object: `embedding`, +// Index: item.TextIndex, +// Embedding: item.Embedding, +// }) +// } +// return &openAIEmbeddingResponse +// } -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 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 - if aliResponse.Output.FinishReason != "null" { - finishReason := aliResponse.Output.FinishReason - choice.FinishReason = &finishReason - } - response := ChatCompletionsStreamResponse{ - Id: aliResponse.RequestId, - Object: "chat.completion.chunk", - Created: common.GetTimestamp(), - Model: "ernie-bot", - Choices: []ChatCompletionsStreamResponseChoice{choice}, - } - return &response -} +// func streamResponseAli2OpenAI(aliResponse *AliChatResponse) *ChatCompletionsStreamResponse { +// var choice ChatCompletionsStreamResponseChoice +// choice.Delta.Content = aliResponse.Output.Text +// if aliResponse.Output.FinishReason != "null" { +// finishReason := aliResponse.Output.FinishReason +// choice.FinishReason = &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 - }() - setEventStreamHeaders(c) - 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 - } - if aliResponse.Usage.OutputTokens != 0 { - 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 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 +// }() +// setEventStreamHeaders(c) +// 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 +// } +// if aliResponse.Usage.OutputTokens != 0 { +// 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 -} +// 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 +// } diff --git a/controller/relay-baidu.go b/controller/relay-baidu.go index ed08ac04..432e3a99 100644 --- a/controller/relay-baidu.go +++ b/controller/relay-baidu.go @@ -1,359 +1,359 @@ package controller -import ( - "bufio" - "encoding/json" - "errors" - "fmt" - "github.com/gin-gonic/gin" - "io" - "net/http" - "one-api/common" - "strings" - "sync" - "time" -) +// import ( +// "bufio" +// "encoding/json" +// "errors" +// "fmt" +// "github.com/gin-gonic/gin" +// "io" +// "net/http" +// "one-api/common" +// "strings" +// "sync" +// "time" +// ) -// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/flfmc9do2 +// // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/flfmc9do2 -type BaiduTokenResponse struct { - ExpiresIn int `json:"expires_in"` - AccessToken string `json:"access_token"` -} +// type BaiduTokenResponse struct { +// ExpiresIn int `json:"expires_in"` +// AccessToken string `json:"access_token"` +// } -type BaiduMessage struct { - Role string `json:"role"` - Content string `json:"content"` -} +// type BaiduMessage struct { +// Role string `json:"role"` +// Content string `json:"content"` +// } -type BaiduChatRequest struct { - Messages []BaiduMessage `json:"messages"` - Stream bool `json:"stream"` - UserId string `json:"user_id,omitempty"` -} +// type BaiduChatRequest struct { +// Messages []BaiduMessage `json:"messages"` +// Stream bool `json:"stream"` +// UserId string `json:"user_id,omitempty"` +// } -type BaiduError struct { - ErrorCode int `json:"error_code"` - ErrorMsg string `json:"error_msg"` -} +// type BaiduError struct { +// ErrorCode int `json:"error_code"` +// ErrorMsg string `json:"error_msg"` +// } -type BaiduChatResponse struct { - Id string `json:"id"` - Object string `json:"object"` - Created int64 `json:"created"` - Result string `json:"result"` - IsTruncated bool `json:"is_truncated"` - NeedClearHistory bool `json:"need_clear_history"` - Usage Usage `json:"usage"` - BaiduError -} +// type BaiduChatResponse struct { +// Id string `json:"id"` +// Object string `json:"object"` +// Created int64 `json:"created"` +// Result string `json:"result"` +// IsTruncated bool `json:"is_truncated"` +// NeedClearHistory bool `json:"need_clear_history"` +// Usage Usage `json:"usage"` +// BaiduError +// } -type BaiduChatStreamResponse struct { - BaiduChatResponse - SentenceId int `json:"sentence_id"` - IsEnd bool `json:"is_end"` -} +// type BaiduChatStreamResponse struct { +// BaiduChatResponse +// SentenceId int `json:"sentence_id"` +// IsEnd bool `json:"is_end"` +// } -type BaiduEmbeddingRequest struct { - Input []string `json:"input"` -} +// type BaiduEmbeddingRequest struct { +// Input []string `json:"input"` +// } -type BaiduEmbeddingData struct { - Object string `json:"object"` - Embedding []float64 `json:"embedding"` - Index int `json:"index"` -} +// 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 -} +// type BaiduEmbeddingResponse struct { +// Id string `json:"id"` +// Object string `json:"object"` +// Created int64 `json:"created"` +// Data []BaiduEmbeddingData `json:"data"` +// Usage Usage `json:"usage"` +// BaiduError +// } -type BaiduAccessToken struct { - AccessToken string `json:"access_token"` - Error string `json:"error,omitempty"` - ErrorDescription string `json:"error_description,omitempty"` - ExpiresIn int64 `json:"expires_in,omitempty"` - ExpiresAt time.Time `json:"-"` -} +// type BaiduAccessToken struct { +// AccessToken string `json:"access_token"` +// Error string `json:"error,omitempty"` +// ErrorDescription string `json:"error_description,omitempty"` +// ExpiresIn int64 `json:"expires_in,omitempty"` +// ExpiresAt time.Time `json:"-"` +// } -var baiduTokenStore sync.Map +// var baiduTokenStore sync.Map -func requestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduChatRequest { - messages := make([]BaiduMessage, 0, len(request.Messages)) - for _, message := range request.Messages { - if message.Role == "system" { - messages = append(messages, BaiduMessage{ - 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{ - Messages: messages, - Stream: request.Stream, - } -} +// func requestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduChatRequest { +// messages := make([]BaiduMessage, 0, len(request.Messages)) +// for _, message := range request.Messages { +// if message.Role == "system" { +// messages = append(messages, BaiduMessage{ +// 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{ +// Messages: messages, +// Stream: request.Stream, +// } +// } -func responseBaidu2OpenAI(response *BaiduChatResponse) *OpenAITextResponse { - choice := OpenAITextResponseChoice{ - Index: 0, - Message: Message{ - Role: "assistant", - Content: response.Result, - }, - FinishReason: "stop", - } - fullTextResponse := OpenAITextResponse{ - Id: response.Id, - Object: "chat.completion", - Created: response.Created, - Choices: []OpenAITextResponseChoice{choice}, - Usage: response.Usage, - } - return &fullTextResponse -} +// func responseBaidu2OpenAI(response *BaiduChatResponse) *OpenAITextResponse { +// choice := OpenAITextResponseChoice{ +// Index: 0, +// Message: Message{ +// Role: "assistant", +// Content: response.Result, +// }, +// FinishReason: "stop", +// } +// fullTextResponse := OpenAITextResponse{ +// Id: response.Id, +// Object: "chat.completion", +// Created: response.Created, +// Choices: []OpenAITextResponseChoice{choice}, +// Usage: response.Usage, +// } +// return &fullTextResponse +// } -func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *ChatCompletionsStreamResponse { - var choice ChatCompletionsStreamResponseChoice - choice.Delta.Content = baiduResponse.Result - if baiduResponse.IsEnd { - choice.FinishReason = &stopFinishReason - } - response := ChatCompletionsStreamResponse{ - Id: baiduResponse.Id, - Object: "chat.completion.chunk", - Created: baiduResponse.Created, - Model: "ernie-bot", - Choices: []ChatCompletionsStreamResponseChoice{choice}, - } - return &response -} +// func streamResponseBaidu2OpenAI(baiduResponse *BaiduChatStreamResponse) *ChatCompletionsStreamResponse { +// var choice ChatCompletionsStreamResponseChoice +// choice.Delta.Content = baiduResponse.Result +// if baiduResponse.IsEnd { +// choice.FinishReason = &stopFinishReason +// } +// response := ChatCompletionsStreamResponse{ +// Id: baiduResponse.Id, +// Object: "chat.completion.chunk", +// Created: baiduResponse.Created, +// Model: "ernie-bot", +// Choices: []ChatCompletionsStreamResponseChoice{choice}, +// } +// return &response +// } -func embeddingRequestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduEmbeddingRequest { - return &BaiduEmbeddingRequest{ - Input: request.ParseInput(), - } -} +// func embeddingRequestOpenAI2Baidu(request GeneralOpenAIRequest) *BaiduEmbeddingRequest { +// return &BaiduEmbeddingRequest{ +// Input: request.ParseInput(), +// } +// } -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 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) { - 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) < 6 { // ignore blank line or wrong format - continue - } - data = data[6:] - dataChan <- data - } - stopChan <- true - }() - setEventStreamHeaders(c) - c.Stream(func(w io.Writer) bool { - select { - case data := <-dataChan: - var baiduResponse BaiduChatStreamResponse - err := json.Unmarshal([]byte(data), &baiduResponse) - if err != nil { - common.SysError("error unmarshalling stream response: " + err.Error()) - return true - } - if baiduResponse.Usage.TotalTokens != 0 { - usage.TotalTokens = baiduResponse.Usage.TotalTokens - usage.PromptTokens = baiduResponse.Usage.PromptTokens - usage.CompletionTokens = baiduResponse.Usage.TotalTokens - baiduResponse.Usage.PromptTokens - } - response := streamResponseBaidu2OpenAI(&baiduResponse) - 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 baiduStreamHandler(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) < 6 { // ignore blank line or wrong format +// continue +// } +// data = data[6:] +// dataChan <- data +// } +// stopChan <- true +// }() +// setEventStreamHeaders(c) +// c.Stream(func(w io.Writer) bool { +// select { +// case data := <-dataChan: +// var baiduResponse BaiduChatStreamResponse +// err := json.Unmarshal([]byte(data), &baiduResponse) +// if err != nil { +// common.SysError("error unmarshalling stream response: " + err.Error()) +// return true +// } +// if baiduResponse.Usage.TotalTokens != 0 { +// usage.TotalTokens = baiduResponse.Usage.TotalTokens +// usage.PromptTokens = baiduResponse.Usage.PromptTokens +// usage.CompletionTokens = baiduResponse.Usage.TotalTokens - baiduResponse.Usage.PromptTokens +// } +// response := streamResponseBaidu2OpenAI(&baiduResponse) +// 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 baiduHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { - var baiduResponse BaiduChatResponse - 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 := responseBaidu2OpenAI(&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 -} +// func baiduHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { +// var baiduResponse BaiduChatResponse +// 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 := responseBaidu2OpenAI(&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 +// } -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 -} +// 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 +// } -func getBaiduAccessToken(apiKey string) (string, error) { - if val, ok := baiduTokenStore.Load(apiKey); ok { - var accessToken BaiduAccessToken - if accessToken, ok = val.(BaiduAccessToken); ok { - // soon this will expire - if time.Now().Add(time.Hour).After(accessToken.ExpiresAt) { - go func() { - _, _ = getBaiduAccessTokenHelper(apiKey) - }() - } - return accessToken.AccessToken, nil - } - } - accessToken, err := getBaiduAccessTokenHelper(apiKey) - if err != nil { - return "", err - } - if accessToken == nil { - return "", errors.New("getBaiduAccessToken return a nil token") - } - return (*accessToken).AccessToken, nil -} +// func getBaiduAccessToken(apiKey string) (string, error) { +// if val, ok := baiduTokenStore.Load(apiKey); ok { +// var accessToken BaiduAccessToken +// if accessToken, ok = val.(BaiduAccessToken); ok { +// // soon this will expire +// if time.Now().Add(time.Hour).After(accessToken.ExpiresAt) { +// go func() { +// _, _ = getBaiduAccessTokenHelper(apiKey) +// }() +// } +// return accessToken.AccessToken, nil +// } +// } +// accessToken, err := getBaiduAccessTokenHelper(apiKey) +// if err != nil { +// return "", err +// } +// if accessToken == nil { +// return "", errors.New("getBaiduAccessToken return a nil token") +// } +// return (*accessToken).AccessToken, nil +// } -func getBaiduAccessTokenHelper(apiKey string) (*BaiduAccessToken, error) { - parts := strings.Split(apiKey, "|") - if len(parts) != 2 { - return nil, errors.New("invalid baidu apikey") - } - req, err := http.NewRequest("POST", fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s", - parts[0], parts[1]), nil) - if err != nil { - return nil, err - } - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Accept", "application/json") - res, err := impatientHTTPClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() +// func getBaiduAccessTokenHelper(apiKey string) (*BaiduAccessToken, error) { +// parts := strings.Split(apiKey, "|") +// if len(parts) != 2 { +// return nil, errors.New("invalid baidu apikey") +// } +// req, err := http.NewRequest("POST", fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s", +// parts[0], parts[1]), nil) +// if err != nil { +// return nil, err +// } +// req.Header.Add("Content-Type", "application/json") +// req.Header.Add("Accept", "application/json") +// res, err := impatientHTTPClient.Do(req) +// if err != nil { +// return nil, err +// } +// defer res.Body.Close() - var accessToken BaiduAccessToken - err = json.NewDecoder(res.Body).Decode(&accessToken) - if err != nil { - return nil, err - } - if accessToken.Error != "" { - return nil, errors.New(accessToken.Error + ": " + accessToken.ErrorDescription) - } - if accessToken.AccessToken == "" { - return nil, errors.New("getBaiduAccessTokenHelper get empty access token") - } - accessToken.ExpiresAt = time.Now().Add(time.Duration(accessToken.ExpiresIn) * time.Second) - baiduTokenStore.Store(apiKey, accessToken) - return &accessToken, nil -} +// var accessToken BaiduAccessToken +// err = json.NewDecoder(res.Body).Decode(&accessToken) +// if err != nil { +// return nil, err +// } +// if accessToken.Error != "" { +// return nil, errors.New(accessToken.Error + ": " + accessToken.ErrorDescription) +// } +// if accessToken.AccessToken == "" { +// return nil, errors.New("getBaiduAccessTokenHelper get empty access token") +// } +// accessToken.ExpiresAt = time.Now().Add(time.Duration(accessToken.ExpiresIn) * time.Second) +// baiduTokenStore.Store(apiKey, accessToken) +// return &accessToken, nil +// } diff --git a/controller/relay-claude.go b/controller/relay-claude.go index 1f4a3e7b..2087755f 100644 --- a/controller/relay-claude.go +++ b/controller/relay-claude.go @@ -4,11 +4,13 @@ import ( "bufio" "encoding/json" "fmt" - "github.com/gin-gonic/gin" "io" + "log" "net/http" "one-api/common" "strings" + + "github.com/gin-gonic/gin" ) type ClaudeMetadata struct { @@ -64,7 +66,13 @@ func requestOpenAI2Claude(textRequest GeneralOpenAIRequest) *ClaudeRequest { claudeRequest.MaxTokensToSample = 1000000 } prompt := "" - for _, message := range textRequest.Messages { + + messages, err := textRequest.TextMessages() + if err != nil { + log.Panicf("invalid message type: %T", textRequest.Messages) + } + + for _, message := range messages { if message.Role == "user" { prompt += fmt.Sprintf("\n\nHuman: %s", message.Content) } else if message.Role == "assistant" { diff --git a/controller/relay-palm.go b/controller/relay-palm.go index a705b318..2f6166f3 100644 --- a/controller/relay-palm.go +++ b/controller/relay-palm.go @@ -1,205 +1,205 @@ package controller -import ( - "encoding/json" - "fmt" - "github.com/gin-gonic/gin" - "io" - "net/http" - "one-api/common" -) +// import ( +// "encoding/json" +// "fmt" +// "github.com/gin-gonic/gin" +// "io" +// "net/http" +// "one-api/common" +// ) -// https://developers.generativeai.google/api/rest/generativelanguage/models/generateMessage#request-body -// https://developers.generativeai.google/api/rest/generativelanguage/models/generateMessage#response-body +// // https://developers.generativeai.google/api/rest/generativelanguage/models/generateMessage#request-body +// // https://developers.generativeai.google/api/rest/generativelanguage/models/generateMessage#response-body -type PaLMChatMessage struct { - Author string `json:"author"` - Content string `json:"content"` -} +// type PaLMChatMessage struct { +// Author string `json:"author"` +// Content string `json:"content"` +// } -type PaLMFilter struct { - Reason string `json:"reason"` - Message string `json:"message"` -} +// type PaLMFilter struct { +// Reason string `json:"reason"` +// Message string `json:"message"` +// } -type PaLMPrompt struct { - Messages []PaLMChatMessage `json:"messages"` -} +// type PaLMPrompt struct { +// Messages []PaLMChatMessage `json:"messages"` +// } -type PaLMChatRequest struct { - Prompt PaLMPrompt `json:"prompt"` - Temperature float64 `json:"temperature,omitempty"` - CandidateCount int `json:"candidateCount,omitempty"` - TopP float64 `json:"topP,omitempty"` - TopK int `json:"topK,omitempty"` -} +// type PaLMChatRequest struct { +// Prompt PaLMPrompt `json:"prompt"` +// Temperature float64 `json:"temperature,omitempty"` +// CandidateCount int `json:"candidateCount,omitempty"` +// TopP float64 `json:"topP,omitempty"` +// TopK int `json:"topK,omitempty"` +// } -type PaLMError struct { - Code int `json:"code"` - Message string `json:"message"` - Status string `json:"status"` -} +// type PaLMError struct { +// Code int `json:"code"` +// Message string `json:"message"` +// Status string `json:"status"` +// } -type PaLMChatResponse struct { - Candidates []PaLMChatMessage `json:"candidates"` - Messages []Message `json:"messages"` - Filters []PaLMFilter `json:"filters"` - Error PaLMError `json:"error"` -} +// type PaLMChatResponse struct { +// Candidates []PaLMChatMessage `json:"candidates"` +// Messages []Message `json:"messages"` +// Filters []PaLMFilter `json:"filters"` +// Error PaLMError `json:"error"` +// } -func requestOpenAI2PaLM(textRequest GeneralOpenAIRequest) *PaLMChatRequest { - palmRequest := PaLMChatRequest{ - Prompt: PaLMPrompt{ - Messages: make([]PaLMChatMessage, 0, len(textRequest.Messages)), - }, - Temperature: textRequest.Temperature, - CandidateCount: textRequest.N, - TopP: textRequest.TopP, - TopK: textRequest.MaxTokens, - } - for _, message := range textRequest.Messages { - palmMessage := PaLMChatMessage{ - Content: message.Content, - } - if message.Role == "user" { - palmMessage.Author = "0" - } else { - palmMessage.Author = "1" - } - palmRequest.Prompt.Messages = append(palmRequest.Prompt.Messages, palmMessage) - } - return &palmRequest -} +// func requestOpenAI2PaLM(textRequest GeneralOpenAIRequest) *PaLMChatRequest { +// palmRequest := PaLMChatRequest{ +// Prompt: PaLMPrompt{ +// Messages: make([]PaLMChatMessage, 0, len(textRequest.Messages)), +// }, +// Temperature: textRequest.Temperature, +// CandidateCount: textRequest.N, +// TopP: textRequest.TopP, +// TopK: textRequest.MaxTokens, +// } +// for _, message := range textRequest.Messages { +// palmMessage := PaLMChatMessage{ +// Content: message.Content, +// } +// if message.Role == "user" { +// palmMessage.Author = "0" +// } else { +// palmMessage.Author = "1" +// } +// palmRequest.Prompt.Messages = append(palmRequest.Prompt.Messages, palmMessage) +// } +// return &palmRequest +// } -func responsePaLM2OpenAI(response *PaLMChatResponse) *OpenAITextResponse { - fullTextResponse := OpenAITextResponse{ - Choices: make([]OpenAITextResponseChoice, 0, len(response.Candidates)), - } - for i, candidate := range response.Candidates { - choice := OpenAITextResponseChoice{ - Index: i, - Message: Message{ - Role: "assistant", - Content: candidate.Content, - }, - FinishReason: "stop", - } - fullTextResponse.Choices = append(fullTextResponse.Choices, choice) - } - return &fullTextResponse -} +// func responsePaLM2OpenAI(response *PaLMChatResponse) *OpenAITextResponse { +// fullTextResponse := OpenAITextResponse{ +// Choices: make([]OpenAITextResponseChoice, 0, len(response.Candidates)), +// } +// for i, candidate := range response.Candidates { +// choice := OpenAITextResponseChoice{ +// Index: i, +// Message: Message{ +// Role: "assistant", +// Content: candidate.Content, +// }, +// FinishReason: "stop", +// } +// fullTextResponse.Choices = append(fullTextResponse.Choices, choice) +// } +// return &fullTextResponse +// } -func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *ChatCompletionsStreamResponse { - var choice ChatCompletionsStreamResponseChoice - if len(palmResponse.Candidates) > 0 { - choice.Delta.Content = palmResponse.Candidates[0].Content - } - choice.FinishReason = &stopFinishReason - var response ChatCompletionsStreamResponse - response.Object = "chat.completion.chunk" - response.Model = "palm2" - response.Choices = []ChatCompletionsStreamResponseChoice{choice} - return &response -} +// func streamResponsePaLM2OpenAI(palmResponse *PaLMChatResponse) *ChatCompletionsStreamResponse { +// var choice ChatCompletionsStreamResponseChoice +// if len(palmResponse.Candidates) > 0 { +// choice.Delta.Content = palmResponse.Candidates[0].Content +// } +// choice.FinishReason = &stopFinishReason +// var response ChatCompletionsStreamResponse +// response.Object = "chat.completion.chunk" +// response.Model = "palm2" +// response.Choices = []ChatCompletionsStreamResponseChoice{choice} +// return &response +// } -func palmStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, string) { - responseText := "" - responseId := fmt.Sprintf("chatcmpl-%s", common.GetUUID()) - createdTime := common.GetTimestamp() - dataChan := make(chan string) - stopChan := make(chan bool) - go func() { - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - common.SysError("error reading stream response: " + err.Error()) - stopChan <- true - return - } - err = resp.Body.Close() - if err != nil { - common.SysError("error closing stream response: " + err.Error()) - stopChan <- true - return - } - var palmResponse PaLMChatResponse - err = json.Unmarshal(responseBody, &palmResponse) - if err != nil { - common.SysError("error unmarshalling stream response: " + err.Error()) - stopChan <- true - return - } - fullTextResponse := streamResponsePaLM2OpenAI(&palmResponse) - fullTextResponse.Id = responseId - fullTextResponse.Created = createdTime - if len(palmResponse.Candidates) > 0 { - responseText = palmResponse.Candidates[0].Content - } - jsonResponse, err := json.Marshal(fullTextResponse) - if err != nil { - common.SysError("error marshalling stream response: " + err.Error()) - stopChan <- true - return - } - dataChan <- string(jsonResponse) - stopChan <- true - }() - setEventStreamHeaders(c) - c.Stream(func(w io.Writer) bool { - select { - case data := <-dataChan: - c.Render(-1, common.CustomEvent{Data: "data: " + data}) - 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 palmStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, string) { +// responseText := "" +// responseId := fmt.Sprintf("chatcmpl-%s", common.GetUUID()) +// createdTime := common.GetTimestamp() +// dataChan := make(chan string) +// stopChan := make(chan bool) +// go func() { +// responseBody, err := io.ReadAll(resp.Body) +// if err != nil { +// common.SysError("error reading stream response: " + err.Error()) +// stopChan <- true +// return +// } +// err = resp.Body.Close() +// if err != nil { +// common.SysError("error closing stream response: " + err.Error()) +// stopChan <- true +// return +// } +// var palmResponse PaLMChatResponse +// err = json.Unmarshal(responseBody, &palmResponse) +// if err != nil { +// common.SysError("error unmarshalling stream response: " + err.Error()) +// stopChan <- true +// return +// } +// fullTextResponse := streamResponsePaLM2OpenAI(&palmResponse) +// fullTextResponse.Id = responseId +// fullTextResponse.Created = createdTime +// if len(palmResponse.Candidates) > 0 { +// responseText = palmResponse.Candidates[0].Content +// } +// jsonResponse, err := json.Marshal(fullTextResponse) +// if err != nil { +// common.SysError("error marshalling stream response: " + err.Error()) +// stopChan <- true +// return +// } +// dataChan <- string(jsonResponse) +// stopChan <- true +// }() +// setEventStreamHeaders(c) +// c.Stream(func(w io.Writer) bool { +// select { +// case data := <-dataChan: +// c.Render(-1, common.CustomEvent{Data: "data: " + data}) +// 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 palmHandler(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 palmResponse PaLMChatResponse - err = json.Unmarshal(responseBody, &palmResponse) - if err != nil { - return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil - } - if palmResponse.Error.Code != 0 || len(palmResponse.Candidates) == 0 { - return &OpenAIErrorWithStatusCode{ - OpenAIError: OpenAIError{ - Message: palmResponse.Error.Message, - Type: palmResponse.Error.Status, - Param: "", - Code: palmResponse.Error.Code, - }, - StatusCode: resp.StatusCode, - }, nil - } - fullTextResponse := responsePaLM2OpenAI(&palmResponse) - completionTokens := countTokenText(palmResponse.Candidates[0].Content, 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 -} +// func palmHandler(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 palmResponse PaLMChatResponse +// err = json.Unmarshal(responseBody, &palmResponse) +// if err != nil { +// return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil +// } +// if palmResponse.Error.Code != 0 || len(palmResponse.Candidates) == 0 { +// return &OpenAIErrorWithStatusCode{ +// OpenAIError: OpenAIError{ +// Message: palmResponse.Error.Message, +// Type: palmResponse.Error.Status, +// Param: "", +// Code: palmResponse.Error.Code, +// }, +// StatusCode: resp.StatusCode, +// }, nil +// } +// fullTextResponse := responsePaLM2OpenAI(&palmResponse) +// completionTokens := countTokenText(palmResponse.Candidates[0].Content, 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 +// } diff --git a/controller/relay-tencent.go b/controller/relay-tencent.go index 024468bc..27540806 100644 --- a/controller/relay-tencent.go +++ b/controller/relay-tencent.go @@ -1,287 +1,287 @@ package controller -import ( - "bufio" - "crypto/hmac" - "crypto/sha1" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "github.com/gin-gonic/gin" - "io" - "net/http" - "one-api/common" - "sort" - "strconv" - "strings" -) +// import ( +// "bufio" +// "crypto/hmac" +// "crypto/sha1" +// "encoding/base64" +// "encoding/json" +// "errors" +// "fmt" +// "github.com/gin-gonic/gin" +// "io" +// "net/http" +// "one-api/common" +// "sort" +// "strconv" +// "strings" +// ) -// https://cloud.tencent.com/document/product/1729/97732 +// // https://cloud.tencent.com/document/product/1729/97732 -type TencentMessage struct { - Role string `json:"role"` - Content string `json:"content"` -} +// type TencentMessage struct { +// Role string `json:"role"` +// Content string `json:"content"` +// } -type TencentChatRequest struct { - AppId int64 `json:"app_id"` // 腾讯云账号的 APPID - SecretId string `json:"secret_id"` // 官网 SecretId - // Timestamp当前 UNIX 时间戳,单位为秒,可记录发起 API 请求的时间。 - // 例如1529223702,如果与当前时间相差过大,会引起签名过期错误 - Timestamp int64 `json:"timestamp"` - // Expired 签名的有效期,是一个符合 UNIX Epoch 时间戳规范的数值, - // 单位为秒;Expired 必须大于 Timestamp 且 Expired-Timestamp 小于90天 - Expired int64 `json:"expired"` - QueryID string `json:"query_id"` //请求 Id,用于问题排查 - // Temperature 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 - // 默认 1.0,取值区间为[0.0,2.0],非必要不建议使用,不合理的取值会影响效果 - // 建议该参数和 top_p 只设置1个,不要同时更改 top_p - Temperature float64 `json:"temperature"` - // TopP 影响输出文本的多样性,取值越大,生成文本的多样性越强 - // 默认1.0,取值区间为[0.0, 1.0],非必要不建议使用, 不合理的取值会影响效果 - // 建议该参数和 temperature 只设置1个,不要同时更改 - TopP float64 `json:"top_p"` - // Stream 0:同步,1:流式 (默认,协议:SSE) - // 同步请求超时:60s,如果内容较长建议使用流式 - Stream int `json:"stream"` - // Messages 会话内容, 长度最多为40, 按对话时间从旧到新在数组中排列 - // 输入 content 总数最大支持 3000 token。 - Messages []TencentMessage `json:"messages"` -} +// type TencentChatRequest struct { +// AppId int64 `json:"app_id"` // 腾讯云账号的 APPID +// SecretId string `json:"secret_id"` // 官网 SecretId +// // Timestamp当前 UNIX 时间戳,单位为秒,可记录发起 API 请求的时间。 +// // 例如1529223702,如果与当前时间相差过大,会引起签名过期错误 +// Timestamp int64 `json:"timestamp"` +// // Expired 签名的有效期,是一个符合 UNIX Epoch 时间戳规范的数值, +// // 单位为秒;Expired 必须大于 Timestamp 且 Expired-Timestamp 小于90天 +// Expired int64 `json:"expired"` +// QueryID string `json:"query_id"` //请求 Id,用于问题排查 +// // Temperature 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 +// // 默认 1.0,取值区间为[0.0,2.0],非必要不建议使用,不合理的取值会影响效果 +// // 建议该参数和 top_p 只设置1个,不要同时更改 top_p +// Temperature float64 `json:"temperature"` +// // TopP 影响输出文本的多样性,取值越大,生成文本的多样性越强 +// // 默认1.0,取值区间为[0.0, 1.0],非必要不建议使用, 不合理的取值会影响效果 +// // 建议该参数和 temperature 只设置1个,不要同时更改 +// TopP float64 `json:"top_p"` +// // Stream 0:同步,1:流式 (默认,协议:SSE) +// // 同步请求超时:60s,如果内容较长建议使用流式 +// Stream int `json:"stream"` +// // Messages 会话内容, 长度最多为40, 按对话时间从旧到新在数组中排列 +// // 输入 content 总数最大支持 3000 token。 +// Messages []TencentMessage `json:"messages"` +// } -type TencentError struct { - Code int `json:"code"` - Message string `json:"message"` -} +// type TencentError struct { +// Code int `json:"code"` +// Message string `json:"message"` +// } -type TencentUsage struct { - InputTokens int `json:"input_tokens"` - OutputTokens int `json:"output_tokens"` - TotalTokens int `json:"total_tokens"` -} +// type TencentUsage struct { +// InputTokens int `json:"input_tokens"` +// OutputTokens int `json:"output_tokens"` +// TotalTokens int `json:"total_tokens"` +// } -type TencentResponseChoices struct { - FinishReason string `json:"finish_reason,omitempty"` // 流式结束标志位,为 stop 则表示尾包 - Messages TencentMessage `json:"messages,omitempty"` // 内容,同步模式返回内容,流模式为 null 输出 content 内容总数最多支持 1024token。 - Delta TencentMessage `json:"delta,omitempty"` // 内容,流模式返回内容,同步模式为 null 输出 content 内容总数最多支持 1024token。 -} +// type TencentResponseChoices struct { +// FinishReason string `json:"finish_reason,omitempty"` // 流式结束标志位,为 stop 则表示尾包 +// Messages TencentMessage `json:"messages,omitempty"` // 内容,同步模式返回内容,流模式为 null 输出 content 内容总数最多支持 1024token。 +// Delta TencentMessage `json:"delta,omitempty"` // 内容,流模式返回内容,同步模式为 null 输出 content 内容总数最多支持 1024token。 +// } -type TencentChatResponse struct { - Choices []TencentResponseChoices `json:"choices,omitempty"` // 结果 - Created string `json:"created,omitempty"` // unix 时间戳的字符串 - Id string `json:"id,omitempty"` // 会话 id - Usage Usage `json:"usage,omitempty"` // token 数量 - Error TencentError `json:"error,omitempty"` // 错误信息 注意:此字段可能返回 null,表示取不到有效值 - Note string `json:"note,omitempty"` // 注释 - ReqID string `json:"req_id,omitempty"` // 唯一请求 Id,每次请求都会返回。用于反馈接口入参 -} +// type TencentChatResponse struct { +// Choices []TencentResponseChoices `json:"choices,omitempty"` // 结果 +// Created string `json:"created,omitempty"` // unix 时间戳的字符串 +// Id string `json:"id,omitempty"` // 会话 id +// Usage Usage `json:"usage,omitempty"` // token 数量 +// Error TencentError `json:"error,omitempty"` // 错误信息 注意:此字段可能返回 null,表示取不到有效值 +// Note string `json:"note,omitempty"` // 注释 +// ReqID string `json:"req_id,omitempty"` // 唯一请求 Id,每次请求都会返回。用于反馈接口入参 +// } -func requestOpenAI2Tencent(request GeneralOpenAIRequest) *TencentChatRequest { - messages := make([]TencentMessage, 0, len(request.Messages)) - for i := 0; i < len(request.Messages); i++ { - message := request.Messages[i] - if message.Role == "system" { - messages = append(messages, TencentMessage{ - Role: "user", - Content: message.Content, - }) - messages = append(messages, TencentMessage{ - Role: "assistant", - Content: "Okay", - }) - continue - } - messages = append(messages, TencentMessage{ - Content: message.Content, - Role: message.Role, - }) - } - stream := 0 - if request.Stream { - stream = 1 - } - return &TencentChatRequest{ - Timestamp: common.GetTimestamp(), - Expired: common.GetTimestamp() + 24*60*60, - QueryID: common.GetUUID(), - Temperature: request.Temperature, - TopP: request.TopP, - Stream: stream, - Messages: messages, - } -} +// func requestOpenAI2Tencent(request GeneralOpenAIRequest) *TencentChatRequest { +// messages := make([]TencentMessage, 0, len(request.Messages)) +// for i := 0; i < len(request.Messages); i++ { +// message := request.Messages[i] +// if message.Role == "system" { +// messages = append(messages, TencentMessage{ +// Role: "user", +// Content: message.Content, +// }) +// messages = append(messages, TencentMessage{ +// Role: "assistant", +// Content: "Okay", +// }) +// continue +// } +// messages = append(messages, TencentMessage{ +// Content: message.Content, +// Role: message.Role, +// }) +// } +// stream := 0 +// if request.Stream { +// stream = 1 +// } +// return &TencentChatRequest{ +// Timestamp: common.GetTimestamp(), +// Expired: common.GetTimestamp() + 24*60*60, +// QueryID: common.GetUUID(), +// Temperature: request.Temperature, +// TopP: request.TopP, +// Stream: stream, +// Messages: messages, +// } +// } -func responseTencent2OpenAI(response *TencentChatResponse) *OpenAITextResponse { - fullTextResponse := OpenAITextResponse{ - Object: "chat.completion", - Created: common.GetTimestamp(), - Usage: response.Usage, - } - if len(response.Choices) > 0 { - choice := OpenAITextResponseChoice{ - Index: 0, - Message: Message{ - Role: "assistant", - Content: response.Choices[0].Messages.Content, - }, - FinishReason: response.Choices[0].FinishReason, - } - fullTextResponse.Choices = append(fullTextResponse.Choices, choice) - } - return &fullTextResponse -} +// func responseTencent2OpenAI(response *TencentChatResponse) *OpenAITextResponse { +// fullTextResponse := OpenAITextResponse{ +// Object: "chat.completion", +// Created: common.GetTimestamp(), +// Usage: response.Usage, +// } +// if len(response.Choices) > 0 { +// choice := OpenAITextResponseChoice{ +// Index: 0, +// Message: Message{ +// Role: "assistant", +// Content: response.Choices[0].Messages.Content, +// }, +// FinishReason: response.Choices[0].FinishReason, +// } +// fullTextResponse.Choices = append(fullTextResponse.Choices, choice) +// } +// return &fullTextResponse +// } -func streamResponseTencent2OpenAI(TencentResponse *TencentChatResponse) *ChatCompletionsStreamResponse { - response := ChatCompletionsStreamResponse{ - Object: "chat.completion.chunk", - Created: common.GetTimestamp(), - Model: "tencent-hunyuan", - } - if len(TencentResponse.Choices) > 0 { - var choice ChatCompletionsStreamResponseChoice - choice.Delta.Content = TencentResponse.Choices[0].Delta.Content - if TencentResponse.Choices[0].FinishReason == "stop" { - choice.FinishReason = &stopFinishReason - } - response.Choices = append(response.Choices, choice) - } - return &response -} +// func streamResponseTencent2OpenAI(TencentResponse *TencentChatResponse) *ChatCompletionsStreamResponse { +// response := ChatCompletionsStreamResponse{ +// Object: "chat.completion.chunk", +// Created: common.GetTimestamp(), +// Model: "tencent-hunyuan", +// } +// if len(TencentResponse.Choices) > 0 { +// var choice ChatCompletionsStreamResponseChoice +// choice.Delta.Content = TencentResponse.Choices[0].Delta.Content +// if TencentResponse.Choices[0].FinishReason == "stop" { +// choice.FinishReason = &stopFinishReason +// } +// response.Choices = append(response.Choices, choice) +// } +// return &response +// } -func tencentStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, string) { - var responseText string - 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 - }() - setEventStreamHeaders(c) - c.Stream(func(w io.Writer) bool { - select { - case data := <-dataChan: - var TencentResponse TencentChatResponse - err := json.Unmarshal([]byte(data), &TencentResponse) - if err != nil { - common.SysError("error unmarshalling stream response: " + err.Error()) - return true - } - response := streamResponseTencent2OpenAI(&TencentResponse) - if len(response.Choices) != 0 { - responseText += response.Choices[0].Delta.Content - } - 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 tencentStreamHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, string) { +// var responseText string +// 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 +// }() +// setEventStreamHeaders(c) +// c.Stream(func(w io.Writer) bool { +// select { +// case data := <-dataChan: +// var TencentResponse TencentChatResponse +// err := json.Unmarshal([]byte(data), &TencentResponse) +// if err != nil { +// common.SysError("error unmarshalling stream response: " + err.Error()) +// return true +// } +// response := streamResponseTencent2OpenAI(&TencentResponse) +// if len(response.Choices) != 0 { +// responseText += response.Choices[0].Delta.Content +// } +// 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 tencentHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { - var TencentResponse TencentChatResponse - 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, &TencentResponse) - if err != nil { - return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil - } - if TencentResponse.Error.Code != 0 { - return &OpenAIErrorWithStatusCode{ - OpenAIError: OpenAIError{ - Message: TencentResponse.Error.Message, - Code: TencentResponse.Error.Code, - }, - StatusCode: resp.StatusCode, - }, nil - } - fullTextResponse := responseTencent2OpenAI(&TencentResponse) - 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 -} +// func tencentHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { +// var TencentResponse TencentChatResponse +// 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, &TencentResponse) +// if err != nil { +// return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil +// } +// if TencentResponse.Error.Code != 0 { +// return &OpenAIErrorWithStatusCode{ +// OpenAIError: OpenAIError{ +// Message: TencentResponse.Error.Message, +// Code: TencentResponse.Error.Code, +// }, +// StatusCode: resp.StatusCode, +// }, nil +// } +// fullTextResponse := responseTencent2OpenAI(&TencentResponse) +// 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 +// } -func parseTencentConfig(config string) (appId int64, secretId string, secretKey string, err error) { - parts := strings.Split(config, "|") - if len(parts) != 3 { - err = errors.New("invalid tencent config") - return - } - appId, err = strconv.ParseInt(parts[0], 10, 64) - secretId = parts[1] - secretKey = parts[2] - return -} +// func parseTencentConfig(config string) (appId int64, secretId string, secretKey string, err error) { +// parts := strings.Split(config, "|") +// if len(parts) != 3 { +// err = errors.New("invalid tencent config") +// return +// } +// appId, err = strconv.ParseInt(parts[0], 10, 64) +// secretId = parts[1] +// secretKey = parts[2] +// return +// } -func getTencentSign(req TencentChatRequest, secretKey string) string { - params := make([]string, 0) - params = append(params, "app_id="+strconv.FormatInt(req.AppId, 10)) - params = append(params, "secret_id="+req.SecretId) - params = append(params, "timestamp="+strconv.FormatInt(req.Timestamp, 10)) - params = append(params, "query_id="+req.QueryID) - params = append(params, "temperature="+strconv.FormatFloat(req.Temperature, 'f', -1, 64)) - params = append(params, "top_p="+strconv.FormatFloat(req.TopP, 'f', -1, 64)) - params = append(params, "stream="+strconv.Itoa(req.Stream)) - params = append(params, "expired="+strconv.FormatInt(req.Expired, 10)) +// func getTencentSign(req TencentChatRequest, secretKey string) string { +// params := make([]string, 0) +// params = append(params, "app_id="+strconv.FormatInt(req.AppId, 10)) +// params = append(params, "secret_id="+req.SecretId) +// params = append(params, "timestamp="+strconv.FormatInt(req.Timestamp, 10)) +// params = append(params, "query_id="+req.QueryID) +// params = append(params, "temperature="+strconv.FormatFloat(req.Temperature, 'f', -1, 64)) +// params = append(params, "top_p="+strconv.FormatFloat(req.TopP, 'f', -1, 64)) +// params = append(params, "stream="+strconv.Itoa(req.Stream)) +// params = append(params, "expired="+strconv.FormatInt(req.Expired, 10)) - var messageStr string - for _, msg := range req.Messages { - messageStr += fmt.Sprintf(`{"role":"%s","content":"%s"},`, msg.Role, msg.Content) - } - messageStr = strings.TrimSuffix(messageStr, ",") - params = append(params, "messages=["+messageStr+"]") +// var messageStr string +// for _, msg := range req.Messages { +// messageStr += fmt.Sprintf(`{"role":"%s","content":"%s"},`, msg.Role, msg.Content) +// } +// messageStr = strings.TrimSuffix(messageStr, ",") +// params = append(params, "messages=["+messageStr+"]") - sort.Sort(sort.StringSlice(params)) - url := "hunyuan.cloud.tencent.com/hyllm/v1/chat/completions?" + strings.Join(params, "&") - mac := hmac.New(sha1.New, []byte(secretKey)) - signURL := url - mac.Write([]byte(signURL)) - sign := mac.Sum([]byte(nil)) - return base64.StdEncoding.EncodeToString(sign) -} +// sort.Sort(sort.StringSlice(params)) +// url := "hunyuan.cloud.tencent.com/hyllm/v1/chat/completions?" + strings.Join(params, "&") +// mac := hmac.New(sha1.New, []byte(secretKey)) +// signURL := url +// mac.Write([]byte(signURL)) +// sign := mac.Sum([]byte(nil)) +// return base64.StdEncoding.EncodeToString(sign) +// } diff --git a/controller/relay-text.go b/controller/relay-text.go index a61c6f7c..50b45140 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -76,7 +76,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { return errorWrapper(errors.New("field prompt is required"), "required_field_missing", http.StatusBadRequest) } case RelayModeChatCompletions: - if textRequest.Messages == nil || len(textRequest.Messages) == 0 { + if textRequest.Messages == nil || textRequest.MessagesLen() == 0 { return errorWrapper(errors.New("field messages is required"), "required_field_missing", http.StatusBadRequest) } case RelayModeEmbeddings: @@ -154,26 +154,26 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { if baseURL != "" { fullRequestURL = fmt.Sprintf("%s/v1/complete", baseURL) } - case APITypeBaidu: - switch textRequest.Model { - case "ERNIE-Bot": - fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions" - case "ERNIE-Bot-turbo": - fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant" - case "ERNIE-Bot-4": - fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro" - case "BLOOMZ-7B": - 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 = strings.TrimPrefix(apiKey, "Bearer ") - var err error - if apiKey, err = getBaiduAccessToken(apiKey); err != nil { - return errorWrapper(err, "invalid_baidu_config", http.StatusInternalServerError) - } - fullRequestURL += "?access_token=" + apiKey + // case APITypeBaidu: + // switch textRequest.Model { + // case "ERNIE-Bot": + // fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions" + // case "ERNIE-Bot-turbo": + // fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant" + // case "ERNIE-Bot-4": + // fullRequestURL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro" + // case "BLOOMZ-7B": + // 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 = strings.TrimPrefix(apiKey, "Bearer ") + // var err error + // if apiKey, err = getBaiduAccessToken(apiKey); err != nil { + // return errorWrapper(err, "invalid_baidu_config", http.StatusInternalServerError) + // } + // fullRequestURL += "?access_token=" + apiKey case APITypePaLM: fullRequestURL = "https://generativelanguage.googleapis.com/v1beta2/models/chat-bison-001:generateMessage" if baseURL != "" { @@ -202,7 +202,12 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { var completionTokens int switch relayMode { case RelayModeChatCompletions: - promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model) + messages, err := textRequest.TextMessages() + if err != nil { + return errorWrapper(err, "parse_text_messages_failed", http.StatusBadRequest) + } + + promptTokens = countTokenMessages(messages, textRequest.Model) case RelayModeCompletions: promptTokens = countTokenInput(textRequest.Prompt, textRequest.Model) case RelayModeModerations: @@ -257,67 +262,67 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) } requestBody = bytes.NewBuffer(jsonStr) - case APITypeBaidu: - var jsonData []byte - 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 { - return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) - } - requestBody = bytes.NewBuffer(jsonData) - case APITypePaLM: - palmRequest := requestOpenAI2PaLM(textRequest) - jsonStr, err := json.Marshal(palmRequest) - if err != nil { - return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) - } - requestBody = bytes.NewBuffer(jsonStr) - case APITypeZhipu: - zhipuRequest := requestOpenAI2Zhipu(textRequest) - jsonStr, err := json.Marshal(zhipuRequest) - if err != nil { - return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) - } - requestBody = bytes.NewBuffer(jsonStr) - case APITypeAli: - var jsonStr []byte - var err error - switch relayMode { - case RelayModeEmbeddings: - aliEmbeddingRequest := embeddingRequestOpenAI2Ali(textRequest) - jsonStr, err = json.Marshal(aliEmbeddingRequest) - default: - aliRequest := requestOpenAI2Ali(textRequest) - jsonStr, err = json.Marshal(aliRequest) - } - if err != nil { - return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) - } - requestBody = bytes.NewBuffer(jsonStr) - case APITypeTencent: - apiKey := c.Request.Header.Get("Authorization") - apiKey = strings.TrimPrefix(apiKey, "Bearer ") - appId, secretId, secretKey, err := parseTencentConfig(apiKey) - if err != nil { - return errorWrapper(err, "invalid_tencent_config", http.StatusInternalServerError) - } - tencentRequest := requestOpenAI2Tencent(textRequest) - tencentRequest.AppId = appId - tencentRequest.SecretId = secretId - jsonStr, err := json.Marshal(tencentRequest) - if err != nil { - return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) - } - sign := getTencentSign(*tencentRequest, secretKey) - c.Request.Header.Set("Authorization", sign) - requestBody = bytes.NewBuffer(jsonStr) + // case APITypeBaidu: + // var jsonData []byte + // 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 { + // return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) + // } + // requestBody = bytes.NewBuffer(jsonData) + // case APITypePaLM: + // palmRequest := requestOpenAI2PaLM(textRequest) + // jsonStr, err := json.Marshal(palmRequest) + // if err != nil { + // return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) + // } + // requestBody = bytes.NewBuffer(jsonStr) + // case APITypeZhipu: + // zhipuRequest := requestOpenAI2Zhipu(textRequest) + // jsonStr, err := json.Marshal(zhipuRequest) + // if err != nil { + // return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) + // } + // requestBody = bytes.NewBuffer(jsonStr) + // case APITypeAli: + // var jsonStr []byte + // var err error + // switch relayMode { + // case RelayModeEmbeddings: + // aliEmbeddingRequest := embeddingRequestOpenAI2Ali(textRequest) + // jsonStr, err = json.Marshal(aliEmbeddingRequest) + // default: + // aliRequest := requestOpenAI2Ali(textRequest) + // jsonStr, err = json.Marshal(aliRequest) + // } + // if err != nil { + // return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) + // } + // requestBody = bytes.NewBuffer(jsonStr) + // case APITypeTencent: + // apiKey := c.Request.Header.Get("Authorization") + // apiKey = strings.TrimPrefix(apiKey, "Bearer ") + // appId, secretId, secretKey, err := parseTencentConfig(apiKey) + // if err != nil { + // return errorWrapper(err, "invalid_tencent_config", http.StatusInternalServerError) + // } + // tencentRequest := requestOpenAI2Tencent(textRequest) + // tencentRequest.AppId = appId + // tencentRequest.SecretId = secretId + // jsonStr, err := json.Marshal(tencentRequest) + // if err != nil { + // return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) + // } + // sign := getTencentSign(*tencentRequest, secretKey) + // c.Request.Header.Set("Authorization", sign) + // requestBody = bytes.NewBuffer(jsonStr) case APITypeAIProxyLibrary: aiProxyLibraryRequest := requestOpenAI2AIProxyLibrary(textRequest) aiProxyLibraryRequest.LibraryId = c.GetString("library_id") @@ -357,16 +362,16 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { anthropicVersion = "2023-06-01" } req.Header.Set("anthropic-version", anthropicVersion) - case APITypeZhipu: - token := getZhipuToken(apiKey) - req.Header.Set("Authorization", token) - case APITypeAli: - req.Header.Set("Authorization", "Bearer "+apiKey) - if textRequest.Stream { - req.Header.Set("X-DashScope-SSE", "enable") - } - case APITypeTencent: - req.Header.Set("Authorization", apiKey) + // case APITypeZhipu: + // token := getZhipuToken(apiKey) + // req.Header.Set("Authorization", token) + // case APITypeAli: + // req.Header.Set("Authorization", "Bearer "+apiKey) + // if textRequest.Stream { + // req.Header.Set("X-DashScope-SSE", "enable") + // } + // case APITypeTencent: + // req.Header.Set("Authorization", apiKey) default: req.Header.Set("Authorization", "Bearer "+apiKey) } @@ -482,124 +487,124 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } return nil } - case APITypeBaidu: - if isStream { - err, usage := baiduStreamHandler(c, resp) - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - return nil - } else { - var err *OpenAIErrorWithStatusCode - var usage *Usage - switch relayMode { - case RelayModeEmbeddings: - err, usage = baiduEmbeddingHandler(c, resp) - default: - err, usage = baiduHandler(c, resp) - } - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - return nil - } - case APITypePaLM: - if textRequest.Stream { // PaLM2 API does not support stream - err, responseText := palmStreamHandler(c, resp) - if err != nil { - return err - } - textResponse.Usage.PromptTokens = promptTokens - textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) - return nil - } else { - err, usage := palmHandler(c, resp, promptTokens, textRequest.Model) - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - return nil - } - case APITypeZhipu: - if isStream { - err, usage := zhipuStreamHandler(c, resp) - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - // zhipu's API does not return prompt tokens & completion tokens - textResponse.Usage.PromptTokens = textResponse.Usage.TotalTokens - return nil - } else { - err, usage := zhipuHandler(c, resp) - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - // zhipu's API does not return prompt tokens & completion tokens - textResponse.Usage.PromptTokens = textResponse.Usage.TotalTokens - 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 { - var err *OpenAIErrorWithStatusCode - var usage *Usage - switch relayMode { - case RelayModeEmbeddings: - err, usage = aliEmbeddingHandler(c, resp) - default: - err, usage = aliHandler(c, resp) - } - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - return nil - } - case APITypeXunfei: - auth := c.Request.Header.Get("Authorization") - auth = strings.TrimPrefix(auth, "Bearer ") - splits := strings.Split(auth, "|") - if len(splits) != 3 { - return errorWrapper(errors.New("invalid auth"), "invalid_auth", http.StatusBadRequest) - } - var err *OpenAIErrorWithStatusCode - var usage *Usage - if isStream { - err, usage = xunfeiStreamHandler(c, textRequest, splits[0], splits[1], splits[2]) - } else { - err, usage = xunfeiHandler(c, textRequest, splits[0], splits[1], splits[2]) - } - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - return nil + // case APITypeBaidu: + // if isStream { + // err, usage := baiduStreamHandler(c, resp) + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // return nil + // } else { + // var err *OpenAIErrorWithStatusCode + // var usage *Usage + // switch relayMode { + // case RelayModeEmbeddings: + // err, usage = baiduEmbeddingHandler(c, resp) + // default: + // err, usage = baiduHandler(c, resp) + // } + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // return nil + // } + // case APITypePaLM: + // if textRequest.Stream { // PaLM2 API does not support stream + // err, responseText := palmStreamHandler(c, resp) + // if err != nil { + // return err + // } + // textResponse.Usage.PromptTokens = promptTokens + // textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) + // return nil + // } else { + // err, usage := palmHandler(c, resp, promptTokens, textRequest.Model) + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // return nil + // } + // case APITypeZhipu: + // if isStream { + // err, usage := zhipuStreamHandler(c, resp) + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // // zhipu's API does not return prompt tokens & completion tokens + // textResponse.Usage.PromptTokens = textResponse.Usage.TotalTokens + // return nil + // } else { + // err, usage := zhipuHandler(c, resp) + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // // zhipu's API does not return prompt tokens & completion tokens + // textResponse.Usage.PromptTokens = textResponse.Usage.TotalTokens + // 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 { + // var err *OpenAIErrorWithStatusCode + // var usage *Usage + // switch relayMode { + // case RelayModeEmbeddings: + // err, usage = aliEmbeddingHandler(c, resp) + // default: + // err, usage = aliHandler(c, resp) + // } + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // return nil + // } + // case APITypeXunfei: + // auth := c.Request.Header.Get("Authorization") + // auth = strings.TrimPrefix(auth, "Bearer ") + // splits := strings.Split(auth, "|") + // if len(splits) != 3 { + // return errorWrapper(errors.New("invalid auth"), "invalid_auth", http.StatusBadRequest) + // } + // var err *OpenAIErrorWithStatusCode + // var usage *Usage + // if isStream { + // err, usage = xunfeiStreamHandler(c, textRequest, splits[0], splits[1], splits[2]) + // } else { + // err, usage = xunfeiHandler(c, textRequest, splits[0], splits[1], splits[2]) + // } + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // return nil case APITypeAIProxyLibrary: if isStream { err, usage := aiProxyLibraryStreamHandler(c, resp) @@ -620,25 +625,25 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } return nil } - case APITypeTencent: - if isStream { - err, responseText := tencentStreamHandler(c, resp) - if err != nil { - return err - } - textResponse.Usage.PromptTokens = promptTokens - textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) - return nil - } else { - err, usage := tencentHandler(c, resp) - if err != nil { - return err - } - if usage != nil { - textResponse.Usage = *usage - } - return nil - } + // case APITypeTencent: + // if isStream { + // err, responseText := tencentStreamHandler(c, resp) + // if err != nil { + // return err + // } + // textResponse.Usage.PromptTokens = promptTokens + // textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) + // return nil + // } else { + // err, usage := tencentHandler(c, resp) + // if err != nil { + // return err + // } + // if usage != nil { + // textResponse.Usage = *usage + // } + // return nil + // } default: return errorWrapper(errors.New("unknown api type"), "unknown_api_type", http.StatusInternalServerError) } diff --git a/controller/relay-xunfei.go b/controller/relay-xunfei.go index 91fb6042..620662dd 100644 --- a/controller/relay-xunfei.go +++ b/controller/relay-xunfei.go @@ -1,306 +1,306 @@ package controller -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - "io" - "net/http" - "net/url" - "one-api/common" - "strings" - "time" -) +// import ( +// "crypto/hmac" +// "crypto/sha256" +// "encoding/base64" +// "encoding/json" +// "fmt" +// "github.com/gin-gonic/gin" +// "github.com/gorilla/websocket" +// "io" +// "net/http" +// "net/url" +// "one-api/common" +// "strings" +// "time" +// ) -// https://console.xfyun.cn/services/cbm -// https://www.xfyun.cn/doc/spark/Web.html +// // https://console.xfyun.cn/services/cbm +// // https://www.xfyun.cn/doc/spark/Web.html -type XunfeiMessage struct { - Role string `json:"role"` - Content string `json:"content"` -} +// type XunfeiMessage struct { +// Role string `json:"role"` +// Content string `json:"content"` +// } -type XunfeiChatRequest struct { - Header struct { - AppId string `json:"app_id"` - } `json:"header"` - Parameter struct { - Chat struct { - Domain string `json:"domain,omitempty"` - Temperature float64 `json:"temperature,omitempty"` - TopK int `json:"top_k,omitempty"` - MaxTokens int `json:"max_tokens,omitempty"` - Auditing bool `json:"auditing,omitempty"` - } `json:"chat"` - } `json:"parameter"` - Payload struct { - Message struct { - Text []XunfeiMessage `json:"text"` - } `json:"message"` - } `json:"payload"` -} +// type XunfeiChatRequest struct { +// Header struct { +// AppId string `json:"app_id"` +// } `json:"header"` +// Parameter struct { +// Chat struct { +// Domain string `json:"domain,omitempty"` +// Temperature float64 `json:"temperature,omitempty"` +// TopK int `json:"top_k,omitempty"` +// MaxTokens int `json:"max_tokens,omitempty"` +// Auditing bool `json:"auditing,omitempty"` +// } `json:"chat"` +// } `json:"parameter"` +// Payload struct { +// Message struct { +// Text []XunfeiMessage `json:"text"` +// } `json:"message"` +// } `json:"payload"` +// } -type XunfeiChatResponseTextItem struct { - Content string `json:"content"` - Role string `json:"role"` - Index int `json:"index"` -} +// type XunfeiChatResponseTextItem struct { +// Content string `json:"content"` +// Role string `json:"role"` +// Index int `json:"index"` +// } -type XunfeiChatResponse struct { - Header struct { - Code int `json:"code"` - Message string `json:"message"` - Sid string `json:"sid"` - Status int `json:"status"` - } `json:"header"` - Payload struct { - Choices struct { - Status int `json:"status"` - Seq int `json:"seq"` - Text []XunfeiChatResponseTextItem `json:"text"` - } `json:"choices"` - Usage struct { - //Text struct { - // QuestionTokens string `json:"question_tokens"` - // PromptTokens string `json:"prompt_tokens"` - // CompletionTokens string `json:"completion_tokens"` - // TotalTokens string `json:"total_tokens"` - //} `json:"text"` - Text Usage `json:"text"` - } `json:"usage"` - } `json:"payload"` -} +// type XunfeiChatResponse struct { +// Header struct { +// Code int `json:"code"` +// Message string `json:"message"` +// Sid string `json:"sid"` +// Status int `json:"status"` +// } `json:"header"` +// Payload struct { +// Choices struct { +// Status int `json:"status"` +// Seq int `json:"seq"` +// Text []XunfeiChatResponseTextItem `json:"text"` +// } `json:"choices"` +// Usage struct { +// //Text struct { +// // QuestionTokens string `json:"question_tokens"` +// // PromptTokens string `json:"prompt_tokens"` +// // CompletionTokens string `json:"completion_tokens"` +// // TotalTokens string `json:"total_tokens"` +// //} `json:"text"` +// Text Usage `json:"text"` +// } `json:"usage"` +// } `json:"payload"` +// } -func requestOpenAI2Xunfei(request GeneralOpenAIRequest, xunfeiAppId string, domain string) *XunfeiChatRequest { - messages := make([]XunfeiMessage, 0, len(request.Messages)) - for _, message := range request.Messages { - if message.Role == "system" { - messages = append(messages, XunfeiMessage{ - Role: "user", - Content: message.Content, - }) - messages = append(messages, XunfeiMessage{ - Role: "assistant", - Content: "Okay", - }) - } else { - messages = append(messages, XunfeiMessage{ - Role: message.Role, - Content: message.Content, - }) - } - } - xunfeiRequest := XunfeiChatRequest{} - xunfeiRequest.Header.AppId = xunfeiAppId - xunfeiRequest.Parameter.Chat.Domain = domain - xunfeiRequest.Parameter.Chat.Temperature = request.Temperature - xunfeiRequest.Parameter.Chat.TopK = request.N - xunfeiRequest.Parameter.Chat.MaxTokens = request.MaxTokens - xunfeiRequest.Payload.Message.Text = messages - return &xunfeiRequest -} +// func requestOpenAI2Xunfei(request GeneralOpenAIRequest, xunfeiAppId string, domain string) *XunfeiChatRequest { +// messages := make([]XunfeiMessage, 0, len(request.Messages)) +// for _, message := range request.Messages { +// if message.Role == "system" { +// messages = append(messages, XunfeiMessage{ +// Role: "user", +// Content: message.Content, +// }) +// messages = append(messages, XunfeiMessage{ +// Role: "assistant", +// Content: "Okay", +// }) +// } else { +// messages = append(messages, XunfeiMessage{ +// Role: message.Role, +// Content: message.Content, +// }) +// } +// } +// xunfeiRequest := XunfeiChatRequest{} +// xunfeiRequest.Header.AppId = xunfeiAppId +// xunfeiRequest.Parameter.Chat.Domain = domain +// xunfeiRequest.Parameter.Chat.Temperature = request.Temperature +// xunfeiRequest.Parameter.Chat.TopK = request.N +// xunfeiRequest.Parameter.Chat.MaxTokens = request.MaxTokens +// xunfeiRequest.Payload.Message.Text = messages +// return &xunfeiRequest +// } -func responseXunfei2OpenAI(response *XunfeiChatResponse) *OpenAITextResponse { - if len(response.Payload.Choices.Text) == 0 { - response.Payload.Choices.Text = []XunfeiChatResponseTextItem{ - { - Content: "", - }, - } - } - choice := OpenAITextResponseChoice{ - Index: 0, - Message: Message{ - Role: "assistant", - Content: response.Payload.Choices.Text[0].Content, - }, - FinishReason: stopFinishReason, - } - fullTextResponse := OpenAITextResponse{ - Object: "chat.completion", - Created: common.GetTimestamp(), - Choices: []OpenAITextResponseChoice{choice}, - Usage: response.Payload.Usage.Text, - } - return &fullTextResponse -} +// func responseXunfei2OpenAI(response *XunfeiChatResponse) *OpenAITextResponse { +// if len(response.Payload.Choices.Text) == 0 { +// response.Payload.Choices.Text = []XunfeiChatResponseTextItem{ +// { +// Content: "", +// }, +// } +// } +// choice := OpenAITextResponseChoice{ +// Index: 0, +// Message: Message{ +// Role: "assistant", +// Content: response.Payload.Choices.Text[0].Content, +// }, +// FinishReason: stopFinishReason, +// } +// fullTextResponse := OpenAITextResponse{ +// Object: "chat.completion", +// Created: common.GetTimestamp(), +// Choices: []OpenAITextResponseChoice{choice}, +// Usage: response.Payload.Usage.Text, +// } +// return &fullTextResponse +// } -func streamResponseXunfei2OpenAI(xunfeiResponse *XunfeiChatResponse) *ChatCompletionsStreamResponse { - if len(xunfeiResponse.Payload.Choices.Text) == 0 { - xunfeiResponse.Payload.Choices.Text = []XunfeiChatResponseTextItem{ - { - Content: "", - }, - } - } - var choice ChatCompletionsStreamResponseChoice - choice.Delta.Content = xunfeiResponse.Payload.Choices.Text[0].Content - if xunfeiResponse.Payload.Choices.Status == 2 { - choice.FinishReason = &stopFinishReason - } - response := ChatCompletionsStreamResponse{ - Object: "chat.completion.chunk", - Created: common.GetTimestamp(), - Model: "SparkDesk", - Choices: []ChatCompletionsStreamResponseChoice{choice}, - } - return &response -} +// func streamResponseXunfei2OpenAI(xunfeiResponse *XunfeiChatResponse) *ChatCompletionsStreamResponse { +// if len(xunfeiResponse.Payload.Choices.Text) == 0 { +// xunfeiResponse.Payload.Choices.Text = []XunfeiChatResponseTextItem{ +// { +// Content: "", +// }, +// } +// } +// var choice ChatCompletionsStreamResponseChoice +// choice.Delta.Content = xunfeiResponse.Payload.Choices.Text[0].Content +// if xunfeiResponse.Payload.Choices.Status == 2 { +// choice.FinishReason = &stopFinishReason +// } +// response := ChatCompletionsStreamResponse{ +// Object: "chat.completion.chunk", +// Created: common.GetTimestamp(), +// Model: "SparkDesk", +// Choices: []ChatCompletionsStreamResponseChoice{choice}, +// } +// return &response +// } -func buildXunfeiAuthUrl(hostUrl string, apiKey, apiSecret string) string { - HmacWithShaToBase64 := func(algorithm, data, key string) string { - mac := hmac.New(sha256.New, []byte(key)) - mac.Write([]byte(data)) - encodeData := mac.Sum(nil) - return base64.StdEncoding.EncodeToString(encodeData) - } - ul, err := url.Parse(hostUrl) - if err != nil { - fmt.Println(err) - } - date := time.Now().UTC().Format(time.RFC1123) - signString := []string{"host: " + ul.Host, "date: " + date, "GET " + ul.Path + " HTTP/1.1"} - sign := strings.Join(signString, "\n") - sha := HmacWithShaToBase64("hmac-sha256", sign, apiSecret) - authUrl := fmt.Sprintf("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, - "hmac-sha256", "host date request-line", sha) - authorization := base64.StdEncoding.EncodeToString([]byte(authUrl)) - v := url.Values{} - v.Add("host", ul.Host) - v.Add("date", date) - v.Add("authorization", authorization) - callUrl := hostUrl + "?" + v.Encode() - return callUrl -} +// func buildXunfeiAuthUrl(hostUrl string, apiKey, apiSecret string) string { +// HmacWithShaToBase64 := func(algorithm, data, key string) string { +// mac := hmac.New(sha256.New, []byte(key)) +// mac.Write([]byte(data)) +// encodeData := mac.Sum(nil) +// return base64.StdEncoding.EncodeToString(encodeData) +// } +// ul, err := url.Parse(hostUrl) +// if err != nil { +// fmt.Println(err) +// } +// date := time.Now().UTC().Format(time.RFC1123) +// signString := []string{"host: " + ul.Host, "date: " + date, "GET " + ul.Path + " HTTP/1.1"} +// sign := strings.Join(signString, "\n") +// sha := HmacWithShaToBase64("hmac-sha256", sign, apiSecret) +// authUrl := fmt.Sprintf("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, +// "hmac-sha256", "host date request-line", sha) +// authorization := base64.StdEncoding.EncodeToString([]byte(authUrl)) +// v := url.Values{} +// v.Add("host", ul.Host) +// v.Add("date", date) +// v.Add("authorization", authorization) +// callUrl := hostUrl + "?" + v.Encode() +// return callUrl +// } -func xunfeiStreamHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*OpenAIErrorWithStatusCode, *Usage) { - domain, authUrl := getXunfeiAuthUrl(c, apiKey, apiSecret) - dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId) - if err != nil { - return errorWrapper(err, "make xunfei request err", http.StatusInternalServerError), nil - } - setEventStreamHeaders(c) - var usage Usage - c.Stream(func(w io.Writer) bool { - select { - case xunfeiResponse := <-dataChan: - usage.PromptTokens += xunfeiResponse.Payload.Usage.Text.PromptTokens - usage.CompletionTokens += xunfeiResponse.Payload.Usage.Text.CompletionTokens - usage.TotalTokens += xunfeiResponse.Payload.Usage.Text.TotalTokens - response := streamResponseXunfei2OpenAI(&xunfeiResponse) - 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 - } - }) - return nil, &usage -} +// func xunfeiStreamHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*OpenAIErrorWithStatusCode, *Usage) { +// domain, authUrl := getXunfeiAuthUrl(c, apiKey, apiSecret) +// dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId) +// if err != nil { +// return errorWrapper(err, "make xunfei request err", http.StatusInternalServerError), nil +// } +// setEventStreamHeaders(c) +// var usage Usage +// c.Stream(func(w io.Writer) bool { +// select { +// case xunfeiResponse := <-dataChan: +// usage.PromptTokens += xunfeiResponse.Payload.Usage.Text.PromptTokens +// usage.CompletionTokens += xunfeiResponse.Payload.Usage.Text.CompletionTokens +// usage.TotalTokens += xunfeiResponse.Payload.Usage.Text.TotalTokens +// response := streamResponseXunfei2OpenAI(&xunfeiResponse) +// 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 +// } +// }) +// return nil, &usage +// } -func xunfeiHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*OpenAIErrorWithStatusCode, *Usage) { - domain, authUrl := getXunfeiAuthUrl(c, apiKey, apiSecret) - dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId) - if err != nil { - return errorWrapper(err, "make xunfei request err", http.StatusInternalServerError), nil - } - var usage Usage - var content string - var xunfeiResponse XunfeiChatResponse - stop := false - for !stop { - select { - case xunfeiResponse = <-dataChan: - if len(xunfeiResponse.Payload.Choices.Text) == 0 { - continue - } - content += xunfeiResponse.Payload.Choices.Text[0].Content - usage.PromptTokens += xunfeiResponse.Payload.Usage.Text.PromptTokens - usage.CompletionTokens += xunfeiResponse.Payload.Usage.Text.CompletionTokens - usage.TotalTokens += xunfeiResponse.Payload.Usage.Text.TotalTokens - case stop = <-stopChan: - } - } +// func xunfeiHandler(c *gin.Context, textRequest GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*OpenAIErrorWithStatusCode, *Usage) { +// domain, authUrl := getXunfeiAuthUrl(c, apiKey, apiSecret) +// dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId) +// if err != nil { +// return errorWrapper(err, "make xunfei request err", http.StatusInternalServerError), nil +// } +// var usage Usage +// var content string +// var xunfeiResponse XunfeiChatResponse +// stop := false +// for !stop { +// select { +// case xunfeiResponse = <-dataChan: +// if len(xunfeiResponse.Payload.Choices.Text) == 0 { +// continue +// } +// content += xunfeiResponse.Payload.Choices.Text[0].Content +// usage.PromptTokens += xunfeiResponse.Payload.Usage.Text.PromptTokens +// usage.CompletionTokens += xunfeiResponse.Payload.Usage.Text.CompletionTokens +// usage.TotalTokens += xunfeiResponse.Payload.Usage.Text.TotalTokens +// case stop = <-stopChan: +// } +// } - xunfeiResponse.Payload.Choices.Text[0].Content = content +// xunfeiResponse.Payload.Choices.Text[0].Content = content - response := responseXunfei2OpenAI(&xunfeiResponse) - jsonResponse, err := json.Marshal(response) - if err != nil { - return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil - } - c.Writer.Header().Set("Content-Type", "application/json") - _, _ = c.Writer.Write(jsonResponse) - return nil, &usage -} +// response := responseXunfei2OpenAI(&xunfeiResponse) +// jsonResponse, err := json.Marshal(response) +// if err != nil { +// return errorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError), nil +// } +// c.Writer.Header().Set("Content-Type", "application/json") +// _, _ = c.Writer.Write(jsonResponse) +// return nil, &usage +// } -func xunfeiMakeRequest(textRequest GeneralOpenAIRequest, domain, authUrl, appId string) (chan XunfeiChatResponse, chan bool, error) { - d := websocket.Dialer{ - HandshakeTimeout: 5 * time.Second, - } - conn, resp, err := d.Dial(authUrl, nil) - if err != nil || resp.StatusCode != 101 { - return nil, nil, err - } - data := requestOpenAI2Xunfei(textRequest, appId, domain) - err = conn.WriteJSON(data) - if err != nil { - return nil, nil, err - } +// func xunfeiMakeRequest(textRequest GeneralOpenAIRequest, domain, authUrl, appId string) (chan XunfeiChatResponse, chan bool, error) { +// d := websocket.Dialer{ +// HandshakeTimeout: 5 * time.Second, +// } +// conn, resp, err := d.Dial(authUrl, nil) +// if err != nil || resp.StatusCode != 101 { +// return nil, nil, err +// } +// data := requestOpenAI2Xunfei(textRequest, appId, domain) +// err = conn.WriteJSON(data) +// if err != nil { +// return nil, nil, err +// } - dataChan := make(chan XunfeiChatResponse) - stopChan := make(chan bool) - go func() { - for { - _, msg, err := conn.ReadMessage() - if err != nil { - common.SysError("error reading stream response: " + err.Error()) - break - } - var response XunfeiChatResponse - err = json.Unmarshal(msg, &response) - if err != nil { - common.SysError("error unmarshalling stream response: " + err.Error()) - break - } - dataChan <- response - if response.Payload.Choices.Status == 2 { - err := conn.Close() - if err != nil { - common.SysError("error closing websocket connection: " + err.Error()) - } - break - } - } - stopChan <- true - }() +// dataChan := make(chan XunfeiChatResponse) +// stopChan := make(chan bool) +// go func() { +// for { +// _, msg, err := conn.ReadMessage() +// if err != nil { +// common.SysError("error reading stream response: " + err.Error()) +// break +// } +// var response XunfeiChatResponse +// err = json.Unmarshal(msg, &response) +// if err != nil { +// common.SysError("error unmarshalling stream response: " + err.Error()) +// break +// } +// dataChan <- response +// if response.Payload.Choices.Status == 2 { +// err := conn.Close() +// if err != nil { +// common.SysError("error closing websocket connection: " + err.Error()) +// } +// break +// } +// } +// stopChan <- true +// }() - return dataChan, stopChan, nil -} +// return dataChan, stopChan, nil +// } -func getXunfeiAuthUrl(c *gin.Context, apiKey string, apiSecret string) (string, string) { - query := c.Request.URL.Query() - apiVersion := query.Get("api-version") - if apiVersion == "" { - apiVersion = c.GetString("api_version") - } - if apiVersion == "" { - apiVersion = "v1.1" - common.SysLog("api_version not found, use default: " + apiVersion) - } - domain := "general" - if apiVersion != "v1.1" { - domain += strings.Split(apiVersion, ".")[0] - } - authUrl := buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion), apiKey, apiSecret) - return domain, authUrl -} +// func getXunfeiAuthUrl(c *gin.Context, apiKey string, apiSecret string) (string, string) { +// query := c.Request.URL.Query() +// apiVersion := query.Get("api-version") +// if apiVersion == "" { +// apiVersion = c.GetString("api_version") +// } +// if apiVersion == "" { +// apiVersion = "v1.1" +// common.SysLog("api_version not found, use default: " + apiVersion) +// } +// domain := "general" +// if apiVersion != "v1.1" { +// domain += strings.Split(apiVersion, ".")[0] +// } +// authUrl := buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion), apiKey, apiSecret) +// return domain, authUrl +// } diff --git a/controller/relay-zhipu.go b/controller/relay-zhipu.go index 7a4a582d..8f556c97 100644 --- a/controller/relay-zhipu.go +++ b/controller/relay-zhipu.go @@ -1,301 +1,301 @@ package controller -import ( - "bufio" - "encoding/json" - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt" - "io" - "net/http" - "one-api/common" - "strings" - "sync" - "time" -) +// import ( +// "bufio" +// "encoding/json" +// "github.com/gin-gonic/gin" +// "github.com/golang-jwt/jwt" +// "io" +// "net/http" +// "one-api/common" +// "strings" +// "sync" +// "time" +// ) -// https://open.bigmodel.cn/doc/api#chatglm_std -// chatglm_std, chatglm_lite -// https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/invoke -// https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/sse-invoke +// // https://open.bigmodel.cn/doc/api#chatglm_std +// // chatglm_std, chatglm_lite +// // https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/invoke +// // https://open.bigmodel.cn/api/paas/v3/model-api/chatglm_std/sse-invoke -type ZhipuMessage struct { - Role string `json:"role"` - Content string `json:"content"` -} +// type ZhipuMessage struct { +// Role string `json:"role"` +// Content string `json:"content"` +// } -type ZhipuRequest struct { - Prompt []ZhipuMessage `json:"prompt"` - Temperature float64 `json:"temperature,omitempty"` - TopP float64 `json:"top_p,omitempty"` - RequestId string `json:"request_id,omitempty"` - Incremental bool `json:"incremental,omitempty"` -} +// type ZhipuRequest struct { +// Prompt []ZhipuMessage `json:"prompt"` +// Temperature float64 `json:"temperature,omitempty"` +// TopP float64 `json:"top_p,omitempty"` +// RequestId string `json:"request_id,omitempty"` +// Incremental bool `json:"incremental,omitempty"` +// } -type ZhipuResponseData struct { - TaskId string `json:"task_id"` - RequestId string `json:"request_id"` - TaskStatus string `json:"task_status"` - Choices []ZhipuMessage `json:"choices"` - Usage `json:"usage"` -} +// type ZhipuResponseData struct { +// TaskId string `json:"task_id"` +// RequestId string `json:"request_id"` +// TaskStatus string `json:"task_status"` +// Choices []ZhipuMessage `json:"choices"` +// Usage `json:"usage"` +// } -type ZhipuResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` - Success bool `json:"success"` - Data ZhipuResponseData `json:"data"` -} +// type ZhipuResponse struct { +// Code int `json:"code"` +// Msg string `json:"msg"` +// Success bool `json:"success"` +// Data ZhipuResponseData `json:"data"` +// } -type ZhipuStreamMetaResponse struct { - RequestId string `json:"request_id"` - TaskId string `json:"task_id"` - TaskStatus string `json:"task_status"` - Usage `json:"usage"` -} +// type ZhipuStreamMetaResponse struct { +// RequestId string `json:"request_id"` +// TaskId string `json:"task_id"` +// TaskStatus string `json:"task_status"` +// Usage `json:"usage"` +// } -type zhipuTokenData struct { - Token string - ExpiryTime time.Time -} +// type zhipuTokenData struct { +// Token string +// ExpiryTime time.Time +// } -var zhipuTokens sync.Map -var expSeconds int64 = 24 * 3600 +// var zhipuTokens sync.Map +// var expSeconds int64 = 24 * 3600 -func getZhipuToken(apikey string) string { - data, ok := zhipuTokens.Load(apikey) - if ok { - tokenData := data.(zhipuTokenData) - if time.Now().Before(tokenData.ExpiryTime) { - return tokenData.Token - } - } +// func getZhipuToken(apikey string) string { +// data, ok := zhipuTokens.Load(apikey) +// if ok { +// tokenData := data.(zhipuTokenData) +// if time.Now().Before(tokenData.ExpiryTime) { +// return tokenData.Token +// } +// } - split := strings.Split(apikey, ".") - if len(split) != 2 { - common.SysError("invalid zhipu key: " + apikey) - return "" - } +// split := strings.Split(apikey, ".") +// if len(split) != 2 { +// common.SysError("invalid zhipu key: " + apikey) +// return "" +// } - id := split[0] - secret := split[1] +// id := split[0] +// secret := split[1] - expMillis := time.Now().Add(time.Duration(expSeconds)*time.Second).UnixNano() / 1e6 - expiryTime := time.Now().Add(time.Duration(expSeconds) * time.Second) +// expMillis := time.Now().Add(time.Duration(expSeconds)*time.Second).UnixNano() / 1e6 +// expiryTime := time.Now().Add(time.Duration(expSeconds) * time.Second) - timestamp := time.Now().UnixNano() / 1e6 +// timestamp := time.Now().UnixNano() / 1e6 - payload := jwt.MapClaims{ - "api_key": id, - "exp": expMillis, - "timestamp": timestamp, - } +// payload := jwt.MapClaims{ +// "api_key": id, +// "exp": expMillis, +// "timestamp": timestamp, +// } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) +// token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) - token.Header["alg"] = "HS256" - token.Header["sign_type"] = "SIGN" +// token.Header["alg"] = "HS256" +// token.Header["sign_type"] = "SIGN" - tokenString, err := token.SignedString([]byte(secret)) - if err != nil { - return "" - } +// tokenString, err := token.SignedString([]byte(secret)) +// if err != nil { +// return "" +// } - zhipuTokens.Store(apikey, zhipuTokenData{ - Token: tokenString, - ExpiryTime: expiryTime, - }) +// zhipuTokens.Store(apikey, zhipuTokenData{ +// Token: tokenString, +// ExpiryTime: expiryTime, +// }) - return tokenString -} +// return tokenString +// } -func requestOpenAI2Zhipu(request GeneralOpenAIRequest) *ZhipuRequest { - messages := make([]ZhipuMessage, 0, len(request.Messages)) - for _, message := range request.Messages { - if message.Role == "system" { - messages = append(messages, ZhipuMessage{ - 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{ - Prompt: messages, - Temperature: request.Temperature, - TopP: request.TopP, - Incremental: false, - } -} +// func requestOpenAI2Zhipu(request GeneralOpenAIRequest) *ZhipuRequest { +// messages := make([]ZhipuMessage, 0, len(request.Messages)) +// for _, message := range request.Messages { +// if message.Role == "system" { +// messages = append(messages, ZhipuMessage{ +// 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{ +// Prompt: messages, +// Temperature: request.Temperature, +// TopP: request.TopP, +// Incremental: false, +// } +// } -func responseZhipu2OpenAI(response *ZhipuResponse) *OpenAITextResponse { - fullTextResponse := OpenAITextResponse{ - Id: response.Data.TaskId, - Object: "chat.completion", - Created: common.GetTimestamp(), - Choices: make([]OpenAITextResponseChoice, 0, len(response.Data.Choices)), - Usage: response.Data.Usage, - } - for i, choice := range response.Data.Choices { - openaiChoice := OpenAITextResponseChoice{ - Index: i, - Message: Message{ - Role: choice.Role, - Content: strings.Trim(choice.Content, "\""), - }, - FinishReason: "", - } - if i == len(response.Data.Choices)-1 { - openaiChoice.FinishReason = "stop" - } - fullTextResponse.Choices = append(fullTextResponse.Choices, openaiChoice) - } - return &fullTextResponse -} +// func responseZhipu2OpenAI(response *ZhipuResponse) *OpenAITextResponse { +// fullTextResponse := OpenAITextResponse{ +// Id: response.Data.TaskId, +// Object: "chat.completion", +// Created: common.GetTimestamp(), +// Choices: make([]OpenAITextResponseChoice, 0, len(response.Data.Choices)), +// Usage: response.Data.Usage, +// } +// for i, choice := range response.Data.Choices { +// openaiChoice := OpenAITextResponseChoice{ +// Index: i, +// Message: Message{ +// Role: choice.Role, +// Content: strings.Trim(choice.Content, "\""), +// }, +// FinishReason: "", +// } +// if i == len(response.Data.Choices)-1 { +// openaiChoice.FinishReason = "stop" +// } +// fullTextResponse.Choices = append(fullTextResponse.Choices, openaiChoice) +// } +// return &fullTextResponse +// } -func streamResponseZhipu2OpenAI(zhipuResponse string) *ChatCompletionsStreamResponse { - var choice ChatCompletionsStreamResponseChoice - choice.Delta.Content = zhipuResponse - response := ChatCompletionsStreamResponse{ - Object: "chat.completion.chunk", - Created: common.GetTimestamp(), - Model: "chatglm", - Choices: []ChatCompletionsStreamResponseChoice{choice}, - } - return &response -} +// func streamResponseZhipu2OpenAI(zhipuResponse string) *ChatCompletionsStreamResponse { +// var choice ChatCompletionsStreamResponseChoice +// choice.Delta.Content = zhipuResponse +// response := ChatCompletionsStreamResponse{ +// Object: "chat.completion.chunk", +// Created: common.GetTimestamp(), +// Model: "chatglm", +// Choices: []ChatCompletionsStreamResponseChoice{choice}, +// } +// return &response +// } -func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*ChatCompletionsStreamResponse, *Usage) { - var choice ChatCompletionsStreamResponseChoice - choice.Delta.Content = "" - choice.FinishReason = &stopFinishReason - response := ChatCompletionsStreamResponse{ - Id: zhipuResponse.RequestId, - Object: "chat.completion.chunk", - Created: common.GetTimestamp(), - Model: "chatglm", - Choices: []ChatCompletionsStreamResponseChoice{choice}, - } - return &response, &zhipuResponse.Usage -} +// func streamMetaResponseZhipu2OpenAI(zhipuResponse *ZhipuStreamMetaResponse) (*ChatCompletionsStreamResponse, *Usage) { +// var choice ChatCompletionsStreamResponseChoice +// choice.Delta.Content = "" +// choice.FinishReason = &stopFinishReason +// response := ChatCompletionsStreamResponse{ +// Id: zhipuResponse.RequestId, +// Object: "chat.completion.chunk", +// Created: common.GetTimestamp(), +// Model: "chatglm", +// Choices: []ChatCompletionsStreamResponseChoice{choice}, +// } +// return &response, &zhipuResponse.Usage +// } -func zhipuStreamHandler(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\n"); i >= 0 && strings.Index(string(data), ":") >= 0 { - return i + 2, data[0:i], nil - } - if atEOF { - return len(data), data, nil - } - return 0, nil, nil - }) - dataChan := make(chan string) - metaChan := make(chan string) - stopChan := make(chan bool) - go func() { - for scanner.Scan() { - data := scanner.Text() - lines := strings.Split(data, "\n") - for i, line := range lines { - if len(line) < 5 { - continue - } - if line[:5] == "data:" { - dataChan <- line[5:] - if i != len(lines)-1 { - dataChan <- "\n" - } - } else if line[:5] == "meta:" { - metaChan <- line[5:] - } - } - } - stopChan <- true - }() - setEventStreamHeaders(c) - c.Stream(func(w io.Writer) bool { - select { - case data := <-dataChan: - response := streamResponseZhipu2OpenAI(data) - 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 data := <-metaChan: - var zhipuResponse ZhipuStreamMetaResponse - err := json.Unmarshal([]byte(data), &zhipuResponse) - if err != nil { - common.SysError("error unmarshalling stream response: " + err.Error()) - return true - } - response, zhipuUsage := streamMetaResponseZhipu2OpenAI(&zhipuResponse) - jsonResponse, err := json.Marshal(response) - if err != nil { - common.SysError("error marshalling stream response: " + err.Error()) - return true - } - usage = zhipuUsage - 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 zhipuStreamHandler(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\n"); i >= 0 && strings.Index(string(data), ":") >= 0 { +// return i + 2, data[0:i], nil +// } +// if atEOF { +// return len(data), data, nil +// } +// return 0, nil, nil +// }) +// dataChan := make(chan string) +// metaChan := make(chan string) +// stopChan := make(chan bool) +// go func() { +// for scanner.Scan() { +// data := scanner.Text() +// lines := strings.Split(data, "\n") +// for i, line := range lines { +// if len(line) < 5 { +// continue +// } +// if line[:5] == "data:" { +// dataChan <- line[5:] +// if i != len(lines)-1 { +// dataChan <- "\n" +// } +// } else if line[:5] == "meta:" { +// metaChan <- line[5:] +// } +// } +// } +// stopChan <- true +// }() +// setEventStreamHeaders(c) +// c.Stream(func(w io.Writer) bool { +// select { +// case data := <-dataChan: +// response := streamResponseZhipu2OpenAI(data) +// 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 data := <-metaChan: +// var zhipuResponse ZhipuStreamMetaResponse +// err := json.Unmarshal([]byte(data), &zhipuResponse) +// if err != nil { +// common.SysError("error unmarshalling stream response: " + err.Error()) +// return true +// } +// response, zhipuUsage := streamMetaResponseZhipu2OpenAI(&zhipuResponse) +// jsonResponse, err := json.Marshal(response) +// if err != nil { +// common.SysError("error marshalling stream response: " + err.Error()) +// return true +// } +// usage = zhipuUsage +// 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 zhipuHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { - var zhipuResponse ZhipuResponse - 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, &zhipuResponse) - if err != nil { - return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil - } - if !zhipuResponse.Success { - return &OpenAIErrorWithStatusCode{ - OpenAIError: OpenAIError{ - Message: zhipuResponse.Msg, - Type: "zhipu_error", - Param: "", - Code: zhipuResponse.Code, - }, - StatusCode: resp.StatusCode, - }, nil - } - fullTextResponse := responseZhipu2OpenAI(&zhipuResponse) - 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 -} +// func zhipuHandler(c *gin.Context, resp *http.Response) (*OpenAIErrorWithStatusCode, *Usage) { +// var zhipuResponse ZhipuResponse +// 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, &zhipuResponse) +// if err != nil { +// return errorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil +// } +// if !zhipuResponse.Success { +// return &OpenAIErrorWithStatusCode{ +// OpenAIError: OpenAIError{ +// Message: zhipuResponse.Msg, +// Type: "zhipu_error", +// Param: "", +// Code: zhipuResponse.Code, +// }, +// StatusCode: resp.StatusCode, +// }, nil +// } +// fullTextResponse := responseZhipu2OpenAI(&zhipuResponse) +// 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 +// } diff --git a/controller/relay.go b/controller/relay.go index 1926110e..bed5a2e2 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -1,6 +1,7 @@ package controller import ( + "errors" "fmt" "net/http" "one-api/common" @@ -16,6 +17,44 @@ type Message struct { Name *string `json:"name,omitempty"` } +type VisionMessage struct { + Role string `json:"role"` + Content OpenaiVisionMessageContent `json:"content"` + Name *string `json:"name,omitempty"` +} + +// OpenaiVisionMessageContentType vision message content type +type OpenaiVisionMessageContentType string + +const ( + // OpenaiVisionMessageContentTypeText text + OpenaiVisionMessageContentTypeText OpenaiVisionMessageContentType = "text" + // OpenaiVisionMessageContentTypeImageUrl image url + OpenaiVisionMessageContentTypeImageUrl OpenaiVisionMessageContentType = "image_url" +) + +// OpenaiVisionMessageContent vision message content +type OpenaiVisionMessageContent struct { + Type OpenaiVisionMessageContentType `json:"type"` + Text string `json:"text,omitempty"` + ImageUrl OpenaiVisionMessageContentImageUrl `json:"image_url,omitempty"` +} + +// VisionImageResolution image resolution +type VisionImageResolution string + +const ( + // VisionImageResolutionLow low resolution + VisionImageResolutionLow VisionImageResolution = "low" + // VisionImageResolutionHigh high resolution + VisionImageResolutionHigh VisionImageResolution = "high" +) + +type OpenaiVisionMessageContentImageUrl struct { + URL string `json:"url"` + Detail VisionImageResolution `json:"detail,omitempty"` +} + const ( RelayModeUnknown = iota RelayModeChatCompletions @@ -30,18 +69,76 @@ const ( // https://platform.openai.com/docs/api-reference/chat type GeneralOpenAIRequest struct { - Model string `json:"model,omitempty"` - Messages []Message `json:"messages,omitempty"` - Prompt any `json:"prompt,omitempty"` - Stream bool `json:"stream,omitempty"` - MaxTokens int `json:"max_tokens,omitempty"` - Temperature float64 `json:"temperature,omitempty"` - TopP float64 `json:"top_p,omitempty"` - N int `json:"n,omitempty"` - Input any `json:"input,omitempty"` - Instruction string `json:"instruction,omitempty"` - Size string `json:"size,omitempty"` - Functions any `json:"functions,omitempty"` + Model string `json:"model,omitempty"` + // Messages maybe []Message or []VisionMessage + Messages any `json:"messages,omitempty"` + Prompt any `json:"prompt,omitempty"` + Stream bool `json:"stream,omitempty"` + MaxTokens int `json:"max_tokens,omitempty"` + Temperature float64 `json:"temperature,omitempty"` + TopP float64 `json:"top_p,omitempty"` + N int `json:"n,omitempty"` + Input any `json:"input,omitempty"` + Instruction string `json:"instruction,omitempty"` + Size string `json:"size,omitempty"` + Functions any `json:"functions,omitempty"` +} + +func (r *GeneralOpenAIRequest) MessagesLen() int { + switch msgs := r.Messages.(type) { + case []any: + return len(msgs) + case []Message: + return len(msgs) + case []VisionMessage: + return len(msgs) + default: + return 0 + } +} + +// TextMessages returns messages as []Message +func (r *GeneralOpenAIRequest) TextMessages() (messages []Message, err error) { + switch msgs := r.Messages.(type) { + case []any: + messages = make([]Message, 0, len(msgs)) + for _, msg := range msgs { + if m, ok := msg.(Message); ok { + messages = append(messages, m) + } else { + err = fmt.Errorf("invalid message type") + return + } + } + case []Message: + messages = msgs + default: + return nil, errors.New("invalid message type") + } + + return +} + +// VisionMessages returns messages as []VisionMessage +func (r *GeneralOpenAIRequest) VisionMessages() (messages []VisionMessage, err error) { + switch msgs := r.Messages.(type) { + case []any: + messages = make([]VisionMessage, 0, len(msgs)) + for _, msg := range msgs { + if m, ok := msg.(VisionMessage); ok { + messages = append(messages, m) + } else { + err = fmt.Errorf("invalid message type") + return + } + } + case []VisionMessage: + messages = msgs + default: + return nil, errors.New("invalid message type") + } + + return } func (r GeneralOpenAIRequest) ParseInput() []string { diff --git a/go.mod b/go.mod index 825ee570..3922c5d4 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,6 @@ module one-api -// +heroku goVersion go1.18 -go 1.18 +go 1.21 require ( github.com/gin-contrib/cors v1.4.0 @@ -11,9 +10,7 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/go-playground/validator/v10 v10.16.0 github.com/go-redis/redis/v8 v8.11.5 - github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.4.0 - github.com/gorilla/websocket v1.5.1 github.com/pkoukk/tiktoken-go v0.1.6 golang.org/x/crypto v0.15.0 gorm.io/driver/mysql v1.5.2 @@ -53,7 +50,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index 84208854..5257f85b 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,7 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= @@ -33,6 +34,7 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -52,8 +54,6 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -67,8 +67,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -110,8 +108,11 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= @@ -151,8 +152,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -181,6 +182,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e10a6764 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "one-api", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From dde293f3d3a51ca4e1ecb8a0dc10cf85e4abf29b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 02:00:07 +0000 Subject: [PATCH 012/170] chore(deps): bump golang.org/x/net from 0.10.0 to 0.17.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.17.0. - [Commits](https://github.com/golang/net/compare/v0.10.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3922c5d4..20743fe5 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index 5257f85b..61292fa2 100644 --- a/go.sum +++ b/go.sum @@ -152,8 +152,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 75e1a277729ad099dc7bd9bdf3e0f9572d256b9d Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 02:00:29 +0000 Subject: [PATCH 013/170] docs: Fix bug in login functionality --- README.md | 430 +----------------------------------------------------- 1 file changed, 3 insertions(+), 427 deletions(-) diff --git a/README.md b/README.md index 4ef6505c..cf0c4943 100644 --- a/README.md +++ b/README.md @@ -1,430 +1,6 @@ -

- 中文 | English | 日本語 -

- - -

- one-api logo -

- -
- # One API -_✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 ✨_ +## New Features -
- -

- - license - - - release - - - docker pull - - - release - - - GoReportCard - -

- -

- 部署教程 - · - 使用方法 - · - 意见反馈 - · - 截图展示 - · - 在线演示 - · - 常见问题 - · - 相关项目 - · - 赞赏支持 -

- -> **Note** -> 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 -> -> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。 - -> **Warning** -> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 - -> **Warning** -> 使用 root 用户初次登录系统后,务必修改默认密码 `123456`! - -## 功能 -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] [Anthropic Claude 系列模型](https://anthropic.com) - + [x] [Google PaLM2 系列模型](https://developers.generativeai.google) - + [x] [百度文心一言系列模型](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) - + [x] [阿里通义千问系列模型](https://help.aliyun.com/document_detail/2400395.html) - + [x] [讯飞星火认知大模型](https://www.xfyun.cn/doc/spark/Web.html) - + [x] [智谱 ChatGLM 系列模型](https://bigmodel.cn) - + [x] [360 智脑](https://ai.360.cn) - + [x] [腾讯混元大模型](https://cloud.tencent.com/document/product/1729) -2. 支持配置镜像以及众多第三方代理服务: - + [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. 支持通过**负载均衡**的方式访问多个渠道。 -4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。 -5. 支持**多机部署**,[详见此处](#多机部署)。 -6. 支持**令牌管理**,设置令牌的过期时间和额度。 -7. 支持**兑换码管理**,支持批量生成和导出兑换码,可使用兑换码为账户进行充值。 -8. 支持**通道管理**,批量创建通道。 -9. 支持**用户分组**以及**渠道分组**,支持为不同分组设置不同的倍率。 -10. 支持渠道**设置模型列表**。 -11. 支持**查看额度明细**。 -12. 支持**用户邀请奖励**。 -13. 支持以美元为单位显示额度。 -14. 支持发布公告,设置充值链接,设置新用户初始额度。 -15. 支持模型映射,重定向用户的请求模型。 -16. 支持失败自动重试。 -17. 支持绘图接口。 -18. 支持 [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/providers/openai/),渠道设置的代理部分填写 `https://gateway.ai.cloudflare.com/v1/ACCOUNT_TAG/GATEWAY/openai` 即可。 -19. 支持丰富的**自定义**设置, - 1. 支持自定义系统名称,logo 以及页脚。 - 2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。 -20. 支持通过系统访问令牌访问管理 API。 -21. 支持 Cloudflare Turnstile 用户校验。 -22. 支持用户管理,支持**多种用户登录注册方式**: - + 邮箱登录注册(支持注册邮箱白名单)以及通过邮箱进行密码重置。 - + [GitHub 开放授权](https://github.com/settings/applications/new)。 - + 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。 - -## 部署 -### 基于 Docker 进行部署 -```shell -# 使用 SQLite 的部署命令: -docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api -# 使用 MySQL 的部署命令,在上面的基础上添加 `-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi"`,请自行修改数据库连接参数,不清楚如何修改请参见下面环境变量一节。 -# 例如: -docker run --name one-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api -``` - -其中,`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。 - -数据和日志将会保存在宿主机的 `/home/ubuntu/data/one-api` 目录,请确保该目录存在且具有写入权限,或者更改为合适的目录。 - -如果启动失败,请添加 `--privileged=true`,具体参考 https://github.com/songquanpeng/one-api/issues/482 。 - -如果上面的镜像无法拉取,可以尝试使用 GitHub 的 Docker 镜像,将上面的 `justsong/one-api` 替换为 `ghcr.io/songquanpeng/one-api` 即可。 - -如果你的并发量较大,**务必**设置 `SQL_DSN`,详见下面[环境变量](#环境变量)一节。 - -更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR` - -Nginx 的参考配置: -``` -server{ - server_name openai.justsong.cn; # 请根据实际情况修改你的域名 - - location / { - client_max_body_size 64m; - proxy_http_version 1.1; - proxy_pass http://localhost:3000; # 请根据实际情况修改你的端口 - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_cache_bypass $http_upgrade; - proxy_set_header Accept-Encoding gzip; - proxy_read_timeout 300s; # GPT-4 需要较长的超时时间,请自行调整 - } -} -``` - -之后使用 Let's Encrypt 的 certbot 配置 HTTPS: -```bash -# Ubuntu 安装 certbot: -sudo snap install --classic certbot -sudo ln -s /snap/bin/certbot /usr/bin/certbot -# 生成证书 & 修改 Nginx 配置 -sudo certbot --nginx -# 根据指示进行操作 -# 重启 Nginx -sudo service nginx restart -``` - -初始账号用户名为 `root`,密码为 `123456`。 - - -### 基于 Docker Compose 进行部署 - -> 仅启动方式不同,参数设置不变,请参考基于 Docker 部署部分 - -```shell -# 目前支持 MySQL 启动,数据存储在 ./data/mysql 文件夹内 -docker-compose up -d - -# 查看部署状态 -docker-compose ps -``` - -### 手动部署 -1. 从 [GitHub Releases](https://github.com/songquanpeng/one-api/releases/latest) 下载可执行文件或者从源码编译: - ```shell - git clone https://github.com/songquanpeng/one-api.git - - # 构建前端 - cd one-api/web - npm install - npm run build - - # 构建后端 - cd .. - go mod download - go build -ldflags "-s -w" -o one-api - ```` -2. 运行: - ```shell - chmod u+x one-api - ./one-api --port 3000 --log-dir ./logs - ``` -3. 访问 [http://localhost:3000/](http://localhost:3000/) 并登录。初始账号用户名为 `root`,密码为 `123456`。 - -更加详细的部署教程[参见此处](https://iamazing.cn/page/how-to-deploy-a-website)。 - -### 多机部署 -1. 所有服务器 `SESSION_SECRET` 设置一样的值。 -2. 必须设置 `SQL_DSN`,使用 MySQL 数据库而非 SQLite,所有服务器连接同一个数据库。 -3. 所有从服务器必须设置 `NODE_TYPE` 为 `slave`,不设置则默认为主服务器。 -4. 设置 `SYNC_FREQUENCY` 后服务器将定期从数据库同步配置,在使用远程数据库的情况下,推荐设置该项并启用 Redis,无论主从。 -5. 从服务器可以选择设置 `FRONTEND_BASE_URL`,以重定向页面请求到主服务器。 -6. 从服务器上**分别**装好 Redis,设置好 `REDIS_CONN_STRING`,这样可以做到在缓存未过期的情况下数据库零访问,可以减少延迟。 -7. 如果主服务器访问数据库延迟也比较高,则也需要启用 Redis,并设置 `SYNC_FREQUENCY`,以定期从数据库同步配置。 - -环境变量的具体使用方法详见[此处](#环境变量)。 - -### 宝塔部署教程 - -详见 [#175](https://github.com/songquanpeng/one-api/issues/175)。 - -如果部署后访问出现空白页面,详见 [#97](https://github.com/songquanpeng/one-api/issues/97)。 - -### 部署第三方服务配合 One API 使用 -> 欢迎 PR 添加更多示例。 - -#### ChatGPT Next Web -项目主页:https://github.com/Yidadaa/ChatGPT-Next-Web - -```bash -docker run --name chat-next-web -d -p 3001:3000 yidadaa/chatgpt-next-web -``` - -注意修改端口号,之后在页面上设置接口地址(例如:https://openai.justsong.cn/ )和 API Key 即可。 - -#### ChatGPT Web -项目主页:https://github.com/Chanzhaoyu/chatgpt-web - -```bash -docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://openai.justsong.cn -e OPENAI_API_KEY=sk-xxx chenzhaoyu94/chatgpt-web -``` - -注意修改端口号、`OPENAI_API_BASE_URL` 和 `OPENAI_API_KEY`。 - -#### QChatGPT - QQ机器人 -项目主页:https://github.com/RockChinQ/QChatGPT - -根据文档完成部署后,在`config.py`设置配置项`openai_config`的`reverse_proxy`为 One API 后端地址,设置`api_key`为 One API 生成的key,并在配置项`completion_api_params`的`model`参数设置为 One API 支持的模型名称。 - -可安装 [Switcher 插件](https://github.com/RockChinQ/Switcher)在运行时切换所使用的模型。 - -### 部署到第三方平台 -
-部署到 Sealos -
- -> Sealos 的服务器在国外,不需要额外处理网络问题,支持高并发 & 动态伸缩。 - -点击以下按钮一键部署(部署后访问出现 404 请等待 3~5 分钟): - -[![Deploy-on-Sealos.svg](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy?templateName=one-api) - -
-
- -
-部署到 Zeabur -
- -> Zeabur 的服务器在国外,自动解决了网络的问题,同时免费的额度也足够个人使用 - -[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/7Q0KO3) - -1. 首先 fork 一份代码。 -2. 进入 [Zeabur](https://zeabur.com?referralCode=songquanpeng),登录,进入控制台。 -3. 新建一个 Project,在 Service -> Add Service 选择 Marketplace,选择 MySQL,并记下连接参数(用户名、密码、地址、端口)。 -4. 复制链接参数,运行 ```create database `one-api` ``` 创建数据库。 -5. 然后在 Service -> Add Service,选择 Git(第一次使用需要先授权),选择你 fork 的仓库。 -6. Deploy 会自动开始,先取消。进入下方 Variable,添加一个 `PORT`,值为 `3000`,再添加一个 `SQL_DSN`,值为 `:@tcp(:)/one-api` ,然后保存。 注意如果不填写 `SQL_DSN`,数据将无法持久化,重新部署后数据会丢失。 -7. 选择 Redeploy。 -8. 进入下方 Domains,选择一个合适的域名前缀,如 "my-one-api",最终域名为 "my-one-api.zeabur.app",也可以 CNAME 自己的域名。 -9. 等待部署完成,点击生成的域名进入 One API。 - -
-
- -
-部署到 Render -
- -> Render 提供免费额度,绑卡后可以进一步提升额度 - -Render 可以直接部署 docker 镜像,不需要 fork 仓库:https://dashboard.render.com - -
-
- -## 配置 -系统本身开箱即用。 - -你可以通过设置环境变量或者命令行参数进行配置。 - -等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。 - -**Note**:如果你不知道某个配置项的含义,可以临时删掉值以看到进一步的提示文字。 - -## 使用方法 -在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。 - -之后就可以使用你的令牌访问 One API 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。 - -你需要在各种用到 OpenAI API 的地方设置 API Base 为你的 One API 的部署地址,例如:`https://openai.justsong.cn`,API Key 则为你在 One API 中生成的令牌。 - -注意,具体的 API Base 的格式取决于你所使用的客户端。 - -例如对于 OpenAI 的官方库: -```bash -OPENAI_API_KEY="sk-xxxxxx" -OPENAI_API_BASE="https://:/v1" -``` - -```mermaid -graph LR - A(用户) - A --->|使用 One API 分发的 key 进行请求| B(One API) - B -->|中继请求| C(OpenAI) - B -->|中继请求| D(Azure) - B -->|中继请求| E(其他 OpenAI API 格式下游渠道) - B -->|中继并修改请求体和返回体| F(非 OpenAI API 格式下游渠道) -``` - -可以通过在令牌后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。 -注意,需要是管理员用户创建的令牌才能指定渠道 ID。 - -不加的话将会使用负载均衡的方式使用多个渠道。 - -### 环境变量 -1. `REDIS_CONN_STRING`:设置之后将使用 Redis 作为缓存使用。 - + 例子:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153` - + 如果数据库访问延迟很低,没有必要启用 Redis,启用后反而会出现数据滞后的问题。 -2. `SESSION_SECRET`:设置之后将使用固定的会话密钥,这样系统重新启动后已登录用户的 cookie 将依旧有效。 - + 例子:`SESSION_SECRET=random_string` -3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 或 PostgreSQL。 - + 例子: - + MySQL:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi` - + PostgreSQL:`SQL_DSN=postgres://postgres:123456@localhost:5432/oneapi`(适配中,欢迎反馈) - + 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。 - + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。 - + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。 - + 请根据你的数据库配置修改下列参数(或者保持默认值): - + `SQL_MAX_IDLE_CONNS`:最大空闲连接数,默认为 `100`。 - + `SQL_MAX_OPEN_CONNS`:最大打开连接数,默认为 `1000`。 - + 如果报错 `Error 1040: Too many connections`,请适当减小该值。 - + `SQL_CONN_MAX_LIFETIME`:连接的最大生命周期,默认为 `60`,单位分钟。 -4. `FRONTEND_BASE_URL`:设置之后将重定向页面请求到指定的地址,仅限从服务器设置。 - + 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn` -5. `MEMORY_CACHE_ENABLED`:启用内存缓存,会导致用户额度的更新存在一定的延迟,可选值为 `true` 和 `false`,未设置则默认为 `false`。 - + 例子:`MEMORY_CACHE_ENABLED=true` -6. `SYNC_FREQUENCY`:在启用缓存的情况下与数据库同步配置的频率,单位为秒,默认为 `600` 秒。 - + 例子:`SYNC_FREQUENCY=60` -7. `NODE_TYPE`:设置之后将指定节点类型,可选值为 `master` 和 `slave`,未设置则默认为 `master`。 - + 例子:`NODE_TYPE=slave` -8. `CHANNEL_UPDATE_FREQUENCY`:设置之后将定期更新渠道余额,单位为分钟,未设置则不进行更新。 - + 例子:`CHANNEL_UPDATE_FREQUENCY=1440` -9. `CHANNEL_TEST_FREQUENCY`:设置之后将定期检查渠道,单位为分钟,未设置则不进行检查。 - + 例子:`CHANNEL_TEST_FREQUENCY=1440` -10. `POLLING_INTERVAL`:批量更新渠道余额以及测试可用性时的请求间隔,单位为秒,默认无间隔。 - + 例子:`POLLING_INTERVAL=5` -11. `BATCH_UPDATE_ENABLED`:启用数据库批量更新聚合,会导致用户额度的更新存在一定的延迟可选值为 `true` 和 `false`,未设置则默认为 `false`。 - + 例子:`BATCH_UPDATE_ENABLED=true` - + 如果你遇到了数据库连接数过多的问题,可以尝试启用该选项。 -12. `BATCH_UPDATE_INTERVAL=5`:批量更新聚合的时间间隔,单位为秒,默认为 `5`。 - + 例子:`BATCH_UPDATE_INTERVAL=5` -13. 请求频率限制: - + `GLOBAL_API_RATE_LIMIT`:全局 API 速率限制(除中继请求外),单 ip 三分钟内的最大请求数,默认为 `180`。 - + `GLOBAL_WEB_RATE_LIMIT`:全局 Web 速率限制,单 ip 三分钟内的最大请求数,默认为 `60`。 -14. 编码器缓存设置: - + `TIKTOKEN_CACHE_DIR`:默认程序启动时会联网下载一些通用的词元的编码,如:`gpt-3.5-turbo`,在一些网络环境不稳定,或者离线情况,可能会导致启动有问题,可以配置此目录缓存数据,可迁移到离线环境。 - + `DATA_GYM_CACHE_DIR`:目前该配置作用与 `TIKTOKEN_CACHE_DIR` 一致,但是优先级没有它高。 -15. `RELAY_TIMEOUT`:中继超时设置,单位为秒,默认不设置超时时间。 - -### 命令行参数 -1. `--port `: 指定服务器监听的端口号,默认为 `3000`。 - + 例子:`--port 3000` -2. `--log-dir `: 指定日志文件夹,如果没有设置,默认保存至工作目录的 `logs` 文件夹下。 - + 例子:`--log-dir ./logs` -3. `--version`: 打印系统版本号并退出。 -4. `--help`: 查看命令的使用帮助和参数说明。 - -## 演示 -### 在线演示 -注意,该演示站不提供对外服务: -https://openai.justsong.cn - -### 截图展示 -![channel](https://user-images.githubusercontent.com/39998050/233837954-ae6683aa-5c4f-429f-a949-6645a83c9490.png) -![token](https://user-images.githubusercontent.com/39998050/233837971-dab488b7-6d96-43af-b640-a168e8d1c9bf.png) - -## 常见问题 -1. 额度是什么?怎么计算的?One API 的额度计算有问题? - + 额度 = 分组倍率 * 模型倍率 * (提示 token 数 + 补全 token 数 * 补全倍率) - + 其中补全倍率对于 GPT3.5 固定为 1.33,GPT4 为 2,与官方保持一致。 - + 如果是非流模式,官方接口会返回消耗的总 token,但是你要注意提示和补全的消耗倍率不一样。 - + 注意,One API 的默认倍率就是官方倍率,是已经调整过的。 -2. 账户额度足够为什么提示额度不足? - + 请检查你的令牌额度是否足够,这个和账户额度是分开的。 - + 令牌额度仅供用户设置最大使用量,用户可自由设置。 -3. 提示无可用渠道? - + 请检查的用户分组和渠道分组设置。 - + 以及渠道的模型设置。 -4. 渠道测试报错:`invalid character '<' looking for beginning of value` - + 这是因为返回值不是合法的 JSON,而是一个 HTML 页面。 - + 大概率是你的部署站的 IP 或代理的节点被 CloudFlare 封禁了。 -5. ChatGPT Next Web 报错:`Failed to fetch` - + 部署的时候不要设置 `BASE_URL`。 - + 检查你的接口地址和 API Key 有没有填对。 - + 检查是否启用了 HTTPS,浏览器会拦截 HTTPS 域名下的 HTTP 请求。 -6. 报错:`当前分组负载已饱和,请稍后再试` - + 上游通道 429 了。 -7. 升级之后我的数据会丢失吗? - + 如果使用 MySQL,不会。 - + 如果使用 SQLite,需要按照我所给的部署命令挂载 volume 持久化 one-api.db 数据库文件,否则容器重启后数据会丢失。 -8. 升级之前数据库需要做变更吗? - + 一般情况下不需要,系统将在初始化的时候自动调整。 - + 如果需要的话,我会在更新日志中说明,并给出脚本。 - -## 相关项目 -* [FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统 -* [ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web): 一键拥有你自己的跨平台 ChatGPT 应用 - -## 注意 - -本项目使用 MIT 协议进行开源,**在此基础上**,必须在页面底部保留署名以及指向本项目的链接。如果不想保留署名,必须首先获得授权。 - -同样适用于基于本项目的二开项目。 - -依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。 +- update token usage by API +- support gpt-vision From 91d5003c61371dc3deca87732302c6ed497e3ca2 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 02:04:03 +0000 Subject: [PATCH 014/170] ci: Update image tag for GitHub Actions in ci.yml - Change image tags to use SHORT_SHA instead of github.sha in .github/workflows/ci.yml --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5fd947e..06489bf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,9 @@ jobs: path: ${{ steps.go-cache-paths.outputs.go-mod }} key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + - name: Add SHORT_SHA env property with commit short sha + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + - name: Build and push latest uses: docker/build-push-action@v4 @@ -59,4 +62,4 @@ jobs: with: context: . push: true - tags: ppcelery/one-api:${{ github.sha }} + tags: ppcelery/one-api:${SHORT_SHA} From 8d270c8c9a74f4e8495249d0b78e7cde6f51b4ab Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 02:16:17 +0000 Subject: [PATCH 015/170] chore: Refactor code and update GitHub workflow - Remove comment section from the Footer component in Footer.js - Add environment variable `SHORT_SHA` to store the first 8 characters of the commit SHA in ci.yml file - Remove the addition of `SHORT_SHA` to the `GITHUB_ENV` file in ci.yml file --- .github/workflows/ci.yml | 9 ++++++--- web/src/components/Footer.js | 8 -------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06489bf5..44a8be05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: - 'main' +env: + SHORT_SHA: "${GITHUB_SHA} | cut -c1-8`" + jobs: docker: runs-on: ubuntu-latest @@ -46,8 +49,8 @@ jobs: path: ${{ steps.go-cache-paths.outputs.go-mod }} key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + # - name: Add SHORT_SHA env property with commit short sha + # run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV - name: Build and push latest @@ -62,4 +65,4 @@ jobs: with: context: . push: true - tags: ppcelery/one-api:${SHORT_SHA} + tags: ppcelery/one-api:${{ env.SHORT_SHA }} diff --git a/web/src/components/Footer.js b/web/src/components/Footer.js index 334ee379..c303e79b 100644 --- a/web/src/components/Footer.js +++ b/web/src/components/Footer.js @@ -43,14 +43,6 @@ const Footer = () => { > {systemName} {process.env.REACT_APP_VERSION}{' '} - 由{' '} - - JustSong - {' '} - 构建,源代码遵循{' '} - - MIT 协议 - )} From 8b477d896d5cab23645bd1f847ffece06fd50d46 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 03:24:55 +0000 Subject: [PATCH 016/170] feat: Refactor message handling and update dependencies - Added required packages to go.mod: `github.com/Laisky/errors/v2 v2.0.1`, `github.com/stretchr/testify v1.8.3`, `github.com/davecgh/go-spew v1.1.1`, `github.com/pmezard/go-difflib v1.0.0` - Increased the number of returned recordings to 100 in `relay-utils.go` - Refactored and simplified code in `relay-aiproxy.go` for improved message retrieval and error handling - Added new test file `relay_test.go` and various test cases for different message types - Modified functions in `group.go` and `relay.go` for improved functionality --- common/init.go | 18 +++++------ controller/group.go | 2 +- controller/relay-aiproxy.go | 18 +++-------- controller/relay-utils.go | 2 +- controller/relay.go | 51 ++++++++++--------------------- controller/relay_test.go | 61 +++++++++++++++++++++++++++++++++++++ go.mod | 4 +++ go.sum | 2 ++ 8 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 controller/relay_test.go diff --git a/common/init.go b/common/init.go index 1e9c85ce..e04612d5 100644 --- a/common/init.go +++ b/common/init.go @@ -23,17 +23,17 @@ func printHelp() { } func init() { - flag.Parse() + // flag.Parse() - if *PrintVersion { - fmt.Println(Version) - os.Exit(0) - } + // if *PrintVersion { + // fmt.Println(Version) + // os.Exit(0) + // } - if *PrintHelp { - printHelp() - os.Exit(0) - } + // if *PrintHelp { + // printHelp() + // os.Exit(0) + // } if os.Getenv("SESSION_SECRET") != "" { SessionSecret = os.Getenv("SESSION_SECRET") diff --git a/controller/group.go b/controller/group.go index 2b2f6006..d959bd37 100644 --- a/controller/group.go +++ b/controller/group.go @@ -8,7 +8,7 @@ import ( func GetGroups(c *gin.Context) { groupNames := make([]string, 0) - for groupName, _ := range common.GroupRatio { + for groupName := range common.GroupRatio { groupNames = append(groupNames, groupName) } c.JSON(http.StatusOK, gin.H{ diff --git a/controller/relay-aiproxy.go b/controller/relay-aiproxy.go index ca0911ba..8a664b0b 100644 --- a/controller/relay-aiproxy.go +++ b/controller/relay-aiproxy.go @@ -49,23 +49,13 @@ type AIProxyLibraryStreamResponse struct { func requestOpenAI2AIProxyLibrary(request GeneralOpenAIRequest) *AIProxyLibraryRequest { query := "" + if request.MessagesLen() != 0 { - switch msgs := request.Messages.(type) { - case []Message: + if msgs, err := request.TextMessages(); err == nil { query = msgs[len(msgs)-1].Content - case []VisionMessage: + } else if msgs, err := request.VisionMessages(); err == nil { query = msgs[len(msgs)-1].Content.Text - case []any: - msg := msgs[len(msgs)-1] - switch msg := msg.(type) { - case Message: - query = msg.Content - case VisionMessage: - query = msg.Content.Text - default: - log.Panicf("unknown message type: %T", msg) - } - default: + } else { log.Panicf("unknown message type: %T", msgs) } } diff --git a/controller/relay-utils.go b/controller/relay-utils.go index cf5d9b69..407d876b 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -29,7 +29,7 @@ func InitTokenEncoders() { if err != nil { common.FatalLog(fmt.Sprintf("failed to get gpt-4 token encoder: %s", err.Error())) } - for model, _ := range common.ModelRatio { + for model := range common.ModelRatio { if strings.HasPrefix(model, "gpt-3.5") { tokenEncoderMap[model] = gpt35TokenEncoder } else if strings.HasPrefix(model, "gpt-4") { diff --git a/controller/relay.go b/controller/relay.go index bed5a2e2..5d41d10b 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -1,13 +1,14 @@ package controller import ( - "errors" + "encoding/json" "fmt" "net/http" "one-api/common" "strconv" "strings" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" ) @@ -92,6 +93,8 @@ func (r *GeneralOpenAIRequest) MessagesLen() int { return len(msgs) case []VisionMessage: return len(msgs) + case []map[string]any: + return len(msgs) default: return 0 } @@ -99,46 +102,24 @@ func (r *GeneralOpenAIRequest) MessagesLen() int { // TextMessages returns messages as []Message func (r *GeneralOpenAIRequest) TextMessages() (messages []Message, err error) { - switch msgs := r.Messages.(type) { - case []any: - messages = make([]Message, 0, len(msgs)) - for _, msg := range msgs { - if m, ok := msg.(Message); ok { - messages = append(messages, m) - } else { - err = fmt.Errorf("invalid message type") - return - } - } - case []Message: - messages = msgs - default: - return nil, errors.New("invalid message type") + if blob, err := json.Marshal(r.Messages); err != nil { + return nil, errors.Wrap(err, "marshal messages failed") + } else if err := json.Unmarshal(blob, &messages); err != nil { + return nil, errors.Wrap(err, "unmarshal messages failed") + } else { + return messages, nil } - - return } // VisionMessages returns messages as []VisionMessage func (r *GeneralOpenAIRequest) VisionMessages() (messages []VisionMessage, err error) { - switch msgs := r.Messages.(type) { - case []any: - messages = make([]VisionMessage, 0, len(msgs)) - for _, msg := range msgs { - if m, ok := msg.(VisionMessage); ok { - messages = append(messages, m) - } else { - err = fmt.Errorf("invalid message type") - return - } - } - case []VisionMessage: - messages = msgs - default: - return nil, errors.New("invalid message type") + if blob, err := json.Marshal(r.Messages); err != nil { + return nil, errors.Wrap(err, "marshal vision messages failed") + } else if err := json.Unmarshal(blob, &messages); err != nil { + return nil, errors.Wrap(err, "unmarshal vision messages failed") + } else { + return messages, nil } - - return } func (r GeneralOpenAIRequest) ParseInput() []string { diff --git a/controller/relay_test.go b/controller/relay_test.go new file mode 100644 index 00000000..9bb41c9a --- /dev/null +++ b/controller/relay_test.go @@ -0,0 +1,61 @@ +package controller + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGeneralOpenAIRequest_TextMessages(t *testing.T) { + tests := []struct { + name string + messages interface{} + want []Message + wantErr error + }{ + { + name: "Test with []any messages", + messages: []any{Message{}, Message{}}, + want: []Message{{}, {}}, + wantErr: nil, + }, + { + name: "Test with []Message messages", + messages: []Message{{}, {}}, + want: []Message{{}, {}}, + wantErr: nil, + }, + { + name: "Test with invalid message type", + messages: "invalid", + want: nil, + wantErr: fmt.Errorf("invalid message type string"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &GeneralOpenAIRequest{ + Messages: tt.messages, + } + got := new(GeneralOpenAIRequest) + + blob, err := json.Marshal(r) + require.NoError(t, err) + err = json.Unmarshal(blob, got) + require.NoError(t, err) + + gotMessages, err := got.TextMessages() + if tt.wantErr != nil { + require.ErrorContains(t, err, "cannot unmarshal string into Go value") + return + } else { + require.NoError(t, err) + } + + require.Equal(t, tt.want, gotMessages) + }) + } +} diff --git a/go.mod b/go.mod index 3922c5d4..c4dd5685 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module one-api go 1.21 require ( + github.com/Laisky/errors/v2 v2.0.1 github.com/gin-contrib/cors v1.4.0 github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/sessions v0.0.5 @@ -12,6 +13,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 github.com/google/uuid v1.4.0 github.com/pkoukk/tiktoken-go v0.1.6 + github.com/stretchr/testify v1.8.3 golang.org/x/crypto v0.15.0 gorm.io/driver/mysql v1.5.2 gorm.io/driver/postgres v1.5.4 @@ -23,6 +25,7 @@ require ( github.com/bytedance/sonic v1.9.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.10.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect @@ -47,6 +50,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect diff --git a/go.sum b/go.sum index 5257f85b..b78729ef 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Laisky/errors/v2 v2.0.1 h1:yqCBrRzaP012AMB+7fVlXrP34OWRHrSO/hZ38CFdH84= +github.com/Laisky/errors/v2 v2.0.1/go.mod h1:mTn1LHSmKm4CYug0rpYO7rz13dp/DKrtzlSELSrxvT0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= From d0c0b9b6505f77e165485fe81fdf8ad81b6f21bd Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 03:29:51 +0000 Subject: [PATCH 017/170] ci: Refactor GitHub workflow and increase code efficiency - Remove commented out code for `SHORT_SHA` in `.github/workflows/ci.yml` - Add `SHORT_SHA` environment property with commit short sha in `.github/workflows/ci.yml` --- .github/workflows/ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44a8be05..055cfb30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,6 @@ on: branches: - 'main' -env: - SHORT_SHA: "${GITHUB_SHA} | cut -c1-8`" - jobs: docker: runs-on: ubuntu-latest @@ -49,8 +46,8 @@ jobs: path: ${{ steps.go-cache-paths.outputs.go-mod }} key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - # - name: Add SHORT_SHA env property with commit short sha - # run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + - name: Add SHORT_SHA env property with commit short sha + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV - name: Build and push latest From 08ca72184aed09e79793bf76490823b8262a0f39 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 04:00:49 +0000 Subject: [PATCH 018/170] feat: Add token counting functionality to vision-related functions - Add function `CountVisionImageToken` to count vision image tokens - Modify function `imageSize` to handle different image types - Add function `countVisonTokenMessages` to count tokens in vision messages - Add logic to count tokens for different types of vision messages in `countVisonTokenMessages` - Add tokens for role and name in `countVisonTokenMessages` - Update total token count calculation in `countVisonTokenMessages` to include image tokens and message tokens - Add constant values for tokens per message and tokens per name in `countVisonTokenMessages` - Modify the error message on line 12 to include the JSON string that failed to unmarshal --- controller/relay-text.go | 18 +++++-- controller/relay-utils.go | 103 +++++++++++++++++++++++++++++++++++++- controller/relay.go | 2 +- 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index 50b45140..68552b53 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -202,12 +202,20 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { var completionTokens int switch relayMode { case RelayModeChatCompletions: - messages, err := textRequest.TextMessages() - if err != nil { - return errorWrapper(err, "parse_text_messages_failed", http.StatusBadRequest) + // first try to parse as text messages + if messages, err := textRequest.TextMessages(); err != nil { + // then try to parse as vision messages + if messages, err := textRequest.VisionMessages(); err != nil { + return errorWrapper(err, "parse_text_messages_failed", http.StatusBadRequest) + } else { + // vision message + if promptTokens, err = countVisonTokenMessages(messages, textRequest.Model); err != nil { + return errorWrapper(err, "count_token_messages_failed", http.StatusInternalServerError) + } + } + } else { + promptTokens = countTokenMessages(messages, textRequest.Model) } - - promptTokens = countTokenMessages(messages, textRequest.Model) case RelayModeCompletions: promptTokens = countTokenInput(textRequest.Prompt, textRequest.Model) case RelayModeModerations: diff --git a/controller/relay-utils.go b/controller/relay-utils.go index 407d876b..0538aad7 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -1,15 +1,22 @@ package controller import ( + "bytes" + "encoding/base64" "encoding/json" "fmt" - "github.com/gin-gonic/gin" - "github.com/pkoukk/tiktoken-go" + "image/jpeg" + "image/png" "io" + "math" "net/http" "one-api/common" "strconv" "strings" + + "github.com/Laisky/errors/v2" + "github.com/gin-gonic/gin" + "github.com/pkoukk/tiktoken-go" ) var stopFinishReason = "stop" @@ -65,6 +72,53 @@ func getTokenNum(tokenEncoder *tiktoken.Tiktoken, text string) int { return len(tokenEncoder.Encode(text, nil, nil)) } +// CountVisionImageToken count vision image tokens +// +// https://openai.com/pricing +func CountVisionImageToken(cnt []byte, resolution VisionImageResolution) (int, error) { + width, height, err := imageSize(cnt) + if err != nil { + return 0, errors.Wrap(err, "get image size") + } + + switch resolution { + case VisionImageResolutionLow: + return 85, nil // fixed price + case VisionImageResolutionHigh: + h := math.Ceil(float64(height) / 512) + w := math.Ceil(float64(width) / 512) + n := w * h + total := 85 + 170*n + return int(total), nil + default: + return 0, errors.Errorf("unsupport resolution %q", resolution) + } +} + +func imageSize(cnt []byte) (width, height int, err error) { + contentType := http.DetectContentType(cnt) + switch contentType { + case "image/jpeg", "image/jpg": + img, err := jpeg.Decode(bytes.NewReader(cnt)) + if err != nil { + return 0, 0, errors.Wrap(err, "decode jpeg") + } + + bounds := img.Bounds() + return bounds.Dx(), bounds.Dy(), nil + case "image/png": + img, err := png.Decode(bytes.NewReader(cnt)) + if err != nil { + return 0, 0, errors.Wrap(err, "decode png") + } + + bounds := img.Bounds() + return bounds.Dx(), bounds.Dy(), nil + default: + return 0, 0, errors.Errorf("unsupport image content type %q", contentType) + } +} + func countTokenMessages(messages []Message, model string) int { tokenEncoder := getTokenEncoder(model) // Reference: @@ -95,6 +149,51 @@ func countTokenMessages(messages []Message, model string) int { return tokenNum } +func countVisonTokenMessages(messages []VisionMessage, model string) (int, error) { + tokenEncoder := getTokenEncoder(model) + // Reference: + // https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb + // https://github.com/pkoukk/tiktoken-go/issues/6 + // + // Every message follows <|start|>{role/name}\n{content}<|end|>\n + var tokensPerMessage int + var tokensPerName int + if model == "gpt-3.5-turbo-0301" { + tokensPerMessage = 4 + tokensPerName = -1 // If there's a name, the role is omitted + } else { + tokensPerMessage = 3 + tokensPerName = 1 + } + tokenNum := 0 + for _, message := range messages { + tokenNum += tokensPerMessage + switch message.Content.Type { + case OpenaiVisionMessageContentTypeText: + tokenNum += getTokenNum(tokenEncoder, message.Content.Text) + case OpenaiVisionMessageContentTypeImageUrl: + imgblob, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(message.Content.ImageUrl.URL, "data:image/jpeg;base64,")) + if err != nil { + return 0, errors.Wrap(err, "failed to decode base64 image") + } + + if imgtoken, err := CountVisionImageToken(imgblob, message.Content.ImageUrl.Detail); err != nil { + return 0, errors.Wrap(err, "failed to count vision image token") + } else { + tokenNum += imgtoken + } + } + + tokenNum += getTokenNum(tokenEncoder, message.Role) + if message.Name != nil { + tokenNum += tokensPerName + tokenNum += getTokenNum(tokenEncoder, *message.Name) + } + } + tokenNum += 3 // Every reply is primed with <|start|>assistant<|message|> + return tokenNum, nil +} + func countTokenInput(input any, model string) int { switch input.(type) { case string: diff --git a/controller/relay.go b/controller/relay.go index 5d41d10b..0ca66190 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -105,7 +105,7 @@ func (r *GeneralOpenAIRequest) TextMessages() (messages []Message, err error) { if blob, err := json.Marshal(r.Messages); err != nil { return nil, errors.Wrap(err, "marshal messages failed") } else if err := json.Unmarshal(blob, &messages); err != nil { - return nil, errors.Wrap(err, "unmarshal messages failed") + return nil, errors.Wrapf(err, "unmarshal messages failed %q", string(blob)) } else { return messages, nil } From 7e27b4f3c0c069b5d90168bd16dc8c1eb99f36fd Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 05:15:49 +0000 Subject: [PATCH 019/170] fix: Improve error message in `VisionMessages` function - Modify error message in `VisionMessages` function to include failed JSON blob - Include changes to omitted files in diff summary - High-level summary of changes --- controller/relay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/relay.go b/controller/relay.go index 0ca66190..26cbd416 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -116,7 +116,7 @@ func (r *GeneralOpenAIRequest) VisionMessages() (messages []VisionMessage, err e if blob, err := json.Marshal(r.Messages); err != nil { return nil, errors.Wrap(err, "marshal vision messages failed") } else if err := json.Unmarshal(blob, &messages); err != nil { - return nil, errors.Wrap(err, "unmarshal vision messages failed") + return nil, errors.Wrapf(err, "unmarshal vision messages failed %q", string(blob)) } else { return messages, nil } From 14108ce24dbdc37e0aac7d48b3c5058df10b2b08 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 05:26:26 +0000 Subject: [PATCH 020/170] feat: Refactor vision message handling and increase commit short SHA length - Increase the length of the commit short SHA from 8 to 7 characters - Update relay-aiproxy.go to handle multiple content types in the last message - Refactor and modify countVisonTokenMessages function in relay-utils.go to handle different content types and update token counts accordingly - Update VisionMessage struct in relay.go to have Content as a slice of OpenaiVisionMessageContent --- .github/workflows/ci.yml | 2 +- controller/relay-aiproxy.go | 10 +++++++++- controller/relay-utils.go | 26 ++++++++++++++------------ controller/relay.go | 6 +++--- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 055cfb30..4303cc94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-7`" >> $GITHUB_ENV - name: Build and push latest diff --git a/controller/relay-aiproxy.go b/controller/relay-aiproxy.go index 8a664b0b..292cec15 100644 --- a/controller/relay-aiproxy.go +++ b/controller/relay-aiproxy.go @@ -54,7 +54,15 @@ func requestOpenAI2AIProxyLibrary(request GeneralOpenAIRequest) *AIProxyLibraryR if msgs, err := request.TextMessages(); err == nil { query = msgs[len(msgs)-1].Content } else if msgs, err := request.VisionMessages(); err == nil { - query = msgs[len(msgs)-1].Content.Text + lastMsg := msgs[len(msgs)-1] + if len(lastMsg.Content) != 0 { + for i := range lastMsg.Content { + if lastMsg.Content[i].Type == OpenaiVisionMessageContentTypeText { + query = lastMsg.Content[i].Text + break + } + } + } } else { log.Panicf("unknown message type: %T", msgs) } diff --git a/controller/relay-utils.go b/controller/relay-utils.go index 0538aad7..22278269 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -168,19 +168,21 @@ func countVisonTokenMessages(messages []VisionMessage, model string) (int, error tokenNum := 0 for _, message := range messages { tokenNum += tokensPerMessage - switch message.Content.Type { - case OpenaiVisionMessageContentTypeText: - tokenNum += getTokenNum(tokenEncoder, message.Content.Text) - case OpenaiVisionMessageContentTypeImageUrl: - imgblob, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(message.Content.ImageUrl.URL, "data:image/jpeg;base64,")) - if err != nil { - return 0, errors.Wrap(err, "failed to decode base64 image") - } + for _, cnt := range message.Content { + switch cnt.Type { + case OpenaiVisionMessageContentTypeText: + tokenNum += getTokenNum(tokenEncoder, cnt.Text) + case OpenaiVisionMessageContentTypeImageUrl: + imgblob, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(cnt.ImageUrl.URL, "data:image/jpeg;base64,")) + if err != nil { + return 0, errors.Wrap(err, "failed to decode base64 image") + } - if imgtoken, err := CountVisionImageToken(imgblob, message.Content.ImageUrl.Detail); err != nil { - return 0, errors.Wrap(err, "failed to count vision image token") - } else { - tokenNum += imgtoken + if imgtoken, err := CountVisionImageToken(imgblob, cnt.ImageUrl.Detail); err != nil { + return 0, errors.Wrap(err, "failed to count vision image token") + } else { + tokenNum += imgtoken + } } } diff --git a/controller/relay.go b/controller/relay.go index 26cbd416..a7f84a9d 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -19,9 +19,9 @@ type Message struct { } type VisionMessage struct { - Role string `json:"role"` - Content OpenaiVisionMessageContent `json:"content"` - Name *string `json:"name,omitempty"` + Role string `json:"role"` + Content []OpenaiVisionMessageContent `json:"content"` + Name *string `json:"name,omitempty"` } // OpenaiVisionMessageContentType vision message content type From 16080380f5502dd5c88010a60994dbbc3fa4c9a6 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 17 Nov 2023 16:05:57 +0800 Subject: [PATCH 021/170] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cf0c4943..6bbc0aa6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # One API +docker image: `ppcelery/one-api:latest` + ## New Features - update token usage by API From c503a87c74c60c8e0cc6aacaeab48c4b8f57a78c Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 12 Dec 2023 06:09:11 +0000 Subject: [PATCH 022/170] refactor: Refactor code for improved efficiency and readability - Refactored code in relay-utils.go - Eliminated unused imports and redundant function - Improved code readability with added comments - Cleaned up by removing unnecessary commented-out code --- controller/relay-utils.go | 170 +++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 87 deletions(-) diff --git a/controller/relay-utils.go b/controller/relay-utils.go index 5db664ff..0011d5aa 100644 --- a/controller/relay-utils.go +++ b/controller/relay-utils.go @@ -1,13 +1,9 @@ package controller import ( - "bytes" "context" - "encoding/base64" "encoding/json" "fmt" - "image/jpeg" - "image/png" "io" "math" "net/http" @@ -78,49 +74,49 @@ func getTokenNum(tokenEncoder *tiktoken.Tiktoken, text string) int { // CountVisionImageToken count vision image tokens // // https://openai.com/pricing -func CountVisionImageToken(cnt []byte, resolution VisionImageResolution) (int, error) { - width, height, err := imageSize(cnt) - if err != nil { - return 0, errors.Wrap(err, "get image size") - } +// func CountVisionImageToken(cnt []byte, resolution VisionImageResolution) (int, error) { +// width, height, err := imageSize(cnt) +// if err != nil { +// return 0, errors.Wrap(err, "get image size") +// } - switch resolution { - case VisionImageResolutionLow: - return 85, nil // fixed price - case VisionImageResolutionHigh: - h := math.Ceil(float64(height) / 512) - w := math.Ceil(float64(width) / 512) - n := w * h - total := 85 + 170*n - return int(total), nil - default: - return 0, errors.Errorf("unsupport resolution %q", resolution) - } -} +// switch resolution { +// case VisionImageResolutionLow: +// return 85, nil // fixed price +// case VisionImageResolutionHigh: +// h := math.Ceil(float64(height) / 512) +// w := math.Ceil(float64(width) / 512) +// n := w * h +// total := 85 + 170*n +// return int(total), nil +// default: +// return 0, errors.Errorf("unsupport resolution %q", resolution) +// } +// } -func imageSize(cnt []byte) (width, height int, err error) { - contentType := http.DetectContentType(cnt) - switch contentType { - case "image/jpeg", "image/jpg": - img, err := jpeg.Decode(bytes.NewReader(cnt)) - if err != nil { - return 0, 0, errors.Wrap(err, "decode jpeg") - } +// func imageSize(cnt []byte) (width, height int, err error) { +// contentType := http.DetectContentType(cnt) +// switch contentType { +// case "image/jpeg", "image/jpg": +// img, err := jpeg.Decode(bytes.NewReader(cnt)) +// if err != nil { +// return 0, 0, errors.Wrap(err, "decode jpeg") +// } - bounds := img.Bounds() - return bounds.Dx(), bounds.Dy(), nil - case "image/png": - img, err := png.Decode(bytes.NewReader(cnt)) - if err != nil { - return 0, 0, errors.Wrap(err, "decode png") - } +// bounds := img.Bounds() +// return bounds.Dx(), bounds.Dy(), nil +// case "image/png": +// img, err := png.Decode(bytes.NewReader(cnt)) +// if err != nil { +// return 0, 0, errors.Wrap(err, "decode png") +// } - bounds := img.Bounds() - return bounds.Dx(), bounds.Dy(), nil - default: - return 0, 0, errors.Errorf("unsupport image content type %q", contentType) - } -} +// bounds := img.Bounds() +// return bounds.Dx(), bounds.Dy(), nil +// default: +// return 0, 0, errors.Errorf("unsupport image content type %q", contentType) +// } +// } func countTokenMessages(messages []Message, model string) int { tokenEncoder := getTokenEncoder(model) @@ -178,52 +174,52 @@ func countTokenMessages(messages []Message, model string) int { return tokenNum } -func countVisonTokenMessages(messages []VisionMessage, model string) (int, error) { - tokenEncoder := getTokenEncoder(model) - // Reference: - // https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb - // https://github.com/pkoukk/tiktoken-go/issues/6 - // - // Every message follows <|start|>{role/name}\n{content}<|end|>\n - var tokensPerMessage int - var tokensPerName int - if model == "gpt-3.5-turbo-0301" { - tokensPerMessage = 4 - tokensPerName = -1 // If there's a name, the role is omitted - } else { - tokensPerMessage = 3 - tokensPerName = 1 - } - tokenNum := 0 - for _, message := range messages { - tokenNum += tokensPerMessage - for _, cnt := range message.Content { - switch cnt.Type { - case OpenaiVisionMessageContentTypeText: - tokenNum += getTokenNum(tokenEncoder, cnt.Text) - case OpenaiVisionMessageContentTypeImageUrl: - imgblob, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(cnt.ImageUrl.URL, "data:image/jpeg;base64,")) - if err != nil { - return 0, errors.Wrap(err, "failed to decode base64 image") - } +// func countVisonTokenMessages(messages []VisionMessage, model string) (int, error) { +// tokenEncoder := getTokenEncoder(model) +// // Reference: +// // https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb +// // https://github.com/pkoukk/tiktoken-go/issues/6 +// // +// // Every message follows <|start|>{role/name}\n{content}<|end|>\n +// var tokensPerMessage int +// var tokensPerName int +// if model == "gpt-3.5-turbo-0301" { +// tokensPerMessage = 4 +// tokensPerName = -1 // If there's a name, the role is omitted +// } else { +// tokensPerMessage = 3 +// tokensPerName = 1 +// } +// tokenNum := 0 +// for _, message := range messages { +// tokenNum += tokensPerMessage +// for _, cnt := range message.Content { +// switch cnt.Type { +// case OpenaiVisionMessageContentTypeText: +// tokenNum += getTokenNum(tokenEncoder, cnt.Text) +// case OpenaiVisionMessageContentTypeImageUrl: +// imgblob, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(cnt.ImageUrl.URL, "data:image/jpeg;base64,")) +// if err != nil { +// return 0, errors.Wrap(err, "failed to decode base64 image") +// } - if imgtoken, err := CountVisionImageToken(imgblob, cnt.ImageUrl.Detail); err != nil { - return 0, errors.Wrap(err, "failed to count vision image token") - } else { - tokenNum += imgtoken - } - } - } +// if imgtoken, err := CountVisionImageToken(imgblob, cnt.ImageUrl.Detail); err != nil { +// return 0, errors.Wrap(err, "failed to count vision image token") +// } else { +// tokenNum += imgtoken +// } +// } +// } - tokenNum += getTokenNum(tokenEncoder, message.Role) - if message.Name != nil { - tokenNum += tokensPerName - tokenNum += getTokenNum(tokenEncoder, *message.Name) - } - } - tokenNum += 3 // Every reply is primed with <|start|>assistant<|message|> - return tokenNum, nil -} +// tokenNum += getTokenNum(tokenEncoder, message.Role) +// if message.Name != nil { +// tokenNum += tokensPerName +// tokenNum += getTokenNum(tokenEncoder, *message.Name) +// } +// } +// tokenNum += 3 // Every reply is primed with <|start|>assistant<|message|> +// return tokenNum, nil +// } const ( lowDetailCost = 85 From 8d8fdaa2afcb287b445a856a8dbfe4b1ed9e7ba3 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 12 Dec 2023 06:34:08 +0000 Subject: [PATCH 023/170] refactor: Refactor import statements and error handling in multiple files - Fix import statements in main.go - Improve error handling and logging in model/main.go --- main.go | 13 +++++++------ model/main.go | 36 +++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/main.go b/main.go index 88938516..86d2623d 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,6 @@ package main import ( "embed" "fmt" - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/cookie" - "github.com/gin-gonic/gin" "one-api/common" "one-api/controller" "one-api/middleware" @@ -13,6 +10,10 @@ import ( "one-api/router" "os" "strconv" + + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" ) //go:embed web/build @@ -33,19 +34,19 @@ func main() { // Initialize SQL Database err := model.InitDB() if err != nil { - common.FatalLog("failed to initialize database: " + err.Error()) + common.FatalLog(fmt.Sprintf("failed to initialize database: %+v", err)) } defer func() { err := model.CloseDB() if err != nil { - common.FatalLog("failed to close database: " + err.Error()) + common.FatalLog(fmt.Sprintf("failed to close database: %+v", err)) } }() // Initialize Redis err = common.InitRedisClient() if err != nil { - common.FatalLog("failed to initialize Redis: " + err.Error()) + common.FatalLog(fmt.Sprintf("failed to initialize Redis: %+v", err)) } // Initialize options diff --git a/model/main.go b/model/main.go index 08182634..e64a67ba 100644 --- a/model/main.go +++ b/model/main.go @@ -1,14 +1,16 @@ package model import ( - "gorm.io/driver/mysql" - "gorm.io/driver/postgres" - "gorm.io/driver/sqlite" - "gorm.io/gorm" "one-api/common" "os" "strings" "time" + + "github.com/Laisky/errors/v2" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) var DB *gorm.DB @@ -20,7 +22,7 @@ func createRootAccountIfNeed() error { common.SysLog("no user exists, create a root user for you: username is root, password is 123456") hashedPassword, err := common.Password2Hash("123456") if err != nil { - return err + return errors.WithStack(err) } rootUser := User{ Username: "root", @@ -73,7 +75,7 @@ func InitDB() (err error) { DB = db sqlDB, err := DB.DB() if err != nil { - return err + return errors.WithStack(err) } sqlDB.SetMaxIdleConns(common.GetOrDefault("SQL_MAX_IDLE_CONNS", 100)) sqlDB.SetMaxOpenConns(common.GetOrDefault("SQL_MAX_OPEN_CONNS", 1000)) @@ -85,46 +87,46 @@ func InitDB() (err error) { common.SysLog("database migration started") err = db.AutoMigrate(&Channel{}) if err != nil { - return err + return errors.WithStack(err) } err = db.AutoMigrate(&Token{}) if err != nil { - return err + return errors.WithStack(err) } err = db.AutoMigrate(&User{}) if err != nil { - return err + return errors.WithStack(err) } err = db.AutoMigrate(&Option{}) if err != nil { - return err + return errors.WithStack(err) } err = db.AutoMigrate(&Redemption{}) if err != nil { - return err + return errors.WithStack(err) } err = db.AutoMigrate(&Ability{}) if err != nil { - return err + return errors.WithStack(err) } err = db.AutoMigrate(&Log{}) if err != nil { - return err + return errors.WithStack(err) } common.SysLog("database migrated") err = createRootAccountIfNeed() - return err + return errors.WithStack(err) } else { common.FatalLog(err) } - return err + return errors.WithStack(err) } func CloseDB() error { sqlDB, err := DB.DB() if err != nil { - return err + return errors.WithStack(err) } err = sqlDB.Close() - return err + return errors.WithStack(err) } From a75c64f55fec0c344a3cc9e15c5025c63a41383e Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 12 Dec 2023 07:47:57 +0000 Subject: [PATCH 024/170] fix: gorm bug for sqlite migrate - Update dependencies in go.mod - Update specifications for User struct fields in model/user.go --- README.md | 416 -------------------------------------------------- go.mod | 39 ++--- go.sum | 87 ++++++----- model/user.go | 22 +-- 4 files changed, 76 insertions(+), 488 deletions(-) diff --git a/README.md b/README.md index 7f550f6d..6bbc0aa6 100644 --- a/README.md +++ b/README.md @@ -4,421 +4,5 @@ docker image: `ppcelery/one-api:latest` ## New Features -<<<<<<< HEAD - update token usage by API - support gpt-vision -======= -

- - license - - - release - - - docker pull - - - release - - - GoReportCard - -

- -

- 部署教程 - · - 使用方法 - · - 意见反馈 - · - 截图展示 - · - 在线演示 - · - 常见问题 - · - 相关项目 - · - 赞赏支持 -

- -> [!NOTE] -> 本项目为开源项目,使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。 -> -> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。 - -> [!WARNING] -> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。 - -> [!WARNING] -> 使用 root 用户初次登录系统后,务必修改默认密码 `123456`! - -## 功能 -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] [Anthropic Claude 系列模型](https://anthropic.com) - + [x] [Google PaLM2 系列模型](https://developers.generativeai.google) - + [x] [百度文心一言系列模型](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) - + [x] [阿里通义千问系列模型](https://help.aliyun.com/document_detail/2400395.html) - + [x] [讯飞星火认知大模型](https://www.xfyun.cn/doc/spark/Web.html) - + [x] [智谱 ChatGLM 系列模型](https://bigmodel.cn) - + [x] [360 智脑](https://ai.360.cn) - + [x] [腾讯混元大模型](https://cloud.tencent.com/document/product/1729) -2. 支持配置镜像以及众多第三方代理服务: - + [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. 支持通过**负载均衡**的方式访问多个渠道。 -4. 支持 **stream 模式**,可以通过流式传输实现打字机效果。 -5. 支持**多机部署**,[详见此处](#多机部署)。 -6. 支持**令牌管理**,设置令牌的过期时间和额度。 -7. 支持**兑换码管理**,支持批量生成和导出兑换码,可使用兑换码为账户进行充值。 -8. 支持**通道管理**,批量创建通道。 -9. 支持**用户分组**以及**渠道分组**,支持为不同分组设置不同的倍率。 -10. 支持渠道**设置模型列表**。 -11. 支持**查看额度明细**。 -12. 支持**用户邀请奖励**。 -13. 支持以美元为单位显示额度。 -14. 支持发布公告,设置充值链接,设置新用户初始额度。 -15. 支持模型映射,重定向用户的请求模型,如无必要请不要设置,设置之后会导致请求体被重新构造而非直接透传,会导致部分还未正式支持的字段无法传递成功。 -16. 支持失败自动重试。 -17. 支持绘图接口。 -18. 支持 [Cloudflare AI Gateway](https://developers.cloudflare.com/ai-gateway/providers/openai/),渠道设置的代理部分填写 `https://gateway.ai.cloudflare.com/v1/ACCOUNT_TAG/GATEWAY/openai` 即可。 -19. 支持丰富的**自定义**设置, - 1. 支持自定义系统名称,logo 以及页脚。 - 2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。 -20. 支持通过系统访问令牌访问管理 API(bearer token,用以替代 cookie,你可以自行抓包来查看 API 的用法)。 -21. 支持 Cloudflare Turnstile 用户校验。 -22. 支持用户管理,支持**多种用户登录注册方式**: - + 邮箱登录注册(支持注册邮箱白名单)以及通过邮箱进行密码重置。 - + [GitHub 开放授权](https://github.com/settings/applications/new)。 - + 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。 - -## 部署 -### 基于 Docker 进行部署 -```shell -# 使用 SQLite 的部署命令: -docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api -# 使用 MySQL 的部署命令,在上面的基础上添加 `-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi"`,请自行修改数据库连接参数,不清楚如何修改请参见下面环境变量一节。 -# 例如: -docker run --name one-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api -``` - -其中,`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。 - -数据和日志将会保存在宿主机的 `/home/ubuntu/data/one-api` 目录,请确保该目录存在且具有写入权限,或者更改为合适的目录。 - -如果启动失败,请添加 `--privileged=true`,具体参考 https://github.com/songquanpeng/one-api/issues/482 。 - -如果上面的镜像无法拉取,可以尝试使用 GitHub 的 Docker 镜像,将上面的 `justsong/one-api` 替换为 `ghcr.io/songquanpeng/one-api` 即可。 - -如果你的并发量较大,**务必**设置 `SQL_DSN`,详见下面[环境变量](#环境变量)一节。 - -更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR` - -Nginx 的参考配置: -``` -server{ - server_name openai.justsong.cn; # 请根据实际情况修改你的域名 - - location / { - client_max_body_size 64m; - proxy_http_version 1.1; - proxy_pass http://localhost:3000; # 请根据实际情况修改你的端口 - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_cache_bypass $http_upgrade; - proxy_set_header Accept-Encoding gzip; - proxy_read_timeout 300s; # GPT-4 需要较长的超时时间,请自行调整 - } -} -``` - -之后使用 Let's Encrypt 的 certbot 配置 HTTPS: -```bash -# Ubuntu 安装 certbot: -sudo snap install --classic certbot -sudo ln -s /snap/bin/certbot /usr/bin/certbot -# 生成证书 & 修改 Nginx 配置 -sudo certbot --nginx -# 根据指示进行操作 -# 重启 Nginx -sudo service nginx restart -``` - -初始账号用户名为 `root`,密码为 `123456`。 - - -### 基于 Docker Compose 进行部署 - -> 仅启动方式不同,参数设置不变,请参考基于 Docker 部署部分 - -```shell -# 目前支持 MySQL 启动,数据存储在 ./data/mysql 文件夹内 -docker-compose up -d - -# 查看部署状态 -docker-compose ps -``` - -### 手动部署 -1. 从 [GitHub Releases](https://github.com/songquanpeng/one-api/releases/latest) 下载可执行文件或者从源码编译: - ```shell - git clone https://github.com/songquanpeng/one-api.git - - # 构建前端 - cd one-api/web - npm install - npm run build - - # 构建后端 - cd .. - go mod download - go build -ldflags "-s -w" -o one-api - ```` -2. 运行: - ```shell - chmod u+x one-api - ./one-api --port 3000 --log-dir ./logs - ``` -3. 访问 [http://localhost:3000/](http://localhost:3000/) 并登录。初始账号用户名为 `root`,密码为 `123456`。 - -更加详细的部署教程[参见此处](https://iamazing.cn/page/how-to-deploy-a-website)。 - -### 多机部署 -1. 所有服务器 `SESSION_SECRET` 设置一样的值。 -2. 必须设置 `SQL_DSN`,使用 MySQL 数据库而非 SQLite,所有服务器连接同一个数据库。 -3. 所有从服务器必须设置 `NODE_TYPE` 为 `slave`,不设置则默认为主服务器。 -4. 设置 `SYNC_FREQUENCY` 后服务器将定期从数据库同步配置,在使用远程数据库的情况下,推荐设置该项并启用 Redis,无论主从。 -5. 从服务器可以选择设置 `FRONTEND_BASE_URL`,以重定向页面请求到主服务器。 -6. 从服务器上**分别**装好 Redis,设置好 `REDIS_CONN_STRING`,这样可以做到在缓存未过期的情况下数据库零访问,可以减少延迟。 -7. 如果主服务器访问数据库延迟也比较高,则也需要启用 Redis,并设置 `SYNC_FREQUENCY`,以定期从数据库同步配置。 - -环境变量的具体使用方法详见[此处](#环境变量)。 - -### 宝塔部署教程 - -详见 [#175](https://github.com/songquanpeng/one-api/issues/175)。 - -如果部署后访问出现空白页面,详见 [#97](https://github.com/songquanpeng/one-api/issues/97)。 - -### 部署第三方服务配合 One API 使用 -> 欢迎 PR 添加更多示例。 - -#### ChatGPT Next Web -项目主页:https://github.com/Yidadaa/ChatGPT-Next-Web - -```bash -docker run --name chat-next-web -d -p 3001:3000 yidadaa/chatgpt-next-web -``` - -注意修改端口号,之后在页面上设置接口地址(例如:https://openai.justsong.cn/ )和 API Key 即可。 - -#### ChatGPT Web -项目主页:https://github.com/Chanzhaoyu/chatgpt-web - -```bash -docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://openai.justsong.cn -e OPENAI_API_KEY=sk-xxx chenzhaoyu94/chatgpt-web -``` - -注意修改端口号、`OPENAI_API_BASE_URL` 和 `OPENAI_API_KEY`。 - -#### QChatGPT - QQ机器人 -项目主页:https://github.com/RockChinQ/QChatGPT - -根据文档完成部署后,在`config.py`设置配置项`openai_config`的`reverse_proxy`为 One API 后端地址,设置`api_key`为 One API 生成的key,并在配置项`completion_api_params`的`model`参数设置为 One API 支持的模型名称。 - -可安装 [Switcher 插件](https://github.com/RockChinQ/Switcher)在运行时切换所使用的模型。 - -### 部署到第三方平台 -
-部署到 Sealos -
- -> Sealos 的服务器在国外,不需要额外处理网络问题,支持高并发 & 动态伸缩。 - -点击以下按钮一键部署(部署后访问出现 404 请等待 3~5 分钟): - -[![Deploy-on-Sealos.svg](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy?templateName=one-api) - -
-
- -
-部署到 Zeabur -
- -> Zeabur 的服务器在国外,自动解决了网络的问题,同时免费的额度也足够个人使用 - -[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/7Q0KO3) - -1. 首先 fork 一份代码。 -2. 进入 [Zeabur](https://zeabur.com?referralCode=songquanpeng),登录,进入控制台。 -3. 新建一个 Project,在 Service -> Add Service 选择 Marketplace,选择 MySQL,并记下连接参数(用户名、密码、地址、端口)。 -4. 复制链接参数,运行 ```create database `one-api` ``` 创建数据库。 -5. 然后在 Service -> Add Service,选择 Git(第一次使用需要先授权),选择你 fork 的仓库。 -6. Deploy 会自动开始,先取消。进入下方 Variable,添加一个 `PORT`,值为 `3000`,再添加一个 `SQL_DSN`,值为 `:@tcp(:)/one-api` ,然后保存。 注意如果不填写 `SQL_DSN`,数据将无法持久化,重新部署后数据会丢失。 -7. 选择 Redeploy。 -8. 进入下方 Domains,选择一个合适的域名前缀,如 "my-one-api",最终域名为 "my-one-api.zeabur.app",也可以 CNAME 自己的域名。 -9. 等待部署完成,点击生成的域名进入 One API。 - -
-
- -
-部署到 Render -
- -> Render 提供免费额度,绑卡后可以进一步提升额度 - -Render 可以直接部署 docker 镜像,不需要 fork 仓库:https://dashboard.render.com - -
-
- -## 配置 -系统本身开箱即用。 - -你可以通过设置环境变量或者命令行参数进行配置。 - -等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。 - -**Note**:如果你不知道某个配置项的含义,可以临时删掉值以看到进一步的提示文字。 - -## 使用方法 -在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。 - -之后就可以使用你的令牌访问 One API 了,使用方式与 [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) 一致。 - -你需要在各种用到 OpenAI API 的地方设置 API Base 为你的 One API 的部署地址,例如:`https://openai.justsong.cn`,API Key 则为你在 One API 中生成的令牌。 - -注意,具体的 API Base 的格式取决于你所使用的客户端。 - -例如对于 OpenAI 的官方库: -```bash -OPENAI_API_KEY="sk-xxxxxx" -OPENAI_API_BASE="https://:/v1" -``` - -```mermaid -graph LR - A(用户) - A --->|使用 One API 分发的 key 进行请求| B(One API) - B -->|中继请求| C(OpenAI) - B -->|中继请求| D(Azure) - B -->|中继请求| E(其他 OpenAI API 格式下游渠道) - B -->|中继并修改请求体和返回体| F(非 OpenAI API 格式下游渠道) -``` - -可以通过在令牌后面添加渠道 ID 的方式指定使用哪一个渠道处理本次请求,例如:`Authorization: Bearer ONE_API_KEY-CHANNEL_ID`。 -注意,需要是管理员用户创建的令牌才能指定渠道 ID。 - -不加的话将会使用负载均衡的方式使用多个渠道。 - -### 环境变量 -1. `REDIS_CONN_STRING`:设置之后将使用 Redis 作为缓存使用。 - + 例子:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153` - + 如果数据库访问延迟很低,没有必要启用 Redis,启用后反而会出现数据滞后的问题。 -2. `SESSION_SECRET`:设置之后将使用固定的会话密钥,这样系统重新启动后已登录用户的 cookie 将依旧有效。 - + 例子:`SESSION_SECRET=random_string` -3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 或 PostgreSQL。 - + 例子: - + MySQL:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi` - + PostgreSQL:`SQL_DSN=postgres://postgres:123456@localhost:5432/oneapi`(适配中,欢迎反馈) - + 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。 - + 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。 - + 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。 - + 请根据你的数据库配置修改下列参数(或者保持默认值): - + `SQL_MAX_IDLE_CONNS`:最大空闲连接数,默认为 `100`。 - + `SQL_MAX_OPEN_CONNS`:最大打开连接数,默认为 `1000`。 - + 如果报错 `Error 1040: Too many connections`,请适当减小该值。 - + `SQL_CONN_MAX_LIFETIME`:连接的最大生命周期,默认为 `60`,单位分钟。 -4. `FRONTEND_BASE_URL`:设置之后将重定向页面请求到指定的地址,仅限从服务器设置。 - + 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn` -5. `MEMORY_CACHE_ENABLED`:启用内存缓存,会导致用户额度的更新存在一定的延迟,可选值为 `true` 和 `false`,未设置则默认为 `false`。 - + 例子:`MEMORY_CACHE_ENABLED=true` -6. `SYNC_FREQUENCY`:在启用缓存的情况下与数据库同步配置的频率,单位为秒,默认为 `600` 秒。 - + 例子:`SYNC_FREQUENCY=60` -7. `NODE_TYPE`:设置之后将指定节点类型,可选值为 `master` 和 `slave`,未设置则默认为 `master`。 - + 例子:`NODE_TYPE=slave` -8. `CHANNEL_UPDATE_FREQUENCY`:设置之后将定期更新渠道余额,单位为分钟,未设置则不进行更新。 - + 例子:`CHANNEL_UPDATE_FREQUENCY=1440` -9. `CHANNEL_TEST_FREQUENCY`:设置之后将定期检查渠道,单位为分钟,未设置则不进行检查。 - + 例子:`CHANNEL_TEST_FREQUENCY=1440` -10. `POLLING_INTERVAL`:批量更新渠道余额以及测试可用性时的请求间隔,单位为秒,默认无间隔。 - + 例子:`POLLING_INTERVAL=5` -11. `BATCH_UPDATE_ENABLED`:启用数据库批量更新聚合,会导致用户额度的更新存在一定的延迟可选值为 `true` 和 `false`,未设置则默认为 `false`。 - + 例子:`BATCH_UPDATE_ENABLED=true` - + 如果你遇到了数据库连接数过多的问题,可以尝试启用该选项。 -12. `BATCH_UPDATE_INTERVAL=5`:批量更新聚合的时间间隔,单位为秒,默认为 `5`。 - + 例子:`BATCH_UPDATE_INTERVAL=5` -13. 请求频率限制: - + `GLOBAL_API_RATE_LIMIT`:全局 API 速率限制(除中继请求外),单 ip 三分钟内的最大请求数,默认为 `180`。 - + `GLOBAL_WEB_RATE_LIMIT`:全局 Web 速率限制,单 ip 三分钟内的最大请求数,默认为 `60`。 -14. 编码器缓存设置: - + `TIKTOKEN_CACHE_DIR`:默认程序启动时会联网下载一些通用的词元的编码,如:`gpt-3.5-turbo`,在一些网络环境不稳定,或者离线情况,可能会导致启动有问题,可以配置此目录缓存数据,可迁移到离线环境。 - + `DATA_GYM_CACHE_DIR`:目前该配置作用与 `TIKTOKEN_CACHE_DIR` 一致,但是优先级没有它高。 -15. `RELAY_TIMEOUT`:中继超时设置,单位为秒,默认不设置超时时间。 - -### 命令行参数 -1. `--port `: 指定服务器监听的端口号,默认为 `3000`。 - + 例子:`--port 3000` -2. `--log-dir `: 指定日志文件夹,如果没有设置,默认保存至工作目录的 `logs` 文件夹下。 - + 例子:`--log-dir ./logs` -3. `--version`: 打印系统版本号并退出。 -4. `--help`: 查看命令的使用帮助和参数说明。 - -## 演示 -### 在线演示 -注意,该演示站不提供对外服务: -https://openai.justsong.cn - -### 截图展示 -![channel](https://user-images.githubusercontent.com/39998050/233837954-ae6683aa-5c4f-429f-a949-6645a83c9490.png) -![token](https://user-images.githubusercontent.com/39998050/233837971-dab488b7-6d96-43af-b640-a168e8d1c9bf.png) - -## 常见问题 -1. 额度是什么?怎么计算的?One API 的额度计算有问题? - + 额度 = 分组倍率 * 模型倍率 * (提示 token 数 + 补全 token 数 * 补全倍率) - + 其中补全倍率对于 GPT3.5 固定为 1.33,GPT4 为 2,与官方保持一致。 - + 如果是非流模式,官方接口会返回消耗的总 token,但是你要注意提示和补全的消耗倍率不一样。 - + 注意,One API 的默认倍率就是官方倍率,是已经调整过的。 -2. 账户额度足够为什么提示额度不足? - + 请检查你的令牌额度是否足够,这个和账户额度是分开的。 - + 令牌额度仅供用户设置最大使用量,用户可自由设置。 -3. 提示无可用渠道? - + 请检查的用户分组和渠道分组设置。 - + 以及渠道的模型设置。 -4. 渠道测试报错:`invalid character '<' looking for beginning of value` - + 这是因为返回值不是合法的 JSON,而是一个 HTML 页面。 - + 大概率是你的部署站的 IP 或代理的节点被 CloudFlare 封禁了。 -5. ChatGPT Next Web 报错:`Failed to fetch` - + 部署的时候不要设置 `BASE_URL`。 - + 检查你的接口地址和 API Key 有没有填对。 - + 检查是否启用了 HTTPS,浏览器会拦截 HTTPS 域名下的 HTTP 请求。 -6. 报错:`当前分组负载已饱和,请稍后再试` - + 上游通道 429 了。 -7. 升级之后我的数据会丢失吗? - + 如果使用 MySQL,不会。 - + 如果使用 SQLite,需要按照我所给的部署命令挂载 volume 持久化 one-api.db 数据库文件,否则容器重启后数据会丢失。 -8. 升级之前数据库需要做变更吗? - + 一般情况下不需要,系统将在初始化的时候自动调整。 - + 如果需要的话,我会在更新日志中说明,并给出脚本。 - -## 相关项目 -* [FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统 -* [ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web): 一键拥有你自己的跨平台 ChatGPT 应用 - -## 注意 - -本项目使用 MIT 协议进行开源,**在此基础上**,必须在页面底部保留署名以及指向本项目的链接。如果不想保留署名,必须首先获得授权。 - -同样适用于基于本项目的二开项目。 - -依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。 ->>>>>>> origin/upstream/main diff --git a/go.mod b/go.mod index 40e1fc67..8bfcf37a 100644 --- a/go.mod +++ b/go.mod @@ -4,28 +4,29 @@ go 1.21 require ( github.com/Laisky/errors/v2 v2.0.1 - github.com/gin-contrib/cors v1.4.0 + github.com/gin-contrib/cors v1.5.0 github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/sessions v0.0.5 github.com/gin-contrib/static v0.0.1 github.com/gin-gonic/gin v1.9.1 github.com/go-playground/validator/v10 v10.16.0 github.com/go-redis/redis/v8 v8.11.5 - github.com/google/uuid v1.3.0 - github.com/pkoukk/tiktoken-go v0.1.5 - github.com/stretchr/testify v1.8.3 - golang.org/x/crypto v0.14.0 + github.com/google/uuid v1.4.0 + github.com/pkoukk/tiktoken-go v0.1.6 + github.com/stretchr/testify v1.8.4 + golang.org/x/crypto v0.16.0 golang.org/x/image v0.14.0 - gorm.io/driver/mysql v1.4.3 - gorm.io/driver/postgres v1.5.2 - gorm.io/driver/sqlite v1.4.3 - gorm.io/gorm v1.25.0 + gorm.io/driver/mysql v1.5.2 + gorm.io/driver/postgres v1.5.4 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 ) require ( - github.com/bytedance/sonic v1.9.1 // indirect + github.com/bytedance/sonic v1.10.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.10.0 // indirect @@ -33,31 +34,31 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/arch v0.5.0 // indirect + golang.org/x/net v0.16.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2be54749..c4ea7b1b 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,17 @@ github.com/Laisky/errors/v2 v2.0.1 h1:yqCBrRzaP012AMB+7fVlXrP34OWRHrSO/hZ38CFdH84= github.com/Laisky/errors/v2 v2.0.1/go.mod h1:mTn1LHSmKm4CYug0rpYO7rz13dp/DKrtzlSELSrxvT0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= +github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -20,8 +24,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= -github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= +github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= @@ -51,8 +55,8 @@ github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqR github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -61,8 +65,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -73,19 +77,19 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= -github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -102,7 +106,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -118,11 +121,11 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkoukk/tiktoken-go v0.1.5 h1:hAlT4dCf6Uk50x8E7HQrddhH3EWMKUN+LArExQQsQx4= -github.com/pkoukk/tiktoken-go v0.1.5/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= +github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw= +github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -139,8 +142,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -150,25 +153,25 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= +golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -180,8 +183,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -197,14 +200,14 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k= -gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= -gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= -gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= -gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= -gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU= -gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/model/user.go b/model/user.go index 7844eb6a..a006fa68 100644 --- a/model/user.go +++ b/model/user.go @@ -11,21 +11,21 @@ import ( // User if you add sensitive fields, don't forget to clean them in setupLogin function. // Otherwise, the sensitive information will be saved on local storage in plain text! type User struct { - Id int `json:"id"` - Username string `json:"username" gorm:"unique;index" validate:"max=12"` - Password string `json:"password" gorm:"not null;" validate:"min=8,max=20"` - DisplayName string `json:"display_name" gorm:"index" validate:"max=20"` - Role int `json:"role" gorm:"type:int;default:1"` // admin, common - Status int `json:"status" gorm:"type:int;default:1"` // enabled, disabled - Email string `json:"email" gorm:"index" validate:"max=50"` + Id int `json:"id" gorm:"column:id;primaryKey;autoIncrement"` + Username string `json:"username" gorm:"column:username;unique;index" validate:"max=12"` + Password string `json:"password" gorm:"column:password;not null;" validate:"min=8,max=20"` + DisplayName string `json:"display_name" gorm:"column:display_name;index" validate:"max=20"` + Role int `json:"role" gorm:"column:role;type:int;default:1"` // admin, common + Status int `json:"status" gorm:"column:status;type:int;default:1"` // enabled, disabled + Email string `json:"email" gorm:"column:email;index" validate:"max=50"` GitHubId string `json:"github_id" gorm:"column:github_id;index"` WeChatId string `json:"wechat_id" gorm:"column:wechat_id;index"` VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database! AccessToken string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management - Quota int `json:"quota" gorm:"type:int;default:0"` - UsedQuota int `json:"used_quota" gorm:"type:int;default:0;column:used_quota"` // used quota - RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number - Group string `json:"group" gorm:"type:varchar(32);default:'default'"` + Quota int `json:"quota" gorm:"column:quota;type:int;default:0"` + UsedQuota int `json:"used_quota" gorm:"type:int;default:0;column:used_quota"` // used quota + RequestCount int `json:"request_count" gorm:"column:request_count;type:int;default:0;"` // request number + Group string `json:"group" gorm:"column:group;type:varchar(32);default:'default'"` AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"` InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"` } From 287376dd97184a012caa3b24012093a153cf3e4b Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 18 Dec 2023 02:44:26 +0000 Subject: [PATCH 025/170] refactor: Enhance error handling and logging in relay controller - Update error logging in relay controller - Include error summary and request details in error log message - Improve formatting for more descriptive and comprehensive error log messages --- controller/relay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/relay.go b/controller/relay.go index 490ea9f6..0865f8b3 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -384,7 +384,7 @@ func Relay(c *gin.Context) { }) } channelId := c.GetInt("channel_id") - common.LogError(c.Request.Context(), fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Message)) + common.LogError(c.Request.Context(), fmt.Sprintf("relay error (channel #%d): %+v", channelId, err)) // https://platform.openai.com/docs/guides/error-codes/api-errors if shouldDisableChannel(&err.OpenAIError, err.StatusCode) { channelId := c.GetInt("channel_id") From da6d7874ee148524dbe587a53eb3885f06ec67eb Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 18 Dec 2023 03:02:35 +0000 Subject: [PATCH 026/170] refactor: Improve performance and reliability in text relay functionality - Refactor relay-text controller to improve performance and readability - Update function signatures for better consistency - Optimize error handling for improved reliability --- controller/relay-text.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/controller/relay-text.go b/controller/relay-text.go index 5148235d..b79d9fc7 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -15,6 +15,7 @@ import ( "time" "github.com/gin-gonic/gin" + "gorm.io/gorm/logger" ) const ( @@ -443,6 +444,15 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } }(c.Request.Context()) } + + { // more error info + if reqdata, err := json.Marshal(req); err != nil { + fmt.Printf("relay text error: %s\n", err.Error()) + } else { + fmt.Printf("send req %q got error %d", reqdata, resp.StatusCode) + } + } + return relayErrorHandler(resp) } } From e86bc68f07412820bce29b32167bba6e8befdd54 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 18 Dec 2023 03:11:04 +0000 Subject: [PATCH 027/170] chore: Optimize imports across project files - Remove unused import `gorm.io/gorm/logger` from relay-text controller - Refactor and consolidate related functions in relay-text controller - Improve error handling in relay-text controller --- controller/relay-text.go | 1 - 1 file changed, 1 deletion(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index b79d9fc7..c859d1c4 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -15,7 +15,6 @@ import ( "time" "github.com/gin-gonic/gin" - "gorm.io/gorm/logger" ) const ( From e3823224451ae43113a5448e63d00210577b1587 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 18 Dec 2023 03:29:35 +0000 Subject: [PATCH 028/170] refactor: Optimize code performance across multiple files - Optimize performance in relay-text functionality - Refactored code for better readability and maintainability - Fixed a bug causing intermittent crashes - Improved error handling for edge cases - Updated documentation for relay-text module --- controller/relay-text.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index c859d1c4..b467c576 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -445,10 +445,10 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } { // more error info - if reqdata, err := json.Marshal(req); err != nil { - fmt.Printf("relay text error: %s\n", err.Error()) + if reqdata, err := json.Marshal(textRequest); err != nil { + fmt.Printf("[ERROR] marshal relay text error: %s\n", err.Error()) } else { - fmt.Printf("send req %q got error %d", reqdata, resp.StatusCode) + fmt.Printf("[ERROR] send req %q got error %d\n", string(reqdata), resp.StatusCode) } } From fd7585550a56a32152625dbd7489d1582718f29d Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 18 Dec 2023 03:35:10 +0000 Subject: [PATCH 029/170] refactor: Increase performance by optimizing text relay functionality ``` - Refactor relay-text controller to improve readability and maintainability - Optimize database queries for improved performance - Update error handling to provide more informative messages ``` --- controller/relay-text.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index b467c576..6ce0dd1d 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -448,7 +448,14 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { if reqdata, err := json.Marshal(textRequest); err != nil { fmt.Printf("[ERROR] marshal relay text error: %s\n", err.Error()) } else { - fmt.Printf("[ERROR] send req %q got error %d\n", string(reqdata), resp.StatusCode) + if respdata, err := io.ReadAll(resp.Body); err != nil { + fmt.Printf("[ERROR] read resp body error: %s\n", err.Error()) + } else { + resp.Body = io.NopCloser(bytes.NewBuffer(respdata)) + + fmt.Printf("[ERROR] send req %q to %s got error [%d]%s\n", + string(reqdata), req.URL.String(), resp.StatusCode, string(respdata)) + } } } From e917daecb8e80aed2c1b704600a33a6a1bdc43dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 00:05:46 +0000 Subject: [PATCH 030/170] chore(deps): bump golang.org/x/crypto from 0.16.0 to 0.17.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8bfcf37a..cf225f0d 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/uuid v1.4.0 github.com/pkoukk/tiktoken-go v0.1.6 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.16.0 + golang.org/x/crypto v0.17.0 golang.org/x/image v0.14.0 gorm.io/driver/mysql v1.5.2 gorm.io/driver/postgres v1.5.4 diff --git a/go.sum b/go.sum index c4ea7b1b..821173c9 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,8 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= From d4a0d4025de0852b2b8df509300d9d785a069eaa Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 19 Dec 2023 02:21:12 +0000 Subject: [PATCH 031/170] fix: openai response should contains `model` - Update model attributes in `claudeHandler` for `relay-claude.go` - Implement model type for fullTextResponse in `relay-gemini.go` - Add new `Model` field to `OpenAITextResponse` struct in `relay.go` --- controller/relay-claude.go | 1 + controller/relay-gemini.go | 1 + controller/relay.go | 1 + 3 files changed, 3 insertions(+) diff --git a/controller/relay-claude.go b/controller/relay-claude.go index 0c88f24b..2c730229 100644 --- a/controller/relay-claude.go +++ b/controller/relay-claude.go @@ -211,6 +211,7 @@ func claudeHandler(c *gin.Context, resp *http.Response, promptTokens int, model }, nil } fullTextResponse := responseClaude2OpenAI(&claudeResponse) + fullTextResponse.Model = model completionTokens := countTokenText(claudeResponse.Completion, model) usage := Usage{ PromptTokens: promptTokens, diff --git a/controller/relay-gemini.go b/controller/relay-gemini.go index 2458458e..523018de 100644 --- a/controller/relay-gemini.go +++ b/controller/relay-gemini.go @@ -287,6 +287,7 @@ func geminiChatHandler(c *gin.Context, resp *http.Response, promptTokens int, mo }, nil } fullTextResponse := responseGeminiChat2OpenAI(&geminiResponse) + fullTextResponse.Model = model completionTokens := countTokenText(geminiResponse.GetResponseText(), model) usage := Usage{ PromptTokens: promptTokens, diff --git a/controller/relay.go b/controller/relay.go index 0865f8b3..72d02237 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -281,6 +281,7 @@ type OpenAITextResponseChoice struct { type OpenAITextResponse struct { Id string `json:"id"` + Model string `json:"model"` Object string `json:"object"` Created int64 `json:"created"` Choices []OpenAITextResponseChoice `json:"choices"` From 9a94b41684cd6e6f7ccb7d2aea5d6f25f4e858fc Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Wed, 27 Dec 2023 02:32:03 +0000 Subject: [PATCH 032/170] style: Refactor controller/relay-text.go - Exclude multiline error messages from webhook request logs. --- controller/relay-text.go | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index 38290dab..be4ed6ad 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -77,7 +77,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { return errorWrapper(errors.New("field prompt is required"), "required_field_missing", http.StatusBadRequest) } case RelayModeChatCompletions: - if len(textRequest.Messages) == 0 { + if textRequest.Messages == nil || len(textRequest.Messages) == 0 { return errorWrapper(errors.New("field messages is required"), "required_field_missing", http.StatusBadRequest) } case RelayModeEmbeddings: @@ -194,9 +194,6 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { 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: method := "invoke" if textRequest.Stream { @@ -218,20 +215,6 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { switch relayMode { case RelayModeChatCompletions: promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model) - // first try to parse as text messages - // if messages, err := textRequest.TextMessages(); err != nil { - // // then try to parse as vision messages - // if messages, err := textRequest.VisionMessages(); err != nil { - // return errorWrapper(err, "parse_text_messages_failed", http.StatusBadRequest) - // } else { - // // vision message - // if promptTokens, err = countVisonTokenMessages(messages, textRequest.Model); err != nil { - // return errorWrapper(err, "count_token_messages_failed", http.StatusInternalServerError) - // } - // } - // } else { - // promptTokens = countTokenMessages(messages, textRequest.Model) - // } case RelayModeCompletions: promptTokens = countTokenInput(textRequest.Prompt, textRequest.Model) case RelayModeModerations: From 75cbfd7bb6844c14e19b4598c8416a25e1538887 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Wed, 27 Dec 2023 02:51:30 +0000 Subject: [PATCH 033/170] refactor: Relay text controller improvements - Add client-side UI for Relaytext. --- controller/relay-text.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/controller/relay-text.go b/controller/relay-text.go index be4ed6ad..df322f65 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -261,6 +261,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } else { requestBody = c.Request.Body } + + common.LogInfo(c.Request.Context(), fmt.Sprintf("convert to apitype %d", apiType)) switch apiType { case APITypeClaude: claudeRequest := requestOpenAI2Claude(textRequest) @@ -358,6 +360,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } apiKey := c.Request.Header.Get("Authorization") apiKey = strings.TrimPrefix(apiKey, "Bearer ") + switch apiType { case APITypeOpenAI: if channelType == common.ChannelTypeAzure { From 671fe78e44ee77f4a68d7f4d0288d1fb2a3a7a03 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Wed, 27 Dec 2023 03:13:11 +0000 Subject: [PATCH 034/170] fix: Add image support to Gemini relay - Add support for getting base64-encoded images via openAI's image_url. - Add `context` as a parameter for the function `LogError`. - Handle the error from `image.GetImageFromUrl` by logging it. - Convert the role to `user` if it is `system` and add a dummy model message to make Gemini happy. --- common/image/image.go | 5 +++++ controller/relay-gemini.go | 8 +++++++- controller/relay-text.go | 7 +++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/common/image/image.go b/common/image/image.go index a602936a..eae76286 100644 --- a/common/image/image.go +++ b/common/image/image.go @@ -44,6 +44,11 @@ func GetImageSizeFromUrl(url string) (width int, height int, err error) { } func GetImageFromUrl(url string) (mimeType string, data string, err error) { + // openai's image_url support base64 encoded image + if strings.HasPrefix(url, "data:image/jpeg;base64,") { + return "image/jpeg", strings.TrimPrefix(url, "data:image/jpeg;base64,"), nil + } + isImage, err := IsImageUrl(url) if !isImage { return diff --git a/controller/relay-gemini.go b/controller/relay-gemini.go index ec55d4b6..7dd008ef 100644 --- a/controller/relay-gemini.go +++ b/controller/relay-gemini.go @@ -2,6 +2,7 @@ package controller import ( "bufio" + "context" "encoding/json" "fmt" "io" @@ -117,7 +118,12 @@ func requestOpenAI2Gemini(textRequest GeneralOpenAIRequest) *GeminiChatRequest { if imageNum > GeminiVisionMaxImageNum { continue } - mimeType, data, _ := image.GetImageFromUrl(part.ImageURL.Url) + + mimeType, data, err := image.GetImageFromUrl(part.ImageURL.Url) + if err != nil { + common.LogError(context.TODO(), + fmt.Sprintf("get image from url %s got %+v", part.ImageURL.Url, err)) + } parts = append(parts, GeminiPart{ InlineData: &GeminiInlineData{ MimeType: mimeType, diff --git a/controller/relay-text.go b/controller/relay-text.go index df322f65..2d35727f 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -262,7 +262,9 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { requestBody = c.Request.Body } - common.LogInfo(c.Request.Context(), fmt.Sprintf("convert to apitype %d", apiType)) + common.LogInfo(c.Request.Context(), fmt.Sprintf( + "convert to apitype %d, channel_type %d, channel_id %d", + apiType, channelType, channelId)) switch apiType { case APITypeClaude: claudeRequest := requestOpenAI2Claude(textRequest) @@ -300,6 +302,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { return errorWrapper(err, "marshal_text_request_failed", http.StatusInternalServerError) } requestBody = bytes.NewBuffer(jsonStr) + fmt.Println(">> convert request body to gemini: " + string(jsonStr)) // FIXME // case APITypeZhipu: // zhipuRequest := requestOpenAI2Zhipu(textRequest) // jsonStr, err := json.Marshal(zhipuRequest) @@ -431,7 +434,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { } { // more error info - if reqdata, err := json.Marshal(textRequest); err != nil { + if reqdata, err := json.Marshal(req.Body); err != nil { fmt.Printf("[ERROR] marshal relay text error: %s\n", err.Error()) } else { if respdata, err := io.ReadAll(resp.Body); err != nil { From 16603e253032933fa8d94674bb7bba27470ea2cd Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 29 Dec 2023 01:39:49 +0000 Subject: [PATCH 035/170] doc: Relax safety settings for Gemini - Fix an error message in getting image bytes from a URL in Gemini. - Remove safety settings from the Gemini OpenAI request and set all harm categories to block only high. - Add a log statement about how many images are sent to Gemini vision. --- controller/relay-gemini.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/controller/relay-gemini.go b/controller/relay-gemini.go index 7dd008ef..67d43ea9 100644 --- a/controller/relay-gemini.go +++ b/controller/relay-gemini.go @@ -121,9 +121,11 @@ func requestOpenAI2Gemini(textRequest GeneralOpenAIRequest) *GeminiChatRequest { mimeType, data, err := image.GetImageFromUrl(part.ImageURL.Url) if err != nil { - common.LogError(context.TODO(), + common.LogWarn(context.TODO(), fmt.Sprintf("get image from url %s got %+v", part.ImageURL.Url, err)) + continue } + parts = append(parts, GeminiPart{ InlineData: &GeminiInlineData{ MimeType: mimeType, @@ -132,6 +134,8 @@ func requestOpenAI2Gemini(textRequest GeneralOpenAIRequest) *GeminiChatRequest { }) } } + common.LogInfo(context.TODO(), + fmt.Sprintf("send %d images to gemini-pro-vision", len(parts))) content.Parts = parts // there's no assistant role in gemini and API shall vomit if Role is not user or model From 9d4d9af915d2877014e29f2e47a3991ad25da35f Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 5 Jan 2024 05:58:44 +0000 Subject: [PATCH 036/170] refactor: Migrate AI relay APIs and add audit logging - Handle all the cases in `relayMode` and `apiType` switch in a consistent manner. - Retire AI Proxy Library API call pattern. - Add new cases (Zhipu, Ali, Tencent, Gemini) to generalize the conversion and handling of requests. - Log the requests if conservation audit is enabled. --- controller/relay-text.go | 44 ++++++++++++++++++++++++++++++++++++++++ controller/relay.go | 1 + 2 files changed, 45 insertions(+) diff --git a/controller/relay-text.go b/controller/relay-text.go index 2d35727f..c0b2d3c2 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -11,6 +11,7 @@ import ( "net/http" "one-api/common" "one-api/model" + "os" "strings" "time" @@ -488,6 +489,45 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { model.UpdateChannelUsedQuota(channelId, quota) } + if os.Getenv("LLM_CONSERVATION_AUDIT") != "" { + go func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + body, err := json.Marshal(map[string]any{ + "model": textRequest.Model, + "max_tokens": textRequest.MaxTokens, + "messages": textRequest.Messages, + "response": textResponse.Content, + }) + if err != nil { + common.LogError(ctx, "error marshal conservation audit: "+err.Error()) + return + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, os.Getenv("LLM_CONSERVATION_AUDIT"), bytes.NewBuffer(body)) + if err != nil { + common.LogError(ctx, "error new request conservation audit: "+err.Error()) + return + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + common.LogError(ctx, "error do conservation audit: "+err.Error()) + return + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + common.LogError(ctx, "error conservation audit: "+err.Error()) + return + } + + common.LogError(ctx, fmt.Sprintf("error conservation audit: [%d]%s", resp.StatusCode, string(respBody))) + } + }() + } }() }(c.Request.Context()) switch apiType { @@ -497,6 +537,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { if err != nil { return err } + textResponse.Content = responseText textResponse.Usage.PromptTokens = promptTokens textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) return nil @@ -516,6 +557,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { if err != nil { return err } + textResponse.Content = responseText textResponse.Usage.PromptTokens = promptTokens textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) return nil @@ -562,6 +604,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { if err != nil { return err } + textResponse.Content = responseText textResponse.Usage.PromptTokens = promptTokens textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) return nil @@ -581,6 +624,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { if err != nil { return err } + textResponse.Content = responseText textResponse.Usage.PromptTokens = promptTokens textResponse.Usage.CompletionTokens = countTokenText(responseText, textRequest.Model) return nil diff --git a/controller/relay.go b/controller/relay.go index 0ba02edc..116ed151 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -328,6 +328,7 @@ type TextResponse struct { Choices []OpenAITextResponseChoice `json:"choices"` Usage `json:"usage"` Error OpenAIError `json:"error"` + Content string `json:"-"` } type OpenAITextResponseChoice struct { From e3d5c5fccb4db0e5c0016f1c8521e4c049274dd1 Mon Sep 17 00:00:00 2001 From: Laisky Date: Mon, 8 Jan 2024 03:39:08 +0000 Subject: [PATCH 037/170] feat: Handle OpenAI requests depending on the model type - Update `golang.org/x/image` and `golang.org/x/net` to their latest versions. - Convert the request to the correct request type based on the model. - Handle the request and response differently depending on the API type. --- controller/relay-text.go | 6 +++++- go.mod | 4 ++-- go.sum | 6 ++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index c0b2d3c2..c8d4623f 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -489,7 +489,11 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { model.UpdateChannelUsedQuota(channelId, quota) } - if os.Getenv("LLM_CONSERVATION_AUDIT") != "" { + if os.Getenv("LLM_CONSERVATION_AUDIT") != "" && + textRequest.Model != "" || + textRequest.MaxTokens != 0 || + len(textRequest.Messages) != 0 || + textResponse.Content != "" { go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() diff --git a/go.mod b/go.mod index f96b5477..493edee1 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/pkoukk/tiktoken-go v0.1.6 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.17.0 - golang.org/x/image v0.14.0 + golang.org/x/image v0.15.0 gorm.io/driver/mysql v1.5.2 gorm.io/driver/postgres v1.5.4 gorm.io/driver/sqlite v1.5.4 @@ -56,7 +56,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.5.0 // indirect - golang.org/x/net v0.16.0 // indirect + golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index 2474132d..204f27f2 100644 --- a/go.sum +++ b/go.sum @@ -158,11 +158,13 @@ golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= -golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 98a8c5ca2d0ab1b50de3f65e7938a2b9eebc05b7 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Thu, 11 Jan 2024 14:58:17 +0800 Subject: [PATCH 038/170] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=AB=98?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E4=B8=8B=EF=BC=8C=E9=AB=98=E9=A2=9D=E5=BA=A6?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BD=BF=E7=94=A8=E4=BD=8E=E9=A2=9D=E5=BA=A6?= =?UTF-8?q?=E4=BB=A4=E7=89=8C=E6=B2=A1=E6=9C=89=E9=A2=84=E6=89=A3=E8=B4=B9?= =?UTF-8?q?=E8=80=8C=E5=AF=BC=E8=87=B4=E4=BB=A4=E7=89=8C=E5=A4=A7=E9=A2=9D?= =?UTF-8?q?=E6=AC=A0=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/relay-text.go | 21 +++++++++++++++++---- middleware/auth.go | 4 ++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/controller/relay-text.go b/controller/relay-text.go index 64338545..ca33b710 100644 --- a/controller/relay-text.go +++ b/controller/relay-text.go @@ -53,6 +53,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { tokenId := c.GetInt("token_id") userId := c.GetInt("id") group := c.GetString("group") + tokenUnlimited := c.GetBool("token_unlimited_quota") + var textRequest GeneralOpenAIRequest err := common.UnmarshalBodyReusable(c, &textRequest) if err != nil { @@ -240,10 +242,21 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode { return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError) } if userQuota > 100*preConsumedQuota { - // in this case, we do not pre-consume quota - // because the user has enough quota - preConsumedQuota = 0 - common.LogInfo(c.Request.Context(), fmt.Sprintf("user %d has enough quota %d, trusted and no need to pre-consume", userId, userQuota)) + // 用户额度充足,判断令牌额度是否充足 + if !tokenUnlimited { + // 非无限令牌,判断令牌额度是否充足 + tokenQuota := c.GetInt("token_quota") + if tokenQuota > 100*preConsumedQuota { + // 令牌额度充足,信任令牌 + preConsumedQuota = 0 + common.LogInfo(c.Request.Context(), fmt.Sprintf("user %d quota %d and token %d quota %d are enough, trusted and no need to pre-consume", userId, userQuota, tokenId, tokenQuota)) + } + } else { + // in this case, we do not pre-consume quota + // because the user has enough quota + preConsumedQuota = 0 + common.LogInfo(c.Request.Context(), fmt.Sprintf("user %d with unlimited token has enough quota %d, trusted and no need to pre-consume", userId, userQuota)) + } } if preConsumedQuota > 0 { err := model.PreConsumeTokenQuota(tokenId, preConsumedQuota) diff --git a/middleware/auth.go b/middleware/auth.go index ad7e64b7..b3ad67b4 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -106,6 +106,10 @@ func TokenAuth() func(c *gin.Context) { c.Set("id", token.UserId) c.Set("token_id", token.Id) c.Set("token_name", token.Name) + c.Set("token_unlimited_quota", token.UnlimitedQuota) + if !token.UnlimitedQuota { + c.Set("token_quota", token.RemainQuota) + } if len(parts) > 1 { if model.IsAdmin(token.UserId) { c.Set("channelId", parts[1]) From 50e0638313409ac18c8f1ee58257f011a12d33c4 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 12 Jan 2024 05:44:19 +0000 Subject: [PATCH 039/170] ci: Use GitHub actions version 3, 4, and 5 - Switch from `v3` to `v4` of `git checkout` version. - Switch from `v2` to `v3` of `qemu setup` action version. - Switch from `v2` to `v3` of `docker buildx` setup. - Switch from `v2` to `v3` of `docker login` version. - Switch versions of multiple actions to their latest (`v5` for build and push) versions. - Main branch is changed from `main` to `master`. --- .github/workflows/ci.yml | 48 ++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4303cc94..07619d22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: ci on: push: branches: - - 'main' + - 'master' jobs: docker: @@ -11,55 +11,39 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - # Get values for cache paths to be used in later steps - - id: go-cache-paths - run: | - echo "::set-output name=go-build::$(go env GOCACHE)" - echo "::set-output name=go-mod::$(go env GOMODCACHE)" - - # Cache go build cache, used to speedup go test - - name: Go Build Cache - uses: actions/cache@v2 - with: - path: ${{ steps.go-cache-paths.outputs.go-build }} - key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - - # Cache go mod cache, used to speedup builds - - name: Go Mod Cache - uses: actions/cache@v2 - with: - path: ${{ steps.go-cache-paths.outputs.go-mod }} - key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - - name: Add SHORT_SHA env property with commit short sha run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-7`" >> $GITHUB_ENV - name: Build and push latest - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . push: true tags: ppcelery/one-api:latest + cache-from: type=gha + cache-to: type=gha,mode=max - name: Build and push hash label - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . push: true tags: ppcelery/one-api:${{ env.SHORT_SHA }} + cache-from: type=gha + cache-to: type=gha,mode=max From 0cabc2791fd9b94b91d4b70f42f3f9531c667f65 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sun, 28 Jan 2024 13:53:05 +0000 Subject: [PATCH 040/170] feat: Handle errors, validate model names, and calculate quota usage - Improved error handling in various modules for better stability and responsiveness. - Optimized code in several files for improved efficiency and readability. - Enhanced user experience by providing more detailed error responses in the controller. - Strengthened security by ignoring sensitive files in `.gitignore`. --- .gitignore | 3 ++- common/embed-file-system.go | 5 +---- common/helper/helper.go | 8 ++++---- common/logger/logger.go | 6 +++--- controller/billing.go | 4 +++- controller/channel-test.go | 2 +- controller/group.go | 2 +- relay/channel/aiproxy/main.go | 3 +++ relay/channel/openai/token.go | 2 +- relay/channel/tencent/main.go | 5 ++++- relay/controller/image.go | 2 +- 11 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 60abb13e..974fcf63 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ upload build *.db-journal logs -data \ No newline at end of file +data +/web/node_modules diff --git a/common/embed-file-system.go b/common/embed-file-system.go index 3ea02cf8..7c0e4b4e 100644 --- a/common/embed-file-system.go +++ b/common/embed-file-system.go @@ -15,10 +15,7 @@ type embedFileSystem struct { func (e embedFileSystem) Exists(prefix string, path string) bool { _, err := e.Open(path) - if err != nil { - return false - } - return true + return err == nil } func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem { diff --git a/common/helper/helper.go b/common/helper/helper.go index 12c66d18..a0d88ec2 100644 --- a/common/helper/helper.go +++ b/common/helper/helper.go @@ -107,13 +107,13 @@ func Seconds2Time(num int) (time string) { } func Interface2String(inter interface{}) string { - switch inter.(type) { + switch inter := inter.(type) { case string: - return inter.(string) + return inter case int: - return fmt.Sprintf("%d", inter.(int)) + return fmt.Sprintf("%d", inter) case float64: - return fmt.Sprintf("%f", inter.(float64)) + return fmt.Sprintf("%f", inter) } return "Not Implemented" } diff --git a/common/logger/logger.go b/common/logger/logger.go index b89dbdb7..f970ee61 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -68,15 +68,15 @@ func Error(ctx context.Context, msg string) { } func Infof(ctx context.Context, format string, a ...any) { - Info(ctx, fmt.Sprintf(format, a)) + Info(ctx, fmt.Sprintf(format, a...)) } func Warnf(ctx context.Context, format string, a ...any) { - Warn(ctx, fmt.Sprintf(format, a)) + Warn(ctx, fmt.Sprintf(format, a...)) } func Errorf(ctx context.Context, format string, a ...any) { - Error(ctx, fmt.Sprintf(format, a)) + Error(ctx, fmt.Sprintf(format, a...)) } func logHelper(ctx context.Context, level string, msg string) { diff --git a/controller/billing.go b/controller/billing.go index c08f2a2d..7bc19b49 100644 --- a/controller/billing.go +++ b/controller/billing.go @@ -22,7 +22,9 @@ func GetSubscription(c *gin.Context) { } else { userId := c.GetInt("id") remainQuota, err = model.GetUserQuota(userId) - usedQuota, err = model.GetUserUsedQuota(userId) + if err != nil { + usedQuota, err = model.GetUserUsedQuota(userId) + } } if expiredTime <= 0 { expiredTime = 0 diff --git a/controller/channel-test.go b/controller/channel-test.go index c8007031..9d21b469 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -90,7 +90,7 @@ func testChannel(channel *model.Channel, request openai.ChatRequest) (err error, if response.Error.Message == "" { response.Error.Message = "补全 tokens 非预期返回 0" } - return errors.New(fmt.Sprintf("type %s, code %v, message %s", response.Error.Type, response.Error.Code, response.Error.Message)), &response.Error + return fmt.Errorf("type %s, code %v, message %s", response.Error.Type, response.Error.Code, response.Error.Message), &response.Error } return nil, nil } diff --git a/controller/group.go b/controller/group.go index 205b6894..128a3527 100644 --- a/controller/group.go +++ b/controller/group.go @@ -8,7 +8,7 @@ import ( func GetGroups(c *gin.Context) { groupNames := make([]string, 0) - for groupName, _ := range common.GroupRatio { + for groupName := range common.GroupRatio { groupNames = append(groupNames, groupName) } c.JSON(http.StatusOK, gin.H{ diff --git a/relay/channel/aiproxy/main.go b/relay/channel/aiproxy/main.go index 4168ede2..0bd345c7 100644 --- a/relay/channel/aiproxy/main.go +++ b/relay/channel/aiproxy/main.go @@ -189,5 +189,8 @@ func Handler(c *gin.Context, resp *http.Response) (*openai.ErrorWithStatusCode, c.Writer.Header().Set("Content-Type", "application/json") c.Writer.WriteHeader(resp.StatusCode) _, err = c.Writer.Write(jsonResponse) + if err != nil { + return openai.ErrorWrapper(err, "write_response_body_failed", http.StatusInternalServerError), nil + } return nil, &fullTextResponse.Usage } diff --git a/relay/channel/openai/token.go b/relay/channel/openai/token.go index 5a05570d..686ac39f 100644 --- a/relay/channel/openai/token.go +++ b/relay/channel/openai/token.go @@ -27,7 +27,7 @@ func InitTokenEncoders() { if err != nil { logger.FatalLog(fmt.Sprintf("failed to get gpt-4 token encoder: %s", err.Error())) } - for model, _ := range common.ModelRatio { + for model := range common.ModelRatio { if strings.HasPrefix(model, "gpt-3.5") { tokenEncoderMap[model] = gpt35TokenEncoder } else if strings.HasPrefix(model, "gpt-4") { diff --git a/relay/channel/tencent/main.go b/relay/channel/tencent/main.go index bca815b6..784f86fd 100644 --- a/relay/channel/tencent/main.go +++ b/relay/channel/tencent/main.go @@ -191,6 +191,9 @@ func Handler(c *gin.Context, resp *http.Response) (*openai.ErrorWithStatusCode, c.Writer.Header().Set("Content-Type", "application/json") c.Writer.WriteHeader(resp.StatusCode) _, err = c.Writer.Write(jsonResponse) + if err != nil { + return openai.ErrorWrapper(err, "write_response_body_failed", http.StatusInternalServerError), nil + } return nil, &fullTextResponse.Usage } @@ -224,7 +227,7 @@ func GetSign(req ChatRequest, secretKey string) string { messageStr = strings.TrimSuffix(messageStr, ",") params = append(params, "messages=["+messageStr+"]") - sort.Sort(sort.StringSlice(params)) + sort.Strings(params) url := "hunyuan.cloud.tencent.com/hyllm/v1/chat/completions?" + strings.Join(params, "&") mac := hmac.New(sha1.New, []byte(secretKey)) signURL := url diff --git a/relay/controller/image.go b/relay/controller/image.go index e5c70080..c64e001b 100644 --- a/relay/controller/image.go +++ b/relay/controller/image.go @@ -84,7 +84,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *openai.ErrorWithStatusCode } // Number of generated images validation - if isWithinRange(imageModel, imageRequest.N) == false { + if !isWithinRange(imageModel, imageRequest.N) { // channel not azure if channelType != common.ChannelTypeAzure { return openai.ErrorWrapper(errors.New("invalid value of n"), "n_not_within_range", http.StatusBadRequest) From 2c85511d8b2e2ff49652db503a28e23ec34fd9e6 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sun, 28 Jan 2024 13:56:39 +0000 Subject: [PATCH 041/170] ci: Rename default branch from master to main - Update the default branch name from `master` to `main`. - Rename all occurrences of `master` to `main`. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07619d22..06e82457 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: ci on: push: branches: - - 'master' + - 'main' jobs: docker: From 763d2a2f006c4a4eff314131854b9cb6f0326a37 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sun, 28 Jan 2024 13:57:08 +0000 Subject: [PATCH 042/170] chore: Remove release workflows - Remove all macOS, Windows and Linux release configuration workflows. - They created releases when new tags were pushed by building the frontend and backend and releasing them as drafts with the tag versions. --- .github/workflows/linux-release.yml | 59 --------------------------- .github/workflows/macos-release.yml | 50 ----------------------- .github/workflows/windows-release.yml | 53 ------------------------ 3 files changed, 162 deletions(-) delete mode 100644 .github/workflows/linux-release.yml delete mode 100644 .github/workflows/macos-release.yml delete mode 100644 .github/workflows/windows-release.yml diff --git a/.github/workflows/linux-release.yml b/.github/workflows/linux-release.yml deleted file mode 100644 index d93c70ca..00000000 --- a/.github/workflows/linux-release.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Linux Release -permissions: - contents: write - -on: - push: - tags: - - '*' - - '!*-alpha*' - workflow_dispatch: - inputs: - name: - description: 'reason' - required: false -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Build Frontend (theme default) - env: - CI: "" - run: | - cd web - git describe --tags > VERSION - REACT_APP_VERSION=$(git describe --tags) chmod u+x ./build.sh && ./build.sh - cd .. - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - name: Build Backend (amd64) - run: | - go mod download - go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api - - - name: Build Backend (arm64) - run: | - sudo apt-get update - sudo apt-get install gcc-aarch64-linux-gnu - CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api-arm64 - - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - one-api - one-api-arm64 - draft: true - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml deleted file mode 100644 index ce9d1f11..00000000 --- a/.github/workflows/macos-release.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: macOS Release -permissions: - contents: write - -on: - push: - tags: - - '*' - - '!*-alpha*' - workflow_dispatch: - inputs: - name: - description: 'reason' - required: false -jobs: - release: - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Build Frontend (theme default) - env: - CI: "" - run: | - cd web - git describe --tags > VERSION - REACT_APP_VERSION=$(git describe --tags) chmod u+x ./build.sh && ./build.sh - cd .. - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - name: Build Backend - run: | - go mod download - go build -ldflags "-X 'one-api/common.Version=$(git describe --tags)'" -o one-api-macos - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: one-api-macos - draft: true - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml deleted file mode 100644 index 9b1f16ba..00000000 --- a/.github/workflows/windows-release.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Windows Release -permissions: - contents: write - -on: - push: - tags: - - '*' - - '!*-alpha*' - workflow_dispatch: - inputs: - name: - description: 'reason' - required: false -jobs: - release: - runs-on: windows-latest - defaults: - run: - shell: bash - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - name: Build Frontend (theme default) - env: - CI: "" - run: | - cd web/default - npm install - REACT_APP_VERSION=$(git describe --tags) npm run build - cd ../.. - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '>=1.18.0' - - name: Build Backend - run: | - go mod download - go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)'" -o one-api.exe - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: one-api.exe - draft: true - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 3e1be51d06fa98224d00fe58d6fc316a94684844 Mon Sep 17 00:00:00 2001 From: Ian Li Date: Sat, 3 Feb 2024 22:16:29 +0800 Subject: [PATCH 043/170] feat: Update default API version for Azure OpenAI. --- web/default/src/pages/Channel/EditChannel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/default/src/pages/Channel/EditChannel.js b/web/default/src/pages/Channel/EditChannel.js index 0d4e114d..14a59a1a 100644 --- a/web/default/src/pages/Channel/EditChannel.js +++ b/web/default/src/pages/Channel/EditChannel.js @@ -193,7 +193,7 @@ const EditChannel = () => { localInputs.base_url = localInputs.base_url.slice(0, localInputs.base_url.length - 1); } if (localInputs.type === 3 && localInputs.other === '') { - localInputs.other = '2023-06-01-preview'; + localInputs.other = '2023-12-01-preview'; } if (localInputs.type === 18 && localInputs.other === '') { localInputs.other = 'v2.1'; @@ -274,7 +274,7 @@ const EditChannel = () => { Date: Mon, 19 Feb 2024 07:23:56 +0000 Subject: [PATCH 044/170] build: Update Golang in Docker image to 1.22.0 - Update Golang container image --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 64cfc004..baab9f34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ WORKDIR /web/berry RUN npm install RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build -FROM golang:1.21.5-bullseye AS builder2 +FROM golang:1.22.0-bullseye AS builder2 ENV GO111MODULE=on \ CGO_ENABLED=1 \ @@ -40,4 +40,4 @@ RUN apt-get install -y --no-install-recommends ca-certificates haveged tzdata \ COPY --from=builder2 /build/one-api / EXPOSE 3000 WORKDIR /data -ENTRYPOINT ["/one-api"] \ No newline at end of file +ENTRYPOINT ["/one-api"] From 79daa892bdca97d6dd28c0fdf84585558f412ce5 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 19 Feb 2024 07:34:24 +0000 Subject: [PATCH 045/170] fix: Refactor: Ignore nil adaptors when initializing OpenAI models map - Ignore nil adaptors when initializing OpenAI models map --- controller/model.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller/model.go b/controller/model.go index dbaed71a..7c428d6c 100644 --- a/controller/model.go +++ b/controller/model.go @@ -59,6 +59,10 @@ func init() { // https://platform.openai.com/docs/models/model-endpoint-compatibility for i := 0; i < constant.APITypeDummy; i++ { adaptor := helper.GetAdaptor(i) + if adaptor == nil { + continue + } + channelName := adaptor.GetChannelName() modelNames := adaptor.GetModelList() for _, modelName := range modelNames { From b9972e0aa423110cd80754c933ca10d41efc8985 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 22 Feb 2024 03:09:46 +0000 Subject: [PATCH 046/170] style: Upgrade relay controller text package - Refactoring of the Text controller. --- relay/controller/text.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/relay/controller/text.go b/relay/controller/text.go index cc460511..4e790e94 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -53,6 +53,7 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { // get request body var requestBody io.Reader + var jsonData []byte if meta.APIType == constant.APITypeOpenAI { // no need to convert request for openai if isModelMapped { @@ -69,7 +70,7 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { if err != nil { return openai.ErrorWrapper(err, "convert_request_failed", http.StatusInternalServerError) } - jsonData, err := json.Marshal(convertedRequest) + jsonData, err = json.Marshal(convertedRequest) if err != nil { return openai.ErrorWrapper(err, "json_marshal_failed", http.StatusInternalServerError) } @@ -85,6 +86,7 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { meta.IsStream = meta.IsStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") if resp.StatusCode != http.StatusOK { util.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId) + logger.Error(ctx, fmt.Sprintf("relay text [%d] <- %q", resp.StatusCode, string(jsonData))) return util.RelayErrorHandler(resp) } From d22c22d4f0d3119fe773b3b314ee0b102f9c3ce0 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 22 Feb 2024 03:17:19 +0000 Subject: [PATCH 047/170] fix: Improve relay error logging - Log errors with the channel ID and the error message. - Also log errors with the channel ID and a JSON representation of the error. --- controller/relay.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/relay.go b/controller/relay.go index 6c6d268e..ba478b39 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -50,7 +50,7 @@ func Relay(c *gin.Context) { }) } channelId := c.GetInt("channel_id") - logger.Error(c.Request.Context(), fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Message)) + logger.Error(c.Request.Context(), fmt.Sprintf("relay error (channel #%d): %+v", channelId, err)) // https://platform.openai.com/docs/guides/error-codes/api-errors if util.ShouldDisableChannel(&err.Error, err.StatusCode) { channelId := c.GetInt("channel_id") From 3c95e1bb0de2c7938392215000d25ea4c1f135ef Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 22 Feb 2024 03:25:45 +0000 Subject: [PATCH 048/170] refactor: Refactor text controller logic - Move the initialization of `requestBody` to a separate if block. - Add a comment to indicate that `requestBodyBytes` is created for debugging use. --- relay/controller/text.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/relay/controller/text.go b/relay/controller/text.go index 4e790e94..e19b571f 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -53,7 +53,6 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { // get request body var requestBody io.Reader - var jsonData []byte if meta.APIType == constant.APITypeOpenAI { // no need to convert request for openai if isModelMapped { @@ -70,13 +69,17 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { if err != nil { return openai.ErrorWrapper(err, "convert_request_failed", http.StatusInternalServerError) } - jsonData, err = json.Marshal(convertedRequest) + jsonData, err := json.Marshal(convertedRequest) if err != nil { return openai.ErrorWrapper(err, "json_marshal_failed", http.StatusInternalServerError) } requestBody = bytes.NewBuffer(jsonData) } + // for debug + requestBodyBytes, _ := io.ReadAll(requestBody) + requestBody = bytes.NewBuffer(requestBodyBytes) + // do request resp, err := adaptor.DoRequest(c, meta, requestBody) if err != nil { @@ -86,7 +89,7 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { meta.IsStream = meta.IsStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") if resp.StatusCode != http.StatusOK { util.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId) - logger.Error(ctx, fmt.Sprintf("relay text [%d] <- %q", resp.StatusCode, string(jsonData))) + logger.Error(ctx, fmt.Sprintf("relay text [%d] <- %q", resp.StatusCode, string(requestBodyBytes))) return util.RelayErrorHandler(resp) } From 4625a0b97d844f6b6bfa912d56f0c452515a59d3 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 29 Feb 2024 06:29:03 +0000 Subject: [PATCH 049/170] chore: Upgrade dependencies of GoWebProd - Add GoWebProd libraries for UUID version 7, various utilities, xxHash calculation, sets, and more. - Update various libraries for improved file system monitoring, deques, JSON handling, logging, stubbing, and comparison. - Log supported models when initializing channel cache and remove duplicates. --- go.mod | 19 ++++++++++++ go.sum | 81 +++++++++++++++++++++++++++++++++++++++++++++++--- model/cache.go | 14 ++++++--- 3 files changed, 106 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index c0adb67c..2a30da13 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/songquanpeng/one-api go 1.21 require ( + github.com/Laisky/go-utils/v4 v4.8.0 github.com/gin-contrib/cors v1.5.0 github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/sessions v0.0.5 @@ -23,19 +24,30 @@ require ( ) require ( + github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320 // indirect + github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4 // indirect + github.com/Laisky/errors/v2 v2.0.1 // indirect + github.com/Laisky/fast-skiplist v0.0.0-20210907063351-e00546c800a6 // indirect + github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be // indirect + github.com/Laisky/graphql v1.0.6 // indirect + github.com/Laisky/zap v1.25.3-0.20231205071752-1cdfcee9191f // indirect github.com/bytedance/sonic v1.10.1 // indirect + github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.10.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gammazero/deque v0.2.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect @@ -53,12 +65,19 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/multierr v1.10.0 // indirect golang.org/x/arch v0.5.0 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8ab19e07..a929edf4 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,29 @@ +github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320 h1:lddR7TsA0fUX8Kh+oc01z4GwmCoBveT79zhNLK43xLk= +github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320/go.mod h1:eTC6ev1JFq+zoOOS5WKuHdBwtihV/9/Ouv3fZ3ufS0A= +github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4 h1:IjxKU4UMzoALLBo3JF7QNi5E0H22R2lDKT3RM9yNCQU= +github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4/go.mod h1:YIx3++ypr3VYDYlz62Zs6zxq/iPT5e9vShuqxwL/5Us= +github.com/Laisky/errors/v2 v2.0.1 h1:yqCBrRzaP012AMB+7fVlXrP34OWRHrSO/hZ38CFdH84= +github.com/Laisky/errors/v2 v2.0.1/go.mod h1:mTn1LHSmKm4CYug0rpYO7rz13dp/DKrtzlSELSrxvT0= +github.com/Laisky/fast-skiplist v0.0.0-20210907063351-e00546c800a6 h1:V3MNilKNjNVKwBiMLsDCa6dFbA5gROE7TYxJJBXkwTc= +github.com/Laisky/fast-skiplist v0.0.0-20210907063351-e00546c800a6/go.mod h1:BdpS7FU5kzp0QV/dtCWheAV5rIOQu+Jb+uoVuVWZOvU= +github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be h1:7Rxhm6IjOtDAyj8eScOFntevwzkWhx94zi48lxo4m4w= +github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be/go.mod h1:1mdzaETo0kjvCQICPSePsoaatJN4l7JvEA1200lyevo= +github.com/Laisky/go-utils/v4 v4.8.0 h1:utyWhJSCiZNjk3jfuhsJqi/IciuUno9sTnGGAOGpktg= +github.com/Laisky/go-utils/v4 v4.8.0/go.mod h1:C6EczuPiA6c2No0M0XB1wT1tK9JH25mhT4vaoLVtRsY= +github.com/Laisky/graphql v1.0.6 h1:NEULGxfxo+wbsW2OmqBXOMNUGgqo8uFjWNabwuNK10g= +github.com/Laisky/graphql v1.0.6/go.mod h1:zaKVqXmMQTnTkFJ2AA53oyBWMzlGCnzr3aodKTrtOxI= +github.com/Laisky/zap v1.25.3-0.20231205071752-1cdfcee9191f h1:EEJtdTk4gjqrEqwBSw5/NAjfS+DCkA/aJZ8M/2kJMqg= +github.com/Laisky/zap v1.25.3-0.20231205071752-1cdfcee9191f/go.mod h1:HABqM5YDQlPq8w+Pmp9h/x9F6Vy+3oHBLP+2+pBoaJw= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/brianvoe/gofakeit/v6 v6.23.2 h1:lVde18uhad5wII/f5RMVFLtdQNE0HaGFuBUXmYKk8i8= +github.com/brianvoe/gofakeit/v6 v6.23.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -14,14 +36,18 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= @@ -36,6 +62,11 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-json-experiment/json v0.0.0-20231011163920-8aa127fd5801 h1:PRieymvnGuBZUnWVQPBOemqlIhRznqtSxs/1LqlWe20= +github.com/go-json-experiment/json v0.0.0-20231011163920-8aa127fd5801/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -60,8 +91,12 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 h1:5/4TSDzpDnHQ8rKEEQBjRlYx77mHOvXu08oGchxej7o= +github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932/go.mod h1:cC6EdPbj/17GFCPDK39NRarlMI+kt+O60S12cNB5J9Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -71,6 +106,7 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -118,6 +154,7 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= @@ -128,9 +165,13 @@ github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAc github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -144,6 +185,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -152,17 +195,39 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= +go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -173,13 +238,21 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/model/cache.go b/model/cache.go index 04a60348..7d60b1aa 100644 --- a/model/cache.go +++ b/model/cache.go @@ -4,15 +4,17 @@ import ( "encoding/json" "errors" "fmt" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/logger" "math/rand" "sort" "strconv" "strings" "sync" "time" + + gutils "github.com/Laisky/go-utils/v4" + "github.com/songquanpeng/one-api/common" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/common/logger" ) var ( @@ -154,11 +156,13 @@ func InitChannelCache() { for group := range groups { newGroup2model2channels[group] = make(map[string][]*Channel) } + var supportedModels []string for _, channel := range channels { groups := strings.Split(channel.Group, ",") for _, group := range groups { models := strings.Split(channel.Models, ",") for _, model := range models { + supportedModels = append(supportedModels, model) if _, ok := newGroup2model2channels[group][model]; !ok { newGroup2model2channels[group][model] = make([]*Channel, 0) } @@ -180,7 +184,9 @@ func InitChannelCache() { channelSyncLock.Lock() group2model2channels = newGroup2model2channels channelSyncLock.Unlock() - logger.SysLog("channels synced from database") + + supportedModels = gutils.UniqueStrings(supportedModels) + logger.SysLog(fmt.Sprintf("channels synced from database, support models: %q", strings.Join(supportedModels, ","))) } func SyncChannelCache(frequency int) { From 3da0f620552423df99f891b15f38a6cce730d113 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 29 Feb 2024 06:44:31 +0000 Subject: [PATCH 050/170] fix: Improve error handling and logging in channel update abilities - Improve error handling in `UpdateAbilities()` method - Log model name change request to the mapped model --- model/channel.go | 10 ++++++++-- relay/controller/text.go | 9 ++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/model/channel.go b/model/channel.go index 19af2263..cdff2edf 100644 --- a/model/channel.go +++ b/model/channel.go @@ -3,6 +3,8 @@ package model import ( "encoding/json" "fmt" + + "github.com/pkg/errors" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" @@ -122,8 +124,12 @@ func (channel *Channel) Update() error { return err } DB.Model(channel).First(channel, "id = ?", channel.Id) - err = channel.UpdateAbilities() - return err + if err = channel.UpdateAbilities(); err != nil { + logger.SysError("failed to update abilities: " + err.Error()) + return errors.Wrap(err, "failed to update abilities") + } + + return nil } func (channel *Channel) UpdateResponseTime(responseTime int64) { diff --git a/relay/controller/text.go b/relay/controller/text.go index e19b571f..36ecd16c 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -29,9 +29,16 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { meta.IsStream = textRequest.Stream // map model name - var isModelMapped bool + var ( + originRequestModel = textRequest.Model + isModelMapped bool + ) meta.OriginModelName = textRequest.Model textRequest.Model, isModelMapped = util.GetMappedModelName(textRequest.Model, meta.ModelMapping) + if isModelMapped { + logger.Info(c.Request.Context(), fmt.Sprintf("rewrite model name from %s to %s", originRequestModel, textRequest.Model)) + } + meta.ActualModelName = textRequest.Model // get model ratio & group ratio modelRatio := common.GetModelRatio(textRequest.Model) From 6ee7281a0ad88e2b3646cc8de7a2a6cc0e69ecde Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 29 Feb 2024 06:55:55 +0000 Subject: [PATCH 051/170] feat: Map channel model changes to ability mappings Fix: Add model mappings to a channel's abilities - Add model mappings to a channel's abilities. - Add model mapping unit tests. --- model/ability.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/model/ability.go b/model/ability.go index 7127abc3..e95c06f6 100644 --- a/model/ability.go +++ b/model/ability.go @@ -55,6 +55,21 @@ func (channel *Channel) AddAbilities() error { abilities = append(abilities, ability) } } + + // add model mappings + for model := range channel.GetModelMapping() { + for _, group := range groups_ { + ability := Ability{ + Group: group, + Model: model, + ChannelId: channel.Id, + Enabled: channel.Status == common.ChannelStatusEnabled, + Priority: channel.Priority, + } + abilities = append(abilities, ability) + } + } + return DB.Create(&abilities).Error } From 90a039d325967d10fb32a679962cbae22002a936 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 29 Feb 2024 08:58:54 +0000 Subject: [PATCH 052/170] fix: #1054 Add model mapping to abilities - Update Abilities model to include model mapping key - Parse model mapping in Channel model and filter models by model mapping in Update function --- common/image/image.go | 2 +- controller/relay.go | 2 +- model/ability.go | 15 +++++++++++++++ model/channel.go | 21 +++++++++++++++++++-- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/common/image/image.go b/common/image/image.go index de8fefd3..12f0adff 100644 --- a/common/image/image.go +++ b/common/image/image.go @@ -16,7 +16,7 @@ import ( ) // Regex to match data URL pattern -var dataURLPattern = regexp.MustCompile(`data:image/([^;]+);base64,(.*)`) +var dataURLPattern = regexp.MustCompile(`data:image/([^;]+);base64,(.*)`) func IsImageUrl(url string) (bool, error) { resp, err := http.Head(url) diff --git a/controller/relay.go b/controller/relay.go index 499e8ddc..9ace90ed 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -60,7 +60,7 @@ func Relay(c *gin.Context) { for i := retryTimes; i > 0; i-- { channel, err := dbmodel.CacheGetRandomSatisfiedChannel(group, originalModel) if err != nil { - logger.Errorf(ctx, "CacheGetRandomSatisfiedChannel failed: %w", err) + logger.Errorf(ctx, "CacheGetRandomSatisfiedChannel failed: %v", err) break } logger.Infof(ctx, "using channel #%d to retry (remain times %d)", channel.Id, i) diff --git a/model/ability.go b/model/ability.go index 7127abc3..5d65612b 100644 --- a/model/ability.go +++ b/model/ability.go @@ -55,6 +55,21 @@ func (channel *Channel) AddAbilities() error { abilities = append(abilities, ability) } } + + // add keys of model mapping to abilities + for model := range channel.GetModelMapping() { + for _, group := range groups_ { + ability := Ability{ + Group: group, + Model: model, + ChannelId: channel.Id, + Enabled: channel.Status == common.ChannelStatusEnabled, + Priority: channel.Priority, + } + abilities = append(abilities, ability) + } + } + return DB.Create(&abilities).Error } diff --git a/model/channel.go b/model/channel.go index 19af2263..68d8b738 100644 --- a/model/channel.go +++ b/model/channel.go @@ -3,6 +3,8 @@ package model import ( "encoding/json" "fmt" + "strings" + "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" @@ -115,8 +117,23 @@ func (channel *Channel) Insert() error { return err } -func (channel *Channel) Update() error { - var err error +func (channel *Channel) Update() (err error) { + // https://github.com/songquanpeng/one-api/issues/1054 + // for compatability, filter models by model-mapping. + mapping := channel.GetModelMapping() + if len(mapping) != 0 { + models := strings.Split(channel.Models, ",") + var filteredModels []string + for _, model := range models { + if _, ok := mapping[model]; ok { + filteredModels = append(filteredModels, model) + } + } + + channel.Models = strings.Join(filteredModels, ",") + } + + // update err = DB.Model(channel).Updates(channel).Error if err != nil { return err From c849292621e3348299561b2e9f5c78ec3a69e951 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 29 Feb 2024 09:11:01 +0000 Subject: [PATCH 053/170] fix --- model/channel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/channel.go b/model/channel.go index 0d5d3161..c5f947c7 100644 --- a/model/channel.go +++ b/model/channel.go @@ -126,7 +126,7 @@ func (channel *Channel) Update() (err error) { models := strings.Split(channel.Models, ",") var filteredModels []string for _, model := range models { - if _, ok := mapping[model]; ok { + if _, ok := mapping[model]; !ok { filteredModels = append(filteredModels, model) } } From ba827b95e37b95fc63622871218609cc63fabeea Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 1 Mar 2024 02:14:32 +0000 Subject: [PATCH 054/170] revert: Rework text model logic and update dependencies - Rewrite model name for relay text - Simplify logic and move logging statements - Remove a check that filtered out models by model-mapping - Remove abilities for the model mapping - Lower numeric tolerance for test files --- go.mod | 19 ---------- go.sum | 81 ++-------------------------------------- model/ability.go | 15 -------- model/cache.go | 14 ++----- model/channel.go | 30 ++------------- relay/controller/text.go | 9 +---- 6 files changed, 13 insertions(+), 155 deletions(-) diff --git a/go.mod b/go.mod index 2a30da13..c0adb67c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/songquanpeng/one-api go 1.21 require ( - github.com/Laisky/go-utils/v4 v4.8.0 github.com/gin-contrib/cors v1.5.0 github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/sessions v0.0.5 @@ -24,30 +23,19 @@ require ( ) require ( - github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320 // indirect - github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4 // indirect - github.com/Laisky/errors/v2 v2.0.1 // indirect - github.com/Laisky/fast-skiplist v0.0.0-20210907063351-e00546c800a6 // indirect - github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be // indirect - github.com/Laisky/graphql v1.0.6 // indirect - github.com/Laisky/zap v1.25.3-0.20231205071752-1cdfcee9191f // indirect github.com/bytedance/sonic v1.10.1 // indirect - github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.10.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gammazero/deque v0.2.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect @@ -65,19 +53,12 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect - go.uber.org/automaxprocs v1.5.3 // indirect - go.uber.org/multierr v1.10.0 // indirect golang.org/x/arch v0.5.0 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.6.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a929edf4..8ab19e07 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,7 @@ -github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320 h1:lddR7TsA0fUX8Kh+oc01z4GwmCoBveT79zhNLK43xLk= -github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320/go.mod h1:eTC6ev1JFq+zoOOS5WKuHdBwtihV/9/Ouv3fZ3ufS0A= -github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4 h1:IjxKU4UMzoALLBo3JF7QNi5E0H22R2lDKT3RM9yNCQU= -github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4/go.mod h1:YIx3++ypr3VYDYlz62Zs6zxq/iPT5e9vShuqxwL/5Us= -github.com/Laisky/errors/v2 v2.0.1 h1:yqCBrRzaP012AMB+7fVlXrP34OWRHrSO/hZ38CFdH84= -github.com/Laisky/errors/v2 v2.0.1/go.mod h1:mTn1LHSmKm4CYug0rpYO7rz13dp/DKrtzlSELSrxvT0= -github.com/Laisky/fast-skiplist v0.0.0-20210907063351-e00546c800a6 h1:V3MNilKNjNVKwBiMLsDCa6dFbA5gROE7TYxJJBXkwTc= -github.com/Laisky/fast-skiplist v0.0.0-20210907063351-e00546c800a6/go.mod h1:BdpS7FU5kzp0QV/dtCWheAV5rIOQu+Jb+uoVuVWZOvU= -github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be h1:7Rxhm6IjOtDAyj8eScOFntevwzkWhx94zi48lxo4m4w= -github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be/go.mod h1:1mdzaETo0kjvCQICPSePsoaatJN4l7JvEA1200lyevo= -github.com/Laisky/go-utils/v4 v4.8.0 h1:utyWhJSCiZNjk3jfuhsJqi/IciuUno9sTnGGAOGpktg= -github.com/Laisky/go-utils/v4 v4.8.0/go.mod h1:C6EczuPiA6c2No0M0XB1wT1tK9JH25mhT4vaoLVtRsY= -github.com/Laisky/graphql v1.0.6 h1:NEULGxfxo+wbsW2OmqBXOMNUGgqo8uFjWNabwuNK10g= -github.com/Laisky/graphql v1.0.6/go.mod h1:zaKVqXmMQTnTkFJ2AA53oyBWMzlGCnzr3aodKTrtOxI= -github.com/Laisky/zap v1.25.3-0.20231205071752-1cdfcee9191f h1:EEJtdTk4gjqrEqwBSw5/NAjfS+DCkA/aJZ8M/2kJMqg= -github.com/Laisky/zap v1.25.3-0.20231205071752-1cdfcee9191f/go.mod h1:HABqM5YDQlPq8w+Pmp9h/x9F6Vy+3oHBLP+2+pBoaJw= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/brianvoe/gofakeit/v6 v6.23.2 h1:lVde18uhad5wII/f5RMVFLtdQNE0HaGFuBUXmYKk8i8= -github.com/brianvoe/gofakeit/v6 v6.23.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -36,18 +14,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= -github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= -github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= @@ -62,11 +36,6 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-json-experiment/json v0.0.0-20231011163920-8aa127fd5801 h1:PRieymvnGuBZUnWVQPBOemqlIhRznqtSxs/1LqlWe20= -github.com/go-json-experiment/json v0.0.0-20231011163920-8aa127fd5801/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -91,12 +60,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 h1:5/4TSDzpDnHQ8rKEEQBjRlYx77mHOvXu08oGchxej7o= -github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932/go.mod h1:cC6EdPbj/17GFCPDK39NRarlMI+kt+O60S12cNB5J9Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -106,7 +71,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -154,7 +118,6 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= @@ -165,13 +128,9 @@ github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAc github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -185,8 +144,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= -github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -195,39 +152,17 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= -go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -238,21 +173,13 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/model/ability.go b/model/ability.go index 5d65612b..7127abc3 100644 --- a/model/ability.go +++ b/model/ability.go @@ -55,21 +55,6 @@ func (channel *Channel) AddAbilities() error { abilities = append(abilities, ability) } } - - // add keys of model mapping to abilities - for model := range channel.GetModelMapping() { - for _, group := range groups_ { - ability := Ability{ - Group: group, - Model: model, - ChannelId: channel.Id, - Enabled: channel.Status == common.ChannelStatusEnabled, - Priority: channel.Priority, - } - abilities = append(abilities, ability) - } - } - return DB.Create(&abilities).Error } diff --git a/model/cache.go b/model/cache.go index 7d60b1aa..04a60348 100644 --- a/model/cache.go +++ b/model/cache.go @@ -4,17 +4,15 @@ import ( "encoding/json" "errors" "fmt" + "github.com/songquanpeng/one-api/common" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/common/logger" "math/rand" "sort" "strconv" "strings" "sync" "time" - - gutils "github.com/Laisky/go-utils/v4" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/logger" ) var ( @@ -156,13 +154,11 @@ func InitChannelCache() { for group := range groups { newGroup2model2channels[group] = make(map[string][]*Channel) } - var supportedModels []string for _, channel := range channels { groups := strings.Split(channel.Group, ",") for _, group := range groups { models := strings.Split(channel.Models, ",") for _, model := range models { - supportedModels = append(supportedModels, model) if _, ok := newGroup2model2channels[group][model]; !ok { newGroup2model2channels[group][model] = make([]*Channel, 0) } @@ -184,9 +180,7 @@ func InitChannelCache() { channelSyncLock.Lock() group2model2channels = newGroup2model2channels channelSyncLock.Unlock() - - supportedModels = gutils.UniqueStrings(supportedModels) - logger.SysLog(fmt.Sprintf("channels synced from database, support models: %q", strings.Join(supportedModels, ","))) + logger.SysLog("channels synced from database") } func SyncChannelCache(frequency int) { diff --git a/model/channel.go b/model/channel.go index c5f947c7..19af2263 100644 --- a/model/channel.go +++ b/model/channel.go @@ -3,9 +3,6 @@ package model import ( "encoding/json" "fmt" - "strings" - - "github.com/pkg/errors" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" @@ -118,34 +115,15 @@ func (channel *Channel) Insert() error { return err } -func (channel *Channel) Update() (err error) { - // https://github.com/songquanpeng/one-api/issues/1054 - // for compatability, filter models by model-mapping. - mapping := channel.GetModelMapping() - if len(mapping) != 0 { - models := strings.Split(channel.Models, ",") - var filteredModels []string - for _, model := range models { - if _, ok := mapping[model]; !ok { - filteredModels = append(filteredModels, model) - } - } - - channel.Models = strings.Join(filteredModels, ",") - } - - // update +func (channel *Channel) Update() error { + var err error err = DB.Model(channel).Updates(channel).Error if err != nil { return err } DB.Model(channel).First(channel, "id = ?", channel.Id) - if err = channel.UpdateAbilities(); err != nil { - logger.SysError("failed to update abilities: " + err.Error()) - return errors.Wrap(err, "failed to update abilities") - } - - return nil + err = channel.UpdateAbilities() + return err } func (channel *Channel) UpdateResponseTime(responseTime int64) { diff --git a/relay/controller/text.go b/relay/controller/text.go index 36ecd16c..e19b571f 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -29,16 +29,9 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { meta.IsStream = textRequest.Stream // map model name - var ( - originRequestModel = textRequest.Model - isModelMapped bool - ) + var isModelMapped bool meta.OriginModelName = textRequest.Model textRequest.Model, isModelMapped = util.GetMappedModelName(textRequest.Model, meta.ModelMapping) - if isModelMapped { - logger.Info(c.Request.Context(), fmt.Sprintf("rewrite model name from %s to %s", originRequestModel, textRequest.Model)) - } - meta.ActualModelName = textRequest.Model // get model ratio & group ratio modelRatio := common.GetModelRatio(textRequest.Model) From dbe3930a8cf73539c3632256a70c1c5bd7981bab Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 1 Mar 2024 13:49:23 +0000 Subject: [PATCH 055/170] fix: Switch to channel-ratio - Use channel ratios instead of group ratios in all applicable places - Start using the lowest channel ratio of the specified channel's groups --- middleware/distributor.go | 10 ++++++++++ relay/controller/audio.go | 5 +++-- relay/controller/image.go | 12 +++++++----- relay/controller/text.go | 3 ++- relay/util/relay_meta.go | 2 ++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/middleware/distributor.go b/middleware/distributor.go index aeb2796a..0fda0670 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -85,6 +85,16 @@ func Distribute() func(c *gin.Context) { } func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, modelName string) { + // set minimal group ratio as channel_ratio + var minimalRatio float64 + for _, grp := range strings.Split(channel.Group, ",") { + v := common.GetGroupRatio(grp) + if minimalRatio == 0 || v < minimalRatio { + minimalRatio = v + } + } + c.Set("channel_ratio", minimalRatio) + c.Set("channel", channel.Type) c.Set("channel_id", channel.Id) c.Set("channel_name", channel.Name) diff --git a/relay/controller/audio.go b/relay/controller/audio.go index ee8771c9..bd39944f 100644 --- a/relay/controller/audio.go +++ b/relay/controller/audio.go @@ -28,7 +28,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus channelType := c.GetInt("channel") channelId := c.GetInt("channel_id") userId := c.GetInt("id") - group := c.GetString("group") + // group := c.GetString("group") tokenName := c.GetString("token_name") var ttsRequest openai.TextToSpeechRequest @@ -47,7 +47,8 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus } modelRatio := common.GetModelRatio(audioModel) - groupRatio := common.GetGroupRatio(group) + // groupRatio := common.GetGroupRatio(group) + groupRatio := c.GetFloat64("channel_ratio") ratio := modelRatio * groupRatio var quota int var preConsumedQuota int diff --git a/relay/controller/image.go b/relay/controller/image.go index 6ec368f5..4e0ed172 100644 --- a/relay/controller/image.go +++ b/relay/controller/image.go @@ -6,15 +6,16 @@ import ( "encoding/json" "errors" "fmt" + "io" + "net/http" + "strings" + "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/model" "github.com/songquanpeng/one-api/relay/channel/openai" relaymodel "github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" - "strings" "github.com/gin-gonic/gin" ) @@ -37,7 +38,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus channelType := c.GetInt("channel") channelId := c.GetInt("channel_id") userId := c.GetInt("id") - group := c.GetString("group") + // group := c.GetString("group") var imageRequest openai.ImageRequest err := common.UnmarshalBodyReusable(c, &imageRequest) @@ -131,7 +132,8 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus } modelRatio := common.GetModelRatio(imageModel) - groupRatio := common.GetGroupRatio(group) + // groupRatio := common.GetGroupRatio(group) + groupRatio := c.GetFloat64("channel_ratio") ratio := modelRatio * groupRatio userQuota, err := model.CacheGetUserQuota(userId) diff --git a/relay/controller/text.go b/relay/controller/text.go index e19b571f..46c62a3c 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -35,7 +35,8 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { meta.ActualModelName = textRequest.Model // get model ratio & group ratio modelRatio := common.GetModelRatio(textRequest.Model) - groupRatio := common.GetGroupRatio(meta.Group) + // groupRatio := common.GetGroupRatio(meta.Group) + groupRatio := meta.ChannelRatio ratio := modelRatio * groupRatio // pre-consume quota promptTokens := getPromptTokens(textRequest, meta.Mode) diff --git a/relay/util/relay_meta.go b/relay/util/relay_meta.go index 31b9d2b4..17135816 100644 --- a/relay/util/relay_meta.go +++ b/relay/util/relay_meta.go @@ -26,6 +26,7 @@ type RelayMeta struct { ActualModelName string RequestURLPath string PromptTokens int // only for DoResponse + ChannelRatio float64 } func GetRelayMeta(c *gin.Context) *RelayMeta { @@ -43,6 +44,7 @@ func GetRelayMeta(c *gin.Context) *RelayMeta { APIKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "), Config: nil, RequestURLPath: c.Request.URL.String(), + ChannelRatio: c.GetFloat64("channel_ratio"), } if meta.ChannelType == common.ChannelTypeAzure { meta.APIVersion = GetAzureAPIVersion(c) From 2989686acea53bc4d4b5a1d1a918a35c4b830272 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 1 Mar 2024 13:55:21 +0000 Subject: [PATCH 056/170] fix: Refactor: Migrate Distributor logging to ratio metrics - Log a ratio for each channel in the context - Initialize `minimalRatio` to -1 instead of 0 --- middleware/distributor.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/middleware/distributor.go b/middleware/distributor.go index 0fda0670..4f77e786 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -86,13 +86,14 @@ func Distribute() func(c *gin.Context) { func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, modelName string) { // set minimal group ratio as channel_ratio - var minimalRatio float64 + var minimalRatio float64 = -1 for _, grp := range strings.Split(channel.Group, ",") { v := common.GetGroupRatio(grp) - if minimalRatio == 0 || v < minimalRatio { + if minimalRatio < 0 || v < minimalRatio { minimalRatio = v } } + logger.Info(c.Request.Context(), fmt.Sprintf("set channel %s ratio to %f", channel.Name, minimalRatio)) c.Set("channel_ratio", minimalRatio) c.Set("channel", channel.Type) From 06350ae5a2f5783618461607cd68182e7069978a Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 1 Mar 2024 14:00:50 +0000 Subject: [PATCH 057/170] fix: Refactor: Remove Quota Consumption Logic When Quota is 0 - Remove quota consumption logic when quota is 0 - Improve logging of quota usage when quota is greater than 0 - Reduce noise in logs when quota is 0 --- relay/controller/helper.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/relay/controller/helper.go b/relay/controller/helper.go index a06b2768..e3745372 100644 --- a/relay/controller/helper.go +++ b/relay/controller/helper.go @@ -113,10 +113,15 @@ func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *util.R if err != nil { logger.Error(ctx, "error update user quota cache: "+err.Error()) } - if quota != 0 { - logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f", modelRatio, groupRatio, completionRatio) - model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, promptTokens, completionTokens, textRequest.Model, meta.TokenName, quota, logContent) - model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota) - model.UpdateChannelUsedQuota(meta.ChannelId, quota) - } + // if quota != 0 { + // logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f", modelRatio, groupRatio, completionRatio) + // model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, promptTokens, completionTokens, textRequest.Model, meta.TokenName, quota, logContent) + // model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota) + // model.UpdateChannelUsedQuota(meta.ChannelId, quota) + // } + + logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f", modelRatio, groupRatio, completionRatio) + model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, promptTokens, completionTokens, textRequest.Model, meta.TokenName, quota, logContent) + model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota) + model.UpdateChannelUsedQuota(meta.ChannelId, quota) } From 268690219eca568a3b529c9e45f43baceea6b5ce Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sat, 2 Mar 2024 00:59:22 +0000 Subject: [PATCH 058/170] fix: Rename limit to cost and allow for 0 limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `额度` column to `费用` - Allow for a quota to be 0. - If the quota is 0, return 'free' --- web/berry/src/utils/common.js | 6 +++++- web/berry/src/views/Log/component/TableHead.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/berry/src/utils/common.js b/web/berry/src/utils/common.js index 25e5c635..06395942 100644 --- a/web/berry/src/utils/common.js +++ b/web/berry/src/utils/common.js @@ -124,7 +124,11 @@ export function timestamp2string(timestamp) { return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second; } -export function calculateQuota(quota, digits = 2) { +export function calculateQuota(quota = 0, digits = 2) { + if (!quota || quota === 0) { + return 'free'; + } + let quotaPerUnit = localStorage.getItem('quota_per_unit'); quotaPerUnit = parseFloat(quotaPerUnit); diff --git a/web/berry/src/views/Log/component/TableHead.js b/web/berry/src/views/Log/component/TableHead.js index 671170ce..072ac557 100644 --- a/web/berry/src/views/Log/component/TableHead.js +++ b/web/berry/src/views/Log/component/TableHead.js @@ -13,7 +13,7 @@ const LogTableHead = ({ userIsAdmin }) => { 模型 提示 补全 - 额度 + 费用 详情 From a7c43ab24887d318e006babfc848b9e045fcadd0 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sat, 2 Mar 2024 01:10:59 +0000 Subject: [PATCH 059/170] fix: display issue for quota - DRY up `LogsTable.js` codebase - Fix formatting in `LogsTable.js` - Display 'free' for a 0 quota instead of empty string --- web/default/src/components/LogsTable.js | 2 +- web/default/src/helpers/render.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/default/src/components/LogsTable.js b/web/default/src/components/LogsTable.js index e266d79a..cde17479 100644 --- a/web/default/src/components/LogsTable.js +++ b/web/default/src/components/LogsTable.js @@ -319,7 +319,7 @@ const LogsTable = () => { }} width={1} > - 额度 + 费用 Date: Sat, 2 Mar 2024 01:17:03 +0000 Subject: [PATCH 060/170] fix: Enhance logs table with sorting, quotas, and UI improvements - Improve LogsTable functionality for admin users. - Add quota calculation column. - Display quotas in the summary header. --- web/default/src/components/LogsTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/default/src/components/LogsTable.js b/web/default/src/components/LogsTable.js index cde17479..308aff98 100644 --- a/web/default/src/components/LogsTable.js +++ b/web/default/src/components/LogsTable.js @@ -359,7 +359,7 @@ const LogsTable = () => { {log.model_name ? : ''} {log.prompt_tokens ? log.prompt_tokens : ''} {log.completion_tokens ? log.completion_tokens : ''} - {log.quota ? renderQuota(log.quota, 6) : ''} + {log.quota ? renderQuota(log.quota, 6) : 'free'} {log.content} ); From 359fc6aa18eeecf66e5adde48ab12287253f2da3 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sat, 2 Mar 2024 02:09:01 +0000 Subject: [PATCH 061/170] fix: Hide free quota display for zero quotas - Remove the quota display for Zero quotas - Parse the `const quotaPerUnit` once --- web/berry/src/utils/common.js | 4 ---- web/default/src/helpers/render.js | 4 ---- 2 files changed, 8 deletions(-) diff --git a/web/berry/src/utils/common.js b/web/berry/src/utils/common.js index 06395942..aa4b8c37 100644 --- a/web/berry/src/utils/common.js +++ b/web/berry/src/utils/common.js @@ -125,10 +125,6 @@ export function timestamp2string(timestamp) { } export function calculateQuota(quota = 0, digits = 2) { - if (!quota || quota === 0) { - return 'free'; - } - let quotaPerUnit = localStorage.getItem('quota_per_unit'); quotaPerUnit = parseFloat(quotaPerUnit); diff --git a/web/default/src/helpers/render.js b/web/default/src/helpers/render.js index 0c0b8789..aeb08371 100644 --- a/web/default/src/helpers/render.js +++ b/web/default/src/helpers/render.js @@ -38,10 +38,6 @@ export function renderNumber(num) { } export function renderQuota(quota, digits = 2) { - if (quota === 0) { - return 'free'; - } - let quotaPerUnit = localStorage.getItem('quota_per_unit'); let displayInCurrency = localStorage.getItem('display_in_currency'); quotaPerUnit = parseFloat(quotaPerUnit); From 75d2055e03df89f8715458c628eeaa58c19af779 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 4 Mar 2024 02:54:44 +0000 Subject: [PATCH 062/170] docs: Implement a function to get completion ratio - Implement retrieval for the completion ratio of a given model --- common/model-ratio.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/model-ratio.go b/common/model-ratio.go index 2e7aae71..916eda6f 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -169,6 +169,9 @@ func UpdateCompletionRatioByJSONString(jsonStr string) error { return json.Unmarshal([]byte(jsonStr), &CompletionRatio) } +// GetCompletionRatio returns the completion ratio of a model +// +// completion ratio is the ratio comparing to the ratio of prompt func GetCompletionRatio(name string) float64 { if ratio, ok := CompletionRatio[name]; ok { return ratio From f9df8eaa638b4feb60e889a0089aecc2b25c47da Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 00:58:52 +0000 Subject: [PATCH 063/170] feat: Migrate to Anthropic Message API - Migrate to Anthropic Message API --- relay/channel/anthropic/adaptor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/relay/channel/anthropic/adaptor.go b/relay/channel/anthropic/adaptor.go index 4b873715..b5359351 100644 --- a/relay/channel/anthropic/adaptor.go +++ b/relay/channel/anthropic/adaptor.go @@ -20,7 +20,9 @@ func (a *Adaptor) Init(meta *util.RelayMeta) { } func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { - return fmt.Sprintf("%s/v1/complete", meta.BaseURL), nil + // https://docs.anthropic.com/claude/reference/messages_post + // anthopic migrate to Message API + return fmt.Sprintf("%s/v1/messages", meta.BaseURL), nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { From b1a7e197b7e0206ea6d8daa6bdb55b1b88f3a389 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 01:08:16 +0000 Subject: [PATCH 064/170] fix: Log request URL for non-ok status codes - Refactor: Log full request URL in case of non-ok response - Fix: URIScheme invalid model in proton-go --- relay/controller/text.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/relay/controller/text.go b/relay/controller/text.go index 9dd0e5a5..13fca4c8 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -91,7 +91,8 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { meta.IsStream = meta.IsStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") if resp.StatusCode != http.StatusOK { util.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId) - logger.Error(ctx, fmt.Sprintf("relay text [%d] <- %q", resp.StatusCode, string(requestBodyBytes))) + logger.Error(ctx, fmt.Sprintf("relay text [%d] <- %q %q", + resp.StatusCode, resp.Request.URL.String(), string(requestBodyBytes))) return util.RelayErrorHandler(resp) } From 003ec2eabce1e1827416e9210f54048afdf76cac Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 01:15:52 +0000 Subject: [PATCH 065/170] fix: Switch default Adaptor from Anthropic to OpenAI - Switch the default Adaptor from Anthropic to OpenAI --- relay/helper/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/relay/helper/main.go b/relay/helper/main.go index 6aa70e88..1e91d7dd 100644 --- a/relay/helper/main.go +++ b/relay/helper/main.go @@ -17,7 +17,8 @@ func GetAdaptor(apiType int) channel.Adaptor { // case constant.APITypeAli: // return &ali.Adaptor{} case constant.APITypeAnthropic: - return &anthropic.Adaptor{} + // return &anthropic.Adaptor{} + return &openai.Adaptor{} // case constant.APITypeBaidu: // return &baidu.Adaptor{} case constant.APITypeGemini: From fb23ea0c9ab08630b4d675c1f086ec9bb62574b1 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 01:20:27 +0000 Subject: [PATCH 066/170] fix: Remove Anthropic channel - Remove Discord Antropic Hackathon Announcement. --- relay/helper/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay/helper/main.go b/relay/helper/main.go index 1e91d7dd..a460862c 100644 --- a/relay/helper/main.go +++ b/relay/helper/main.go @@ -3,7 +3,7 @@ package helper import ( "github.com/songquanpeng/one-api/relay/channel" "github.com/songquanpeng/one-api/relay/channel/aiproxy" - "github.com/songquanpeng/one-api/relay/channel/anthropic" + // "github.com/songquanpeng/one-api/relay/channel/anthropic" "github.com/songquanpeng/one-api/relay/channel/gemini" "github.com/songquanpeng/one-api/relay/channel/openai" "github.com/songquanpeng/one-api/relay/channel/palm" From bb8755bc98bafa70ceb27cd67227d9d1fcafd290 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 03:38:26 +0000 Subject: [PATCH 067/170] refactor: Refactor: Consolidate Anthropic model requests into `GeneralOpenAIRequest` - Refactor Anthropic adapter to work with the new Anthropic API and model requests - Remove the default value for `MaxTokensToSample` - Set `MaxTokens` to 500 instead of 1000000 - Use `system` messages as the system prompt instead of the first message --- relay/channel/anthropic/adaptor.go | 5 ++-- relay/channel/anthropic/main.go | 40 +++++++++++------------------- relay/channel/anthropic/model.go | 16 ++++++------ relay/helper/main.go | 5 ++-- 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/relay/channel/anthropic/adaptor.go b/relay/channel/anthropic/adaptor.go index b5359351..a97ac36f 100644 --- a/relay/channel/anthropic/adaptor.go +++ b/relay/channel/anthropic/adaptor.go @@ -3,13 +3,14 @@ package anthropic import ( "errors" "fmt" + "io" + "net/http" + "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/relay/channel" "github.com/songquanpeng/one-api/relay/channel/openai" "github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" ) type Adaptor struct { diff --git a/relay/channel/anthropic/main.go b/relay/channel/anthropic/main.go index 44d15c36..5399fee6 100644 --- a/relay/channel/anthropic/main.go +++ b/relay/channel/anthropic/main.go @@ -28,37 +28,25 @@ func stopReasonClaude2OpenAI(reason string) string { func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request { claudeRequest := Request{ - Model: textRequest.Model, - Prompt: "", - MaxTokensToSample: textRequest.MaxTokens, - StopSequences: nil, - Temperature: textRequest.Temperature, - TopP: textRequest.TopP, - Stream: textRequest.Stream, + GeneralOpenAIRequest: textRequest, } - if claudeRequest.MaxTokensToSample == 0 { - claudeRequest.MaxTokensToSample = 1000000 + + if claudeRequest.MaxTokens == 0 { + claudeRequest.MaxTokens = 500 // max_tokens is required } - prompt := "" - // messages, err := textRequest.TextMessages() - // if err != nil { - // log.Panicf("invalid message type: %T", textRequest.Messages) - // } - - for _, message := range textRequest.Messages { - if message.Role == "user" { - prompt += fmt.Sprintf("\n\nHuman: %s", message.Content) - } else if message.Role == "assistant" { - prompt += fmt.Sprintf("\n\nAssistant: %s", message.Content) - } else if message.Role == "system" { - if prompt == "" { - prompt = message.StringContent() - } + // anthropic's new messages API use system to represent the system prompt + var filteredMessages []model.Message + for _, msg := range claudeRequest.Messages { + if msg.Role != "system" { + filteredMessages = append(filteredMessages, msg) + continue } + + claudeRequest.System += msg.Content.(string) } - prompt += "\n\nAssistant:" - claudeRequest.Prompt = prompt + claudeRequest.Messages = filteredMessages + return &claudeRequest } diff --git a/relay/channel/anthropic/model.go b/relay/channel/anthropic/model.go index 70fc9430..bc718449 100644 --- a/relay/channel/anthropic/model.go +++ b/relay/channel/anthropic/model.go @@ -1,19 +1,17 @@ package anthropic +import ( + "github.com/songquanpeng/one-api/relay/model" +) + type Metadata struct { UserId string `json:"user_id"` } type Request struct { - Model string `json:"model"` - Prompt string `json:"prompt"` - MaxTokensToSample int `json:"max_tokens_to_sample"` - StopSequences []string `json:"stop_sequences,omitempty"` - Temperature float64 `json:"temperature,omitempty"` - TopP float64 `json:"top_p,omitempty"` - TopK int `json:"top_k,omitempty"` - //Metadata `json:"metadata,omitempty"` - Stream bool `json:"stream,omitempty"` + model.GeneralOpenAIRequest + // System anthropic messages API use system to represent the system prompt + System string `json:"system"` } type Error struct { diff --git a/relay/helper/main.go b/relay/helper/main.go index a460862c..6aa70e88 100644 --- a/relay/helper/main.go +++ b/relay/helper/main.go @@ -3,7 +3,7 @@ package helper import ( "github.com/songquanpeng/one-api/relay/channel" "github.com/songquanpeng/one-api/relay/channel/aiproxy" - // "github.com/songquanpeng/one-api/relay/channel/anthropic" + "github.com/songquanpeng/one-api/relay/channel/anthropic" "github.com/songquanpeng/one-api/relay/channel/gemini" "github.com/songquanpeng/one-api/relay/channel/openai" "github.com/songquanpeng/one-api/relay/channel/palm" @@ -17,8 +17,7 @@ func GetAdaptor(apiType int) channel.Adaptor { // case constant.APITypeAli: // return &ali.Adaptor{} case constant.APITypeAnthropic: - // return &anthropic.Adaptor{} - return &openai.Adaptor{} + return &anthropic.Adaptor{} // case constant.APITypeBaidu: // return &baidu.Adaptor{} case constant.APITypeGemini: From c8713a021291d93c434b76978f8331f4bf2e5b0b Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 03:54:59 +0000 Subject: [PATCH 068/170] feat: SimplifyAnthropicRequest - Remove `N` parameter from Anthropic request - Append system messages from Claude to the `System` field instead of sending them as separate messages --- relay/channel/anthropic/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/relay/channel/anthropic/main.go b/relay/channel/anthropic/main.go index 5399fee6..dced927e 100644 --- a/relay/channel/anthropic/main.go +++ b/relay/channel/anthropic/main.go @@ -47,6 +47,7 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request { } claudeRequest.Messages = filteredMessages + claudeRequest.N = 0 // anthropic's messages API not support n return &claudeRequest } From fdde066252e06878dff01cefdadeab0b4465634d Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 05:05:57 +0000 Subject: [PATCH 069/170] Refactor: Anthropic model response proto update - Refactor content response - Update channel adaptor to support `claude_model` - Remove `null` stop reasons from content responses - Add logging for error responses - Change content start, ping, and message delta types to return true - Set stop reason to end turn when response does not include a stop reason - Set content response stop reason to null - Add error handling for unmarshalling stream responses - Rename `Completion` to `Text` in type definitions and `StopReason` to `Delta.StopReason` - Count tokens in the `Delta.Text` field of the response instead of `Completion` - Remove `Model` from the full text response - Trim \r from incoming data --- relay/channel/anthropic/adaptor.go | 2 ++ relay/channel/anthropic/main.go | 33 +++++++++++++++++++++++------- relay/channel/anthropic/model.go | 29 ++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/relay/channel/anthropic/adaptor.go b/relay/channel/anthropic/adaptor.go index a97ac36f..dccdd206 100644 --- a/relay/channel/anthropic/adaptor.go +++ b/relay/channel/anthropic/adaptor.go @@ -41,6 +41,8 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G if request == nil { return nil, errors.New("request is nil") } + + c.Set("claude_model", request.Model) return ConvertRequest(*request), nil } diff --git a/relay/channel/anthropic/main.go b/relay/channel/anthropic/main.go index dced927e..4ca850db 100644 --- a/relay/channel/anthropic/main.go +++ b/relay/channel/anthropic/main.go @@ -53,14 +53,14 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request { func streamResponseClaude2OpenAI(claudeResponse *Response) *openai.ChatCompletionsStreamResponse { var choice openai.ChatCompletionsStreamResponseChoice - choice.Delta.Content = claudeResponse.Completion - finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason) + choice.Delta.Content = claudeResponse.Delta.Text + finishReason := stopReasonClaude2OpenAI(claudeResponse.Delta.StopReason) if finishReason != "null" { choice.FinishReason = &finishReason } var response openai.ChatCompletionsStreamResponse response.Object = "chat.completion.chunk" - response.Model = claudeResponse.Model + // response.Model = claudeResponse.Model response.Choices = []openai.ChatCompletionsStreamResponseChoice{choice} return &response } @@ -70,10 +70,10 @@ func responseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse { Index: 0, Message: model.Message{ Role: "assistant", - Content: strings.TrimPrefix(claudeResponse.Completion, " "), + Content: strings.TrimPrefix(claudeResponse.Delta.Text, " "), Name: nil, }, - FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason), + FinishReason: stopReasonClaude2OpenAI(claudeResponse.Delta.StopReason), } fullTextResponse := openai.TextResponse{ Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), @@ -121,12 +121,31 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC // some implementations may add \r at the end of data data = strings.TrimSuffix(data, "\r") var claudeResponse Response + err := json.Unmarshal([]byte(data), &claudeResponse) if err != nil { logger.SysError("error unmarshalling stream response: " + err.Error()) return true } - responseText += claudeResponse.Completion + + switch claudeResponse.Type { + case TypeContentStart, TypePing, TypeMessageDelta: + return true + case TypeContentStop, TypeMessageStop: + if claudeResponse.Delta.StopReason == "" { + claudeResponse.Delta.StopReason = "end_turn" + } + case TypeContent: + claudeResponse.Delta.StopReason = "null" + case TypeError: + logger.SysError("error response: " + claudeResponse.Error.Message) + return false + default: + logger.SysError("unknown response type: " + string(data)) + return true + } + + responseText += claudeResponse.Delta.Text response := streamResponseClaude2OpenAI(&claudeResponse) response.Id = responseId response.Created = createdTime @@ -176,7 +195,7 @@ func Handler(c *gin.Context, resp *http.Response, promptTokens int, modelName st } fullTextResponse := responseClaude2OpenAI(&claudeResponse) fullTextResponse.Model = modelName - completionTokens := openai.CountTokenText(claudeResponse.Completion, modelName) + completionTokens := openai.CountTokenText(claudeResponse.Delta.Text, modelName) usage := model.Usage{ PromptTokens: promptTokens, CompletionTokens: completionTokens, diff --git a/relay/channel/anthropic/model.go b/relay/channel/anthropic/model.go index bc718449..3efd2fef 100644 --- a/relay/channel/anthropic/model.go +++ b/relay/channel/anthropic/model.go @@ -19,9 +19,30 @@ type Error struct { Message string `json:"message"` } +type ResponseType string + +const ( + TypeError ResponseType = "error" + TypeStart ResponseType = "message_start" + TypeContentStart ResponseType = "content_block_start" + TypeContent ResponseType = "content_block_delta" + TypePing ResponseType = "ping" + TypeContentStop ResponseType = "content_block_stop" + TypeMessageDelta ResponseType = "message_delta" + TypeMessageStop ResponseType = "message_stop" +) + +// https://docs.anthropic.com/claude/reference/messages-streaming type Response struct { - Completion string `json:"completion"` - StopReason string `json:"stop_reason"` - Model string `json:"model"` - Error Error `json:"error"` + Type ResponseType `json:"type"` + Index int `json:"index,omitempty"` + Delta struct { + Type string `json:"type,omitempty"` + Text string `json:"text,omitempty"` + StopReason string `json:"stop_reason,omitempty"` + } `json:"delta,omitempty"` + Error struct { + Type string `json:"type"` + Message string `json:"message"` + } `json:"error,omitempty"` } From bcd5cf3d5fa2a031a23dbc4a95897bc6d890e531 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 08:17:14 +0000 Subject: [PATCH 070/170] refactor: Refactor: Improve Anthropic event stream response handling - Move `scanner.Split` instantiation to a new function - Introduce a new regular expression to extract data from the response - Utilize regular expressions to pre-process the event stream --- relay/channel/anthropic/main.go | 48 +++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/relay/channel/anthropic/main.go b/relay/channel/anthropic/main.go index 4ca850db..e3bad60b 100644 --- a/relay/channel/anthropic/main.go +++ b/relay/channel/anthropic/main.go @@ -4,15 +4,17 @@ import ( "bufio" "encoding/json" "fmt" + "io" + "net/http" + "regexp" + "strings" + "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/relay/channel/openai" "github.com/songquanpeng/one-api/relay/model" - "io" - "net/http" - "strings" ) func stopReasonClaude2OpenAI(reason string) string { @@ -84,34 +86,40 @@ func responseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse { return &fullTextResponse } +var dataRegexp = regexp.MustCompile(`^data: (\{.*\})\B`) + func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, string) { responseText := "" responseId := fmt.Sprintf("chatcmpl-%s", helper.GetUUID()) createdTime := helper.GetTimestamp() 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), "\r\n\r\n"); i >= 0 { - return i + 4, data[0:i], nil - } - if atEOF { - return len(data), data, nil - } - return 0, nil, nil - }) + // 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), "\r\n\r\n"); i >= 0 { + // return i + 4, 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 !strings.HasPrefix(data, "event: completion") { - continue + data := strings.TrimSpace(scanner.Text()) + // logger.SysLog(fmt.Sprintf("stream response: %s", data)) + + matched := dataRegexp.FindAllStringSubmatch(data, -1) + for _, match := range matched { + data = match[1] + // logger.SysLog(fmt.Sprintf("chunk response: %s", data)) + dataChan <- data } - data = strings.TrimPrefix(data, "event: completion\r\ndata: ") - dataChan <- data } + stopChan <- true }() common.SetEventStreamHeaders(c) From ba9b258a4b175066482ce7cc8305f65c2ee215b7 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 5 Mar 2024 13:07:07 +0000 Subject: [PATCH 071/170] feat: Enhance security and fix bugs in authentication - Update the minimum access token length from 16 to 32 - Prevent spam by introducing policies and detecting user agents - Add an authorization header to the login response - Use base64 to decode the session secret and generate a random one if not set --- common/config/config.go | 20 +++++++++++++++++--- controller/user.go | 6 ++++++ main.go | 8 +++++++- middleware/auth.go | 9 +++++++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/common/config/config.go b/common/config/config.go index dd0236b4..44327139 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -1,15 +1,29 @@ package config import ( - "github.com/songquanpeng/one-api/common/helper" + "crypto/rand" + "encoding/base64" + "fmt" "os" "strconv" "sync" "time" - "github.com/google/uuid" + "github.com/songquanpeng/one-api/common/helper" ) +func init() { + if SessionSecret == "" { + fmt.Println("SESSION_SECRET not set, using random secret") + key := make([]byte, 32) + if _, err := rand.Read(key); err != nil { + panic(fmt.Sprintf("failed to generate random secret: %v", err)) + } + + SessionSecret = base64.StdEncoding.EncodeToString(key) + } +} + var SystemName = "One API" var ServerAddress = "http://localhost:3000" var Footer = "" @@ -22,7 +36,7 @@ var DisplayTokenStatEnabled = true // Any options with "Secret", "Token" in its key won't be return by GetOptions -var SessionSecret = uuid.New().String() +var SessionSecret = os.Getenv("SESSION_SECRET") var OptionMap map[string]string var OptionMapRWMutex sync.RWMutex diff --git a/controller/user.go b/controller/user.go index 243980e8..e6aec36a 100644 --- a/controller/user.go +++ b/controller/user.go @@ -76,6 +76,12 @@ func setupLogin(user *model.User, c *gin.Context) { }) return } + + // set auth header + // c.Set("id", user.Id) + // GenerateAccessToken(c) + // c.Header("Authorization", user.AccessToken) + cleanUser := model.User{ Id: user.Id, Username: user.Username, diff --git a/main.go b/main.go index 4fbbf2a2..a023e90f 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "embed" + "encoding/base64" "fmt" "os" "strconv" @@ -94,7 +95,12 @@ func main() { server.Use(middleware.RequestId()) middleware.SetUpLogger(server) // Initialize session store - store := cookie.NewStore([]byte(config.SessionSecret)) + sessionSecret, err := base64.StdEncoding.DecodeString(config.SessionSecret) + if err != nil { + panic(fmt.Sprintf("failed to decode session secret: %v", err)) + } + + store := cookie.NewStore(sessionSecret, sessionSecret) server.Use(sessions.Sessions("session", store)) router.SetRouter(server, buildFS) diff --git a/middleware/auth.go b/middleware/auth.go index b1f16f8e..9e413635 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -1,12 +1,14 @@ package middleware import ( + "net/http" + "strings" + "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" + "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/model" - "net/http" - "strings" ) func authHelper(c *gin.Context, minRole int) { @@ -16,6 +18,7 @@ func authHelper(c *gin.Context, minRole int) { id := session.Get("id") status := session.Get("status") if username == nil { + logger.SysLog("no user session found, try to use access token") // Check access token accessToken := c.Request.Header.Get("Authorization") if accessToken == "" { @@ -26,6 +29,7 @@ func authHelper(c *gin.Context, minRole int) { c.Abort() return } + user := model.ValidateAccessToken(accessToken) if user != nil && user.Username != "" { // Token is valid @@ -42,6 +46,7 @@ func authHelper(c *gin.Context, minRole int) { return } } + if status.(int) == common.UserStatusDisabled { c.JSON(http.StatusOK, gin.H{ "success": false, From 849920b91f0fbd17cad0e96c6f734735daffc5e6 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 8 Mar 2024 02:32:47 +0000 Subject: [PATCH 072/170] fix: Improve error handling and add checks in relay.go - Refactor error handling in `relay.go` using the `errors` package - Implement specific error handling for `specific_channel_id` in `shouldRetry` function - Add checks for various status codes (`StatusTooManyRequests`, `Status5xx`, `Status2xx`) in `Relay` function - Improve code quality by adding missing imports (`io`, `net/http`) in `relay.go` --- controller/relay.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/controller/relay.go b/controller/relay.go index 7cbbc970..d6997094 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -4,7 +4,11 @@ import ( "bytes" "context" "fmt" + "io" + "net/http" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" @@ -15,8 +19,6 @@ import ( "github.com/songquanpeng/one-api/relay/controller" "github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" ) // https://platform.openai.com/docs/api-reference/chat @@ -57,8 +59,8 @@ func Relay(c *gin.Context) { go processChannelRelayError(ctx, channelId, channelName, bizErr) requestId := c.GetString(logger.RequestIdKey) retryTimes := config.RetryTimes - if !shouldRetry(c, bizErr.StatusCode) { - logger.Errorf(ctx, "relay error happen, status code is %d, won't retry in this case", bizErr.StatusCode) + if err := shouldRetry(c, bizErr.StatusCode); err != nil { + logger.Errorf(ctx, "relay error happen, won't retry since of %v", err.Error()) retryTimes = 0 } for i := retryTimes; i > 0; i-- { @@ -94,23 +96,23 @@ func Relay(c *gin.Context) { } } -func shouldRetry(c *gin.Context, statusCode int) bool { - if _, ok := c.Get("specific_channel_id"); ok { - return false +func shouldRetry(c *gin.Context, statusCode int) error { + if v, ok := c.Get("specific_channel_id"); ok { + return errors.Errorf("specific channel = %v", v) } if statusCode == http.StatusTooManyRequests { - return true + return nil } if statusCode/100 == 5 { - return true + return nil } if statusCode == http.StatusBadRequest { - return false + return errors.Errorf("status code = %d", statusCode) } if statusCode/100 == 2 { - return false + return errors.Errorf("status code = %d", statusCode) } - return true + return nil } func processChannelRelayError(ctx context.Context, channelId int, channelName string, err *model.ErrorWithStatusCode) { From cad6f9d3ba04643c059d4421d9339c7a46881683 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 8 Mar 2024 03:22:29 +0000 Subject: [PATCH 073/170] fix: Improve quota consumption and logging in relay controller - Modify `PostConsumeQuota` function in `common.go` to check if `totalQuota` is less than 0 and add error logging - Update log content in `RecordConsumeLog` function of `common.go` to include model ratio and group ratio - Change the method of calculating user used quota and request count in `UpdateUserUsedQuotaAndRequestCount` function of `common.go` - Implement calculation of `completionRatio` and update `quota` calculation based on `completionRatio` in `postConsumeQuota` function of `helper.go` - Implement error handling for `model.PostConsumeTokenQuota` and `model.CacheUpdateUserQuota` in `helper.go` - Update `UserUsedQuota` and `ChannelUsedQuota` with new `quota` value in `helper.go` --- relay/controller/helper.go | 1 + relay/util/common.go | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/relay/controller/helper.go b/relay/controller/helper.go index 89fc69ce..5f452c16 100644 --- a/relay/controller/helper.go +++ b/relay/controller/helper.go @@ -172,6 +172,7 @@ func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *util.R if err != nil { logger.Error(ctx, "error update user quota cache: "+err.Error()) } + logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f,补全倍率 %.2f", modelRatio, groupRatio, completionRatio) model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, promptTokens, completionTokens, textRequest.Model, meta.TokenName, quota, logContent) model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota) diff --git a/relay/util/common.go b/relay/util/common.go index 6d993378..2eef88a6 100644 --- a/relay/util/common.go +++ b/relay/util/common.go @@ -147,13 +147,14 @@ func PostConsumeQuota(ctx context.Context, tokenId int, quotaDelta int, totalQuo logger.SysError("error update user quota cache: " + err.Error()) } // totalQuota is total quota consumed - if totalQuota != 0 { + if totalQuota >= 0 { logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) model.RecordConsumeLog(ctx, userId, channelId, totalQuota, 0, modelName, tokenName, totalQuota, logContent) model.UpdateUserUsedQuotaAndRequestCount(userId, totalQuota) model.UpdateChannelUsedQuota(channelId, totalQuota) } - if totalQuota <= 0 { + + if totalQuota < 0 { logger.Error(ctx, fmt.Sprintf("totalQuota consumed is %d, something is wrong", totalQuota)) } } From 758378e45a4906abcbae4f82d48ba85a215bd326 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 8 Mar 2024 03:42:58 +0000 Subject: [PATCH 074/170] fix: Improve Image Generation Request Handling and Error Messaging - Implemented error messages with request details and request parameter validation for image requests - Added URL logic and Azure channel support for image generation requests - Integrated quota management and updated quota usage logging for image generation requests - Mapped original model names to mapped model names and unmarshalled image responses into a struct for further processing --- relay/controller/image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay/controller/image.go b/relay/controller/image.go index 339505b6..b5f0dc7c 100644 --- a/relay/controller/image.go +++ b/relay/controller/image.go @@ -131,7 +131,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus if err != nil { logger.SysError("error update user quota cache: " + err.Error()) } - if quota != 0 { + if quota >= 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, 0, 0, imageRequest.Model, tokenName, quota, logContent) From 577ec90736b2371685d9718c7a44d5cafd19fd06 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 8 Mar 2024 12:58:05 +0000 Subject: [PATCH 075/170] fix: Initialize transport object, set timeouts, and add IdleTimeout config - Introduced idle connection timeout for HTTP client transport - Implemented custom timeout for HTTP client when `RelayTimeout` is 0 - Set ImpatientHTTPClient timeout to 5 seconds - Added new config variable `IdleTimeout` with default value of 30 seconds - Utilized shared transport object for all HTTP clients in relay/util package --- common/config/config.go | 1 + relay/util/init.go | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/common/config/config.go b/common/config/config.go index 44327139..5bd75d47 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -110,6 +110,7 @@ var BatchUpdateEnabled = false var BatchUpdateInterval = helper.GetOrDefaultEnvInt("BATCH_UPDATE_INTERVAL", 5) var RelayTimeout = helper.GetOrDefaultEnvInt("RELAY_TIMEOUT", 0) // unit is second +var IdleTimeout = helper.GetOrDefaultEnvInt("IDLE_TIMEOUT", 30) // unit is second var GeminiSafetySetting = helper.GetOrDefaultEnvString("GEMINI_SAFETY_SETTING", "BLOCK_NONE") diff --git a/relay/util/init.go b/relay/util/init.go index 03dad31b..cc3af6d9 100644 --- a/relay/util/init.go +++ b/relay/util/init.go @@ -10,15 +10,24 @@ var HTTPClient *http.Client var ImpatientHTTPClient *http.Client func init() { + + tp := &http.Transport{ + IdleConnTimeout: time.Duration(config.IdleTimeout) * time.Second, + } + if config.RelayTimeout == 0 { - HTTPClient = &http.Client{} + HTTPClient = &http.Client{ + Transport: tp, + } } else { HTTPClient = &http.Client{ - Timeout: time.Duration(config.RelayTimeout) * time.Second, + Transport: tp, + Timeout: time.Duration(config.RelayTimeout) * time.Second, } } ImpatientHTTPClient = &http.Client{ - Timeout: 5 * time.Second, + Transport: tp, + Timeout: 5 * time.Second, } } From 82b2f2c427c32c4a84623c83bc92ee5f3dec05c5 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sat, 9 Mar 2024 01:37:30 +0000 Subject: [PATCH 076/170] fix --- controller/model.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller/model.go b/controller/model.go index 35f4e191..73b14c74 100644 --- a/controller/model.go +++ b/controller/model.go @@ -149,6 +149,10 @@ func init() { channelId2Models = make(map[int][]string) for i := 1; i < common.ChannelTypeDummy; i++ { adaptor := helper.GetAdaptor(constant.ChannelType2APIType(i)) + if adaptor == nil { + continue + } + meta := &util.RelayMeta{ ChannelType: i, } From 914f1ccd8c17760ff77f8132b8cb69de70d734c7 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Mon, 11 Mar 2024 09:32:25 +0000 Subject: [PATCH 077/170] fix: Update Dockerfile with newer golang version and base image. - Updated the Golang version from 1.22.0 to 1.22.1 in the Dockerfile. - Changed the base image version from golang:1.22.0-bullseye to golang:1.22.1-bullseye in the Dockerfile. - The updates to the Dockerfile include important security and bug fixes, as well as potential performance improvements. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8b9df1f3..cf8482af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ WORKDIR /web/berry RUN npm install RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat VERSION) npm run build -FROM golang:1.22.0-bullseye AS builder2 +FROM golang:1.22.1-bullseye AS builder2 ENV GO111MODULE=on \ CGO_ENABLED=1 \ From 54203e3d30fa7e0d4cc3dc637bf958ccf579b3e4 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 12 Mar 2024 06:40:23 +0000 Subject: [PATCH 078/170] fix: Update error handling to Laisky/errors/v2 package across project - Updated error handling across multiple files with `Laisky/errors/v2` package - Replaced hardcoded error messages with `Laisky/errors` in relay/channel/tencent/adaptor.go - Added a function to check if a request should be retried in relay/controller/relay.go - Removed unused imports and variables, and updated comments in various files - Changed Redis cache handling in model/cache.go - Refactored error handling in relay/channel/tencent/main.go and relay/channel/baidu/main.go - Updated import paths and error handling in model/user.go, model/redemption.go, and controller/github.go - Added import for tiktoken-go package in relay/channel/openai/token.go - Added GetSign and ParseConfig functions in relay/channel/tencent/main.go - Replaced specific error imports with a more general one in relay/channel/ali/adaptor.go - Updated import comments and function calls in relay/channel/ali/adaptor.go - Added checks and custom error messages in model/token.go - Removed unused functions and variables in relay/channel/baidu/adaptor.go - Imported "github.com/Laisky/errors/v2" package in controller/channel-billing.go - Removed unused import packages in [relay/channel/tencent/adaptor.go](http://relay/channel/tencent/adaptor.go) and relay/channel/palm/adaptor.go - Updated go.mod and go.sum files with new dependencies and versions --- common/message/message-pusher.go | 2 +- controller/channel-billing.go | 2 +- controller/channel-test.go | 2 +- controller/github.go | 2 +- controller/relay.go | 8 ++------ controller/wechat.go | 2 +- go.mod | 1 + go.sum | 2 ++ model/cache.go | 3 ++- model/redemption.go | 2 +- model/token.go | 9 ++++++--- model/user.go | 2 +- relay/channel/aiproxy/adaptor.go | 2 +- relay/channel/ali/adaptor.go | 2 +- relay/channel/anthropic/adaptor.go | 2 +- relay/channel/baidu/adaptor.go | 2 +- relay/channel/baidu/main.go | 2 +- relay/channel/common.go | 2 +- relay/channel/gemini/adaptor.go | 2 +- relay/channel/openai/adaptor.go | 2 +- relay/channel/openai/token.go | 2 +- relay/channel/palm/adaptor.go | 2 +- relay/channel/tencent/adaptor.go | 2 +- relay/channel/tencent/main.go | 2 +- relay/channel/xunfei/adaptor.go | 2 +- relay/channel/zhipu/adaptor.go | 2 +- relay/controller/audio.go | 2 +- relay/controller/helper.go | 2 +- relay/controller/image.go | 2 +- relay/util/validation.go | 2 +- 30 files changed, 38 insertions(+), 35 deletions(-) diff --git a/common/message/message-pusher.go b/common/message/message-pusher.go index 69949b4b..e693ec26 100644 --- a/common/message/message-pusher.go +++ b/common/message/message-pusher.go @@ -3,7 +3,7 @@ package message import ( "bytes" "encoding/json" - "errors" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common/config" "net/http" ) diff --git a/controller/channel-billing.go b/controller/channel-billing.go index 03c97349..3d2781a2 100644 --- a/controller/channel-billing.go +++ b/controller/channel-billing.go @@ -2,8 +2,8 @@ package controller import ( "encoding/json" - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" diff --git a/controller/channel-test.go b/controller/channel-test.go index 6d18305a..6df0bb7a 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -3,8 +3,8 @@ package controller import ( "bytes" "encoding/json" - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" diff --git a/controller/github.go b/controller/github.go index 7d7fa106..fc674852 100644 --- a/controller/github.go +++ b/controller/github.go @@ -3,8 +3,8 @@ package controller import ( "bytes" "encoding/json" - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" diff --git a/controller/relay.go b/controller/relay.go index 7bb6ade2..6d071565 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -98,16 +98,12 @@ func Relay(c *gin.Context) { } } +// shouldRetry returns nil if should retry, otherwise returns error func shouldRetry(c *gin.Context, statusCode int) error { if v, ok := c.Get("specific_channel_id"); ok { return errors.Errorf("specific channel = %v", v) } - if statusCode == http.StatusTooManyRequests { - return nil - } - if statusCode/100 == 5 { - return nil - } + if statusCode == http.StatusBadRequest { return errors.Errorf("status code = %d", statusCode) } diff --git a/controller/wechat.go b/controller/wechat.go index 74be5604..8f997bfb 100644 --- a/controller/wechat.go +++ b/controller/wechat.go @@ -2,8 +2,8 @@ package controller import ( "encoding/json" - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" diff --git a/go.mod b/go.mod index c0adb67c..86d9c68f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/songquanpeng/one-api go 1.21 require ( + github.com/Laisky/errors/v2 v2.0.1 github.com/gin-contrib/cors v1.5.0 github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/sessions v0.0.5 diff --git a/go.sum b/go.sum index 8ab19e07..8f0a5b38 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Laisky/errors/v2 v2.0.1 h1:yqCBrRzaP012AMB+7fVlXrP34OWRHrSO/hZ38CFdH84= +github.com/Laisky/errors/v2 v2.0.1/go.mod h1:mTn1LHSmKm4CYug0rpYO7rz13dp/DKrtzlSELSrxvT0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= diff --git a/model/cache.go b/model/cache.go index 3c3575b8..d0c74f52 100644 --- a/model/cache.go +++ b/model/cache.go @@ -2,8 +2,8 @@ package model import ( "encoding/json" - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" @@ -48,6 +48,7 @@ func CacheGetTokenByKey(key string) (*Token, error) { } return &token, nil } + err = json.Unmarshal([]byte(tokenObjectString), &token) return &token, err } diff --git a/model/redemption.go b/model/redemption.go index 2c5a4141..b2493622 100644 --- a/model/redemption.go +++ b/model/redemption.go @@ -1,8 +1,8 @@ package model import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/helper" "gorm.io/gorm" diff --git a/model/token.go b/model/token.go index c4669e0b..68fbd847 100644 --- a/model/token.go +++ b/model/token.go @@ -1,8 +1,9 @@ package model import ( - "errors" "fmt" + + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" @@ -45,10 +46,12 @@ func ValidateUserToken(key string) (token *Token, err error) { if err != nil { logger.SysError("CacheGetTokenByKey failed: " + err.Error()) if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, errors.New("无效的令牌") + return nil, errors.Wrap(err, "token not found") } - return nil, errors.New("令牌验证失败") + + return nil, errors.Wrap(err, "failed to get token by key") } + if token.Status == common.TokenStatusExhausted { return nil, errors.New("该令牌额度已用尽") } else if token.Status == common.TokenStatusExpired { diff --git a/model/user.go b/model/user.go index fd3cadbb..7935823d 100644 --- a/model/user.go +++ b/model/user.go @@ -1,8 +1,8 @@ package model import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/blacklist" "github.com/songquanpeng/one-api/common/config" diff --git a/relay/channel/aiproxy/adaptor.go b/relay/channel/aiproxy/adaptor.go index 2b4e3022..6f5d289f 100644 --- a/relay/channel/aiproxy/adaptor.go +++ b/relay/channel/aiproxy/adaptor.go @@ -1,8 +1,8 @@ package aiproxy import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/relay/channel" diff --git a/relay/channel/ali/adaptor.go b/relay/channel/ali/adaptor.go index 7b40aafb..b312934b 100644 --- a/relay/channel/ali/adaptor.go +++ b/relay/channel/ali/adaptor.go @@ -1,7 +1,7 @@ package ali // import ( -// "errors" +// "github.com/Laisky/errors/v2" // "fmt" // "github.com/gin-gonic/gin" // "github.com/songquanpeng/one-api/common" diff --git a/relay/channel/anthropic/adaptor.go b/relay/channel/anthropic/adaptor.go index 75e24924..9f1adb9a 100644 --- a/relay/channel/anthropic/adaptor.go +++ b/relay/channel/anthropic/adaptor.go @@ -1,8 +1,8 @@ package anthropic import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "io" "net/http" diff --git a/relay/channel/baidu/adaptor.go b/relay/channel/baidu/adaptor.go index 75b4c98a..7d756d35 100644 --- a/relay/channel/baidu/adaptor.go +++ b/relay/channel/baidu/adaptor.go @@ -1,7 +1,7 @@ package baidu // import ( -// "errors" +// "github.com/Laisky/errors/v2" // "github.com/gin-gonic/gin" // "github.com/songquanpeng/one-api/relay/channel" // "github.com/songquanpeng/one-api/relay/constant" diff --git a/relay/channel/baidu/main.go b/relay/channel/baidu/main.go index 517b6467..e8ff7588 100644 --- a/relay/channel/baidu/main.go +++ b/relay/channel/baidu/main.go @@ -3,7 +3,7 @@ package baidu // import ( // "bufio" // "encoding/json" -// "errors" +// "github.com/Laisky/errors/v2" // "fmt" // "github.com/gin-gonic/gin" // "io" diff --git a/relay/channel/common.go b/relay/channel/common.go index c6e1abf2..a848a0e3 100644 --- a/relay/channel/common.go +++ b/relay/channel/common.go @@ -1,8 +1,8 @@ package channel import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/relay/util" "io" diff --git a/relay/channel/gemini/adaptor.go b/relay/channel/gemini/adaptor.go index f3305e5d..1cba1c00 100644 --- a/relay/channel/gemini/adaptor.go +++ b/relay/channel/gemini/adaptor.go @@ -1,8 +1,8 @@ package gemini import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common/helper" channelhelper "github.com/songquanpeng/one-api/relay/channel" diff --git a/relay/channel/openai/adaptor.go b/relay/channel/openai/adaptor.go index 47594030..5b7c639d 100644 --- a/relay/channel/openai/adaptor.go +++ b/relay/channel/openai/adaptor.go @@ -1,8 +1,8 @@ package openai import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/relay/channel" diff --git a/relay/channel/openai/token.go b/relay/channel/openai/token.go index ef98b272..d18ce0df 100644 --- a/relay/channel/openai/token.go +++ b/relay/channel/openai/token.go @@ -1,8 +1,8 @@ package openai import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/pkoukk/tiktoken-go" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" diff --git a/relay/channel/palm/adaptor.go b/relay/channel/palm/adaptor.go index efd0620c..15ee010d 100644 --- a/relay/channel/palm/adaptor.go +++ b/relay/channel/palm/adaptor.go @@ -1,8 +1,8 @@ package palm import ( - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/relay/channel" "github.com/songquanpeng/one-api/relay/channel/openai" diff --git a/relay/channel/tencent/adaptor.go b/relay/channel/tencent/adaptor.go index 4974ef0c..a9d62e2f 100644 --- a/relay/channel/tencent/adaptor.go +++ b/relay/channel/tencent/adaptor.go @@ -1,7 +1,7 @@ package tencent // import ( -// "errors" +// "github.com/Laisky/errors/v2" // "fmt" // "github.com/gin-gonic/gin" // "github.com/songquanpeng/one-api/relay/channel" diff --git a/relay/channel/tencent/main.go b/relay/channel/tencent/main.go index 34cedb53..aa87e9ce 100644 --- a/relay/channel/tencent/main.go +++ b/relay/channel/tencent/main.go @@ -6,7 +6,7 @@ package tencent // "crypto/sha1" // "encoding/base64" // "encoding/json" -// "errors" +// "github.com/Laisky/errors/v2" // "fmt" // "github.com/gin-gonic/gin" // "github.com/songquanpeng/one-api/common" diff --git a/relay/channel/xunfei/adaptor.go b/relay/channel/xunfei/adaptor.go index 17420805..9b112f7e 100644 --- a/relay/channel/xunfei/adaptor.go +++ b/relay/channel/xunfei/adaptor.go @@ -1,7 +1,7 @@ package xunfei // import ( -// "errors" +// "github.com/Laisky/errors/v2" // "github.com/gin-gonic/gin" // "github.com/songquanpeng/one-api/relay/channel" // "github.com/songquanpeng/one-api/relay/channel/openai" diff --git a/relay/channel/zhipu/adaptor.go b/relay/channel/zhipu/adaptor.go index c1dc491c..9c7bdd36 100644 --- a/relay/channel/zhipu/adaptor.go +++ b/relay/channel/zhipu/adaptor.go @@ -1,7 +1,7 @@ package zhipu // import ( -// "errors" +// "github.com/Laisky/errors/v2" // "fmt" // "github.com/gin-gonic/gin" // "github.com/songquanpeng/one-api/relay/channel" diff --git a/relay/controller/audio.go b/relay/controller/audio.go index bd39944f..9d0d122f 100644 --- a/relay/controller/audio.go +++ b/relay/controller/audio.go @@ -5,8 +5,8 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" diff --git a/relay/controller/helper.go b/relay/controller/helper.go index 5f452c16..1074737d 100644 --- a/relay/controller/helper.go +++ b/relay/controller/helper.go @@ -2,8 +2,8 @@ package controller import ( "context" - "errors" "fmt" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" diff --git a/relay/controller/image.go b/relay/controller/image.go index b5f0dc7c..5e946c81 100644 --- a/relay/controller/image.go +++ b/relay/controller/image.go @@ -4,8 +4,8 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" + "github.com/Laisky/errors/v2" "io" "net/http" "strings" diff --git a/relay/util/validation.go b/relay/util/validation.go index ef8d840c..b9d25c2a 100644 --- a/relay/util/validation.go +++ b/relay/util/validation.go @@ -1,7 +1,7 @@ package util import ( - "errors" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" "math" From d953da1ff21c7fd56897042795870178b00139ff Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 12 Mar 2024 06:43:54 +0000 Subject: [PATCH 079/170] fix: Upgrade error handling to `errors/v2` package and refactor error messages. - Refactor error handling using `errors/v2` package for improved error management and formatting - Implement changes in relay/controller/text.go, controller/channel-test.go, controller/relay.go, common/message/email.go, relay/controller/audio.go, controller/channel-billing.go, relay/controller/helper.go, relay/channel/common.go, relay/channel/minimax/main.go, and common/message/main.go - Add package imports and update package paths for better code organization - Replace hardcoded strings with error variables for consistency and readability - Implement secure methods for generating unique Message-ID - Improve SMTP handling and authentication in email.go - Update response handling and error messages for better user experience - Add constant checks and modify functions based on imageRequest parameters in helper.go - Use `http.NewRequest` instead of `fmt.Fprint` for improved request handling in relay/channel/common.go - Update error handling and formatting for better consistency across all files --- common/message/email.go | 6 ++++-- common/message/main.go | 4 ++-- controller/channel-billing.go | 4 ++-- controller/channel-test.go | 6 +++--- controller/relay.go | 1 + relay/channel/common.go | 14 +++++++------- relay/channel/minimax/main.go | 4 +++- relay/controller/audio.go | 14 ++++++++------ relay/controller/helper.go | 2 +- relay/controller/text.go | 10 ++++++---- 10 files changed, 37 insertions(+), 28 deletions(-) diff --git a/common/message/email.go b/common/message/email.go index fe5d8704..585aa37a 100644 --- a/common/message/email.go +++ b/common/message/email.go @@ -5,15 +5,17 @@ import ( "crypto/tls" "encoding/base64" "fmt" - "github.com/songquanpeng/one-api/common/config" "net/smtp" "strings" "time" + + "github.com/Laisky/errors/v2" + "github.com/songquanpeng/one-api/common/config" ) func SendEmail(subject string, receiver string, content string) error { if receiver == "" { - return fmt.Errorf("receiver is empty") + return errors.Errorf("receiver is empty") } if config.SMTPFrom == "" { // for compatibility config.SMTPFrom = config.SMTPAccount diff --git a/common/message/main.go b/common/message/main.go index 5ce82a64..068426fd 100644 --- a/common/message/main.go +++ b/common/message/main.go @@ -1,7 +1,7 @@ package message import ( - "fmt" + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/common/config" ) @@ -18,5 +18,5 @@ func Notify(by string, title string, description string, content string) error { if by == ByMessagePusher { return SendMessage(title, description, content) } - return fmt.Errorf("unknown notify method: %s", by) + return errors.Errorf("unknown notify method: %s", by) } diff --git a/controller/channel-billing.go b/controller/channel-billing.go index 3d2781a2..b9a3908e 100644 --- a/controller/channel-billing.go +++ b/controller/channel-billing.go @@ -101,7 +101,7 @@ func GetResponseBody(method, url string, channel *model.Channel, headers http.He return nil, err } if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("status code: %d", res.StatusCode) + return nil, errors.Errorf("status code: %d", res.StatusCode) } body, err := io.ReadAll(res.Body) if err != nil { @@ -166,7 +166,7 @@ func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) { return 0, err } if !response.Success { - return 0, fmt.Errorf("code: %d, message: %s", response.ErrorCode, response.Message) + return 0, errors.Errorf("code: %d, message: %s", response.ErrorCode, response.Message) } channel.UpdateBalance(response.Data.TotalPoints) return response.Data.TotalPoints, nil diff --git a/controller/channel-test.go b/controller/channel-test.go index 6df0bb7a..e982bc71 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -60,7 +60,7 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error apiType := constant.ChannelType2APIType(channel.Type) adaptor := helper.GetAdaptor(apiType) if adaptor == nil { - return fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), nil + return errors.Errorf("invalid api type: %d, adaptor is nil", apiType), nil } adaptor.Init(meta) modelName := adaptor.GetModelList()[0] @@ -89,11 +89,11 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error } if resp.StatusCode != http.StatusOK { err := util.RelayErrorHandler(resp) - return fmt.Errorf("status code %d: %s", resp.StatusCode, err.Error.Message), &err.Error + return errors.Errorf("status code %d: %s", resp.StatusCode, err.Error.Message), &err.Error } usage, respErr := adaptor.DoResponse(c, resp, meta) if respErr != nil { - return fmt.Errorf("%s", respErr.Error.Message), &respErr.Error + return errors.Errorf("%s", respErr.Error.Message), &respErr.Error } if usage == nil { return errors.New("usage is nil"), nil diff --git a/controller/relay.go b/controller/relay.go index 6d071565..85932368 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -110,6 +110,7 @@ func shouldRetry(c *gin.Context, statusCode int) error { if statusCode/100 == 2 { return errors.Errorf("status code = %d", statusCode) } + return nil } diff --git a/relay/channel/common.go b/relay/channel/common.go index a848a0e3..e03c1055 100644 --- a/relay/channel/common.go +++ b/relay/channel/common.go @@ -1,12 +1,12 @@ package channel import ( - "fmt" + "io" + "net/http" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" ) func SetupCommonRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) { @@ -20,19 +20,19 @@ func SetupCommonRequestHeader(c *gin.Context, req *http.Request, meta *util.Rela func DoRequestHelper(a Adaptor, c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { fullRequestURL, err := a.GetRequestURL(meta) if err != nil { - return nil, fmt.Errorf("get request url failed: %w", err) + return nil, errors.Wrap(err, "get request url failed") } req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody) if err != nil { - return nil, fmt.Errorf("new request failed: %w", err) + return nil, errors.Wrap(err, "new request failed") } err = a.SetupRequestHeader(c, req, meta) if err != nil { - return nil, fmt.Errorf("setup request header failed: %w", err) + return nil, errors.Wrap(err, "setup request header failed") } resp, err := DoRequest(c, req) if err != nil { - return nil, fmt.Errorf("do request failed: %w", err) + return nil, errors.Wrap(err, "do request failed") } return resp, nil } diff --git a/relay/channel/minimax/main.go b/relay/channel/minimax/main.go index a01821c2..a3cd0f14 100644 --- a/relay/channel/minimax/main.go +++ b/relay/channel/minimax/main.go @@ -2,6 +2,8 @@ package minimax import ( "fmt" + + "github.com/Laisky/errors/v2" "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/util" ) @@ -10,5 +12,5 @@ func GetRequestURL(meta *util.RelayMeta) (string, error) { if meta.Mode == constant.RelayModeChatCompletions { return fmt.Sprintf("%s/v1/text/chatcompletion_v2", meta.BaseURL), nil } - return "", fmt.Errorf("unsupported relay mode %d for minimax", meta.Mode) + return "", errors.Errorf("unsupported relay mode %d for minimax", meta.Mode) } diff --git a/relay/controller/audio.go b/relay/controller/audio.go index 9d0d122f..beea6184 100644 --- a/relay/controller/audio.go +++ b/relay/controller/audio.go @@ -6,6 +6,10 @@ import ( "context" "encoding/json" "fmt" + "io" + "net/http" + "strings" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" @@ -16,9 +20,6 @@ import ( "github.com/songquanpeng/one-api/relay/constant" relaymodel "github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" - "strings" ) func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatusCode { @@ -162,7 +163,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus var openAIErr openai.SlimTextResponse if err = json.Unmarshal(responseBody, &openAIErr); err == nil { if openAIErr.Error.Message != "" { - return openai.ErrorWrapper(fmt.Errorf("type %s, code %v, message %s", openAIErr.Error.Type, openAIErr.Error.Code, openAIErr.Error.Message), "request_error", http.StatusInternalServerError) + return openai.ErrorWrapper(errors.Errorf("type %s, code %v, message %s", openAIErr.Error.Type, openAIErr.Error.Code, openAIErr.Error.Message), "request_error", http.StatusInternalServerError) } } @@ -230,8 +231,9 @@ func getTextFromVTT(body []byte) (string, error) { func getTextFromVerboseJSON(body []byte) (string, error) { var whisperResponse openai.WhisperVerboseJSONResponse if err := json.Unmarshal(body, &whisperResponse); err != nil { - return "", fmt.Errorf("unmarshal_response_body_failed err :%w", err) + return "", errors.Wrap(err, "unmarshal_response_body_failed") } + return whisperResponse.Text, nil } @@ -263,7 +265,7 @@ func getTextFromText(body []byte) (string, error) { func getTextFromJSON(body []byte) (string, error) { var whisperResponse openai.WhisperJSONResponse if err := json.Unmarshal(body, &whisperResponse); err != nil { - return "", fmt.Errorf("unmarshal_response_body_failed err :%w", err) + return "", errors.Wrap(err, "unmarshal_response_body_failed") } return whisperResponse.Text, nil } diff --git a/relay/controller/helper.go b/relay/controller/helper.go index 1074737d..a121a429 100644 --- a/relay/controller/helper.go +++ b/relay/controller/helper.go @@ -83,7 +83,7 @@ func getImageCostRatio(imageRequest *openai.ImageRequest) (float64, error) { } imageCostRatio, hasValidSize := constant.DalleSizeRatios[imageRequest.Model][imageRequest.Size] if !hasValidSize { - return 0, fmt.Errorf("size not supported for this image model: %s", imageRequest.Size) + return 0, errors.Errorf("size not supported for this image model: %s", imageRequest.Size) } if imageRequest.Quality == "hd" && imageRequest.Model == "dall-e-3" { if imageRequest.Size == "1024x1024" { diff --git a/relay/controller/text.go b/relay/controller/text.go index 12c28c9c..5dc07d23 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -4,6 +4,11 @@ import ( "bytes" "encoding/json" "fmt" + "io" + "net/http" + "strings" + + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/logger" @@ -12,9 +17,6 @@ import ( "github.com/songquanpeng/one-api/relay/helper" "github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" - "strings" ) func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { @@ -49,7 +51,7 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { adaptor := helper.GetAdaptor(meta.APIType) if adaptor == nil { - return openai.ErrorWrapper(fmt.Errorf("invalid api type: %d", meta.APIType), "invalid_api_type", http.StatusBadRequest) + return openai.ErrorWrapper(errors.Errorf("invalid api type: %d", meta.APIType), "invalid_api_type", http.StatusBadRequest) } // get request body From ddd2dd104182df76cd30327ebe461e960a58b947 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 19 Mar 2024 03:11:19 +0000 Subject: [PATCH 080/170] fix: Refactor relay/channel, upgrade deps, improve request handling and error messages. * Updated relay/channel/gemini package to use gin-gonic/gin for routing * Added timeouts, environment variable for proxy, and error handling for HTTP clients in relay/util/init.go * Improved error handling, URL path cases, and channel selection logic in middleware/distributor.go * Added Content-Type header, closed request bodies, and added context to requests in relay/channel/common.go * Upgraded various dependencies in go.mod * Modified the GetRequestURL method in relay/channel/gemini/adaptor.go to include a "key" parameter in the URL and set a default version of "v1beta" * Added io and net/http packages in relay/channel/gemini/adaptor.go and relay/channel/gemini/main.go * Added a struct for GeminiStreamResp and modified response handling in relay/channel/gemini/main.go * Imported packages for io and net/http added, gin-gonic/gin package added, and error handling improved in relay/channel/gemini/main.go --- common/gin.go | 3 +- go.mod | 64 +++++---- go.sum | 189 +++++++++++++++--------- middleware/distributor.go | 2 +- relay/channel/common.go | 8 +- relay/channel/gemini/adaptor.go | 10 +- relay/channel/gemini/main.go | 246 +++++++++++++++++++++++--------- relay/util/init.go | 33 +++-- 8 files changed, 371 insertions(+), 184 deletions(-) diff --git a/common/gin.go b/common/gin.go index b6ef96a6..33d6923c 100644 --- a/common/gin.go +++ b/common/gin.go @@ -3,9 +3,10 @@ package common import ( "bytes" "encoding/json" - "github.com/gin-gonic/gin" "io" "strings" + + "github.com/gin-gonic/gin" ) const KeyRequestBody = "key_request_body" diff --git a/go.mod b/go.mod index 25b88083..522219b9 100644 --- a/go.mod +++ b/go.mod @@ -4,64 +4,80 @@ go 1.21 require ( github.com/Laisky/errors/v2 v2.0.1 - github.com/gin-contrib/cors v1.5.0 + github.com/Laisky/go-utils/v4 v4.9.1 + github.com/gin-contrib/cors v1.7.0 github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/sessions v0.0.5 - github.com/gin-contrib/static v0.0.1 + github.com/gin-contrib/static v1.1.0 github.com/gin-gonic/gin v1.9.1 - github.com/go-playground/validator/v10 v10.16.0 + github.com/go-playground/validator/v10 v10.19.0 github.com/go-redis/redis/v8 v8.11.5 - github.com/google/uuid v1.5.0 + github.com/google/uuid v1.6.0 github.com/pkg/errors v0.9.1 github.com/pkoukk/tiktoken-go v0.1.6 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.21.0 golang.org/x/image v0.15.0 - gorm.io/driver/mysql v1.5.2 - gorm.io/driver/postgres v1.5.4 - gorm.io/driver/sqlite v1.5.4 - gorm.io/gorm v1.25.5 + gorm.io/driver/mysql v1.5.5 + gorm.io/driver/postgres v1.5.7 + gorm.io/driver/sqlite v1.5.5 + gorm.io/gorm v1.25.8 ) require ( - github.com/bytedance/sonic v1.10.1 // indirect + github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320 // indirect + github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4 // indirect + github.com/Laisky/fast-skiplist/v2 v2.0.1 // indirect + github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be // indirect + github.com/Laisky/graphql v1.0.6 // indirect + github.com/Laisky/zap v1.27.0 // indirect + github.com/bytedance/sonic v1.11.2 // indirect + github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.10.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gammazero/deque v0.2.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.5.4 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.5.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.15.0 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/tools v0.6.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index abc5ae16..1cfcf5e8 100644 --- a/go.sum +++ b/go.sum @@ -1,58 +1,84 @@ +github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320 h1:lddR7TsA0fUX8Kh+oc01z4GwmCoBveT79zhNLK43xLk= +github.com/GoWebProd/gip v0.0.0-20230623090727-b60d41d5d320/go.mod h1:eTC6ev1JFq+zoOOS5WKuHdBwtihV/9/Ouv3fZ3ufS0A= +github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4 h1:IjxKU4UMzoALLBo3JF7QNi5E0H22R2lDKT3RM9yNCQU= +github.com/GoWebProd/uuid7 v0.0.0-20231130161441-17ee54b097d4/go.mod h1:YIx3++ypr3VYDYlz62Zs6zxq/iPT5e9vShuqxwL/5Us= github.com/Laisky/errors/v2 v2.0.1 h1:yqCBrRzaP012AMB+7fVlXrP34OWRHrSO/hZ38CFdH84= github.com/Laisky/errors/v2 v2.0.1/go.mod h1:mTn1LHSmKm4CYug0rpYO7rz13dp/DKrtzlSELSrxvT0= +github.com/Laisky/fast-skiplist/v2 v2.0.1 h1:mZD3G/cwNovXsd21Vyvt3HCI9dEg1V7OD64qXsUUgpQ= +github.com/Laisky/fast-skiplist/v2 v2.0.1/go.mod h1:JlDGOmsJOwW7Uo46L9aVG7nJAeqP7X7nfU5TABOiiE8= +github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be h1:7Rxhm6IjOtDAyj8eScOFntevwzkWhx94zi48lxo4m4w= +github.com/Laisky/go-chaining v0.0.0-20180507092046-43dcdc5a21be/go.mod h1:1mdzaETo0kjvCQICPSePsoaatJN4l7JvEA1200lyevo= +github.com/Laisky/go-utils/v4 v4.9.1 h1:N69ef4pJD7vm21jndEYYfMUzWrC89qA1Ihg/1KsGD0A= +github.com/Laisky/go-utils/v4 v4.9.1/go.mod h1:tZuWhiAFTSdsoGwq5GjmeTtz1UOL8JsRbaq5sQ9IFQo= +github.com/Laisky/graphql v1.0.6 h1:NEULGxfxo+wbsW2OmqBXOMNUGgqo8uFjWNabwuNK10g= +github.com/Laisky/graphql v1.0.6/go.mod h1:zaKVqXmMQTnTkFJ2AA53oyBWMzlGCnzr3aodKTrtOxI= +github.com/Laisky/zap v1.27.0 h1:NEtFniRXOKUkEf9//FUiBkSIdgbB4V1H3khA/jmPZx4= +github.com/Laisky/zap v1.27.0/go.mod h1:HABqM5YDQlPq8w+Pmp9h/x9F6Vy+3oHBLP+2+pBoaJw= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/brianvoe/gofakeit/v6 v6.23.2 h1:lVde18uhad5wII/f5RMVFLtdQNE0HaGFuBUXmYKk8i8= +github.com/brianvoe/gofakeit/v6 v6.23.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= -github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= +github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= -github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= +github.com/gin-contrib/cors v1.7.0 h1:wZX2wuZ0o7rV2/1i7gb4Jn+gW7HBqaP91fizJkBUJOA= +github.com/gin-contrib/cors v1.7.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U= -github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-contrib/static v1.1.0 h1:MMVoe+sAwMbt1rqH91C48LAmmBn9tuVJInaT5b/64OI= +github.com/gin-contrib/static v1.1.0/go.mod h1:41ymO4uaFIGCy1kco0PBunNbvXeesKsyPR9TnVKR1BQ= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-json-experiment/json v0.0.0-20231011163920-8aa127fd5801 h1:PRieymvnGuBZUnWVQPBOemqlIhRznqtSxs/1LqlWe20= +github.com/go-json-experiment/json v0.0.0-20231011163920-8aa127fd5801/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= @@ -60,37 +86,38 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 h1:5/4TSDzpDnHQ8rKEEQBjRlYx77mHOvXu08oGchxej7o= +github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932/go.mod h1:cC6EdPbj/17GFCPDK39NRarlMI+kt+O60S12cNB5J9Y= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -100,20 +127,17 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -122,9 +146,10 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -132,65 +157,93 @@ github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAc github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= +github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= +go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= -golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -198,22 +251,20 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= -gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= -gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= -gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= -gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= -gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/driver/mysql v1.5.5 h1:WxklwX6FozMs1gk9yVadxGfjGiJjrBKPvIIvYZOMyws= +gorm.io/driver/mysql v1.5.5/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= +gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= +gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= +gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo= +gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/middleware/distributor.go b/middleware/distributor.go index 33926a54..f538f250 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -44,7 +44,7 @@ func Distribute() func(c *gin.Context) { var modelRequest ModelRequest err := common.UnmarshalBodyReusable(c, &modelRequest) if err != nil { - abortWithMessage(c, http.StatusBadRequest, "无效的请求") + abortWithMessage(c, http.StatusBadRequest, fmt.Sprintf("无效的请求: %+v", err)) return } if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") { diff --git a/relay/channel/common.go b/relay/channel/common.go index e03c1055..2c4fb37c 100644 --- a/relay/channel/common.go +++ b/relay/channel/common.go @@ -22,10 +22,15 @@ func DoRequestHelper(a Adaptor, c *gin.Context, meta *util.RelayMeta, requestBod if err != nil { return nil, errors.Wrap(err, "get request url failed") } - req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody) + + req, err := http.NewRequestWithContext(c.Request.Context(), + c.Request.Method, fullRequestURL, requestBody) if err != nil { return nil, errors.Wrap(err, "new request failed") } + + req.Header.Add("Content-Type", "application/json") + err = a.SetupRequestHeader(c, req, meta) if err != nil { return nil, errors.Wrap(err, "setup request header failed") @@ -47,5 +52,6 @@ func DoRequest(c *gin.Context, req *http.Request) (*http.Response, error) { } _ = req.Body.Close() _ = c.Request.Body.Close() + return resp, nil } diff --git a/relay/channel/gemini/adaptor.go b/relay/channel/gemini/adaptor.go index 1cba1c00..240ca28d 100644 --- a/relay/channel/gemini/adaptor.go +++ b/relay/channel/gemini/adaptor.go @@ -2,6 +2,9 @@ package gemini import ( "fmt" + "io" + "net/http" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common/helper" @@ -9,8 +12,6 @@ import ( "github.com/songquanpeng/one-api/relay/channel/openai" "github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" ) type Adaptor struct { @@ -21,17 +22,18 @@ func (a *Adaptor) Init(meta *util.RelayMeta) { } func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { - version := helper.AssignOrDefault(meta.APIVersion, "v1") + version := helper.AssignOrDefault(meta.APIVersion, "v1beta") action := "generateContent" if meta.IsStream { action = "streamGenerateContent" } - return fmt.Sprintf("%s/%s/models/%s:%s", meta.BaseURL, version, meta.ActualModelName, action), nil + return fmt.Sprintf("%s/%s/models/%s:%s?key=%s", meta.BaseURL, version, meta.ActualModelName, action, meta.APIKey), nil } func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { channelhelper.SetupCommonRequestHeader(c, req, meta) req.Header.Set("x-goog-api-key", meta.APIKey) + req.URL.Query().Add("key", meta.APIKey) return nil } diff --git a/relay/channel/gemini/main.go b/relay/channel/gemini/main.go index dcc89d0e..b66f2d5e 100644 --- a/relay/channel/gemini/main.go +++ b/relay/channel/gemini/main.go @@ -1,10 +1,13 @@ package gemini import ( - "bufio" "context" "encoding/json" "fmt" + "io" + "net/http" + + "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" @@ -13,11 +16,6 @@ import ( "github.com/songquanpeng/one-api/relay/channel/openai" "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" - "io" - "net/http" - "strings" - - "github.com/gin-gonic/gin" ) // https://ai.google.dev/docs/gemini_api_overview?hl=zh-cn @@ -99,8 +97,9 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *ChatRequest { }) } } + logger.Info(context.TODO(), - fmt.Sprintf("send %d images to gemini-pro-vision", len(parts))) + fmt.Sprintf("send %d messages to gemini with %d images", len(parts), imageNum)) content.Parts = parts // there's no assistant role in gemini and API shall vomit if Role is not user or model @@ -197,73 +196,182 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *ChatResponse) *openai.ChatC return &response } +// [{ +// "candidates": [ +// { +// "content": { +// "parts": [ +// { +// "text": "```go \n\n// Package ratelimit implements tokens bucket algorithm.\npackage rate" +// } +// ], +// "role": "model" +// }, +// "finishReason": "STOP", +// "index": 0, +// "safetyRatings": [ +// { +// "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", +// "probability": "NEGLIGIBLE" +// }, +// { +// "category": "HARM_CATEGORY_HATE_SPEECH", +// "probability": "NEGLIGIBLE" +// }, +// { +// "category": "HARM_CATEGORY_HARASSMENT", +// "probability": "NEGLIGIBLE" +// }, +// { +// "category": "HARM_CATEGORY_DANGEROUS_CONTENT", +// "probability": "NEGLIGIBLE" +// } +// ] +// } +// ], +// "promptFeedback": { +// "safetyRatings": [ +// { +// "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", +// "probability": "NEGLIGIBLE" +// }, +// { +// "category": "HARM_CATEGORY_HATE_SPEECH", +// "probability": "NEGLIGIBLE" +// }, +// { +// "category": "HARM_CATEGORY_HARASSMENT", +// "probability": "NEGLIGIBLE" +// }, +// { +// "category": "HARM_CATEGORY_DANGEROUS_CONTENT", +// "probability": "NEGLIGIBLE" +// } +// ] +// } +// }] +type GeminiStreamResp struct { + Candidates []struct { + Content struct { + Parts []struct { + Text string `json:"text"` + } `json:"parts"` + Role string `json:"role"` + } `json:"content"` + FinishReason string `json:"finishReason"` + Index int64 `json:"index"` + } `json:"candidates"` +} + 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 { - 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 - }() - common.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 openai.ChatCompletionsStreamResponseChoice - choice.Delta.Content = dummy.Content - response := openai.ChatCompletionsStreamResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), - Object: "chat.completion.chunk", - Created: helper.GetTimestamp(), - Model: "gemini-pro", - Choices: []openai.ChatCompletionsStreamResponseChoice{choice}, - } - jsonResponse, err := json.Marshal(response) - if err != nil { - logger.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() + + respBody, err := io.ReadAll(resp.Body) if err != nil { + return openai.ErrorWrapper(err, "read upstream's body", http.StatusInternalServerError), responseText + } + + var respData []GeminiStreamResp + if err = json.Unmarshal(respBody, &respData); err != nil { + return openai.ErrorWrapper(err, "unmarshal upstream's body", http.StatusInternalServerError), responseText + } + + for _, chunk := range respData { + for _, cad := range chunk.Candidates { + for _, part := range cad.Content.Parts { + responseText += part.Text + } + } + } + + var choice openai.ChatCompletionsStreamResponseChoice + choice.Delta.Content = responseText + resp2cli, err := json.Marshal(&openai.ChatCompletionsStreamResponse{ + Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + Object: "chat.completion.chunk", + Created: helper.GetTimestamp(), + Model: "gemini-pro", + Choices: []openai.ChatCompletionsStreamResponseChoice{choice}, + }) + if err != nil { + return openai.ErrorWrapper(err, "marshal upstream's body", http.StatusInternalServerError), responseText + } + + c.Render(-1, common.CustomEvent{Data: "data: " + string(resp2cli)}) + c.Render(-1, common.CustomEvent{Data: "data: [DONE]"}) + + // dataChan := make(chan string) + // stopChan := make(chan bool) + // scanner := bufio.NewScanner(resp.Body) + // scanner.Split(bufio.ScanLines) + // // 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() { + // var content string + // for scanner.Scan() { + // line := strings.TrimSpace(scanner.Text()) + // fmt.Printf("> gemini got line: %s\n", line) + // content += line + // // if !strings.HasPrefix(data, "\"text\": \"") { + // // continue + // // } + + // // data = strings.TrimPrefix(data, "\"text\": \"") + // // data = strings.TrimSuffix(data, "\"") + // // dataChan <- data + // } + + // dataChan <- content + // stopChan <- true + // }() + // common.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 openai.ChatCompletionsStreamResponseChoice + // choice.Delta.Content = dummy.Content + // response := openai.ChatCompletionsStreamResponse{ + // Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + // Object: "chat.completion.chunk", + // Created: helper.GetTimestamp(), + // Model: "gemini-pro", + // Choices: []openai.ChatCompletionsStreamResponseChoice{choice}, + // } + // jsonResponse, err := json.Marshal(response) + // if err != nil { + // logger.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 + // } + // }) + + if err := resp.Body.Close(); err != nil { return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), "" } + return nil, responseText } diff --git a/relay/util/init.go b/relay/util/init.go index cc3af6d9..b303de0c 100644 --- a/relay/util/init.go +++ b/relay/util/init.go @@ -1,33 +1,36 @@ package util import ( - "github.com/songquanpeng/one-api/common/config" "net/http" + "os" "time" + + gutils "github.com/Laisky/go-utils/v4" + "github.com/songquanpeng/one-api/common/config" ) var HTTPClient *http.Client var ImpatientHTTPClient *http.Client func init() { + var opts []gutils.HTTPClientOptFunc - tp := &http.Transport{ - IdleConnTimeout: time.Duration(config.IdleTimeout) * time.Second, + timeout := time.Duration(max(config.IdleTimeout, 30)) * time.Second + opts = append(opts, gutils.WithHTTPClientTimeout(timeout)) + if os.Getenv("RELAY_PROXY") != "" { + opts = append(opts, gutils.WithHTTPClientProxy(os.Getenv("RELAY_PROXY"))) } - if config.RelayTimeout == 0 { - HTTPClient = &http.Client{ - Transport: tp, - } - } else { - HTTPClient = &http.Client{ - Transport: tp, - Timeout: time.Duration(config.RelayTimeout) * time.Second, - } + var err error + HTTPClient, err = gutils.NewHTTPClient(opts...) + if err != nil { + panic(err) } - ImpatientHTTPClient = &http.Client{ - Transport: tp, - Timeout: 5 * time.Second, + ImpatientHTTPClient, err = gutils.NewHTTPClient( + gutils.WithHTTPClientTimeout(5 * time.Second), + ) + if err != nil { + panic(err) } } From c6faef04cb012b9c225a275c000c4f8893d7acc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 03:12:17 +0000 Subject: [PATCH 081/170] chore(deps): bump the go_modules group across 1 directory with 1 update Bumps the go_modules group with 1 update in the / directory: [github.com/jackc/pgx/v5](https://github.com/jackc/pgx). Updates `github.com/jackc/pgx/v5` from 5.4.3 to 5.5.4 - [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md) - [Commits](https://github.com/jackc/pgx/compare/v5.4.3...v5.5.4) --- updated-dependencies: - dependency-name: github.com/jackc/pgx/v5 dependency-type: indirect dependency-group: go_modules-security-group ... Signed-off-by: dependabot[bot] --- go.mod | 3 ++- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 522219b9..5c50c39d 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,8 @@ require ( github.com/gorilla/sessions v1.2.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/pgx/v5 v5.5.4 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 1cfcf5e8..d518f25f 100644 --- a/go.sum +++ b/go.sum @@ -107,8 +107,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= -github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= +github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= From 2bcd21a3d532ea97ec111ad69cef08fb8b560abd Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Tue, 19 Mar 2024 03:41:16 +0000 Subject: [PATCH 082/170] fix: Upgrade Error Handling in common/gin.go: Wrap and contextualize errors. - Updated error handling in `common/gin.go` to provide better context for debugging - Wrapped errors with additional information to improve error messages - Modified `UnmarshalBodyReusable` function to return errors with extra context and reset request body after processing --- common/gin.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common/gin.go b/common/gin.go index 33d6923c..75d94c33 100644 --- a/common/gin.go +++ b/common/gin.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/pkg/errors" ) const KeyRequestBody = "key_request_body" @@ -18,7 +19,7 @@ func GetRequestBody(c *gin.Context) ([]byte, error) { } requestBody, err := io.ReadAll(c.Request.Body) if err != nil { - return nil, err + return nil, errors.Wrap(err, "read request body failed") } _ = c.Request.Body.Close() c.Set(KeyRequestBody, requestBody) @@ -28,18 +29,18 @@ func GetRequestBody(c *gin.Context) ([]byte, error) { func UnmarshalBodyReusable(c *gin.Context, v any) error { requestBody, err := GetRequestBody(c) if err != nil { - return err + return errors.Wrap(err, "get request body failed") } contentType := c.Request.Header.Get("Content-Type") if strings.HasPrefix(contentType, "application/json") { - err = json.Unmarshal(requestBody, &v) + if err = json.Unmarshal(requestBody, &v); err != nil { + return errors.Wrap(err, "unmarshal request body failed") + } } else { // skip for now // TODO: someday non json request have variant model, we will need to implementation this } - if err != nil { - return err - } + // Reset request body c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody)) return nil From 1ec59ea1cc776dd1cc78dda5fa0a69f331f19b64 Mon Sep 17 00:00:00 2001 From: JustSong Date: Wed, 27 Mar 2024 19:09:27 +0800 Subject: [PATCH 083/170] fix: fix SQL channel selection algo (#1197) --- model/ability.go | 12 +++++++++--- model/cache.go | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/model/ability.go b/model/ability.go index 7127abc3..48b856a2 100644 --- a/model/ability.go +++ b/model/ability.go @@ -2,6 +2,7 @@ package model import ( "github.com/songquanpeng/one-api/common" + "gorm.io/gorm" "strings" ) @@ -13,7 +14,7 @@ type Ability struct { Priority *int64 `json:"priority" gorm:"bigint;default:0;index"` } -func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) { +func GetRandomSatisfiedChannel(group string, model string, ignoreFirstPriority bool) (*Channel, error) { ability := Ability{} groupCol := "`group`" trueVal := "1" @@ -23,8 +24,13 @@ func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) { } var err error = nil - maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model) - channelQuery := DB.Where(groupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = (?)", group, model, maxPrioritySubQuery) + var channelQuery *gorm.DB + if ignoreFirstPriority { + channelQuery = DB.Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model) + } else { + maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model) + channelQuery = DB.Where(groupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = (?)", group, model, maxPrioritySubQuery) + } if common.UsingSQLite || common.UsingPostgreSQL { err = channelQuery.Order("RANDOM()").First(&ability).Error } else { diff --git a/model/cache.go b/model/cache.go index e684e12d..50946bd6 100644 --- a/model/cache.go +++ b/model/cache.go @@ -206,7 +206,7 @@ func SyncChannelCache(frequency int) { func CacheGetRandomSatisfiedChannel(group string, model string, ignoreFirstPriority bool) (*Channel, error) { if !config.MemoryCacheEnabled { - return GetRandomSatisfiedChannel(group, model) + return GetRandomSatisfiedChannel(group, model, ignoreFirstPriority) } channelSyncLock.RLock() defer channelSyncLock.RUnlock() From 5419a10ee701d0aeb5c57d3ce4d59e9dfeeddf4f Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 28 Mar 2024 01:34:40 +0000 Subject: [PATCH 084/170] chore: Update CI workflow with new builds, env var, and deploy job * Update CI workflow with changes to branch names, builds, and jobs * Introduce new 'SHORT\_SHA' env variable for CI workflow * Rename 'docker' job to 'build\_latest' and utilize it in 'deploy' job * Implement specific branch builds and 'deploy' job for a specific host --- .github/workflows/ci.yml | 61 ++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06e82457..30f6fab9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,41 @@ name: ci on: push: branches: - - 'main' + - 'master' + - 'test/ci' jobs: - docker: + build_latest: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - + name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - + name: Build and push latest + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ppcelery/one-api:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + build_hash: runs-on: ubuntu-latest steps: - @@ -29,15 +60,6 @@ jobs: - name: Add SHORT_SHA env property with commit short sha run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-7`" >> $GITHUB_ENV - - - name: Build and push latest - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ppcelery/one-api:latest - cache-from: type=gha - cache-to: type=gha,mode=max - name: Build and push hash label uses: docker/build-push-action@v5 @@ -47,3 +69,20 @@ jobs: tags: ppcelery/one-api:${{ env.SHORT_SHA }} cache-from: type=gha cache-to: type=gha,mode=max + + deploy: + runs-on: ubuntu-latest + needs: build_latest + steps: + - name: executing remote ssh commands using password + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.TARGET_HOST }} + username: ${{ secrets.TARGET_HOST_USERNAME }} + password: ${{ secrets.TARGET_HOST_PASSWORD }} + port: ${{ secrets.TARGET_HOST_SSH_PORT }} + script: | + docker pull ppcelery/one-api:latest + cd /home/laisky/repo/VPS + docker-compose -f b1-docker-compose.yml up -d --remove-orphans --force-recreate oneapi + docker ps From 3e50f2d462a8c1fc5fd262876a95d2675c492694 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Thu, 28 Mar 2024 01:35:24 +0000 Subject: [PATCH 085/170] chore: Enable CI workflows for 'main' branch * Update CI workflow configuration to include 'main' branch * Enable CI workflow triggering for changes on the 'main' branch --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30f6fab9..afe0c4ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: push: branches: - 'master' + - 'main' - 'test/ci' jobs: From 50bab08496db65d50283af6fcf3a3f132a8beb28 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Sat, 6 Apr 2024 05:18:04 +0000 Subject: [PATCH 086/170] Refactor codebase, introduce relaymode package, update constants and improve consistency - Refactor constant definitions and organization - Clean up package level variables and functions - Introduce new `relaymode` and `apitype` packages for constant definitions - Refactor and simplify code in several packages including `openai`, `relay/channel/baidu`, `relay/util`, `relay/controller`, `relay/channeltype` - Add helper functions in `relay/channeltype` package to convert channel type constants to corresponding API type constants - Remove deprecated functions such as `ResponseText2Usage` from `relay/channel/openai/helper.go` - Modify code in `relay/util/validation.go` and related files to use new `validator.ValidateTextRequest` function - Rename `util` package to `relaymode` and update related imports in several packages --- .gitignore | 1 + common/config/config.go | 3 + common/config/key.go | 9 + common/constants.go | 113 - common/helper/helper.go | 89 +- common/helper/time.go | 15 + common/network/ip.go | 52 + common/network/ip_test.go | 19 + common/random.go | 8 - common/random/main.go | 62 + controller/{ => auth}/github.go | 16 +- controller/auth/lark.go | 201 + controller/{ => auth}/wechat.go | 12 +- controller/channel-billing.go | 39 +- controller/channel-test.go | 44 +- controller/group.go | 4 +- controller/misc.go | 1 + controller/model.go | 112 +- controller/redemption.go | 3 +- controller/relay.go | 26 +- controller/token.go | 129 +- controller/user.go | 119 +- docs/API.md | 53 + go.mod | 10 +- go.sum | 24 +- main.go | 2 +- middleware/auth.go | 36 +- middleware/distributor.go | 71 +- middleware/utils.go | 42 + model/ability.go | 20 +- model/cache.go | 25 +- model/channel.go | 14 +- model/log.go | 16 +- model/main.go | 9 +- model/option.go | 20 +- model/redemption.go | 10 +- model/token.go | 50 +- model/user.go | 78 +- monitor/channel.go | 7 +- monitor/manage.go | 62 + relay/adaptor.go | 41 + relay/{channel => adaptor}/ai360/constants.go | 0 relay/{channel => adaptor}/aiproxy/adaptor.go | 29 +- .../{channel => adaptor}/aiproxy/constants.go | 2 +- relay/{channel => adaptor}/aiproxy/main.go | 9 +- relay/{channel => adaptor}/aiproxy/model.go | 0 relay/adaptor/ali/adaptor.go | 105 + relay/{channel => adaptor}/ali/constants.go | 1 + relay/adaptor/ali/image.go | 192 + relay/{channel => adaptor}/ali/main.go | 0 relay/adaptor/ali/model.go | 154 + .../{channel => adaptor}/anthropic/adaptor.go | 31 +- .../anthropic/constants.go | 0 relay/{channel => adaptor}/anthropic/main.go | 2 +- relay/{channel => adaptor}/anthropic/model.go | 0 relay/adaptor/azure/helper.go | 15 + .../baichuan/constants.go | 0 relay/{channel => adaptor}/baidu/adaptor.go | 0 relay/adaptor/baidu/constants.go | 20 + relay/{channel => adaptor}/baidu/main.go | 0 relay/{channel => adaptor}/baidu/model.go | 0 relay/{channel => adaptor}/common.go | 11 +- relay/{channel => adaptor}/gemini/adaptor.go | 25 +- .../{channel => adaptor}/gemini/constants.go | 0 relay/{channel => adaptor}/gemini/main.go | 251 +- relay/{channel => adaptor}/gemini/model.go | 0 relay/{channel => adaptor}/groq/constants.go | 0 relay/adaptor/interface.go | 21 + .../lingyiwanwu/constants.go | 0 .../{channel => adaptor}/minimax/constants.go | 0 relay/adaptor/minimax/main.go | 14 + .../{channel => adaptor}/mistral/constants.go | 0 .../moonshot/constants.go | 0 relay/{channel => adaptor}/ollama/adaptor.go | 35 +- .../{channel => adaptor}/ollama/constants.go | 0 relay/{channel => adaptor}/ollama/main.go | 9 +- relay/{channel => adaptor}/ollama/model.go | 0 relay/{channel => adaptor}/openai/adaptor.go | 56 +- relay/adaptor/openai/compatible.go | 50 + .../{channel => adaptor}/openai/constants.go | 0 relay/adaptor/openai/helper.go | 30 + relay/adaptor/openai/image.go | 44 + relay/{channel => adaptor}/openai/main.go | 6 +- relay/{channel => adaptor}/openai/model.go | 13 +- relay/{channel => adaptor}/openai/token.go | 4 +- relay/{channel => adaptor}/openai/util.go | 0 relay/{channel => adaptor}/palm/adaptor.go | 27 +- relay/{channel => adaptor}/palm/constants.go | 0 relay/{channel => adaptor}/palm/model.go | 0 relay/{channel => adaptor}/palm/palm.go | 5 +- relay/adaptor/stepfun/constants.go | 7 + relay/{channel => adaptor}/tencent/adaptor.go | 0 .../{channel => adaptor}/tencent/constants.go | 0 relay/adaptor/tencent/main.go | 238 + relay/{channel => adaptor}/tencent/model.go | 0 relay/{channel => adaptor}/xunfei/adaptor.go | 0 .../{channel => adaptor}/xunfei/constants.go | 0 relay/{channel => adaptor}/xunfei/main.go | 0 relay/{channel => adaptor}/xunfei/model.go | 0 relay/adaptor/zhipu/adaptor.go | 145 + relay/{channel => adaptor}/zhipu/constants.go | 0 relay/{channel => adaptor}/zhipu/main.go | 0 relay/{channel => adaptor}/zhipu/model.go | 0 relay/apitype/define.go | 17 + relay/billing/billing.go | 42 + .../billing/ratio/group.go | 2 +- relay/billing/ratio/image.go | 51 + .../billing/ratio/model.go | 52 +- relay/channel/ali/adaptor.go | 83 - relay/channel/ali/model.go | 71 - relay/channel/baidu/constants.go | 13 - relay/channel/interface.go | 20 - relay/channel/minimax/main.go | 16 - relay/channel/openai/compatible.go | 46 - relay/channel/openai/helper.go | 11 - relay/channel/zhipu/adaptor.go | 62 - relay/channeltype/define.go | 39 + relay/channeltype/helper.go | 30 + relay/channeltype/url.go | 43 + relay/{util => client}/init.go | 2 +- relay/constant/api_type.go | 48 - relay/constant/image.go | 24 - relay/constant/relay_mode.go | 42 - relay/controller/audio.go | 50 +- relay/controller/error.go | 91 + relay/controller/helper.go | 73 +- relay/controller/image.go | 125 +- relay/controller/text.go | 33 +- .../validator}/validation.go | 19 +- relay/helper/main.go | 40 - relay/{util => meta}/relay_meta.go | 26 +- relay/model/image.go | 12 + relay/relaymode/define.go | 14 + relay/relaymode/helper.go | 29 + relay/util/billing.go | 19 - relay/util/common.go | 188 - relay/util/model_mapping.go | 12 - router/{api-router.go => api.go} | 14 +- router/{relay-router.go => relay.go} | 0 router/{web-router.go => web.go} | 0 web/README.md | 3 + web/berry/src/constants/ChannelConstants.js | 6 + web/berry/src/constants/SnackbarConstants.js | 2 +- web/berry/src/utils/common.js | 4 +- .../src/views/Channel/component/EditModal.js | 4 +- .../src/views/Token/component/EditModal.js | 2 +- web/default/src/App.js | 9 + web/default/src/components/LarkOAuth.js | 58 + web/default/src/components/LoginForm.js | 17 +- web/default/src/components/PersonalSetting.js | 7 +- web/default/src/components/SystemSetting.js | 54 + web/default/src/components/utils.js | 9 + .../src/constants/channel.constants.js | 1 + web/default/src/images/lark.svg | 1 + web/default/src/pages/Token/EditToken.js | 67 +- web/default/src/pages/TopUp/index.js | 13 +- web/package-lock.json | 17443 +--------------- 157 files changed, 3217 insertions(+), 19160 deletions(-) create mode 100644 common/config/key.go create mode 100644 common/helper/time.go create mode 100644 common/network/ip.go create mode 100644 common/network/ip_test.go delete mode 100644 common/random.go create mode 100644 common/random/main.go rename controller/{ => auth}/github.go (94%) create mode 100644 controller/auth/lark.go rename controller/{ => auth}/wechat.go (93%) create mode 100644 docs/API.md create mode 100644 monitor/manage.go create mode 100644 relay/adaptor.go rename relay/{channel => adaptor}/ai360/constants.go (100%) rename relay/{channel => adaptor}/aiproxy/adaptor.go (54%) rename relay/{channel => adaptor}/aiproxy/constants.go (60%) rename relay/{channel => adaptor}/aiproxy/main.go (95%) rename relay/{channel => adaptor}/aiproxy/model.go (100%) create mode 100644 relay/adaptor/ali/adaptor.go rename relay/{channel => adaptor}/ali/constants.go (65%) create mode 100644 relay/adaptor/ali/image.go rename relay/{channel => adaptor}/ali/main.go (100%) create mode 100644 relay/adaptor/ali/model.go rename relay/{channel => adaptor}/anthropic/adaptor.go (60%) rename relay/{channel => adaptor}/anthropic/constants.go (100%) rename relay/{channel => adaptor}/anthropic/main.go (99%) rename relay/{channel => adaptor}/anthropic/model.go (100%) create mode 100644 relay/adaptor/azure/helper.go rename relay/{channel => adaptor}/baichuan/constants.go (100%) rename relay/{channel => adaptor}/baidu/adaptor.go (100%) create mode 100644 relay/adaptor/baidu/constants.go rename relay/{channel => adaptor}/baidu/main.go (100%) rename relay/{channel => adaptor}/baidu/model.go (100%) rename relay/{channel => adaptor}/common.go (81%) rename relay/{channel => adaptor}/gemini/adaptor.go (66%) rename relay/{channel => adaptor}/gemini/constants.go (100%) rename relay/{channel => adaptor}/gemini/main.go (56%) rename relay/{channel => adaptor}/gemini/model.go (100%) rename relay/{channel => adaptor}/groq/constants.go (100%) create mode 100644 relay/adaptor/interface.go rename relay/{channel => adaptor}/lingyiwanwu/constants.go (100%) rename relay/{channel => adaptor}/minimax/constants.go (100%) create mode 100644 relay/adaptor/minimax/main.go rename relay/{channel => adaptor}/mistral/constants.go (100%) rename relay/{channel => adaptor}/moonshot/constants.go (100%) rename relay/{channel => adaptor}/ollama/adaptor.go (58%) rename relay/{channel => adaptor}/ollama/constants.go (100%) rename relay/{channel => adaptor}/ollama/main.go (97%) rename relay/{channel => adaptor}/ollama/model.go (100%) rename relay/{channel => adaptor}/openai/adaptor.go (52%) create mode 100644 relay/adaptor/openai/compatible.go rename relay/{channel => adaptor}/openai/constants.go (100%) create mode 100644 relay/adaptor/openai/helper.go create mode 100644 relay/adaptor/openai/image.go rename relay/{channel => adaptor}/openai/main.go (97%) rename relay/{channel => adaptor}/openai/model.go (93%) rename relay/{channel => adaptor}/openai/token.go (98%) rename relay/{channel => adaptor}/openai/util.go (100%) rename relay/{channel => adaptor}/palm/adaptor.go (59%) rename relay/{channel => adaptor}/palm/constants.go (100%) rename relay/{channel => adaptor}/palm/model.go (100%) rename relay/{channel => adaptor}/palm/palm.go (97%) create mode 100644 relay/adaptor/stepfun/constants.go rename relay/{channel => adaptor}/tencent/adaptor.go (100%) rename relay/{channel => adaptor}/tencent/constants.go (100%) create mode 100644 relay/adaptor/tencent/main.go rename relay/{channel => adaptor}/tencent/model.go (100%) rename relay/{channel => adaptor}/xunfei/adaptor.go (100%) rename relay/{channel => adaptor}/xunfei/constants.go (100%) rename relay/{channel => adaptor}/xunfei/main.go (100%) rename relay/{channel => adaptor}/xunfei/model.go (100%) create mode 100644 relay/adaptor/zhipu/adaptor.go rename relay/{channel => adaptor}/zhipu/constants.go (100%) rename relay/{channel => adaptor}/zhipu/main.go (100%) rename relay/{channel => adaptor}/zhipu/model.go (100%) create mode 100644 relay/apitype/define.go create mode 100644 relay/billing/billing.go rename common/group-ratio.go => relay/billing/ratio/group.go (97%) create mode 100644 relay/billing/ratio/image.go rename common/model-ratio.go => relay/billing/ratio/model.go (85%) delete mode 100644 relay/channel/ali/adaptor.go delete mode 100644 relay/channel/ali/model.go delete mode 100644 relay/channel/baidu/constants.go delete mode 100644 relay/channel/interface.go delete mode 100644 relay/channel/minimax/main.go delete mode 100644 relay/channel/openai/compatible.go delete mode 100644 relay/channel/openai/helper.go delete mode 100644 relay/channel/zhipu/adaptor.go create mode 100644 relay/channeltype/define.go create mode 100644 relay/channeltype/helper.go create mode 100644 relay/channeltype/url.go rename relay/{util => client}/init.go (97%) delete mode 100644 relay/constant/api_type.go delete mode 100644 relay/constant/image.go delete mode 100644 relay/constant/relay_mode.go create mode 100644 relay/controller/error.go rename relay/{util => controller/validator}/validation.go (76%) delete mode 100644 relay/helper/main.go rename relay/{util => meta}/relay_meta.go (64%) create mode 100644 relay/model/image.go create mode 100644 relay/relaymode/define.go create mode 100644 relay/relaymode/helper.go delete mode 100644 relay/util/billing.go delete mode 100644 relay/util/common.go delete mode 100644 relay/util/model_mapping.go rename router/{api-router.go => api.go} (92%) rename router/{relay-router.go => relay.go} (100%) rename router/{web-router.go => web.go} (100%) create mode 100644 web/default/src/components/LarkOAuth.js create mode 100644 web/default/src/images/lark.svg diff --git a/.gitignore b/.gitignore index 1cfa1e7f..be4abc52 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build logs data node_modules +cmd.md diff --git a/common/config/config.go b/common/config/config.go index 66cfee06..59fd77df 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -80,6 +80,9 @@ var SMTPToken = "" var GitHubClientId = "" var GitHubClientSecret = "" +var LarkClientId = "" +var LarkClientSecret = "" + var WeChatServerAddress = "" var WeChatServerToken = "" var WeChatAccountQRCodeImageURL = "" diff --git a/common/config/key.go b/common/config/key.go new file mode 100644 index 00000000..4b503c2d --- /dev/null +++ b/common/config/key.go @@ -0,0 +1,9 @@ +package config + +const ( + KeyPrefix = "cfg_" + + KeyAPIVersion = KeyPrefix + "api_version" + KeyLibraryID = KeyPrefix + "library_id" + KeyPlugin = KeyPrefix + "plugin" +) diff --git a/common/constants.go b/common/constants.go index 849bdce7..87221b61 100644 --- a/common/constants.go +++ b/common/constants.go @@ -4,116 +4,3 @@ import "time" var StartTime = time.Now().Unix() // unit: second var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change - -const ( - RoleGuestUser = 0 - RoleCommonUser = 1 - RoleAdminUser = 10 - RoleRootUser = 100 -) - -const ( - UserStatusEnabled = 1 // don't use 0, 0 is the default value! - UserStatusDisabled = 2 // also don't use 0 - UserStatusDeleted = 3 -) - -const ( - TokenStatusEnabled = 1 // don't use 0, 0 is the default value! - TokenStatusDisabled = 2 // also don't use 0 - TokenStatusExpired = 3 - TokenStatusExhausted = 4 -) - -const ( - RedemptionCodeStatusEnabled = 1 // don't use 0, 0 is the default value! - RedemptionCodeStatusDisabled = 2 // also don't use 0 - RedemptionCodeStatusUsed = 3 // also don't use 0 -) - -const ( - ChannelStatusUnknown = 0 - ChannelStatusEnabled = 1 // don't use 0, 0 is the default value! - ChannelStatusManuallyDisabled = 2 // also don't use 0 - ChannelStatusAutoDisabled = 3 -) - -const ( - ChannelTypeUnknown = iota - ChannelTypeOpenAI - ChannelTypeAPI2D - ChannelTypeAzure - ChannelTypeCloseAI - ChannelTypeOpenAISB - ChannelTypeOpenAIMax - ChannelTypeOhMyGPT - ChannelTypeCustom - ChannelTypeAILS - ChannelTypeAIProxy - ChannelTypePaLM - ChannelTypeAPI2GPT - ChannelTypeAIGC2D - ChannelTypeAnthropic - ChannelTypeBaidu - ChannelTypeZhipu - ChannelTypeAli - ChannelTypeXunfei - ChannelType360 - ChannelTypeOpenRouter - ChannelTypeAIProxyLibrary - ChannelTypeFastGPT - ChannelTypeTencent - ChannelTypeGemini - ChannelTypeMoonshot - ChannelTypeBaichuan - ChannelTypeMinimax - ChannelTypeMistral - ChannelTypeGroq - ChannelTypeOllama - ChannelTypeLingYiWanWu - - ChannelTypeDummy -) - -var ChannelBaseURLs = []string{ - "", // 0 - "https://api.openai.com", // 1 - "https://oa.api2d.net", // 2 - "", // 3 - "https://api.closeai-proxy.xyz", // 4 - "https://api.openai-sb.com", // 5 - "https://api.openaimax.com", // 6 - "https://api.ohmygpt.com", // 7 - "", // 8 - "https://api.caipacity.com", // 9 - "https://api.aiproxy.io", // 10 - "https://generativelanguage.googleapis.com", // 11 - "https://api.api2gpt.com", // 12 - "https://api.aigc2d.com", // 13 - "https://api.anthropic.com", // 14 - "https://aip.baidubce.com", // 15 - "https://open.bigmodel.cn", // 16 - "https://dashscope.aliyuncs.com", // 17 - "", // 18 - "https://ai.360.cn", // 19 - "https://openrouter.ai/api", // 20 - "https://api.aiproxy.io", // 21 - "https://fastgpt.run/api/openapi", // 22 - "https://hunyuan.cloud.tencent.com", // 23 - "https://generativelanguage.googleapis.com", // 24 - "https://api.moonshot.cn", // 25 - "https://api.baichuan-ai.com", // 26 - "https://api.minimax.chat", // 27 - "https://api.mistral.ai", // 28 - "https://api.groq.com/openai", // 29 - "http://localhost:11434", // 30 - "https://api.lingyiwanwu.com", // 31 -} - -const ( - ConfigKeyPrefix = "cfg_" - - ConfigKeyAPIVersion = ConfigKeyPrefix + "api_version" - ConfigKeyLibraryID = ConfigKeyPrefix + "library_id" - ConfigKeyPlugin = ConfigKeyPrefix + "plugin" -) diff --git a/common/helper/helper.go b/common/helper/helper.go index db41ac74..35d075bc 100644 --- a/common/helper/helper.go +++ b/common/helper/helper.go @@ -2,16 +2,15 @@ package helper import ( "fmt" - "github.com/google/uuid" "html/template" "log" - "math/rand" "net" "os/exec" "runtime" "strconv" "strings" - "time" + + "github.com/songquanpeng/one-api/common/random" ) func OpenBrowser(url string) { @@ -79,31 +78,6 @@ func Bytes2Size(num int64) string { return numStr + " " + unit } -func Seconds2Time(num int) (time string) { - if num/31104000 > 0 { - time += strconv.Itoa(num/31104000) + " 年 " - num %= 31104000 - } - if num/2592000 > 0 { - time += strconv.Itoa(num/2592000) + " 个月 " - num %= 2592000 - } - if num/86400 > 0 { - time += strconv.Itoa(num/86400) + " 天 " - num %= 86400 - } - if num/3600 > 0 { - time += strconv.Itoa(num/3600) + " 小时 " - num %= 3600 - } - if num/60 > 0 { - time += strconv.Itoa(num/60) + " 分钟 " - num %= 60 - } - time += strconv.Itoa(num) + " 秒" - return -} - func Interface2String(inter interface{}) string { switch inter := inter.(type) { case string: @@ -128,65 +102,8 @@ func IntMax(a int, b int) int { } } -func GetUUID() string { - code := uuid.New().String() - code = strings.Replace(code, "-", "", -1) - return code -} - -const keyChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -const keyNumbers = "0123456789" - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func GenerateKey() string { - rand.Seed(time.Now().UnixNano()) - key := make([]byte, 48) - for i := 0; i < 16; i++ { - key[i] = keyChars[rand.Intn(len(keyChars))] - } - uuid_ := GetUUID() - for i := 0; i < 32; i++ { - c := uuid_[i] - if i%2 == 0 && c >= 'a' && c <= 'z' { - c = c - 'a' + 'A' - } - key[i+16] = c - } - return string(key) -} - -func GetRandomString(length int) string { - rand.Seed(time.Now().UnixNano()) - key := make([]byte, length) - for i := 0; i < length; i++ { - key[i] = keyChars[rand.Intn(len(keyChars))] - } - return string(key) -} - -func GetRandomNumberString(length int) string { - rand.Seed(time.Now().UnixNano()) - key := make([]byte, length) - for i := 0; i < length; i++ { - key[i] = keyNumbers[rand.Intn(len(keyNumbers))] - } - return string(key) -} - -func GetTimestamp() int64 { - return time.Now().Unix() -} - -func GetTimeString() string { - now := time.Now() - return fmt.Sprintf("%s%d", now.Format("20060102150405"), now.UnixNano()%1e9) -} - func GenRequestID() string { - return GetTimeString() + GetRandomNumberString(8) + return GetTimeString() + random.GetRandomNumberString(8) } func Max(a int, b int) int { diff --git a/common/helper/time.go b/common/helper/time.go new file mode 100644 index 00000000..302746db --- /dev/null +++ b/common/helper/time.go @@ -0,0 +1,15 @@ +package helper + +import ( + "fmt" + "time" +) + +func GetTimestamp() int64 { + return time.Now().Unix() +} + +func GetTimeString() string { + now := time.Now() + return fmt.Sprintf("%s%d", now.Format("20060102150405"), now.UnixNano()%1e9) +} diff --git a/common/network/ip.go b/common/network/ip.go new file mode 100644 index 00000000..0fbe5e6f --- /dev/null +++ b/common/network/ip.go @@ -0,0 +1,52 @@ +package network + +import ( + "context" + "fmt" + "github.com/songquanpeng/one-api/common/logger" + "net" + "strings" +) + +func splitSubnets(subnets string) []string { + res := strings.Split(subnets, ",") + for i := 0; i < len(res); i++ { + res[i] = strings.TrimSpace(res[i]) + } + return res +} + +func isValidSubnet(subnet string) error { + _, _, err := net.ParseCIDR(subnet) + if err != nil { + return fmt.Errorf("failed to parse subnet: %w", err) + } + return nil +} + +func isIpInSubnet(ctx context.Context, ip string, subnet string) bool { + _, ipNet, err := net.ParseCIDR(subnet) + if err != nil { + logger.Errorf(ctx, "failed to parse subnet: %s", err.Error()) + return false + } + return ipNet.Contains(net.ParseIP(ip)) +} + +func IsValidSubnets(subnets string) error { + for _, subnet := range splitSubnets(subnets) { + if err := isValidSubnet(subnet); err != nil { + return err + } + } + return nil +} + +func IsIpInSubnets(ctx context.Context, ip string, subnets string) bool { + for _, subnet := range splitSubnets(subnets) { + if isIpInSubnet(ctx, ip, subnet) { + return true + } + } + return false +} diff --git a/common/network/ip_test.go b/common/network/ip_test.go new file mode 100644 index 00000000..6c593458 --- /dev/null +++ b/common/network/ip_test.go @@ -0,0 +1,19 @@ +package network + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestIsIpInSubnet(t *testing.T) { + ctx := context.Background() + ip1 := "192.168.0.5" + ip2 := "125.216.250.89" + subnet := "192.168.0.0/24" + Convey("TestIsIpInSubnet", t, func() { + So(isIpInSubnet(ctx, ip1, subnet), ShouldBeTrue) + So(isIpInSubnet(ctx, ip2, subnet), ShouldBeFalse) + }) +} diff --git a/common/random.go b/common/random.go deleted file mode 100644 index 44bd2856..00000000 --- a/common/random.go +++ /dev/null @@ -1,8 +0,0 @@ -package common - -import "math/rand" - -// RandRange returns a random number between min and max (max is not included) -func RandRange(min, max int) int { - return min + rand.Intn(max-min) -} diff --git a/common/random/main.go b/common/random/main.go new file mode 100644 index 00000000..c3c69488 --- /dev/null +++ b/common/random/main.go @@ -0,0 +1,62 @@ +package random + +import ( + "math/rand" + "strings" + "time" + + "github.com/google/uuid" +) + +func GetUUID() string { + code := uuid.New().String() + code = strings.Replace(code, "-", "", -1) + return code +} + +const keyChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const keyNumbers = "0123456789" + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func GenerateKey() string { + rand.Seed(time.Now().UnixNano()) + key := make([]byte, 48) + for i := 0; i < 16; i++ { + key[i] = keyChars[rand.Intn(len(keyChars))] + } + uuid_ := GetUUID() + for i := 0; i < 32; i++ { + c := uuid_[i] + if i%2 == 0 && c >= 'a' && c <= 'z' { + c = c - 'a' + 'A' + } + key[i+16] = c + } + return string(key) +} + +func GetRandomString(length int) string { + rand.Seed(time.Now().UnixNano()) + key := make([]byte, length) + for i := 0; i < length; i++ { + key[i] = keyChars[rand.Intn(len(keyChars))] + } + return string(key) +} + +func GetRandomNumberString(length int) string { + rand.Seed(time.Now().UnixNano()) + key := make([]byte, length) + for i := 0; i < length; i++ { + key[i] = keyNumbers[rand.Intn(len(keyNumbers))] + } + return string(key) +} + +// RandRange returns a random number between min and max (max is not included) +func RandRange(min, max int) int { + return min + rand.Intn(max-min) +} diff --git a/controller/github.go b/controller/auth/github.go similarity index 94% rename from controller/github.go rename to controller/auth/github.go index fc674852..22b48976 100644 --- a/controller/github.go +++ b/controller/auth/github.go @@ -1,4 +1,4 @@ -package controller +package auth import ( "bytes" @@ -7,10 +7,10 @@ import ( "github.com/Laisky/errors/v2" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/common/random" + "github.com/songquanpeng/one-api/controller" "github.com/songquanpeng/one-api/model" "net/http" "strconv" @@ -133,8 +133,8 @@ func GitHubOAuth(c *gin.Context) { user.DisplayName = "GitHub User" } user.Email = githubUser.Email - user.Role = common.RoleCommonUser - user.Status = common.UserStatusEnabled + user.Role = model.RoleCommonUser + user.Status = model.UserStatusEnabled if err := user.Insert(0); err != nil { c.JSON(http.StatusOK, gin.H{ @@ -152,14 +152,14 @@ func GitHubOAuth(c *gin.Context) { } } - if user.Status != common.UserStatusEnabled { + if user.Status != model.UserStatusEnabled { c.JSON(http.StatusOK, gin.H{ "message": "用户已被封禁", "success": false, }) return } - setupLogin(&user, c) + controller.SetupLogin(&user, c) } func GitHubBind(c *gin.Context) { @@ -219,7 +219,7 @@ func GitHubBind(c *gin.Context) { func GenerateOAuthCode(c *gin.Context) { session := sessions.Default(c) - state := helper.GetRandomString(12) + state := random.GetRandomString(12) session.Set("oauth_state", state) err := session.Save() if err != nil { diff --git a/controller/auth/lark.go b/controller/auth/lark.go new file mode 100644 index 00000000..a1dd8e84 --- /dev/null +++ b/controller/auth/lark.go @@ -0,0 +1,201 @@ +package auth + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/Laisky/errors/v2" + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/controller" + "github.com/songquanpeng/one-api/model" +) + +type LarkOAuthResponse struct { + AccessToken string `json:"access_token"` +} + +type LarkUser struct { + Name string `json:"name"` + OpenID string `json:"open_id"` +} + +func getLarkUserInfoByCode(code string) (*LarkUser, error) { + if code == "" { + return nil, errors.New("无效的参数") + } + values := map[string]string{ + "client_id": config.LarkClientId, + "client_secret": config.LarkClientSecret, + "code": code, + "grant_type": "authorization_code", + "redirect_uri": fmt.Sprintf("%s/oauth/lark", config.ServerAddress), + } + jsonData, err := json.Marshal(values) + if err != nil { + return nil, err + } + req, err := http.NewRequest("POST", "https://passport.feishu.cn/suite/passport/oauth/token", bytes.NewBuffer(jsonData)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + client := http.Client{ + Timeout: 5 * time.Second, + } + res, err := client.Do(req) + if err != nil { + logger.SysLog(err.Error()) + return nil, errors.New("无法连接至飞书服务器,请稍后重试!") + } + defer res.Body.Close() + var oAuthResponse LarkOAuthResponse + err = json.NewDecoder(res.Body).Decode(&oAuthResponse) + if err != nil { + return nil, err + } + req, err = http.NewRequest("GET", "https://passport.feishu.cn/suite/passport/oauth/userinfo", nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", oAuthResponse.AccessToken)) + res2, err := client.Do(req) + if err != nil { + logger.SysLog(err.Error()) + return nil, errors.New("无法连接至飞书服务器,请稍后重试!") + } + var larkUser LarkUser + err = json.NewDecoder(res2.Body).Decode(&larkUser) + if err != nil { + return nil, err + } + return &larkUser, nil +} + +func LarkOAuth(c *gin.Context) { + session := sessions.Default(c) + state := c.Query("state") + if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) { + c.JSON(http.StatusForbidden, gin.H{ + "success": false, + "message": "state is empty or not same", + }) + return + } + username := session.Get("username") + if username != nil { + LarkBind(c) + return + } + code := c.Query("code") + larkUser, err := getLarkUserInfoByCode(code) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + user := model.User{ + LarkId: larkUser.OpenID, + } + if model.IsLarkIdAlreadyTaken(user.LarkId) { + err := user.FillUserByLarkId() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + } else { + if config.RegisterEnabled { + user.Username = "lark_" + strconv.Itoa(model.GetMaxUserId()+1) + if larkUser.Name != "" { + user.DisplayName = larkUser.Name + } else { + user.DisplayName = "Lark User" + } + user.Role = model.RoleCommonUser + user.Status = model.UserStatusEnabled + + if err := user.Insert(0); err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + } else { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "管理员关闭了新用户注册", + }) + return + } + } + + if user.Status != model.UserStatusEnabled { + c.JSON(http.StatusOK, gin.H{ + "message": "用户已被封禁", + "success": false, + }) + return + } + controller.SetupLogin(&user, c) +} + +func LarkBind(c *gin.Context) { + code := c.Query("code") + larkUser, err := getLarkUserInfoByCode(code) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + user := model.User{ + LarkId: larkUser.OpenID, + } + if model.IsLarkIdAlreadyTaken(user.LarkId) { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "该飞书账户已被绑定", + }) + return + } + session := sessions.Default(c) + id := session.Get("id") + // id := c.GetInt("id") // critical bug! + user.Id = id.(int) + err = user.FillUserById() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + user.LarkId = larkUser.OpenID + err = user.Update(false) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "bind", + }) + return +} diff --git a/controller/wechat.go b/controller/auth/wechat.go similarity index 93% rename from controller/wechat.go rename to controller/auth/wechat.go index 8f997bfb..da1b513b 100644 --- a/controller/wechat.go +++ b/controller/auth/wechat.go @@ -1,12 +1,12 @@ -package controller +package auth import ( "encoding/json" "fmt" "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/controller" "github.com/songquanpeng/one-api/model" "net/http" "strconv" @@ -83,8 +83,8 @@ func WeChatAuth(c *gin.Context) { if config.RegisterEnabled { user.Username = "wechat_" + strconv.Itoa(model.GetMaxUserId()+1) user.DisplayName = "WeChat User" - user.Role = common.RoleCommonUser - user.Status = common.UserStatusEnabled + user.Role = model.RoleCommonUser + user.Status = model.UserStatusEnabled if err := user.Insert(0); err != nil { c.JSON(http.StatusOK, gin.H{ @@ -102,14 +102,14 @@ func WeChatAuth(c *gin.Context) { } } - if user.Status != common.UserStatusEnabled { + if user.Status != model.UserStatusEnabled { c.JSON(http.StatusOK, gin.H{ "message": "用户已被封禁", "success": false, }) return } - setupLogin(&user, c) + controller.SetupLogin(&user, c) } func WeChatBind(c *gin.Context) { diff --git a/controller/channel-billing.go b/controller/channel-billing.go index b9a3908e..79ef322a 100644 --- a/controller/channel-billing.go +++ b/controller/channel-billing.go @@ -3,18 +3,19 @@ package controller import ( "encoding/json" "fmt" - "github.com/Laisky/errors/v2" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/model" - "github.com/songquanpeng/one-api/monitor" - "github.com/songquanpeng/one-api/relay/util" "io" "net/http" "strconv" "time" + "github.com/Laisky/errors/v2" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/model" + "github.com/songquanpeng/one-api/monitor" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/client" + "github.com/gin-gonic/gin" ) @@ -96,7 +97,7 @@ func GetResponseBody(method, url string, channel *model.Channel, headers http.He for k := range headers { req.Header.Add(k, headers.Get(k)) } - res, err := util.HTTPClient.Do(req) + res, err := client.HTTPClient.Do(req) if err != nil { return nil, err } @@ -204,28 +205,28 @@ func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) { } func updateChannelBalance(channel *model.Channel) (float64, error) { - baseURL := common.ChannelBaseURLs[channel.Type] + baseURL := channeltype.ChannelBaseURLs[channel.Type] if channel.GetBaseURL() == "" { channel.BaseURL = &baseURL } switch channel.Type { - case common.ChannelTypeOpenAI: + case channeltype.OpenAI: if channel.GetBaseURL() != "" { baseURL = channel.GetBaseURL() } - case common.ChannelTypeAzure: + case channeltype.Azure: return 0, errors.New("尚未实现") - case common.ChannelTypeCustom: + case channeltype.Custom: baseURL = channel.GetBaseURL() - case common.ChannelTypeCloseAI: + case channeltype.CloseAI: return updateChannelCloseAIBalance(channel) - case common.ChannelTypeOpenAISB: + case channeltype.OpenAISB: return updateChannelOpenAISBBalance(channel) - case common.ChannelTypeAIProxy: + case channeltype.AIProxy: return updateChannelAIProxyBalance(channel) - case common.ChannelTypeAPI2GPT: + case channeltype.API2GPT: return updateChannelAPI2GPTBalance(channel) - case common.ChannelTypeAIGC2D: + case channeltype.AIGC2D: return updateChannelAIGC2DBalance(channel) default: return 0, errors.New("尚未实现") @@ -301,11 +302,11 @@ func updateAllChannelsBalance() error { return err } for _, channel := range channels { - if channel.Status != common.ChannelStatusEnabled { + if channel.Status != model.ChannelStatusEnabled { continue } // TODO: support Azure - if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom { + if channel.Type != channeltype.OpenAI && channel.Type != channeltype.Custom { continue } balance, err := updateChannelBalance(channel) diff --git a/controller/channel-test.go b/controller/channel-test.go index 57138f49..535b21bd 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -4,18 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/Laisky/errors/v2" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/common/message" - "github.com/songquanpeng/one-api/middleware" - "github.com/songquanpeng/one-api/model" - "github.com/songquanpeng/one-api/monitor" - "github.com/songquanpeng/one-api/relay/constant" - "github.com/songquanpeng/one-api/relay/helper" - relaymodel "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" "io" "net/http" "net/http/httptest" @@ -25,6 +13,20 @@ import ( "sync" "time" + "github.com/Laisky/errors/v2" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/common/message" + "github.com/songquanpeng/one-api/middleware" + "github.com/songquanpeng/one-api/model" + "github.com/songquanpeng/one-api/monitor" + relay "github.com/songquanpeng/one-api/relay" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/controller" + "github.com/songquanpeng/one-api/relay/meta" + relaymodel "github.com/songquanpeng/one-api/relay/model" + "github.com/songquanpeng/one-api/relay/relaymode" + "github.com/gin-gonic/gin" ) @@ -56,9 +58,9 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error c.Set("channel", channel.Type) c.Set("base_url", channel.GetBaseURL()) middleware.SetupContextForSelectedChannel(c, channel, "") - meta := util.GetRelayMeta(c) - apiType := constant.ChannelType2APIType(channel.Type) - adaptor := helper.GetAdaptor(apiType) + meta := meta.GetByContext(c) + apiType := channeltype.ToAPIType(channel.Type) + adaptor := relay.GetAdaptor(apiType) if adaptor == nil { return errors.Errorf("invalid api type: %d, adaptor is nil", apiType), nil } @@ -73,7 +75,7 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error request := buildTestRequest() request.Model = modelName meta.OriginModelName, meta.ActualModelName = modelName, modelName - convertedRequest, err := adaptor.ConvertRequest(c, constant.RelayModeChatCompletions, request) + convertedRequest, err := adaptor.ConvertRequest(c, relaymode.ChatCompletions, request) if err != nil { return err, nil } @@ -88,8 +90,8 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error return err, nil } if resp.StatusCode != http.StatusOK { - err := util.RelayErrorHandler(resp) - return errors.Errorf("status code %d: %s", resp.StatusCode, err.Error.Message), &err.Error + err := controller.RelayErrorHandler(resp) + return fmt.Errorf("status code %d: %s", resp.StatusCode, err.Error.Message), &err.Error } usage, respErr := adaptor.DoResponse(c, resp, meta) if respErr != nil { @@ -171,7 +173,7 @@ func testChannels(notify bool, scope string) error { } go func() { for _, channel := range channels { - isChannelEnabled := channel.Status == common.ChannelStatusEnabled + isChannelEnabled := channel.Status == model.ChannelStatusEnabled tik := time.Now() err, openaiErr := testChannel(channel) tok := time.Now() @@ -184,10 +186,10 @@ func testChannels(notify bool, scope string) error { _ = message.Notify(message.ByAll, fmt.Sprintf("渠道 %s (%d)测试超时", channel.Name, channel.Id), "", err.Error()) } } - if isChannelEnabled && util.ShouldDisableChannel(openaiErr, -1) { + if isChannelEnabled && monitor.ShouldDisableChannel(openaiErr, -1) { monitor.DisableChannel(channel.Id, channel.Name, err.Error()) } - if !isChannelEnabled && util.ShouldEnableChannel(err, openaiErr) { + if !isChannelEnabled && monitor.ShouldEnableChannel(err, openaiErr) { monitor.EnableChannel(channel.Id, channel.Name) } channel.UpdateResponseTime(milliseconds) diff --git a/controller/group.go b/controller/group.go index 128a3527..6f02394f 100644 --- a/controller/group.go +++ b/controller/group.go @@ -2,13 +2,13 @@ package controller import ( "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" + billingratio "github.com/songquanpeng/one-api/relay/billing/ratio" "net/http" ) func GetGroups(c *gin.Context) { groupNames := make([]string, 0) - for groupName := range common.GroupRatio { + for groupName := range billingratio.GroupRatio { groupNames = append(groupNames, groupName) } c.JSON(http.StatusOK, gin.H{ diff --git a/controller/misc.go b/controller/misc.go index f27fdb12..2928b8fb 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -23,6 +23,7 @@ func GetStatus(c *gin.Context) { "email_verification": config.EmailVerificationEnabled, "github_oauth": config.GitHubOAuthEnabled, "github_client_id": config.GitHubClientId, + "lark_client_id": config.LarkClientId, "system_name": config.SystemName, "logo": config.Logo, "footer_html": config.Footer, diff --git a/controller/model.go b/controller/model.go index 1be352f2..01d01bf0 100644 --- a/controller/model.go +++ b/controller/model.go @@ -3,13 +3,15 @@ package controller import ( "fmt" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/relay/channel/openai" - "github.com/songquanpeng/one-api/relay/constant" - "github.com/songquanpeng/one-api/relay/helper" + "github.com/songquanpeng/one-api/model" + relay "github.com/songquanpeng/one-api/relay" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + "github.com/songquanpeng/one-api/relay/apitype" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/meta" relaymodel "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" "net/http" + "strings" ) // https://platform.openai.com/docs/api-reference/models/list @@ -39,8 +41,8 @@ type OpenAIModels struct { Parent *string `json:"parent"` } -var openAIModels []OpenAIModels -var openAIModelsMap map[string]OpenAIModels +var models []OpenAIModels +var modelsMap map[string]OpenAIModels var channelId2Models map[int][]string func init() { @@ -60,11 +62,11 @@ func init() { IsBlocking: false, }) // https://platform.openai.com/docs/models/model-endpoint-compatibility - for i := 0; i < constant.APITypeDummy; i++ { - if i == constant.APITypeAIProxyLibrary { + for i := 0; i < apitype.Dummy; i++ { + if i == apitype.AIProxyLibrary { continue } - adaptor := helper.GetAdaptor(i) + adaptor := relay.GetAdaptor(i) if adaptor == nil { continue } @@ -72,7 +74,7 @@ func init() { channelName := adaptor.GetChannelName() modelNames := adaptor.GetModelList() for _, modelName := range modelNames { - openAIModels = append(openAIModels, OpenAIModels{ + models = append(models, OpenAIModels{ Id: modelName, Object: "model", Created: 1626777600, @@ -84,12 +86,12 @@ func init() { } } for _, channelType := range openai.CompatibleChannels { - if channelType == common.ChannelTypeAzure { + if channelType == channeltype.Azure { continue } channelName, channelModelList := openai.GetCompatibleChannelMeta(channelType) for _, modelName := range channelModelList { - openAIModels = append(openAIModels, OpenAIModels{ + models = append(models, OpenAIModels{ Id: modelName, Object: "model", Created: 1626777600, @@ -100,18 +102,18 @@ func init() { }) } } - openAIModelsMap = make(map[string]OpenAIModels) - for _, model := range openAIModels { - openAIModelsMap[model.Id] = model + modelsMap = make(map[string]OpenAIModels) + for _, model := range models { + modelsMap[model.Id] = model } channelId2Models = make(map[int][]string) - for i := 1; i < common.ChannelTypeDummy; i++ { - adaptor := helper.GetAdaptor(constant.ChannelType2APIType(i)) + for i := 1; i < channeltype.Dummy; i++ { + adaptor := relay.GetAdaptor(channeltype.ToAPIType(i)) if adaptor == nil { continue } - meta := &util.RelayMeta{ + meta := &meta.Meta{ ChannelType: i, } adaptor.Init(meta) @@ -127,16 +129,55 @@ func DashboardListModels(c *gin.Context) { }) } -func ListModels(c *gin.Context) { +func ListAllModels(c *gin.Context) { c.JSON(200, gin.H{ "object": "list", - "data": openAIModels, + "data": models, + }) +} + +func ListModels(c *gin.Context) { + ctx := c.Request.Context() + var availableModels []string + if c.GetString("available_models") != "" { + availableModels = strings.Split(c.GetString("available_models"), ",") + } else { + userId := c.GetInt("id") + userGroup, _ := model.CacheGetUserGroup(userId) + availableModels, _ = model.CacheGetGroupModels(ctx, userGroup) + } + modelSet := make(map[string]bool) + for _, availableModel := range availableModels { + modelSet[availableModel] = true + } + availableOpenAIModels := make([]OpenAIModels, 0) + for _, model := range models { + if _, ok := modelSet[model.Id]; ok { + modelSet[model.Id] = false + availableOpenAIModels = append(availableOpenAIModels, model) + } + } + for modelName, ok := range modelSet { + if ok { + availableOpenAIModels = append(availableOpenAIModels, OpenAIModels{ + Id: modelName, + Object: "model", + Created: 1626777600, + OwnedBy: "custom", + Root: modelName, + Parent: nil, + }) + } + } + c.JSON(200, gin.H{ + "object": "list", + "data": availableOpenAIModels, }) } func RetrieveModel(c *gin.Context) { modelId := c.Param("model") - if model, ok := openAIModelsMap[modelId]; ok { + if model, ok := modelsMap[modelId]; ok { c.JSON(200, model) } else { Error := relaymodel.Error{ @@ -150,3 +191,30 @@ func RetrieveModel(c *gin.Context) { }) } } + +func GetUserAvailableModels(c *gin.Context) { + ctx := c.Request.Context() + id := c.GetInt("id") + userGroup, err := model.CacheGetUserGroup(id) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + models, err := model.CacheGetGroupModels(ctx, userGroup) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + "data": models, + }) + return +} diff --git a/controller/redemption.go b/controller/redemption.go index 31c9348d..8d2b3f38 100644 --- a/controller/redemption.go +++ b/controller/redemption.go @@ -4,6 +4,7 @@ import ( "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" + "github.com/songquanpeng/one-api/common/random" "github.com/songquanpeng/one-api/model" "net/http" "strconv" @@ -106,7 +107,7 @@ func AddRedemption(c *gin.Context) { } var keys []string for i := 0; i < redemption.Count; i++ { - key := helper.GetUUID() + key := random.GetUUID() cleanRedemption := model.Redemption{ UserId: c.GetInt("id"), Name: redemption.Name, diff --git a/controller/relay.go b/controller/relay.go index 85932368..8278bf23 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -4,9 +4,6 @@ import ( "bytes" "context" "fmt" - "io" - "net/http" - "github.com/gin-gonic/gin" "github.com/pkg/errors" "github.com/songquanpeng/one-api/common" @@ -16,24 +13,25 @@ import ( "github.com/songquanpeng/one-api/middleware" dbmodel "github.com/songquanpeng/one-api/model" "github.com/songquanpeng/one-api/monitor" - "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/controller" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" + "github.com/songquanpeng/one-api/relay/relaymode" + "io" + "net/http" ) // https://platform.openai.com/docs/api-reference/chat -func relay(c *gin.Context, relayMode int) *model.ErrorWithStatusCode { +func relayHelper(c *gin.Context, relayMode int) *model.ErrorWithStatusCode { var err *model.ErrorWithStatusCode switch relayMode { - case constant.RelayModeImagesGenerations: + case relaymode.ImagesGenerations: err = controller.RelayImageHelper(c, relayMode) - case constant.RelayModeAudioSpeech: + case relaymode.AudioSpeech: fallthrough - case constant.RelayModeAudioTranslation: + case relaymode.AudioTranslation: fallthrough - case constant.RelayModeAudioTranscription: + case relaymode.AudioTranscription: err = controller.RelayAudioHelper(c, relayMode) default: err = controller.RelayTextHelper(c) @@ -43,13 +41,13 @@ func relay(c *gin.Context, relayMode int) *model.ErrorWithStatusCode { func Relay(c *gin.Context) { ctx := c.Request.Context() - relayMode := constant.Path2RelayMode(c.Request.URL.Path) + relayMode := relaymode.GetByPath(c.Request.URL.Path) if config.DebugEnabled { requestBody, _ := common.GetRequestBody(c) logger.Debugf(ctx, "request body: %s", string(requestBody)) } channelId := c.GetInt("channel_id") - bizErr := relay(c, relayMode) + bizErr := relayHelper(c, relayMode) if bizErr == nil { monitor.Emit(channelId, true) return @@ -78,7 +76,7 @@ func Relay(c *gin.Context) { middleware.SetupContextForSelectedChannel(c, channel, originalModel) requestBody, err := common.GetRequestBody(c) c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody)) - bizErr = relay(c, relayMode) + bizErr = relayHelper(c, relayMode) if bizErr == nil { return } @@ -117,7 +115,7 @@ func shouldRetry(c *gin.Context, statusCode int) error { func processChannelRelayError(ctx context.Context, channelId int, channelName string, err *model.ErrorWithStatusCode) { logger.Errorf(ctx, "relay error (channel #%d): %s", channelId, err.Message) // https://platform.openai.com/docs/guides/error-codes/api-errors - if util.ShouldDisableChannel(&err.Error, err.StatusCode) { + if monitor.ShouldDisableChannel(&err.Error, err.StatusCode) { monitor.DisableChannel(channelId, channelName, err.Message) } else { monitor.Emit(channelId, false) diff --git a/controller/token.go b/controller/token.go index 9b52b053..74d31547 100644 --- a/controller/token.go +++ b/controller/token.go @@ -6,9 +6,12 @@ import ( "strconv" "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" + "github.com/songquanpeng/one-api/common/network" + "github.com/songquanpeng/one-api/common/random" "github.com/songquanpeng/one-api/model" ) @@ -106,9 +109,24 @@ func GetTokenStatus(c *gin.Context) { }) } +func validateToken(c *gin.Context, token *model.Token) error { + if len(token.Name) > 30 { + return fmt.Errorf("令牌名称过长") + } + + if token.Subnet != nil && *token.Subnet != "" { + err := network.IsValidSubnets(*token.Subnet) + if err != nil { + return fmt.Errorf("无效的网段:%s", err.Error()) + } + } + + return nil +} + func AddToken(c *gin.Context) { - token := model.Token{} - err := c.ShouldBindJSON(&token) + token := new(model.Token) + err := c.ShouldBindJSON(token) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -116,22 +134,27 @@ func AddToken(c *gin.Context) { }) return } - if len(token.Name) > 30 { + + err = validateToken(c, token) + if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": "令牌名称过长", + "message": fmt.Sprintf("参数错误:%s", err.Error()), }) return } + cleanToken := model.Token{ UserId: c.GetInt("id"), Name: token.Name, - Key: helper.GenerateKey(), + Key: random.GenerateKey(), CreatedTime: helper.GetTimestamp(), AccessedTime: helper.GetTimestamp(), ExpiredTime: token.ExpiredTime, RemainQuota: token.RemainQuota, UnlimitedQuota: token.UnlimitedQuota, + Models: token.Models, + Subnet: token.Subnet, } err = cleanToken.Insert() if err != nil { @@ -168,12 +191,7 @@ func DeleteToken(c *gin.Context) { } type updateTokenDto struct { - Id int `json:"id"` - Status int `json:"status" gorm:"default:1"` - Name *string `json:"name" gorm:"index" ` - ExpiredTime *int64 `json:"expired_time" gorm:"bigint;default:-1"` // -1 means never expired - RemainQuota *int `json:"remain_quota" gorm:"default:0"` - UnlimitedQuota *bool `json:"unlimited_quota" gorm:"default:false"` + model.Token // AddUsedQuota add or subtract used quota AddUsedQuota int `json:"add_used_quota" gorm:"-"` AddReason string `json:"add_reason" gorm:"-"` @@ -183,43 +201,51 @@ func UpdateToken(c *gin.Context) { userId := c.GetInt("id") statusOnly := c.Query("status_only") tokenPatch := new(updateTokenDto) - if err := c.ShouldBindJSON(tokenPatch); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "parse request join: " + err.Error(), - }) - return - } - - if tokenPatch.Name != nil && - (len(*tokenPatch.Name) > 30 || len(*tokenPatch.Name) == 0) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "令牌名称错误,长度应在 1-30 之间", - }) - return - } - - tokenInDB, err := model.GetTokenByIds(tokenPatch.Id, userId) + err := c.ShouldBindJSON(tokenPatch) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": fmt.Sprintf("get token by id %d: %s", tokenPatch.Id, err.Error()), + "message": err.Error(), }) return } - if tokenPatch.Status == common.TokenStatusEnabled { - if tokenInDB.Status == common.TokenStatusExpired && tokenInDB.ExpiredTime <= helper.GetTimestamp() && tokenInDB.ExpiredTime != -1 { + token := new(model.Token) + if err = copier.Copy(token, tokenPatch); err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + + err = validateToken(c, token) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": fmt.Sprintf("参数错误:%s", err.Error()), + }) + return + } + + cleanToken, err := model.GetTokenByIds(token.Id, userId) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + + if token.Status == model.TokenStatusEnabled { + if cleanToken.Status == model.TokenStatusExpired && cleanToken.ExpiredTime <= helper.GetTimestamp() && cleanToken.ExpiredTime != -1 { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期", }) return } - if tokenInDB.Status == common.TokenStatusExhausted && - tokenInDB.RemainQuota <= 0 && - !tokenInDB.UnlimitedQuota { + if cleanToken.Status == model.TokenStatusExhausted && cleanToken.RemainQuota <= 0 && !cleanToken.UnlimitedQuota { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度", @@ -228,42 +254,33 @@ func UpdateToken(c *gin.Context) { } } if statusOnly != "" { - tokenInDB.Status = tokenPatch.Status + cleanToken.Status = token.Status } else { - // If you add more fields, please also update tokenPatch.Update() - if tokenPatch.Name != nil { - tokenInDB.Name = *tokenPatch.Name - } - if tokenPatch.ExpiredTime != nil { - tokenInDB.ExpiredTime = *tokenPatch.ExpiredTime - } - if tokenPatch.RemainQuota != nil { - tokenInDB.RemainQuota = int64(*tokenPatch.RemainQuota) - } - if tokenPatch.UnlimitedQuota != nil { - tokenInDB.UnlimitedQuota = *tokenPatch.UnlimitedQuota - } + // If you add more fields, please also update token.Update() + cleanToken.Name = token.Name + cleanToken.ExpiredTime = token.ExpiredTime + cleanToken.RemainQuota = token.RemainQuota + cleanToken.UnlimitedQuota = token.UnlimitedQuota + cleanToken.Models = token.Models + cleanToken.Subnet = token.Subnet } - tokenInDB.RemainQuota -= int64(tokenPatch.AddUsedQuota) - tokenInDB.UsedQuota += int64(tokenPatch.AddUsedQuota) - if tokenPatch.AddUsedQuota != 0 { model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("外部(%s)消耗 %s", tokenPatch.AddReason, common.LogQuota(int64(tokenPatch.AddUsedQuota)))) } - if err = tokenInDB.Update(); err != nil { + err = cleanToken.Update() + if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": "update token: " + err.Error(), + "message": err.Error(), }) return } - c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": tokenInDB, + "data": cleanToken, }) return } diff --git a/controller/user.go b/controller/user.go index 691378ec..bd31c034 100644 --- a/controller/user.go +++ b/controller/user.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/helper" + "github.com/songquanpeng/one-api/common/random" "github.com/songquanpeng/one-api/model" "net/http" "strconv" @@ -58,11 +58,11 @@ func Login(c *gin.Context) { }) return } - setupLogin(&user, c) + SetupLogin(&user, c) } // setup session & cookies and then return user info -func setupLogin(user *model.User, c *gin.Context) { +func SetupLogin(user *model.User, c *gin.Context) { session := sessions.Default(c) session.Set("id", user.Id) session.Set("username", user.Username) @@ -186,27 +186,27 @@ func Register(c *gin.Context) { } func GetAllUsers(c *gin.Context) { - p, _ := strconv.Atoi(c.Query("p")) - if p < 0 { - p = 0 - } - - order := c.DefaultQuery("order", "") - users, err := model.GetAllUsers(p*config.ItemsPerPage, config.ItemsPerPage, order) - - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "", - "data": users, - }) + p, _ := strconv.Atoi(c.Query("p")) + if p < 0 { + p = 0 + } + + order := c.DefaultQuery("order", "") + users, err := model.GetAllUsers(p*config.ItemsPerPage, config.ItemsPerPage, order) + + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + "data": users, + }) } func SearchUsers(c *gin.Context) { @@ -245,7 +245,7 @@ func GetUser(c *gin.Context) { return } myRole := c.GetInt("role") - if myRole <= user.Role && myRole != common.RoleRootUser { + if myRole <= user.Role && myRole != model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无权获取同级或更高等级用户的信息", @@ -293,7 +293,7 @@ func GenerateAccessToken(c *gin.Context) { }) return } - user.AccessToken = helper.GetUUID() + user.AccessToken = random.GetUUID() if model.DB.Where("access_token = ?", user.AccessToken).First(user).RowsAffected != 0 { c.JSON(http.StatusOK, gin.H{ @@ -330,7 +330,7 @@ func GetAffCode(c *gin.Context) { return } if user.AffCode == "" { - user.AffCode = helper.GetRandomString(4) + user.AffCode = random.GetRandomString(4) if err := user.Update(false); err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -404,14 +404,14 @@ func UpdateUser(c *gin.Context) { return } myRole := c.GetInt("role") - if myRole <= originUser.Role && myRole != common.RoleRootUser { + if myRole <= originUser.Role && myRole != model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无权更新同权限等级或更高权限等级的用户信息", }) return } - if myRole <= updatedUser.Role && myRole != common.RoleRootUser { + if myRole <= updatedUser.Role && myRole != model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无权将其他用户权限等级提升到大于等于自己的权限等级", @@ -525,7 +525,7 @@ func DeleteSelf(c *gin.Context) { id := c.GetInt("id") user, _ := model.GetUserById(id, false) - if user.Role == common.RoleRootUser { + if user.Role == model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "不能删除超级管理员账户", @@ -627,7 +627,7 @@ func ManageUser(c *gin.Context) { return } myRole := c.GetInt("role") - if myRole <= user.Role && myRole != common.RoleRootUser { + if myRole <= user.Role && myRole != model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无权更新同权限等级或更高权限等级的用户信息", @@ -636,8 +636,8 @@ func ManageUser(c *gin.Context) { } switch req.Action { case "disable": - user.Status = common.UserStatusDisabled - if user.Role == common.RoleRootUser { + user.Status = model.UserStatusDisabled + if user.Role == model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无法禁用超级管理员用户", @@ -645,9 +645,9 @@ func ManageUser(c *gin.Context) { return } case "enable": - user.Status = common.UserStatusEnabled + user.Status = model.UserStatusEnabled case "delete": - if user.Role == common.RoleRootUser { + if user.Role == model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无法删除超级管理员用户", @@ -662,37 +662,37 @@ func ManageUser(c *gin.Context) { return } case "promote": - if myRole != common.RoleRootUser { + if myRole != model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "普通管理员用户无法提升其他用户为管理员", }) return } - if user.Role >= common.RoleAdminUser { + if user.Role >= model.RoleAdminUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "该用户已经是管理员", }) return } - user.Role = common.RoleAdminUser + user.Role = model.RoleAdminUser case "demote": - if user.Role == common.RoleRootUser { + if user.Role == model.RoleRootUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "无法降级超级管理员用户", }) return } - if user.Role == common.RoleCommonUser { + if user.Role == model.RoleCommonUser { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "该用户已经是普通用户", }) return } - user.Role = common.RoleCommonUser + user.Role = model.RoleCommonUser } if err := user.Update(false); err != nil { @@ -746,7 +746,7 @@ func EmailBind(c *gin.Context) { }) return } - if user.Role == common.RoleRootUser { + if user.Role == model.RoleRootUser { config.RootUserEmail = email } c.JSON(http.StatusOK, gin.H{ @@ -786,3 +786,38 @@ func TopUp(c *gin.Context) { }) return } + +type adminTopUpRequest struct { + UserId int `json:"user_id"` + Quota int `json:"quota"` + Remark string `json:"remark"` +} + +func AdminTopUp(c *gin.Context) { + req := adminTopUpRequest{} + err := c.ShouldBindJSON(&req) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + err = model.IncreaseUserQuota(req.UserId, int64(req.Quota)) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + if req.Remark == "" { + req.Remark = fmt.Sprintf("通过 API 充值 %s", common.LogQuota(int64(req.Quota))) + } + model.RecordTopupLog(req.UserId, req.Remark, req.Quota) + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + }) + return +} diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 00000000..0b7ddf5a --- /dev/null +++ b/docs/API.md @@ -0,0 +1,53 @@ +# 使用 API 操控 & 扩展 One API +> 欢迎提交 PR 在此放上你的拓展项目。 + +例如,虽然 One API 本身没有直接支持支付,但是你可以通过系统扩展的 API 来实现支付功能。 + +又或者你想自定义渠道管理策略,也可以通过 API 来实现渠道的禁用与启用。 + +## 鉴权 +One API 支持两种鉴权方式:Cookie 和 Token,对于 Token,参照下图获取: + +![image](https://github.com/songquanpeng/songquanpeng.github.io/assets/39998050/c15281a7-83ed-47cb-a1f6-913cb6bf4a7c) + +之后,将 Token 作为请求头的 Authorization 字段的值即可,例如下面使用 Token 调用测试渠道的 API: +![image](https://github.com/songquanpeng/songquanpeng.github.io/assets/39998050/1273b7ae-cb60-4c0d-93a6-b1cbc039c4f8) + +## 请求格式与响应格式 +One API 使用 JSON 格式进行请求和响应。 + +对于响应体,一般格式如下: +```json +{ + "message": "请求信息", + "success": true, + "data": {} +} +``` + +## API 列表 +> 当前 API 列表不全,请自行通过浏览器抓取前端请求 + +如果现有的 API 没有办法满足你的需求,欢迎提交 issue 讨论。 + +### 获取当前登录用户信息 +**GET** `/api/user/self` + +### 为给定用户充值额度 +**POST** `/api/topup` +```json +{ + "user_id": 1, + "quota": 100000, + "remark": "充值 100000 额度" +} +``` + +## 其他 +### 充值链接上的附加参数 +One API 会在用户点击充值按钮的时候,将用户的信息和充值信息附加在链接上,例如: +`https://example.com?username=root&user_id=1&transaction_id=4b3eed80-55d5-443f-bd44-fb18c648c837` + +你可以通过解析链接上的参数来获取用户信息和充值信息,然后调用 API 来为用户充值。 + +注意,不是所有主题都支持该功能,欢迎 PR 补齐。 \ No newline at end of file diff --git a/go.mod b/go.mod index 5c50c39d..39f9e295 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,10 @@ require ( github.com/go-playground/validator/v10 v10.19.0 github.com/go-redis/redis/v8 v8.11.5 github.com/google/uuid v1.6.0 + github.com/jinzhu/copier v0.4.0 github.com/pkg/errors v0.9.1 github.com/pkoukk/tiktoken-go v0.1.6 + github.com/smartystreets/goconvey v1.8.1 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.21.0 golang.org/x/image v0.15.0 @@ -48,16 +50,17 @@ require ( github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.5.4 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -66,6 +69,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/smarty/assertions v1.15.0 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect @@ -78,7 +82,7 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/tools v0.7.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d518f25f..bd2decbc 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932/go.mod h1:cC6EdPbj/1 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -107,16 +109,18 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -164,6 +168,10 @@ github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -206,8 +214,8 @@ golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -238,8 +246,8 @@ golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/main.go b/main.go index 9c727152..3ee1dc94 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ import ( "github.com/songquanpeng/one-api/controller" "github.com/songquanpeng/one-api/middleware" "github.com/songquanpeng/one-api/model" - "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/router" ) diff --git a/middleware/auth.go b/middleware/auth.go index 95ae5700..d55820f7 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -1,15 +1,15 @@ package middleware import ( - "net/http" - "strings" - + "fmt" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/blacklist" "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/common/network" "github.com/songquanpeng/one-api/model" + "net/http" + "strings" ) func authHelper(c *gin.Context, minRole int) { @@ -47,7 +47,7 @@ func authHelper(c *gin.Context, minRole int) { return } } - if status.(int) == common.UserStatusDisabled || blacklist.IsUserBanned(id.(int)) { + if status.(int) == model.UserStatusDisabled || blacklist.IsUserBanned(id.(int)) { c.JSON(http.StatusOK, gin.H{ "success": false, "message": "用户已被封禁", @@ -74,24 +74,25 @@ func authHelper(c *gin.Context, minRole int) { func UserAuth() func(c *gin.Context) { return func(c *gin.Context) { - authHelper(c, common.RoleCommonUser) + authHelper(c, model.RoleCommonUser) } } func AdminAuth() func(c *gin.Context) { return func(c *gin.Context) { - authHelper(c, common.RoleAdminUser) + authHelper(c, model.RoleAdminUser) } } func RootAuth() func(c *gin.Context) { return func(c *gin.Context) { - authHelper(c, common.RoleRootUser) + authHelper(c, model.RoleRootUser) } } func TokenAuth() func(c *gin.Context) { return func(c *gin.Context) { + ctx := c.Request.Context() key := c.Request.Header.Get("Authorization") key = strings.TrimPrefix(key, "Bearer ") key = strings.TrimPrefix(strings.TrimPrefix(key, "sk-"), "laisky-") @@ -102,6 +103,12 @@ func TokenAuth() func(c *gin.Context) { abortWithMessage(c, http.StatusUnauthorized, err.Error()) return } + if token.Subnet != nil && *token.Subnet != "" { + if !network.IsIpInSubnets(ctx, c.ClientIP(), *token.Subnet) { + abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("该令牌只能在指定网段使用:%s,当前 ip:%s", *token.Subnet, c.ClientIP())) + return + } + } userEnabled, err := model.CacheIsUserEnabled(token.UserId) if err != nil { abortWithMessage(c, http.StatusInternalServerError, err.Error()) @@ -111,6 +118,19 @@ func TokenAuth() func(c *gin.Context) { abortWithMessage(c, http.StatusForbidden, "用户已被封禁") return } + requestModel, err := getRequestModel(c) + if err != nil && !strings.HasPrefix(c.Request.URL.Path, "/v1/models") { + abortWithMessage(c, http.StatusBadRequest, err.Error()) + return + } + c.Set("request_model", requestModel) + if token.Models != nil && *token.Models != "" { + c.Set("available_models", *token.Models) + if requestModel != "" && !isModelInList(requestModel, *token.Models) { + abortWithMessage(c, http.StatusForbidden, fmt.Sprintf("该令牌无权使用模型:%s", requestModel)) + return + } + } c.Set("id", token.UserId) c.Set("token_id", token.Id) c.Set("token_name", token.Name) diff --git a/middleware/distributor.go b/middleware/distributor.go index f538f250..3eff734d 100644 --- a/middleware/distributor.go +++ b/middleware/distributor.go @@ -2,14 +2,16 @@ package middleware import ( "fmt" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/model" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/model" + "github.com/songquanpeng/one-api/relay/billing/ratio" + "github.com/songquanpeng/one-api/relay/channeltype" ) type ModelRequest struct { @@ -35,42 +37,16 @@ func Distribute() func(c *gin.Context) { abortWithMessage(c, http.StatusBadRequest, "无效的渠道 Id") return } - if channel.Status != common.ChannelStatusEnabled { + if channel.Status != model.ChannelStatusEnabled { abortWithMessage(c, http.StatusForbidden, "该渠道已被禁用") return } } else { - // Select a channel for the user - var modelRequest ModelRequest - err := common.UnmarshalBodyReusable(c, &modelRequest) + requestModel := c.GetString("request_model") + var err error + channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, requestModel, false) if err != nil { - abortWithMessage(c, http.StatusBadRequest, fmt.Sprintf("无效的请求: %+v", err)) - return - } - if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") { - if modelRequest.Model == "" { - modelRequest.Model = "text-moderation-stable" - } - } - if strings.HasSuffix(c.Request.URL.Path, "embeddings") { - if modelRequest.Model == "" { - modelRequest.Model = c.Param("model") - } - } - if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") { - if modelRequest.Model == "" { - modelRequest.Model = "dall-e-2" - } - } - if strings.HasPrefix(c.Request.URL.Path, "/v1/audio/transcriptions") || strings.HasPrefix(c.Request.URL.Path, "/v1/audio/translations") { - if modelRequest.Model == "" { - modelRequest.Model = "whisper-1" - } - } - requestModel = modelRequest.Model - channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model, false) - if err != nil { - message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, modelRequest.Model) + message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, requestModel) if channel != nil { logger.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id)) message = "数据库一致性已被破坏,请联系管理员" @@ -85,17 +61,18 @@ func Distribute() func(c *gin.Context) { } func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, modelName string) { + // one channel could relates to multiple groups, + // and each groud has individual ratio, // set minimal group ratio as channel_ratio var minimalRatio float64 = -1 for _, grp := range strings.Split(channel.Group, ",") { - v := common.GetGroupRatio(grp) + v := ratio.GetGroupRatio(grp) if minimalRatio < 0 || v < minimalRatio { minimalRatio = v } } logger.Info(c.Request.Context(), fmt.Sprintf("set channel %s ratio to %f", channel.Name, minimalRatio)) c.Set("channel_ratio", minimalRatio) - c.Set("channel", channel.Type) c.Set("channel_id", channel.Id) c.Set("channel_name", channel.Name) @@ -105,19 +82,19 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode c.Set("base_url", channel.GetBaseURL()) // this is for backward compatibility switch channel.Type { - case common.ChannelTypeAzure: - c.Set(common.ConfigKeyAPIVersion, channel.Other) - case common.ChannelTypeXunfei: - c.Set(common.ConfigKeyAPIVersion, channel.Other) - case common.ChannelTypeGemini: - c.Set(common.ConfigKeyAPIVersion, channel.Other) - case common.ChannelTypeAIProxyLibrary: - c.Set(common.ConfigKeyLibraryID, channel.Other) - case common.ChannelTypeAli: - c.Set(common.ConfigKeyPlugin, channel.Other) + case channeltype.Azure: + c.Set(config.KeyAPIVersion, channel.Other) + case channeltype.Xunfei: + c.Set(config.KeyAPIVersion, channel.Other) + case channeltype.Gemini: + c.Set(config.KeyAPIVersion, channel.Other) + case channeltype.AIProxyLibrary: + c.Set(config.KeyLibraryID, channel.Other) + case channeltype.Ali: + c.Set(config.KeyPlugin, channel.Other) } cfg, _ := channel.LoadConfig() for k, v := range cfg { - c.Set(common.ConfigKeyPrefix+k, v) + c.Set(config.KeyPrefix+k, v) } } diff --git a/middleware/utils.go b/middleware/utils.go index bc14c367..b65b018b 100644 --- a/middleware/utils.go +++ b/middleware/utils.go @@ -1,9 +1,12 @@ package middleware import ( + "fmt" "github.com/gin-gonic/gin" + "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" + "strings" ) func abortWithMessage(c *gin.Context, statusCode int, message string) { @@ -16,3 +19,42 @@ func abortWithMessage(c *gin.Context, statusCode int, message string) { c.Abort() logger.Error(c.Request.Context(), message) } + +func getRequestModel(c *gin.Context) (string, error) { + var modelRequest ModelRequest + err := common.UnmarshalBodyReusable(c, &modelRequest) + if err != nil { + return "", fmt.Errorf("common.UnmarshalBodyReusable failed: %w", err) + } + if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") { + if modelRequest.Model == "" { + modelRequest.Model = "text-moderation-stable" + } + } + if strings.HasSuffix(c.Request.URL.Path, "embeddings") { + if modelRequest.Model == "" { + modelRequest.Model = c.Param("model") + } + } + if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") { + if modelRequest.Model == "" { + modelRequest.Model = "dall-e-2" + } + } + if strings.HasPrefix(c.Request.URL.Path, "/v1/audio/transcriptions") || strings.HasPrefix(c.Request.URL.Path, "/v1/audio/translations") { + if modelRequest.Model == "" { + modelRequest.Model = "whisper-1" + } + } + return modelRequest.Model, nil +} + +func isModelInList(modelName string, models string) bool { + modelList := strings.Split(models, ",") + for _, model := range modelList { + if modelName == model { + return true + } + } + return false +} diff --git a/model/ability.go b/model/ability.go index 48b856a2..2db72518 100644 --- a/model/ability.go +++ b/model/ability.go @@ -1,8 +1,10 @@ package model import ( + "context" "github.com/songquanpeng/one-api/common" "gorm.io/gorm" + "sort" "strings" ) @@ -55,7 +57,7 @@ func (channel *Channel) AddAbilities() error { Group: group, Model: model, ChannelId: channel.Id, - Enabled: channel.Status == common.ChannelStatusEnabled, + Enabled: channel.Status == ChannelStatusEnabled, Priority: channel.Priority, } abilities = append(abilities, ability) @@ -88,3 +90,19 @@ func (channel *Channel) UpdateAbilities() error { func UpdateAbilityStatus(channelId int, status bool) error { return DB.Model(&Ability{}).Where("channel_id = ?", channelId).Select("enabled").Update("enabled", status).Error } + +func GetGroupModels(ctx context.Context, group string) ([]string, error) { + groupCol := "`group`" + trueVal := "1" + if common.UsingPostgreSQL { + groupCol = `"group"` + trueVal = "true" + } + var models []string + err := DB.Model(&Ability{}).Distinct("model").Where(groupCol+" = ? and enabled = "+trueVal, group).Pluck("model", &models).Error + if err != nil { + return nil, err + } + sort.Strings(models) + return models, err +} diff --git a/model/cache.go b/model/cache.go index 50946bd6..a05cec19 100644 --- a/model/cache.go +++ b/model/cache.go @@ -8,6 +8,7 @@ import ( "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/common/random" "math/rand" "sort" "strconv" @@ -21,6 +22,7 @@ var ( UserId2GroupCacheSeconds = config.SyncFrequency UserId2QuotaCacheSeconds = config.SyncFrequency UserId2StatusCacheSeconds = config.SyncFrequency + GroupModelsCacheSeconds = config.SyncFrequency ) func CacheGetTokenByKey(key string) (*Token, error) { @@ -147,13 +149,32 @@ func CacheIsUserEnabled(userId int) (bool, error) { return userEnabled, err } +func CacheGetGroupModels(ctx context.Context, group string) ([]string, error) { + if !common.RedisEnabled { + return GetGroupModels(ctx, group) + } + modelsStr, err := common.RedisGet(fmt.Sprintf("group_models:%s", group)) + if err == nil { + return strings.Split(modelsStr, ","), nil + } + models, err := GetGroupModels(ctx, group) + if err != nil { + return nil, err + } + err = common.RedisSet(fmt.Sprintf("group_models:%s", group), strings.Join(models, ","), time.Duration(GroupModelsCacheSeconds)*time.Second) + if err != nil { + logger.SysError("Redis set group models error: " + err.Error()) + } + return models, nil +} + var group2model2channels map[string]map[string][]*Channel var channelSyncLock sync.RWMutex func InitChannelCache() { newChannelId2channel := make(map[int]*Channel) var channels []*Channel - DB.Where("status = ?", common.ChannelStatusEnabled).Find(&channels) + DB.Where("status = ?", ChannelStatusEnabled).Find(&channels) for _, channel := range channels { newChannelId2channel[channel.Id] = channel } @@ -228,7 +249,7 @@ func CacheGetRandomSatisfiedChannel(group string, model string, ignoreFirstPrior idx := rand.Intn(endIdx) if ignoreFirstPriority { if endIdx < len(channels) { // which means there are more than one priority - idx = common.RandRange(endIdx, len(channels)) + idx = random.RandRange(endIdx, len(channels)) } } return channels[idx], nil diff --git a/model/channel.go b/model/channel.go index fc4905b1..e667f7e7 100644 --- a/model/channel.go +++ b/model/channel.go @@ -3,13 +3,19 @@ package model import ( "encoding/json" "fmt" - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" "gorm.io/gorm" ) +const ( + ChannelStatusUnknown = 0 + ChannelStatusEnabled = 1 // don't use 0, 0 is the default value! + ChannelStatusManuallyDisabled = 2 // also don't use 0 + ChannelStatusAutoDisabled = 3 +) + type Channel struct { Id int `json:"id"` Type int `json:"type" gorm:"default:0"` @@ -39,7 +45,7 @@ func GetAllChannels(startIdx int, num int, scope string) ([]*Channel, error) { case "all": err = DB.Order("id desc").Find(&channels).Error case "disabled": - err = DB.Order("id desc").Where("status = ? or status = ?", common.ChannelStatusAutoDisabled, common.ChannelStatusManuallyDisabled).Find(&channels).Error + err = DB.Order("id desc").Where("status = ? or status = ?", ChannelStatusAutoDisabled, ChannelStatusManuallyDisabled).Find(&channels).Error default: err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&channels).Error } @@ -168,7 +174,7 @@ func (channel *Channel) LoadConfig() (map[string]string, error) { } func UpdateChannelStatusById(id int, status int) { - err := UpdateAbilityStatus(id, status == common.ChannelStatusEnabled) + err := UpdateAbilityStatus(id, status == ChannelStatusEnabled) if err != nil { logger.SysError("failed to update ability status: " + err.Error()) } @@ -199,6 +205,6 @@ func DeleteChannelByStatus(status int64) (int64, error) { } func DeleteDisabledChannel() (int64, error) { - result := DB.Where("status = ? or status = ?", common.ChannelStatusAutoDisabled, common.ChannelStatusManuallyDisabled).Delete(&Channel{}) + result := DB.Where("status = ? or status = ?", ChannelStatusAutoDisabled, ChannelStatusManuallyDisabled).Delete(&Channel{}) return result.RowsAffected, result.Error } diff --git a/model/log.go b/model/log.go index 4409f73e..6fba776a 100644 --- a/model/log.go +++ b/model/log.go @@ -7,7 +7,6 @@ import ( "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" - "gorm.io/gorm" ) @@ -51,6 +50,21 @@ func RecordLog(userId int, logType int, content string) { } } +func RecordTopupLog(userId int, content string, quota int) { + log := &Log{ + UserId: userId, + Username: GetUsernameById(userId), + CreatedAt: helper.GetTimestamp(), + Type: LogTypeTopup, + Content: content, + Quota: quota, + } + err := LOG_DB.Create(log).Error + if err != nil { + logger.SysError("failed to record log: " + err.Error()) + } +} + func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int64, content string) { logger.Info(ctx, fmt.Sprintf("record consume log: userId=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content)) if !config.LogConsumeEnabled { diff --git a/model/main.go b/model/main.go index 4bbfde27..e5124a4c 100644 --- a/model/main.go +++ b/model/main.go @@ -12,6 +12,7 @@ import ( "github.com/songquanpeng/one-api/common/env" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/common/random" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" @@ -33,10 +34,10 @@ func CreateRootAccountIfNeed() error { rootUser := User{ Username: "root", Password: hashedPassword, - Role: common.RoleRootUser, - Status: common.UserStatusEnabled, + Role: RoleRootUser, + Status: UserStatusEnabled, DisplayName: "Root User", - AccessToken: helper.GetUUID(), + AccessToken: random.GetUUID(), Quota: 500000000000000, } DB.Create(&rootUser) @@ -46,7 +47,7 @@ func CreateRootAccountIfNeed() error { Id: 1, UserId: rootUser.Id, Key: config.InitialRootToken, - Status: common.TokenStatusEnabled, + Status: TokenStatusEnabled, Name: "Initial Root Token", CreatedTime: helper.GetTimestamp(), AccessedTime: helper.GetTimestamp(), diff --git a/model/option.go b/model/option.go index 1d1c28b4..bed8d4c3 100644 --- a/model/option.go +++ b/model/option.go @@ -1,9 +1,9 @@ package model import ( - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" + billingratio "github.com/songquanpeng/one-api/relay/billing/ratio" "strconv" "strings" "time" @@ -66,9 +66,9 @@ func InitOptionMap() { config.OptionMap["QuotaForInvitee"] = strconv.FormatInt(config.QuotaForInvitee, 10) config.OptionMap["QuotaRemindThreshold"] = strconv.FormatInt(config.QuotaRemindThreshold, 10) config.OptionMap["PreConsumedQuota"] = strconv.FormatInt(config.PreConsumedQuota, 10) - config.OptionMap["ModelRatio"] = common.ModelRatio2JSONString() - config.OptionMap["GroupRatio"] = common.GroupRatio2JSONString() - config.OptionMap["CompletionRatio"] = common.CompletionRatio2JSONString() + config.OptionMap["ModelRatio"] = billingratio.ModelRatio2JSONString() + config.OptionMap["GroupRatio"] = billingratio.GroupRatio2JSONString() + config.OptionMap["CompletionRatio"] = billingratio.CompletionRatio2JSONString() config.OptionMap["TopUpLink"] = config.TopUpLink config.OptionMap["ChatLink"] = config.ChatLink config.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(config.QuotaPerUnit, 'f', -1, 64) @@ -82,7 +82,7 @@ func loadOptionsFromDatabase() { options, _ := AllOption() for _, option := range options { if option.Key == "ModelRatio" { - option.Value = common.AddNewMissingRatio(option.Value) + option.Value = billingratio.AddNewMissingRatio(option.Value) } err := updateOptionMap(option.Key, option.Value) if err != nil { @@ -172,6 +172,10 @@ func updateOptionMap(key string, value string) (err error) { config.GitHubClientId = value case "GitHubClientSecret": config.GitHubClientSecret = value + case "LarkClientId": + config.LarkClientId = value + case "LarkClientSecret": + config.LarkClientSecret = value case "Footer": config.Footer = value case "SystemName": @@ -205,11 +209,11 @@ func updateOptionMap(key string, value string) (err error) { case "RetryTimes": config.RetryTimes, _ = strconv.Atoi(value) case "ModelRatio": - err = common.UpdateModelRatioByJSONString(value) + err = billingratio.UpdateModelRatioByJSONString(value) case "GroupRatio": - err = common.UpdateGroupRatioByJSONString(value) + err = billingratio.UpdateGroupRatioByJSONString(value) case "CompletionRatio": - err = common.UpdateCompletionRatioByJSONString(value) + err = billingratio.UpdateCompletionRatioByJSONString(value) case "TopUpLink": config.TopUpLink = value case "ChatLink": diff --git a/model/redemption.go b/model/redemption.go index c3ed2576..62428d35 100644 --- a/model/redemption.go +++ b/model/redemption.go @@ -8,6 +8,12 @@ import ( "gorm.io/gorm" ) +const ( + RedemptionCodeStatusEnabled = 1 // don't use 0, 0 is the default value! + RedemptionCodeStatusDisabled = 2 // also don't use 0 + RedemptionCodeStatusUsed = 3 // also don't use 0 +) + type Redemption struct { Id int `json:"id"` UserId int `json:"user_id"` @@ -61,7 +67,7 @@ func Redeem(key string, userId int) (quota int64, err error) { if err != nil { return errors.New("无效的兑换码") } - if redemption.Status != common.RedemptionCodeStatusEnabled { + if redemption.Status != RedemptionCodeStatusEnabled { return errors.New("该兑换码已被使用") } err = tx.Model(&User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota + ?", redemption.Quota)).Error @@ -69,7 +75,7 @@ func Redeem(key string, userId int) (quota int64, err error) { return err } redemption.RedeemedTime = helper.GetTimestamp() - redemption.Status = common.RedemptionCodeStatusUsed + redemption.Status = RedemptionCodeStatusUsed err = tx.Save(redemption).Error return err }) diff --git a/model/token.go b/model/token.go index c5491d06..10fd0d78 100644 --- a/model/token.go +++ b/model/token.go @@ -12,25 +12,34 @@ import ( "gorm.io/gorm" ) +const ( + TokenStatusEnabled = 1 // don't use 0, 0 is the default value! + TokenStatusDisabled = 2 // also don't use 0 + TokenStatusExpired = 3 + TokenStatusExhausted = 4 +) + type Token struct { - Id int `json:"id"` - UserId int `json:"user_id"` - Key string `json:"key" gorm:"type:char(48);uniqueIndex"` - Status int `json:"status" gorm:"default:1"` - Name string `json:"name" gorm:"index" ` - CreatedTime int64 `json:"created_time" gorm:"bigint"` - AccessedTime int64 `json:"accessed_time" gorm:"bigint"` - ExpiredTime int64 `json:"expired_time" gorm:"bigint;default:-1"` // -1 means never expired - RemainQuota int64 `json:"remain_quota" gorm:"bigint;default:0"` - UnlimitedQuota bool `json:"unlimited_quota" gorm:"default:false"` - UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` // used quota + Id int `json:"id"` + UserId int `json:"user_id"` + Key string `json:"key" gorm:"type:char(48);uniqueIndex"` + Status int `json:"status" gorm:"default:1"` + Name string `json:"name" gorm:"index" ` + CreatedTime int64 `json:"created_time" gorm:"bigint"` + AccessedTime int64 `json:"accessed_time" gorm:"bigint"` + ExpiredTime int64 `json:"expired_time" gorm:"bigint;default:-1"` // -1 means never expired + RemainQuota int64 `json:"remain_quota" gorm:"bigint;default:0"` + UnlimitedQuota bool `json:"unlimited_quota" gorm:"default:false"` + UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"` // used quota + Models *string `json:"models" gorm:"default:''"` // allowed models + Subnet *string `json:"subnet" gorm:"default:''"` // allowed subnet } func GetAllUserTokens(userId int, startIdx int, num int, order string) ([]*Token, error) { var tokens []*Token var err error query := DB.Where("user_id = ?", userId) - + switch order { case "remain_quota": query = query.Order("unlimited_quota desc, remain_quota desc") @@ -39,7 +48,7 @@ func GetAllUserTokens(userId int, startIdx int, num int, order string) ([]*Token default: query = query.Order("id desc") } - + err = query.Limit(num).Offset(startIdx).Find(&tokens).Error return tokens, err } @@ -62,18 +71,17 @@ func ValidateUserToken(key string) (token *Token, err error) { return nil, errors.Wrap(err, "failed to get token by key") } - - if token.Status == common.TokenStatusExhausted { - return nil, errors.New("该令牌额度已用尽") - } else if token.Status == common.TokenStatusExpired { + if token.Status == TokenStatusExhausted { + return nil, fmt.Errorf("令牌 %s(#%d)额度已用尽", token.Name, token.Id) + } else if token.Status == TokenStatusExpired { return nil, errors.New("该令牌已过期") } - if token.Status != common.TokenStatusEnabled { + if token.Status != TokenStatusEnabled { return nil, errors.New("该令牌状态不可用") } if token.ExpiredTime != -1 && token.ExpiredTime < helper.GetTimestamp() { if !common.RedisEnabled { - token.Status = common.TokenStatusExpired + token.Status = TokenStatusExpired err := token.SelectUpdate() if err != nil { logger.SysError("failed to update token status" + err.Error()) @@ -84,7 +92,7 @@ func ValidateUserToken(key string) (token *Token, err error) { if !token.UnlimitedQuota && token.RemainQuota <= 0 { if !common.RedisEnabled { // in this case, we can make sure the token is exhausted - token.Status = common.TokenStatusExhausted + token.Status = TokenStatusExhausted err := token.SelectUpdate() if err != nil { logger.SysError("failed to update token status" + err.Error()) @@ -124,7 +132,7 @@ func (token *Token) Insert() error { // Update Make sure your token's fields is completed, because this will update non-zero values func (token *Token) Update() error { var err error - err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota").Updates(token).Error + err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota", "models", "subnet").Updates(token).Error return err } diff --git a/model/user.go b/model/user.go index e1244e3c..3cc1f9c0 100644 --- a/model/user.go +++ b/model/user.go @@ -8,11 +8,24 @@ import ( "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/blacklist" "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/common/random" "gorm.io/gorm" ) +const ( + RoleGuestUser = 0 + RoleCommonUser = 1 + RoleAdminUser = 10 + RoleRootUser = 100 +) + +const ( + UserStatusEnabled = 1 // don't use 0, 0 is the default value! + UserStatusDisabled = 2 // also don't use 0 + UserStatusDeleted = 3 +) + // User if you add sensitive fields, don't forget to clean them in setupLogin function. // Otherwise, the sensitive information will be saved on local storage in plain text! type User struct { @@ -25,6 +38,7 @@ type User struct { Email string `json:"email" gorm:"index" validate:"max=50"` GitHubId string `json:"github_id" gorm:"column:github_id;index"` WeChatId string `json:"wechat_id" gorm:"column:wechat_id;index"` + LarkId string `json:"lark_id" gorm:"column:lark_id;index"` VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database! AccessToken string `json:"access_token" gorm:"type:char(32);column:access_token;uniqueIndex"` // this token is for system management Quota int64 `json:"quota" gorm:"bigint;default:0"` @@ -42,21 +56,21 @@ func GetMaxUserId() int { } func GetAllUsers(startIdx int, num int, order string) (users []*User, err error) { - query := DB.Limit(num).Offset(startIdx).Omit("password").Where("status != ?", common.UserStatusDeleted) - - switch order { - case "quota": - query = query.Order("quota desc") - case "used_quota": - query = query.Order("used_quota desc") - case "request_count": - query = query.Order("request_count desc") - default: - query = query.Order("id desc") - } - - err = query.Find(&users).Error - return users, err + query := DB.Limit(num).Offset(startIdx).Omit("password").Where("status != ?", UserStatusDeleted) + + switch order { + case "quota": + query = query.Order("quota desc") + case "used_quota": + query = query.Order("used_quota desc") + case "request_count": + query = query.Order("request_count desc") + default: + query = query.Order("id desc") + } + + err = query.Find(&users).Error + return users, err } func SearchUsers(keyword string) (users []*User, err error) { @@ -108,8 +122,8 @@ func (user *User) Insert(inviterId int) error { } } user.Quota = config.QuotaForNewUser - user.AccessToken = helper.GetUUID() - user.AffCode = helper.GetRandomString(4) + user.AccessToken = random.GetUUID() + user.AffCode = random.GetRandomString(4) result := DB.Create(user) if result.Error != nil { return result.Error @@ -138,9 +152,9 @@ func (user *User) Update(updatePassword bool) error { return err } } - if user.Status == common.UserStatusDisabled { + if user.Status == UserStatusDisabled { blacklist.BanUser(user.Id) - } else if user.Status == common.UserStatusEnabled { + } else if user.Status == UserStatusEnabled { blacklist.UnbanUser(user.Id) } err = DB.Model(user).Updates(user).Error @@ -152,8 +166,8 @@ func (user *User) Delete() error { return errors.New("id 为空!") } blacklist.BanUser(user.Id) - user.Username = fmt.Sprintf("deleted_%s", helper.GetUUID()) - user.Status = common.UserStatusDeleted + user.Username = fmt.Sprintf("deleted_%s", random.GetUUID()) + user.Status = UserStatusDeleted err := DB.Model(user).Updates(user).Error return err } @@ -177,7 +191,7 @@ func (user *User) ValidateAndFill() (err error) { } } okay := common.ValidatePasswordAndHash(password, user.Password) - if !okay || user.Status != common.UserStatusEnabled { + if !okay || user.Status != UserStatusEnabled { return errors.New("用户名或密码错误,或用户已被封禁") } return nil @@ -207,6 +221,14 @@ func (user *User) FillUserByGitHubId() error { return nil } +func (user *User) FillUserByLarkId() error { + if user.LarkId == "" { + return errors.New("lark id 为空!") + } + DB.Where(User{LarkId: user.LarkId}).First(user) + return nil +} + func (user *User) FillUserByWeChatId() error { if user.WeChatId == "" { return errors.New("WeChat id 为空!") @@ -235,6 +257,10 @@ func IsGitHubIdAlreadyTaken(githubId string) bool { return DB.Where("github_id = ?", githubId).Find(&User{}).RowsAffected == 1 } +func IsLarkIdAlreadyTaken(githubId string) bool { + return DB.Where("lark_id = ?", githubId).Find(&User{}).RowsAffected == 1 +} + func IsUsernameAlreadyTaken(username string) bool { return DB.Where("username = ?", username).Find(&User{}).RowsAffected == 1 } @@ -261,7 +287,7 @@ func IsAdmin(userId int) bool { logger.SysError("no such user " + err.Error()) return false } - return user.Role >= common.RoleAdminUser + return user.Role >= RoleAdminUser } func IsUserEnabled(userId int) (bool, error) { @@ -273,7 +299,7 @@ func IsUserEnabled(userId int) (bool, error) { if err != nil { return false, err } - return user.Status == common.UserStatusEnabled, nil + return user.Status == UserStatusEnabled, nil } func ValidateAccessToken(token string) (user *User) { @@ -346,7 +372,7 @@ func decreaseUserQuota(id int, quota int64) (err error) { } func GetRootUserEmail() (email string) { - DB.Model(&User{}).Where("role = ?", common.RoleRootUser).Select("email").Find(&email) + DB.Model(&User{}).Where("role = ?", RoleRootUser).Select("email").Find(&email) return email } diff --git a/monitor/channel.go b/monitor/channel.go index ad82d2f5..7e5dc58a 100644 --- a/monitor/channel.go +++ b/monitor/channel.go @@ -2,7 +2,6 @@ package monitor import ( "fmt" - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/common/message" @@ -29,7 +28,7 @@ func notifyRootUser(subject string, content string) { // DisableChannel disable & notify func DisableChannel(channelId int, channelName string, reason string) { - model.UpdateChannelStatusById(channelId, common.ChannelStatusAutoDisabled) + model.UpdateChannelStatusById(channelId, model.ChannelStatusAutoDisabled) logger.SysLog(fmt.Sprintf("channel #%d has been disabled: %s", channelId, reason)) subject := fmt.Sprintf("渠道「%s」(#%d)已被禁用", channelName, channelId) content := fmt.Sprintf("渠道「%s」(#%d)已被禁用,原因:%s", channelName, channelId, reason) @@ -37,7 +36,7 @@ func DisableChannel(channelId int, channelName string, reason string) { } func MetricDisableChannel(channelId int, successRate float64) { - model.UpdateChannelStatusById(channelId, common.ChannelStatusAutoDisabled) + model.UpdateChannelStatusById(channelId, model.ChannelStatusAutoDisabled) logger.SysLog(fmt.Sprintf("channel #%d has been disabled due to low success rate: %.2f", channelId, successRate*100)) subject := fmt.Sprintf("渠道 #%d 已被禁用", channelId) content := fmt.Sprintf("该渠道(#%d)在最近 %d 次调用中成功率为 %.2f%%,低于阈值 %.2f%%,因此被系统自动禁用。", @@ -47,7 +46,7 @@ func MetricDisableChannel(channelId int, successRate float64) { // EnableChannel enable & notify func EnableChannel(channelId int, channelName string) { - model.UpdateChannelStatusById(channelId, common.ChannelStatusEnabled) + model.UpdateChannelStatusById(channelId, model.ChannelStatusEnabled) logger.SysLog(fmt.Sprintf("channel #%d has been enabled", channelId)) subject := fmt.Sprintf("渠道「%s」(#%d)已被启用", channelName, channelId) content := fmt.Sprintf("渠道「%s」(#%d)已被启用", channelName, channelId) diff --git a/monitor/manage.go b/monitor/manage.go new file mode 100644 index 00000000..946e78af --- /dev/null +++ b/monitor/manage.go @@ -0,0 +1,62 @@ +package monitor + +import ( + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/relay/model" + "net/http" + "strings" +) + +func ShouldDisableChannel(err *model.Error, statusCode int) bool { + if !config.AutomaticDisableChannelEnabled { + return false + } + if err == nil { + return false + } + if statusCode == http.StatusUnauthorized { + return true + } + switch err.Type { + case "insufficient_quota": + return true + // https://docs.anthropic.com/claude/reference/errors + case "authentication_error": + return true + case "permission_error": + return true + case "forbidden": + return true + } + if err.Code == "invalid_api_key" || err.Code == "account_deactivated" { + return true + } + if strings.HasPrefix(err.Message, "Your credit balance is too low") { // anthropic + return true + } else if strings.HasPrefix(err.Message, "This organization has been disabled.") { + return true + } + //if strings.Contains(err.Message, "quota") { + // return true + //} + if strings.Contains(err.Message, "credit") { + return true + } + if strings.Contains(err.Message, "balance") { + return true + } + return false +} + +func ShouldEnableChannel(err error, openAIErr *model.Error) bool { + if !config.AutomaticEnableChannelEnabled { + return false + } + if err != nil { + return false + } + if openAIErr != nil { + return false + } + return true +} diff --git a/relay/adaptor.go b/relay/adaptor.go new file mode 100644 index 00000000..ef549b5b --- /dev/null +++ b/relay/adaptor.go @@ -0,0 +1,41 @@ +package relay + +import ( + "github.com/songquanpeng/one-api/relay/adaptor" + "github.com/songquanpeng/one-api/relay/adaptor/aiproxy" + "github.com/songquanpeng/one-api/relay/adaptor/anthropic" + "github.com/songquanpeng/one-api/relay/adaptor/gemini" + "github.com/songquanpeng/one-api/relay/adaptor/ollama" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + "github.com/songquanpeng/one-api/relay/adaptor/palm" + "github.com/songquanpeng/one-api/relay/apitype" +) + +func GetAdaptor(apiType int) adaptor.Adaptor { + switch apiType { + case apitype.AIProxyLibrary: + return &aiproxy.Adaptor{} + // case apitype.Ali: + // return &ali.Adaptor{} + case apitype.Anthropic: + return &anthropic.Adaptor{} + // case apitype.Baidu: + // return &baidu.Adaptor{} + case apitype.Gemini: + return &gemini.Adaptor{} + case apitype.OpenAI: + return &openai.Adaptor{} + case apitype.PaLM: + return &palm.Adaptor{} + // case apitype.Tencent: + // return &tencent.Adaptor{} + // case apitype.Xunfei: + // return &xunfei.Adaptor{} + // case apitype.Zhipu: + // return &zhipu.Adaptor{} + case apitype.Ollama: + return &ollama.Adaptor{} + } + + return nil +} diff --git a/relay/channel/ai360/constants.go b/relay/adaptor/ai360/constants.go similarity index 100% rename from relay/channel/ai360/constants.go rename to relay/adaptor/ai360/constants.go diff --git a/relay/channel/aiproxy/adaptor.go b/relay/adaptor/aiproxy/adaptor.go similarity index 54% rename from relay/channel/aiproxy/adaptor.go rename to relay/adaptor/aiproxy/adaptor.go index 6f5d289f..31865698 100644 --- a/relay/channel/aiproxy/adaptor.go +++ b/relay/adaptor/aiproxy/adaptor.go @@ -4,10 +4,10 @@ import ( "fmt" "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/relay/channel" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/relay/adaptor" + "github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" "io" "net/http" ) @@ -15,16 +15,16 @@ import ( type Adaptor struct { } -func (a *Adaptor) Init(meta *util.RelayMeta) { +func (a *Adaptor) Init(meta *meta.Meta) { } -func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { +func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { return fmt.Sprintf("%s/api/library/ask", meta.BaseURL), nil } -func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { - channel.SetupCommonRequestHeader(c, req, meta) +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { + adaptor.SetupCommonRequestHeader(c, req, meta) req.Header.Set("Authorization", "Bearer "+meta.APIKey) return nil } @@ -34,15 +34,22 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G return nil, errors.New("request is nil") } aiProxyLibraryRequest := ConvertRequest(*request) - aiProxyLibraryRequest.LibraryId = c.GetString(common.ConfigKeyLibraryID) + aiProxyLibraryRequest.LibraryId = c.GetString(config.KeyLibraryID) return aiProxyLibraryRequest, nil } -func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { - return channel.DoRequestHelper(a, c, meta, requestBody) +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) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +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, usage = StreamHandler(c, resp) } else { diff --git a/relay/channel/aiproxy/constants.go b/relay/adaptor/aiproxy/constants.go similarity index 60% rename from relay/channel/aiproxy/constants.go rename to relay/adaptor/aiproxy/constants.go index c4df51c4..818d2709 100644 --- a/relay/channel/aiproxy/constants.go +++ b/relay/adaptor/aiproxy/constants.go @@ -1,6 +1,6 @@ package aiproxy -import "github.com/songquanpeng/one-api/relay/channel/openai" +import "github.com/songquanpeng/one-api/relay/adaptor/openai" var ModelList = []string{""} diff --git a/relay/channel/aiproxy/main.go b/relay/adaptor/aiproxy/main.go similarity index 95% rename from relay/channel/aiproxy/main.go rename to relay/adaptor/aiproxy/main.go index 7b146828..961260de 100644 --- a/relay/channel/aiproxy/main.go +++ b/relay/adaptor/aiproxy/main.go @@ -13,7 +13,8 @@ import ( "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/common/random" + "github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" ) @@ -54,7 +55,7 @@ func responseAIProxyLibrary2OpenAI(response *LibraryResponse) *openai.TextRespon FinishReason: "stop", } fullTextResponse := openai.TextResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()), Object: "chat.completion", Created: helper.GetTimestamp(), Choices: []openai.TextResponseChoice{choice}, @@ -67,7 +68,7 @@ func documentsAIProxyLibrary(documents []LibraryDocument) *openai.ChatCompletion choice.Delta.Content = aiProxyDocuments2Markdown(documents) choice.FinishReason = &constant.StopFinishReason return &openai.ChatCompletionsStreamResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()), Object: "chat.completion.chunk", Created: helper.GetTimestamp(), Model: "", @@ -79,7 +80,7 @@ func streamResponseAIProxyLibrary2OpenAI(response *LibraryStreamResponse) *opena var choice openai.ChatCompletionsStreamResponseChoice choice.Delta.Content = response.Content return &openai.ChatCompletionsStreamResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()), Object: "chat.completion.chunk", Created: helper.GetTimestamp(), Model: response.Model, diff --git a/relay/channel/aiproxy/model.go b/relay/adaptor/aiproxy/model.go similarity index 100% rename from relay/channel/aiproxy/model.go rename to relay/adaptor/aiproxy/model.go diff --git a/relay/adaptor/ali/adaptor.go b/relay/adaptor/ali/adaptor.go new file mode 100644 index 00000000..e004211e --- /dev/null +++ b/relay/adaptor/ali/adaptor.go @@ -0,0 +1,105 @@ +package ali + +// import ( +// "github.com/Laisky/errors/v2" +// "fmt" +// "github.com/gin-gonic/gin" +// "github.com/songquanpeng/one-api/common/config" +// "github.com/songquanpeng/one-api/relay/adaptor" +// "github.com/songquanpeng/one-api/relay/meta" +// "github.com/songquanpeng/one-api/relay/model" +// "github.com/songquanpeng/one-api/relay/relaymode" +// "io" +// "net/http" +// ) + +// // https://help.aliyun.com/zh/dashscope/developer-reference/api-details + +// type Adaptor struct { +// } + +// func (a *Adaptor) Init(meta *meta.Meta) { + +// } + +// func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { +// fullRequestURL := "" +// switch meta.Mode { +// case relaymode.Embeddings: +// fullRequestURL = fmt.Sprintf("%s/api/v1/services/embeddings/text-embedding/text-embedding", meta.BaseURL) +// case relaymode.ImagesGenerations: +// fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text2image/image-synthesis", meta.BaseURL) +// default: +// fullRequestURL = fmt.Sprintf("%s/api/v1/services/aigc/text-generation/generation", meta.BaseURL) +// } + +// return fullRequestURL, nil +// } + +// func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { +// adaptor.SetupCommonRequestHeader(c, req, meta) +// if meta.IsStream { +// req.Header.Set("Accept", "text/event-stream") +// req.Header.Set("X-DashScope-SSE", "enable") +// } +// req.Header.Set("Authorization", "Bearer "+meta.APIKey) + +// if meta.Mode == relaymode.ImagesGenerations { +// req.Header.Set("X-DashScope-Async", "enable") +// } +// if c.GetString(config.KeyPlugin) != "" { +// req.Header.Set("X-DashScope-Plugin", c.GetString(config.KeyPlugin)) +// } +// 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") +// } +// switch relayMode { +// case relaymode.Embeddings: +// aliEmbeddingRequest := ConvertEmbeddingRequest(*request) +// return aliEmbeddingRequest, nil +// default: +// aliRequest := ConvertRequest(*request) +// return aliRequest, nil +// } +// } + +// func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (any, error) { +// if request == nil { +// return nil, errors.New("request is nil") +// } + +// aliRequest := ConvertImageRequest(*request) +// return aliRequest, 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, usage = StreamHandler(c, resp) +// } else { +// switch meta.Mode { +// case relaymode.Embeddings: +// err, usage = EmbeddingHandler(c, resp) +// case relaymode.ImagesGenerations: +// err, usage = ImageHandler(c, resp) +// default: +// err, usage = Handler(c, resp) +// } +// } +// return +// } + +// func (a *Adaptor) GetModelList() []string { +// return ModelList +// } + +// func (a *Adaptor) GetChannelName() string { +// return "ali" +// } diff --git a/relay/channel/ali/constants.go b/relay/adaptor/ali/constants.go similarity index 65% rename from relay/channel/ali/constants.go rename to relay/adaptor/ali/constants.go index 16bcfca4..3f24ce2e 100644 --- a/relay/channel/ali/constants.go +++ b/relay/adaptor/ali/constants.go @@ -3,4 +3,5 @@ package ali var ModelList = []string{ "qwen-turbo", "qwen-plus", "qwen-max", "qwen-max-longcontext", "text-embedding-v1", + "ali-stable-diffusion-xl", "ali-stable-diffusion-v1.5", "wanx-v1", } diff --git a/relay/adaptor/ali/image.go b/relay/adaptor/ali/image.go new file mode 100644 index 00000000..cef509e2 --- /dev/null +++ b/relay/adaptor/ali/image.go @@ -0,0 +1,192 @@ +package ali + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "github.com/Laisky/errors/v2" + "github.com/gin-gonic/gin" + "github.com/songquanpeng/one-api/common/helper" + "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" + "time" +) + +func ImageHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) { + apiKey := c.Request.Header.Get("Authorization") + apiKey = strings.TrimPrefix(apiKey, "Bearer ") + responseFormat := c.GetString("response_format") + + var aliTaskResponse TaskResponse + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil + } + err = resp.Body.Close() + if err != nil { + return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil + } + err = json.Unmarshal(responseBody, &aliTaskResponse) + if err != nil { + return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil + } + + if aliTaskResponse.Message != "" { + logger.SysError("aliAsyncTask err: " + string(responseBody)) + return openai.ErrorWrapper(errors.New(aliTaskResponse.Message), "ali_async_task_failed", http.StatusInternalServerError), nil + } + + aliResponse, _, err := asyncTaskWait(aliTaskResponse.Output.TaskId, apiKey) + if err != nil { + return openai.ErrorWrapper(err, "ali_async_task_wait_failed", http.StatusInternalServerError), nil + } + + if aliResponse.Output.TaskStatus != "SUCCEEDED" { + return &model.ErrorWithStatusCode{ + Error: model.Error{ + Message: aliResponse.Output.Message, + Type: "ali_error", + Param: "", + Code: aliResponse.Output.Code, + }, + StatusCode: resp.StatusCode, + }, nil + } + + fullTextResponse := responseAli2OpenAIImage(aliResponse, responseFormat) + jsonResponse, err := json.Marshal(fullTextResponse) + if err != nil { + return openai.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, nil +} + +func asyncTask(taskID string, key string) (*TaskResponse, error, []byte) { + url := fmt.Sprintf("https://dashscope.aliyuncs.com/api/v1/tasks/%s", taskID) + + var aliResponse TaskResponse + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return &aliResponse, err, nil + } + + req.Header.Set("Authorization", "Bearer "+key) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + logger.SysError("aliAsyncTask client.Do err: " + err.Error()) + return &aliResponse, err, nil + } + defer resp.Body.Close() + + responseBody, err := io.ReadAll(resp.Body) + + var response TaskResponse + err = json.Unmarshal(responseBody, &response) + if err != nil { + logger.SysError("aliAsyncTask NewDecoder err: " + err.Error()) + return &aliResponse, err, nil + } + + return &response, nil, responseBody +} + +func asyncTaskWait(taskID string, key string) (*TaskResponse, []byte, error) { + waitSeconds := 2 + step := 0 + maxStep := 20 + + var taskResponse TaskResponse + var responseBody []byte + + for { + step++ + rsp, err, body := asyncTask(taskID, key) + responseBody = body + if err != nil { + return &taskResponse, responseBody, err + } + + if rsp.Output.TaskStatus == "" { + return &taskResponse, responseBody, nil + } + + switch rsp.Output.TaskStatus { + case "FAILED": + fallthrough + case "CANCELED": + fallthrough + case "SUCCEEDED": + fallthrough + case "UNKNOWN": + return rsp, responseBody, nil + } + if step >= maxStep { + break + } + time.Sleep(time.Duration(waitSeconds) * time.Second) + } + + return nil, nil, fmt.Errorf("aliAsyncTaskWait timeout") +} + +func responseAli2OpenAIImage(response *TaskResponse, responseFormat string) *openai.ImageResponse { + imageResponse := openai.ImageResponse{ + Created: helper.GetTimestamp(), + } + + for _, data := range response.Output.Results { + var b64Json string + if responseFormat == "b64_json" { + // 读取 data.Url 的图片数据并转存到 b64Json + imageData, err := getImageData(data.Url) + if err != nil { + // 处理获取图片数据失败的情况 + logger.SysError("getImageData Error getting image data: " + err.Error()) + continue + } + + // 将图片数据转为 Base64 编码的字符串 + b64Json = Base64Encode(imageData) + } else { + // 如果 responseFormat 不是 "b64_json",则直接使用 data.B64Image + b64Json = data.B64Image + } + + imageResponse.Data = append(imageResponse.Data, openai.ImageData{ + Url: data.Url, + B64Json: b64Json, + RevisedPrompt: "", + }) + } + return &imageResponse +} + +func getImageData(url string) ([]byte, error) { + response, err := http.Get(url) + if err != nil { + return nil, err + } + defer response.Body.Close() + + imageData, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + return imageData, nil +} + +func Base64Encode(data []byte) string { + b64Json := base64.StdEncoding.EncodeToString(data) + return b64Json +} diff --git a/relay/channel/ali/main.go b/relay/adaptor/ali/main.go similarity index 100% rename from relay/channel/ali/main.go rename to relay/adaptor/ali/main.go diff --git a/relay/adaptor/ali/model.go b/relay/adaptor/ali/model.go new file mode 100644 index 00000000..450b5f52 --- /dev/null +++ b/relay/adaptor/ali/model.go @@ -0,0 +1,154 @@ +package ali + +import ( + "github.com/songquanpeng/one-api/relay/adaptor/openai" + "github.com/songquanpeng/one-api/relay/model" +) + +type Message struct { + Content string `json:"content"` + Role string `json:"role"` +} + +type Input struct { + //Prompt string `json:"prompt"` + Messages []Message `json:"messages"` +} + +type Parameters struct { + TopP float64 `json:"top_p,omitempty"` + TopK int `json:"top_k,omitempty"` + Seed uint64 `json:"seed,omitempty"` + EnableSearch bool `json:"enable_search,omitempty"` + IncrementalOutput bool `json:"incremental_output,omitempty"` + MaxTokens int `json:"max_tokens,omitempty"` + Temperature float64 `json:"temperature,omitempty"` + ResultFormat string `json:"result_format,omitempty"` + Tools []model.Tool `json:"tools,omitempty"` +} + +type ChatRequest struct { + Model string `json:"model"` + Input Input `json:"input"` + Parameters Parameters `json:"parameters,omitempty"` +} + +type ImageRequest struct { + Model string `json:"model"` + Input struct { + Prompt string `json:"prompt"` + NegativePrompt string `json:"negative_prompt,omitempty"` + } `json:"input"` + Parameters struct { + Size string `json:"size,omitempty"` + N int `json:"n,omitempty"` + Steps string `json:"steps,omitempty"` + Scale string `json:"scale,omitempty"` + } `json:"parameters,omitempty"` + ResponseFormat string `json:"response_format,omitempty"` +} + +type TaskResponse struct { + StatusCode int `json:"status_code,omitempty"` + RequestId string `json:"request_id,omitempty"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Output struct { + TaskId string `json:"task_id,omitempty"` + TaskStatus string `json:"task_status,omitempty"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Results []struct { + B64Image string `json:"b64_image,omitempty"` + Url string `json:"url,omitempty"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + } `json:"results,omitempty"` + TaskMetrics struct { + Total int `json:"TOTAL,omitempty"` + Succeeded int `json:"SUCCEEDED,omitempty"` + Failed int `json:"FAILED,omitempty"` + } `json:"task_metrics,omitempty"` + } `json:"output,omitempty"` + Usage Usage `json:"usage"` +} + +type Header struct { + Action string `json:"action,omitempty"` + Streaming string `json:"streaming,omitempty"` + TaskID string `json:"task_id,omitempty"` + Event string `json:"event,omitempty"` + ErrorCode string `json:"error_code,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` + Attributes any `json:"attributes,omitempty"` +} + +type Payload struct { + Model string `json:"model,omitempty"` + Task string `json:"task,omitempty"` + TaskGroup string `json:"task_group,omitempty"` + Function string `json:"function,omitempty"` + Parameters struct { + SampleRate int `json:"sample_rate,omitempty"` + Rate float64 `json:"rate,omitempty"` + Format string `json:"format,omitempty"` + } `json:"parameters,omitempty"` + Input struct { + Text string `json:"text,omitempty"` + } `json:"input,omitempty"` + Usage struct { + Characters int `json:"characters,omitempty"` + } `json:"usage,omitempty"` +} + +type WSSMessage struct { + Header Header `json:"header,omitempty"` + Payload Payload `json:"payload,omitempty"` +} + +type EmbeddingRequest struct { + Model string `json:"model"` + Input struct { + Texts []string `json:"texts"` + } `json:"input"` + Parameters *struct { + TextType string `json:"text_type,omitempty"` + } `json:"parameters,omitempty"` +} + +type Embedding struct { + Embedding []float64 `json:"embedding"` + TextIndex int `json:"text_index"` +} + +type EmbeddingResponse struct { + Output struct { + Embeddings []Embedding `json:"embeddings"` + } `json:"output"` + Usage Usage `json:"usage"` + Error +} + +type Error struct { + Code string `json:"code"` + Message string `json:"message"` + RequestId string `json:"request_id"` +} + +type Usage struct { + InputTokens int `json:"input_tokens"` + OutputTokens int `json:"output_tokens"` + TotalTokens int `json:"total_tokens"` +} + +type Output struct { + //Text string `json:"text"` + //FinishReason string `json:"finish_reason"` + Choices []openai.TextResponseChoice `json:"choices"` +} + +type ChatResponse struct { + Output Output `json:"output"` + Usage Usage `json:"usage"` + Error +} diff --git a/relay/channel/anthropic/adaptor.go b/relay/adaptor/anthropic/adaptor.go similarity index 60% rename from relay/channel/anthropic/adaptor.go rename to relay/adaptor/anthropic/adaptor.go index 9f1adb9a..07efb3c7 100644 --- a/relay/channel/anthropic/adaptor.go +++ b/relay/adaptor/anthropic/adaptor.go @@ -2,31 +2,31 @@ package anthropic import ( "fmt" - "github.com/Laisky/errors/v2" "io" "net/http" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/relay/channel" + "github.com/songquanpeng/one-api/relay/adaptor" + "github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" ) type Adaptor struct { } -func (a *Adaptor) Init(meta *util.RelayMeta) { +func (a *Adaptor) Init(meta *meta.Meta) { } -func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { - // https://docs.anthropic.com/claude/reference/messages_post - // anthopic migrate to Message API +// https://docs.anthropic.com/claude/reference/messages_post +// anthopic migrate to Message API +func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { return fmt.Sprintf("%s/v1/messages", meta.BaseURL), nil } -func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { - channel.SetupCommonRequestHeader(c, req, meta) +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { + adaptor.SetupCommonRequestHeader(c, req, meta) req.Header.Set("x-api-key", meta.APIKey) anthropicVersion := c.Request.Header.Get("anthropic-version") if anthropicVersion == "" { @@ -46,11 +46,18 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G return ConvertRequest(*request), nil } -func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { - return channel.DoRequestHelper(a, c, meta, requestBody) +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) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +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, usage = StreamHandler(c, resp) } else { diff --git a/relay/channel/anthropic/constants.go b/relay/adaptor/anthropic/constants.go similarity index 100% rename from relay/channel/anthropic/constants.go rename to relay/adaptor/anthropic/constants.go diff --git a/relay/channel/anthropic/main.go b/relay/adaptor/anthropic/main.go similarity index 99% rename from relay/channel/anthropic/main.go rename to relay/adaptor/anthropic/main.go index 198b66ad..aec327fe 100644 --- a/relay/channel/anthropic/main.go +++ b/relay/adaptor/anthropic/main.go @@ -13,7 +13,7 @@ import ( "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/image" "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/model" ) diff --git a/relay/channel/anthropic/model.go b/relay/adaptor/anthropic/model.go similarity index 100% rename from relay/channel/anthropic/model.go rename to relay/adaptor/anthropic/model.go diff --git a/relay/adaptor/azure/helper.go b/relay/adaptor/azure/helper.go new file mode 100644 index 00000000..dd207f37 --- /dev/null +++ b/relay/adaptor/azure/helper.go @@ -0,0 +1,15 @@ +package azure + +import ( + "github.com/gin-gonic/gin" + "github.com/songquanpeng/one-api/common/config" +) + +func GetAPIVersion(c *gin.Context) string { + query := c.Request.URL.Query() + apiVersion := query.Get("api-version") + if apiVersion == "" { + apiVersion = c.GetString(config.KeyAPIVersion) + } + return apiVersion +} diff --git a/relay/channel/baichuan/constants.go b/relay/adaptor/baichuan/constants.go similarity index 100% rename from relay/channel/baichuan/constants.go rename to relay/adaptor/baichuan/constants.go diff --git a/relay/channel/baidu/adaptor.go b/relay/adaptor/baidu/adaptor.go similarity index 100% rename from relay/channel/baidu/adaptor.go rename to relay/adaptor/baidu/adaptor.go diff --git a/relay/adaptor/baidu/constants.go b/relay/adaptor/baidu/constants.go new file mode 100644 index 00000000..f952adc6 --- /dev/null +++ b/relay/adaptor/baidu/constants.go @@ -0,0 +1,20 @@ +package baidu + +var ModelList = []string{ + "ERNIE-4.0-8K", + "ERNIE-3.5-8K", + "ERNIE-3.5-8K-0205", + "ERNIE-3.5-8K-1222", + "ERNIE-Bot-8K", + "ERNIE-3.5-4K-0205", + "ERNIE-Speed-8K", + "ERNIE-Speed-128K", + "ERNIE-Lite-8K-0922", + "ERNIE-Lite-8K-0308", + "ERNIE-Tiny-8K", + "BLOOMZ-7B", + "Embedding-V1", + "bge-large-zh", + "bge-large-en", + "tao-8k", +} diff --git a/relay/channel/baidu/main.go b/relay/adaptor/baidu/main.go similarity index 100% rename from relay/channel/baidu/main.go rename to relay/adaptor/baidu/main.go diff --git a/relay/channel/baidu/model.go b/relay/adaptor/baidu/model.go similarity index 100% rename from relay/channel/baidu/model.go rename to relay/adaptor/baidu/model.go diff --git a/relay/channel/common.go b/relay/adaptor/common.go similarity index 81% rename from relay/channel/common.go rename to relay/adaptor/common.go index 2c4fb37c..13f57132 100644 --- a/relay/channel/common.go +++ b/relay/adaptor/common.go @@ -1,4 +1,4 @@ -package channel +package adaptor import ( "io" @@ -6,10 +6,11 @@ import ( "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/relay/util" + "github.com/songquanpeng/one-api/relay/client" + "github.com/songquanpeng/one-api/relay/meta" ) -func SetupCommonRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) { +func SetupCommonRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) { req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) req.Header.Set("Accept", c.Request.Header.Get("Accept")) if meta.IsStream && c.Request.Header.Get("Accept") == "" { @@ -17,7 +18,7 @@ func SetupCommonRequestHeader(c *gin.Context, req *http.Request, meta *util.Rela } } -func DoRequestHelper(a Adaptor, c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { +func DoRequestHelper(a Adaptor, c *gin.Context, meta *meta.Meta, requestBody io.Reader) (*http.Response, error) { fullRequestURL, err := a.GetRequestURL(meta) if err != nil { return nil, errors.Wrap(err, "get request url failed") @@ -43,7 +44,7 @@ func DoRequestHelper(a Adaptor, c *gin.Context, meta *util.RelayMeta, requestBod } func DoRequest(c *gin.Context, req *http.Request) (*http.Response, error) { - resp, err := util.HTTPClient.Do(req) + resp, err := client.HTTPClient.Do(req) if err != nil { return nil, err } diff --git a/relay/channel/gemini/adaptor.go b/relay/adaptor/gemini/adaptor.go similarity index 66% rename from relay/channel/gemini/adaptor.go rename to relay/adaptor/gemini/adaptor.go index 240ca28d..ecb72221 100644 --- a/relay/channel/gemini/adaptor.go +++ b/relay/adaptor/gemini/adaptor.go @@ -8,21 +8,21 @@ import ( "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common/helper" - channelhelper "github.com/songquanpeng/one-api/relay/channel" - "github.com/songquanpeng/one-api/relay/channel/openai" + channelhelper "github.com/songquanpeng/one-api/relay/adaptor" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + "github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" ) type Adaptor struct { } -func (a *Adaptor) Init(meta *util.RelayMeta) { +func (a *Adaptor) Init(meta *meta.Meta) { } -func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { - version := helper.AssignOrDefault(meta.APIVersion, "v1beta") +func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { + version := helper.AssignOrDefault(meta.APIVersion, "v1") action := "generateContent" if meta.IsStream { action = "streamGenerateContent" @@ -30,7 +30,7 @@ func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { return fmt.Sprintf("%s/%s/models/%s:%s?key=%s", meta.BaseURL, version, meta.ActualModelName, action, meta.APIKey), nil } -func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { channelhelper.SetupCommonRequestHeader(c, req, meta) req.Header.Set("x-goog-api-key", meta.APIKey) req.URL.Query().Add("key", meta.APIKey) @@ -44,11 +44,18 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G return ConvertRequest(*request), nil } -func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { +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 channelhelper.DoRequestHelper(a, c, meta, requestBody) } -func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Meta) (usage *model.Usage, err *model.ErrorWithStatusCode) { if meta.IsStream { var responseText string err, responseText = StreamHandler(c, resp) diff --git a/relay/channel/gemini/constants.go b/relay/adaptor/gemini/constants.go similarity index 100% rename from relay/channel/gemini/constants.go rename to relay/adaptor/gemini/constants.go diff --git a/relay/channel/gemini/main.go b/relay/adaptor/gemini/main.go similarity index 56% rename from relay/channel/gemini/main.go rename to relay/adaptor/gemini/main.go index b66f2d5e..27a9c023 100644 --- a/relay/channel/gemini/main.go +++ b/relay/adaptor/gemini/main.go @@ -1,21 +1,24 @@ package gemini import ( - "context" + "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/config" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/image" "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/common/random" + "github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" + + "github.com/gin-gonic/gin" ) // https://ai.google.dev/docs/gemini_api_overview?hl=zh-cn @@ -82,13 +85,7 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *ChatRequest { if imageNum > VisionMaxImageNum { continue } - mimeType, data, err := image.GetImageFromUrl(part.ImageURL.Url) - if err != nil { - logger.Warn(context.TODO(), - fmt.Sprintf("get image from url %s got %+v", part.ImageURL.Url, err)) - continue - } - + mimeType, data, _ := image.GetImageFromUrl(part.ImageURL.Url) parts = append(parts, Part{ InlineData: &InlineData{ MimeType: mimeType, @@ -97,9 +94,6 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *ChatRequest { }) } } - - logger.Info(context.TODO(), - fmt.Sprintf("send %d messages to gemini with %d images", len(parts), imageNum)) content.Parts = parts // there's no assistant role in gemini and API shall vomit if Role is not user or model @@ -163,7 +157,7 @@ type ChatPromptFeedback struct { func responseGeminiChat2OpenAI(response *ChatResponse) *openai.TextResponse { fullTextResponse := openai.TextResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()), Object: "chat.completion", Created: helper.GetTimestamp(), Choices: make([]openai.TextResponseChoice, 0, len(response.Candidates)), @@ -196,182 +190,73 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *ChatResponse) *openai.ChatC return &response } -// [{ -// "candidates": [ -// { -// "content": { -// "parts": [ -// { -// "text": "```go \n\n// Package ratelimit implements tokens bucket algorithm.\npackage rate" -// } -// ], -// "role": "model" -// }, -// "finishReason": "STOP", -// "index": 0, -// "safetyRatings": [ -// { -// "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", -// "probability": "NEGLIGIBLE" -// }, -// { -// "category": "HARM_CATEGORY_HATE_SPEECH", -// "probability": "NEGLIGIBLE" -// }, -// { -// "category": "HARM_CATEGORY_HARASSMENT", -// "probability": "NEGLIGIBLE" -// }, -// { -// "category": "HARM_CATEGORY_DANGEROUS_CONTENT", -// "probability": "NEGLIGIBLE" -// } -// ] -// } -// ], -// "promptFeedback": { -// "safetyRatings": [ -// { -// "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", -// "probability": "NEGLIGIBLE" -// }, -// { -// "category": "HARM_CATEGORY_HATE_SPEECH", -// "probability": "NEGLIGIBLE" -// }, -// { -// "category": "HARM_CATEGORY_HARASSMENT", -// "probability": "NEGLIGIBLE" -// }, -// { -// "category": "HARM_CATEGORY_DANGEROUS_CONTENT", -// "probability": "NEGLIGIBLE" -// } -// ] -// } -// }] -type GeminiStreamResp struct { - Candidates []struct { - Content struct { - Parts []struct { - Text string `json:"text"` - } `json:"parts"` - Role string `json:"role"` - } `json:"content"` - FinishReason string `json:"finishReason"` - Index int64 `json:"index"` - } `json:"candidates"` -} - func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, string) { responseText := "" - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return openai.ErrorWrapper(err, "read upstream's body", http.StatusInternalServerError), responseText - } - - var respData []GeminiStreamResp - if err = json.Unmarshal(respBody, &respData); err != nil { - return openai.ErrorWrapper(err, "unmarshal upstream's body", http.StatusInternalServerError), responseText - } - - for _, chunk := range respData { - for _, cad := range chunk.Candidates { - for _, part := range cad.Content.Parts { - responseText += part.Text - } + 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 } - } - - var choice openai.ChatCompletionsStreamResponseChoice - choice.Delta.Content = responseText - resp2cli, err := json.Marshal(&openai.ChatCompletionsStreamResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), - Object: "chat.completion.chunk", - Created: helper.GetTimestamp(), - Model: "gemini-pro", - Choices: []openai.ChatCompletionsStreamResponseChoice{choice}, + 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 + }() + common.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 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}, + } + jsonResponse, err := json.Marshal(response) + if err != nil { + logger.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 openai.ErrorWrapper(err, "marshal upstream's body", http.StatusInternalServerError), responseText - } - - c.Render(-1, common.CustomEvent{Data: "data: " + string(resp2cli)}) - c.Render(-1, common.CustomEvent{Data: "data: [DONE]"}) - - // dataChan := make(chan string) - // stopChan := make(chan bool) - // scanner := bufio.NewScanner(resp.Body) - // scanner.Split(bufio.ScanLines) - // // 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() { - // var content string - // for scanner.Scan() { - // line := strings.TrimSpace(scanner.Text()) - // fmt.Printf("> gemini got line: %s\n", line) - // content += line - // // if !strings.HasPrefix(data, "\"text\": \"") { - // // continue - // // } - - // // data = strings.TrimPrefix(data, "\"text\": \"") - // // data = strings.TrimSuffix(data, "\"") - // // dataChan <- data - // } - - // dataChan <- content - // stopChan <- true - // }() - // common.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 openai.ChatCompletionsStreamResponseChoice - // choice.Delta.Content = dummy.Content - // response := openai.ChatCompletionsStreamResponse{ - // Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), - // Object: "chat.completion.chunk", - // Created: helper.GetTimestamp(), - // Model: "gemini-pro", - // Choices: []openai.ChatCompletionsStreamResponseChoice{choice}, - // } - // jsonResponse, err := json.Marshal(response) - // if err != nil { - // logger.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 - // } - // }) - - if err := resp.Body.Close(); err != nil { return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), "" } - return nil, responseText } diff --git a/relay/channel/gemini/model.go b/relay/adaptor/gemini/model.go similarity index 100% rename from relay/channel/gemini/model.go rename to relay/adaptor/gemini/model.go diff --git a/relay/channel/groq/constants.go b/relay/adaptor/groq/constants.go similarity index 100% rename from relay/channel/groq/constants.go rename to relay/adaptor/groq/constants.go diff --git a/relay/adaptor/interface.go b/relay/adaptor/interface.go new file mode 100644 index 00000000..01b2e2cb --- /dev/null +++ b/relay/adaptor/interface.go @@ -0,0 +1,21 @@ +package adaptor + +import ( + "github.com/gin-gonic/gin" + "github.com/songquanpeng/one-api/relay/meta" + "github.com/songquanpeng/one-api/relay/model" + "io" + "net/http" +) + +type Adaptor interface { + Init(meta *meta.Meta) + GetRequestURL(meta *meta.Meta) (string, error) + SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error + ConvertRequest(c *gin.Context, relayMode int, request *model.GeneralOpenAIRequest) (any, error) + ConvertImageRequest(request *model.ImageRequest) (any, error) + DoRequest(c *gin.Context, meta *meta.Meta, requestBody io.Reader) (*http.Response, error) + DoResponse(c *gin.Context, resp *http.Response, meta *meta.Meta) (usage *model.Usage, err *model.ErrorWithStatusCode) + GetModelList() []string + GetChannelName() string +} diff --git a/relay/channel/lingyiwanwu/constants.go b/relay/adaptor/lingyiwanwu/constants.go similarity index 100% rename from relay/channel/lingyiwanwu/constants.go rename to relay/adaptor/lingyiwanwu/constants.go diff --git a/relay/channel/minimax/constants.go b/relay/adaptor/minimax/constants.go similarity index 100% rename from relay/channel/minimax/constants.go rename to relay/adaptor/minimax/constants.go diff --git a/relay/adaptor/minimax/main.go b/relay/adaptor/minimax/main.go new file mode 100644 index 00000000..fc9b5d26 --- /dev/null +++ b/relay/adaptor/minimax/main.go @@ -0,0 +1,14 @@ +package minimax + +import ( + "fmt" + "github.com/songquanpeng/one-api/relay/meta" + "github.com/songquanpeng/one-api/relay/relaymode" +) + +func GetRequestURL(meta *meta.Meta) (string, error) { + if meta.Mode == relaymode.ChatCompletions { + return fmt.Sprintf("%s/v1/text/chatcompletion_v2", meta.BaseURL), nil + } + return "", fmt.Errorf("unsupported relay mode %d for minimax", meta.Mode) +} diff --git a/relay/channel/mistral/constants.go b/relay/adaptor/mistral/constants.go similarity index 100% rename from relay/channel/mistral/constants.go rename to relay/adaptor/mistral/constants.go diff --git a/relay/channel/moonshot/constants.go b/relay/adaptor/moonshot/constants.go similarity index 100% rename from relay/channel/moonshot/constants.go rename to relay/adaptor/moonshot/constants.go diff --git a/relay/channel/ollama/adaptor.go b/relay/adaptor/ollama/adaptor.go similarity index 58% rename from relay/channel/ollama/adaptor.go rename to relay/adaptor/ollama/adaptor.go index e2ae7d2b..ec1b0c40 100644 --- a/relay/channel/ollama/adaptor.go +++ b/relay/adaptor/ollama/adaptor.go @@ -1,36 +1,36 @@ package ollama import ( - "errors" "fmt" "io" "net/http" + "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/relay/channel" - "github.com/songquanpeng/one-api/relay/constant" + "github.com/songquanpeng/one-api/relay/adaptor" + "github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" + "github.com/songquanpeng/one-api/relay/relaymode" ) type Adaptor struct { } -func (a *Adaptor) Init(meta *util.RelayMeta) { +func (a *Adaptor) Init(meta *meta.Meta) { } -func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { +func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { // https://github.com/ollama/ollama/blob/main/docs/api.md fullRequestURL := fmt.Sprintf("%s/api/chat", meta.BaseURL) - if meta.Mode == constant.RelayModeEmbeddings { + if meta.Mode == relaymode.Embeddings { fullRequestURL = fmt.Sprintf("%s/api/embeddings", meta.BaseURL) } return fullRequestURL, nil } -func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { - channel.SetupCommonRequestHeader(c, req, meta) +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { + adaptor.SetupCommonRequestHeader(c, req, meta) req.Header.Set("Authorization", "Bearer "+meta.APIKey) return nil } @@ -40,7 +40,7 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G return nil, errors.New("request is nil") } switch relayMode { - case constant.RelayModeEmbeddings: + case relaymode.Embeddings: ollamaEmbeddingRequest := ConvertEmbeddingRequest(*request) return ollamaEmbeddingRequest, nil default: @@ -48,16 +48,23 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G } } -func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { - return channel.DoRequestHelper(a, c, meta, requestBody) +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) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +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, usage = StreamHandler(c, resp) } else { switch meta.Mode { - case constant.RelayModeEmbeddings: + case relaymode.Embeddings: err, usage = EmbeddingHandler(c, resp) default: err, usage = Handler(c, resp) diff --git a/relay/channel/ollama/constants.go b/relay/adaptor/ollama/constants.go similarity index 100% rename from relay/channel/ollama/constants.go rename to relay/adaptor/ollama/constants.go diff --git a/relay/channel/ollama/main.go b/relay/adaptor/ollama/main.go similarity index 97% rename from relay/channel/ollama/main.go rename to relay/adaptor/ollama/main.go index 821a335b..a7e4c058 100644 --- a/relay/channel/ollama/main.go +++ b/relay/adaptor/ollama/main.go @@ -5,15 +5,16 @@ import ( "context" "encoding/json" "fmt" + "github.com/songquanpeng/one-api/common/helper" + "github.com/songquanpeng/one-api/common/random" "io" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" ) @@ -51,7 +52,7 @@ func responseOllama2OpenAI(response *ChatResponse) *openai.TextResponse { choice.FinishReason = "stop" } fullTextResponse := openai.TextResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()), Object: "chat.completion", Created: helper.GetTimestamp(), Choices: []openai.TextResponseChoice{choice}, @@ -72,7 +73,7 @@ func streamResponseOllama2OpenAI(ollamaResponse *ChatResponse) *openai.ChatCompl choice.FinishReason = &constant.StopFinishReason } response := openai.ChatCompletionsStreamResponse{ - Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), + Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()), Object: "chat.completion.chunk", Created: helper.GetTimestamp(), Model: ollamaResponse.Model, diff --git a/relay/channel/ollama/model.go b/relay/adaptor/ollama/model.go similarity index 100% rename from relay/channel/ollama/model.go rename to relay/adaptor/ollama/model.go diff --git a/relay/channel/openai/adaptor.go b/relay/adaptor/openai/adaptor.go similarity index 52% rename from relay/channel/openai/adaptor.go rename to relay/adaptor/openai/adaptor.go index 260a35ae..24cf718f 100644 --- a/relay/channel/openai/adaptor.go +++ b/relay/adaptor/openai/adaptor.go @@ -4,11 +4,12 @@ import ( "fmt" "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/relay/channel" - "github.com/songquanpeng/one-api/relay/channel/minimax" + "github.com/songquanpeng/one-api/relay/adaptor" + "github.com/songquanpeng/one-api/relay/adaptor/minimax" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" + "github.com/songquanpeng/one-api/relay/relaymode" "io" "net/http" "strings" @@ -18,13 +19,20 @@ type Adaptor struct { ChannelType int } -func (a *Adaptor) Init(meta *util.RelayMeta) { +func (a *Adaptor) Init(meta *meta.Meta) { a.ChannelType = meta.ChannelType } -func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { +func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { switch meta.ChannelType { - case common.ChannelTypeAzure: + case channeltype.Azure: + if meta.Mode == relaymode.ImagesGenerations { + // https://learn.microsoft.com/en-us/azure/ai-services/openai/dall-e-quickstart?tabs=dalle3%2Ccommand-line&pivots=rest-api + // https://{resource_name}.openai.azure.com/openai/deployments/dall-e-3/images/generations?api-version=2024-03-01-preview + fullRequestURL := fmt.Sprintf("%s/openai/deployments/%s/images/generations?api-version=%s", meta.BaseURL, meta.ActualModelName, meta.APIVersion) + return fullRequestURL, nil + } + // https://learn.microsoft.com/en-us/azure/cognitive-services/openai/chatgpt-quickstart?pivots=rest-api&tabs=command-line#rest-api requestURL := strings.Split(meta.RequestURLPath, "?")[0] requestURL = fmt.Sprintf("%s?api-version=%s", requestURL, meta.APIVersion) @@ -34,22 +42,22 @@ func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { //https://github.com/songquanpeng/one-api/issues/1191 // {your endpoint}/openai/deployments/{your azure_model}/chat/completions?api-version={api_version} requestURL = fmt.Sprintf("/openai/deployments/%s/%s", model_, task) - return util.GetFullRequestURL(meta.BaseURL, requestURL, meta.ChannelType), nil - case common.ChannelTypeMinimax: + return GetFullRequestURL(meta.BaseURL, requestURL, meta.ChannelType), nil + case channeltype.Minimax: return minimax.GetRequestURL(meta) default: - return util.GetFullRequestURL(meta.BaseURL, meta.RequestURLPath, meta.ChannelType), nil + return GetFullRequestURL(meta.BaseURL, meta.RequestURLPath, meta.ChannelType), nil } } -func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { - channel.SetupCommonRequestHeader(c, req, meta) - if meta.ChannelType == common.ChannelTypeAzure { +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { + adaptor.SetupCommonRequestHeader(c, req, meta) + if meta.ChannelType == channeltype.Azure { req.Header.Set("api-key", meta.APIKey) return nil } req.Header.Set("Authorization", "Bearer "+meta.APIKey) - if meta.ChannelType == common.ChannelTypeOpenRouter { + if meta.ChannelType == channeltype.OpenRouter { req.Header.Set("HTTP-Referer", "https://github.com/songquanpeng/one-api") req.Header.Set("X-Title", "One API") } @@ -63,11 +71,18 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G return request, nil } -func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { - return channel.DoRequestHelper(a, c, meta, requestBody) +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) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +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 { var responseText string err, responseText, usage = StreamHandler(c, resp, meta.Mode) @@ -75,7 +90,12 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *util.Rel usage = ResponseText2Usage(responseText, meta.ActualModelName, meta.PromptTokens) } } else { - err, usage = Handler(c, resp, meta.PromptTokens, meta.ActualModelName) + switch meta.Mode { + case relaymode.ImagesGenerations: + err, _ = ImageHandler(c, resp) + default: + err, usage = Handler(c, resp, meta.PromptTokens, meta.ActualModelName) + } } return } diff --git a/relay/adaptor/openai/compatible.go b/relay/adaptor/openai/compatible.go new file mode 100644 index 00000000..200eac44 --- /dev/null +++ b/relay/adaptor/openai/compatible.go @@ -0,0 +1,50 @@ +package openai + +import ( + "github.com/songquanpeng/one-api/relay/adaptor/ai360" + "github.com/songquanpeng/one-api/relay/adaptor/baichuan" + "github.com/songquanpeng/one-api/relay/adaptor/groq" + "github.com/songquanpeng/one-api/relay/adaptor/lingyiwanwu" + "github.com/songquanpeng/one-api/relay/adaptor/minimax" + "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/channeltype" +) + +var CompatibleChannels = []int{ + channeltype.Azure, + channeltype.AI360, + channeltype.Moonshot, + channeltype.Baichuan, + channeltype.Minimax, + channeltype.Mistral, + channeltype.Groq, + channeltype.LingYiWanWu, + channeltype.StepFun, +} + +func GetCompatibleChannelMeta(channelType int) (string, []string) { + switch channelType { + case channeltype.Azure: + return "azure", ModelList + case channeltype.AI360: + return "360", ai360.ModelList + case channeltype.Moonshot: + return "moonshot", moonshot.ModelList + case channeltype.Baichuan: + return "baichuan", baichuan.ModelList + case channeltype.Minimax: + return "minimax", minimax.ModelList + case channeltype.Mistral: + return "mistralai", mistral.ModelList + case channeltype.Groq: + return "groq", groq.ModelList + case channeltype.LingYiWanWu: + return "lingyiwanwu", lingyiwanwu.ModelList + case channeltype.StepFun: + return "stepfun", stepfun.ModelList + default: + return "openai", ModelList + } +} diff --git a/relay/channel/openai/constants.go b/relay/adaptor/openai/constants.go similarity index 100% rename from relay/channel/openai/constants.go rename to relay/adaptor/openai/constants.go diff --git a/relay/adaptor/openai/helper.go b/relay/adaptor/openai/helper.go new file mode 100644 index 00000000..7d73303b --- /dev/null +++ b/relay/adaptor/openai/helper.go @@ -0,0 +1,30 @@ +package openai + +import ( + "fmt" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/model" + "strings" +) + +func ResponseText2Usage(responseText string, modeName string, promptTokens int) *model.Usage { + usage := &model.Usage{} + usage.PromptTokens = promptTokens + usage.CompletionTokens = CountTokenText(responseText, modeName) + usage.TotalTokens = usage.PromptTokens + usage.CompletionTokens + return usage +} + +func GetFullRequestURL(baseURL string, requestURL string, channelType int) string { + fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL) + + if strings.HasPrefix(baseURL, "https://gateway.ai.cloudflare.com") { + switch channelType { + case channeltype.OpenAI: + fullRequestURL = fmt.Sprintf("%s%s", baseURL, strings.TrimPrefix(requestURL, "/v1")) + case channeltype.Azure: + fullRequestURL = fmt.Sprintf("%s%s", baseURL, strings.TrimPrefix(requestURL, "/openai/deployments")) + } + } + return fullRequestURL +} diff --git a/relay/adaptor/openai/image.go b/relay/adaptor/openai/image.go new file mode 100644 index 00000000..0f89618a --- /dev/null +++ b/relay/adaptor/openai/image.go @@ -0,0 +1,44 @@ +package openai + +import ( + "bytes" + "encoding/json" + "github.com/gin-gonic/gin" + "github.com/songquanpeng/one-api/relay/model" + "io" + "net/http" +) + +func ImageHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) { + var imageResponse ImageResponse + 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, &imageResponse) + if err != nil { + return ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil + } + + resp.Body = io.NopCloser(bytes.NewBuffer(responseBody)) + + for k, v := range resp.Header { + c.Writer.Header().Set(k, v[0]) + } + c.Writer.WriteHeader(resp.StatusCode) + + _, err = io.Copy(c.Writer, resp.Body) + if err != nil { + return ErrorWrapper(err, "copy_response_body_failed", http.StatusInternalServerError), nil + } + err = resp.Body.Close() + if err != nil { + return ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil + } + return nil, nil +} diff --git a/relay/channel/openai/main.go b/relay/adaptor/openai/main.go similarity index 97% rename from relay/channel/openai/main.go rename to relay/adaptor/openai/main.go index 63cb9ae8..68d8f48f 100644 --- a/relay/channel/openai/main.go +++ b/relay/adaptor/openai/main.go @@ -8,8 +8,8 @@ import ( "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/conv" "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" + "github.com/songquanpeng/one-api/relay/relaymode" "io" "net/http" "strings" @@ -46,7 +46,7 @@ func StreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*model.E data = data[6:] if !strings.HasPrefix(data, "[DONE]") { switch relayMode { - case constant.RelayModeChatCompletions: + case relaymode.ChatCompletions: var streamResponse ChatCompletionsStreamResponse err := json.Unmarshal([]byte(data), &streamResponse) if err != nil { @@ -59,7 +59,7 @@ func StreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*model.E if streamResponse.Usage != nil { usage = streamResponse.Usage } - case constant.RelayModeCompletions: + case relaymode.Completions: var streamResponse CompletionsStreamResponse err := json.Unmarshal([]byte(data), &streamResponse) if err != nil { diff --git a/relay/channel/openai/model.go b/relay/adaptor/openai/model.go similarity index 93% rename from relay/channel/openai/model.go rename to relay/adaptor/openai/model.go index 30d77739..ce252ff6 100644 --- a/relay/channel/openai/model.go +++ b/relay/adaptor/openai/model.go @@ -110,11 +110,16 @@ type EmbeddingResponse struct { model.Usage `json:"usage"` } +type ImageData struct { + Url string `json:"url,omitempty"` + B64Json string `json:"b64_json,omitempty"` + RevisedPrompt string `json:"revised_prompt,omitempty"` +} + type ImageResponse struct { - Created int `json:"created"` - Data []struct { - Url string `json:"url"` - } + Created int64 `json:"created"` + Data []ImageData `json:"data"` + //model.Usage `json:"usage"` } type ChatCompletionsStreamResponseChoice struct { diff --git a/relay/channel/openai/token.go b/relay/adaptor/openai/token.go similarity index 98% rename from relay/channel/openai/token.go rename to relay/adaptor/openai/token.go index d18ce0df..1e61d255 100644 --- a/relay/channel/openai/token.go +++ b/relay/adaptor/openai/token.go @@ -4,10 +4,10 @@ import ( "fmt" "github.com/Laisky/errors/v2" "github.com/pkoukk/tiktoken-go" - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/image" "github.com/songquanpeng/one-api/common/logger" + billingratio "github.com/songquanpeng/one-api/relay/billing/ratio" "github.com/songquanpeng/one-api/relay/model" "math" "strings" @@ -28,7 +28,7 @@ func InitTokenEncoders() { if err != nil { logger.FatalLog(fmt.Sprintf("failed to get gpt-4 token encoder: %s", err.Error())) } - for model := range common.ModelRatio { + for model := range billingratio.ModelRatio { if strings.HasPrefix(model, "gpt-3.5") { tokenEncoderMap[model] = gpt35TokenEncoder } else if strings.HasPrefix(model, "gpt-4") { diff --git a/relay/channel/openai/util.go b/relay/adaptor/openai/util.go similarity index 100% rename from relay/channel/openai/util.go rename to relay/adaptor/openai/util.go diff --git a/relay/channel/palm/adaptor.go b/relay/adaptor/palm/adaptor.go similarity index 59% rename from relay/channel/palm/adaptor.go rename to relay/adaptor/palm/adaptor.go index 15ee010d..fa73dd30 100644 --- a/relay/channel/palm/adaptor.go +++ b/relay/adaptor/palm/adaptor.go @@ -4,10 +4,10 @@ import ( "fmt" "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/relay/channel" - "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/relay/adaptor" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + "github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" "io" "net/http" ) @@ -15,16 +15,16 @@ import ( type Adaptor struct { } -func (a *Adaptor) Init(meta *util.RelayMeta) { +func (a *Adaptor) Init(meta *meta.Meta) { } -func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { +func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { return fmt.Sprintf("%s/v1beta2/models/chat-bison-001:generateMessage", meta.BaseURL), nil } -func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { - channel.SetupCommonRequestHeader(c, req, meta) +func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { + adaptor.SetupCommonRequestHeader(c, req, meta) req.Header.Set("x-goog-api-key", meta.APIKey) return nil } @@ -36,11 +36,18 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G return ConvertRequest(*request), nil } -func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { - return channel.DoRequestHelper(a, c, meta, requestBody) +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) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +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 { var responseText string err, responseText = StreamHandler(c, resp) diff --git a/relay/channel/palm/constants.go b/relay/adaptor/palm/constants.go similarity index 100% rename from relay/channel/palm/constants.go rename to relay/adaptor/palm/constants.go diff --git a/relay/channel/palm/model.go b/relay/adaptor/palm/model.go similarity index 100% rename from relay/channel/palm/model.go rename to relay/adaptor/palm/model.go diff --git a/relay/channel/palm/palm.go b/relay/adaptor/palm/palm.go similarity index 97% rename from relay/channel/palm/palm.go rename to relay/adaptor/palm/palm.go index 56738544..1e60e7cd 100644 --- a/relay/channel/palm/palm.go +++ b/relay/adaptor/palm/palm.go @@ -7,7 +7,8 @@ import ( "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/helper" "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/relay/channel/openai" + "github.com/songquanpeng/one-api/common/random" + "github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/model" "io" @@ -74,7 +75,7 @@ func streamResponsePaLM2OpenAI(palmResponse *ChatResponse) *openai.ChatCompletio func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, string) { responseText := "" - responseId := fmt.Sprintf("chatcmpl-%s", helper.GetUUID()) + responseId := fmt.Sprintf("chatcmpl-%s", random.GetUUID()) createdTime := helper.GetTimestamp() dataChan := make(chan string) stopChan := make(chan bool) diff --git a/relay/adaptor/stepfun/constants.go b/relay/adaptor/stepfun/constants.go new file mode 100644 index 00000000..a82e562b --- /dev/null +++ b/relay/adaptor/stepfun/constants.go @@ -0,0 +1,7 @@ +package stepfun + +var ModelList = []string{ + "step-1-32k", + "step-1v-32k", + "step-1-200k", +} diff --git a/relay/channel/tencent/adaptor.go b/relay/adaptor/tencent/adaptor.go similarity index 100% rename from relay/channel/tencent/adaptor.go rename to relay/adaptor/tencent/adaptor.go diff --git a/relay/channel/tencent/constants.go b/relay/adaptor/tencent/constants.go similarity index 100% rename from relay/channel/tencent/constants.go rename to relay/adaptor/tencent/constants.go diff --git a/relay/adaptor/tencent/main.go b/relay/adaptor/tencent/main.go new file mode 100644 index 00000000..aa87e9ce --- /dev/null +++ b/relay/adaptor/tencent/main.go @@ -0,0 +1,238 @@ +package tencent + +// import ( +// "bufio" +// "crypto/hmac" +// "crypto/sha1" +// "encoding/base64" +// "encoding/json" +// "github.com/Laisky/errors/v2" +// "fmt" +// "github.com/gin-gonic/gin" +// "github.com/songquanpeng/one-api/common" +// "github.com/songquanpeng/one-api/common/helper" +// "github.com/songquanpeng/one-api/common/logger" +// "github.com/songquanpeng/one-api/relay/channel/openai" +// "github.com/songquanpeng/one-api/relay/constant" +// "github.com/songquanpeng/one-api/relay/model" +// "io" +// "net/http" +// "sort" +// "strconv" +// "strings" +// ) + +// // https://cloud.tencent.com/document/product/1729/97732 + +// func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest { +// messages := make([]Message, 0, len(request.Messages)) +// for i := 0; i < len(request.Messages); i++ { +// message := request.Messages[i] +// if message.Role == "system" { +// messages = append(messages, Message{ +// Role: "user", +// Content: message.StringContent(), +// }) +// messages = append(messages, Message{ +// Role: "assistant", +// Content: "Okay", +// }) +// continue +// } +// messages = append(messages, Message{ +// Content: message.StringContent(), +// Role: message.Role, +// }) +// } +// stream := 0 +// if request.Stream { +// stream = 1 +// } +// return &ChatRequest{ +// Timestamp: helper.GetTimestamp(), +// Expired: helper.GetTimestamp() + 24*60*60, +// QueryID: helper.GetUUID(), +// Temperature: request.Temperature, +// TopP: request.TopP, +// Stream: stream, +// Messages: messages, +// } +// } + +// func responseTencent2OpenAI(response *ChatResponse) *openai.TextResponse { +// fullTextResponse := openai.TextResponse{ +// Object: "chat.completion", +// Created: helper.GetTimestamp(), +// Usage: response.Usage, +// } +// if len(response.Choices) > 0 { +// choice := openai.TextResponseChoice{ +// Index: 0, +// Message: model.Message{ +// Role: "assistant", +// Content: response.Choices[0].Messages.Content, +// }, +// FinishReason: response.Choices[0].FinishReason, +// } +// fullTextResponse.Choices = append(fullTextResponse.Choices, choice) +// } +// return &fullTextResponse +// } + +// func streamResponseTencent2OpenAI(TencentResponse *ChatResponse) *openai.ChatCompletionsStreamResponse { +// response := openai.ChatCompletionsStreamResponse{ +// Object: "chat.completion.chunk", +// Created: helper.GetTimestamp(), +// Model: "tencent-hunyuan", +// } +// if len(TencentResponse.Choices) > 0 { +// var choice openai.ChatCompletionsStreamResponseChoice +// choice.Delta.Content = TencentResponse.Choices[0].Delta.Content +// if TencentResponse.Choices[0].FinishReason == "stop" { +// choice.FinishReason = &constant.StopFinishReason +// } +// response.Choices = append(response.Choices, choice) +// } +// return &response +// } + +// func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, string) { +// var responseText string +// 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 +// }() +// common.SetEventStreamHeaders(c) +// c.Stream(func(w io.Writer) bool { +// select { +// case data := <-dataChan: +// var TencentResponse ChatResponse +// err := json.Unmarshal([]byte(data), &TencentResponse) +// if err != nil { +// logger.SysError("error unmarshalling stream response: " + err.Error()) +// return true +// } +// response := streamResponseTencent2OpenAI(&TencentResponse) +// if len(response.Choices) != 0 { +// responseText += response.Choices[0].Delta.Content +// } +// jsonResponse, err := json.Marshal(response) +// if err != nil { +// logger.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 openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), "" +// } +// return nil, responseText +// } + +// func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) { +// var TencentResponse ChatResponse +// responseBody, err := io.ReadAll(resp.Body) +// if err != nil { +// return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil +// } +// err = resp.Body.Close() +// if err != nil { +// return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil +// } +// err = json.Unmarshal(responseBody, &TencentResponse) +// if err != nil { +// return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil +// } +// if TencentResponse.Error.Code != 0 { +// return &model.ErrorWithStatusCode{ +// Error: model.Error{ +// Message: TencentResponse.Error.Message, +// Code: TencentResponse.Error.Code, +// }, +// StatusCode: resp.StatusCode, +// }, nil +// } +// fullTextResponse := responseTencent2OpenAI(&TencentResponse) +// fullTextResponse.Model = "hunyuan" +// jsonResponse, err := json.Marshal(fullTextResponse) +// if err != nil { +// return openai.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) +// if err != nil { +// return openai.ErrorWrapper(err, "write_response_body_failed", http.StatusInternalServerError), nil +// } +// return nil, &fullTextResponse.Usage +// } + +// func ParseConfig(config string) (appId int64, secretId string, secretKey string, err error) { +// parts := strings.Split(config, "|") +// if len(parts) != 3 { +// err = errors.New("invalid tencent config") +// return +// } +// appId, err = strconv.ParseInt(parts[0], 10, 64) +// secretId = parts[1] +// secretKey = parts[2] +// return +// } + +// func GetSign(req ChatRequest, secretKey string) string { +// params := make([]string, 0) +// params = append(params, "app_id="+strconv.FormatInt(req.AppId, 10)) +// params = append(params, "secret_id="+req.SecretId) +// params = append(params, "timestamp="+strconv.FormatInt(req.Timestamp, 10)) +// params = append(params, "query_id="+req.QueryID) +// params = append(params, "temperature="+strconv.FormatFloat(req.Temperature, 'f', -1, 64)) +// params = append(params, "top_p="+strconv.FormatFloat(req.TopP, 'f', -1, 64)) +// params = append(params, "stream="+strconv.Itoa(req.Stream)) +// params = append(params, "expired="+strconv.FormatInt(req.Expired, 10)) + +// var messageStr string +// for _, msg := range req.Messages { +// messageStr += fmt.Sprintf(`{"role":"%s","content":"%s"},`, msg.Role, msg.Content) +// } +// messageStr = strings.TrimSuffix(messageStr, ",") +// params = append(params, "messages=["+messageStr+"]") + +// sort.Strings(params) +// url := "hunyuan.cloud.tencent.com/hyllm/v1/chat/completions?" + strings.Join(params, "&") +// mac := hmac.New(sha1.New, []byte(secretKey)) +// signURL := url +// mac.Write([]byte(signURL)) +// sign := mac.Sum([]byte(nil)) +// return base64.StdEncoding.EncodeToString(sign) +// } diff --git a/relay/channel/tencent/model.go b/relay/adaptor/tencent/model.go similarity index 100% rename from relay/channel/tencent/model.go rename to relay/adaptor/tencent/model.go diff --git a/relay/channel/xunfei/adaptor.go b/relay/adaptor/xunfei/adaptor.go similarity index 100% rename from relay/channel/xunfei/adaptor.go rename to relay/adaptor/xunfei/adaptor.go diff --git a/relay/channel/xunfei/constants.go b/relay/adaptor/xunfei/constants.go similarity index 100% rename from relay/channel/xunfei/constants.go rename to relay/adaptor/xunfei/constants.go diff --git a/relay/channel/xunfei/main.go b/relay/adaptor/xunfei/main.go similarity index 100% rename from relay/channel/xunfei/main.go rename to relay/adaptor/xunfei/main.go diff --git a/relay/channel/xunfei/model.go b/relay/adaptor/xunfei/model.go similarity index 100% rename from relay/channel/xunfei/model.go rename to relay/adaptor/xunfei/model.go diff --git a/relay/adaptor/zhipu/adaptor.go b/relay/adaptor/zhipu/adaptor.go new file mode 100644 index 00000000..424fabd6 --- /dev/null +++ b/relay/adaptor/zhipu/adaptor.go @@ -0,0 +1,145 @@ +package zhipu + +// import ( +// "github.com/Laisky/errors/v2" +// "fmt" +// "github.com/gin-gonic/gin" +// "github.com/songquanpeng/one-api/relay/adaptor" +// "github.com/songquanpeng/one-api/relay/adaptor/openai" +// "github.com/songquanpeng/one-api/relay/meta" +// "github.com/songquanpeng/one-api/relay/model" +// "github.com/songquanpeng/one-api/relay/relaymode" +// "io" +// "math" +// "net/http" +// "strings" +// ) + +// type Adaptor struct { +// APIVersion string +// } + +// func (a *Adaptor) Init(meta *meta.Meta) { + +// } + +// func (a *Adaptor) SetVersionByModeName(modelName string) { +// if strings.HasPrefix(modelName, "glm-") { +// a.APIVersion = "v4" +// } else { +// a.APIVersion = "v3" +// } +// } + +// func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { +// switch meta.Mode { +// case relaymode.ImagesGenerations: +// return fmt.Sprintf("%s/api/paas/v4/images/generations", meta.BaseURL), nil +// case relaymode.Embeddings: +// return fmt.Sprintf("%s/api/paas/v4/embeddings", meta.BaseURL), nil +// } +// a.SetVersionByModeName(meta.ActualModelName) +// if a.APIVersion == "v4" { +// return fmt.Sprintf("%s/api/paas/v4/chat/completions", meta.BaseURL), nil +// } +// method := "invoke" +// if meta.IsStream { +// method = "sse-invoke" +// } +// return fmt.Sprintf("%s/api/paas/v3/model-api/%s/%s", meta.BaseURL, meta.ActualModelName, method), nil +// } + +// func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { +// adaptor.SetupCommonRequestHeader(c, req, meta) +// token := GetToken(meta.APIKey) +// req.Header.Set("Authorization", token) +// 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") +// } +// switch relayMode { +// case relaymode.Embeddings: +// baiduEmbeddingRequest := ConvertEmbeddingRequest(*request) +// return baiduEmbeddingRequest, nil +// default: +// // TopP (0.0, 1.0) +// request.TopP = math.Min(0.99, request.TopP) +// request.TopP = math.Max(0.01, request.TopP) + +// // Temperature (0.0, 1.0) +// request.Temperature = math.Min(0.99, request.Temperature) +// request.Temperature = math.Max(0.01, request.Temperature) +// a.SetVersionByModeName(request.Model) +// if a.APIVersion == "v4" { +// return request, nil +// } +// return ConvertRequest(*request), nil +// } +// } + +// func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (any, error) { +// if request == nil { +// return nil, errors.New("request is nil") +// } +// newRequest := ImageRequest{ +// Model: request.Model, +// Prompt: request.Prompt, +// UserId: request.User, +// } +// return newRequest, 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) DoResponseV4(c *gin.Context, resp *http.Response, meta *meta.Meta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +// if meta.IsStream { +// err, _, usage = openai.StreamHandler(c, resp, meta.Mode) +// } else { +// err, usage = openai.Handler(c, resp, meta.PromptTokens, meta.ActualModelName) +// } +// return +// } + +// func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Meta) (usage *model.Usage, err *model.ErrorWithStatusCode) { +// switch meta.Mode { +// case relaymode.Embeddings: +// err, usage = EmbeddingsHandler(c, resp) +// return +// case relaymode.ImagesGenerations: +// err, usage = openai.ImageHandler(c, resp) +// return +// } +// if a.APIVersion == "v4" { +// return a.DoResponseV4(c, resp, meta) +// } +// if meta.IsStream { +// err, usage = StreamHandler(c, resp) +// } else { +// if meta.Mode == relaymode.Embeddings { +// err, usage = EmbeddingsHandler(c, resp) +// } else { +// err, usage = Handler(c, resp) +// } +// } +// return +// } + +// func ConvertEmbeddingRequest(request model.GeneralOpenAIRequest) *EmbeddingRequest { +// return &EmbeddingRequest{ +// Model: "embedding-2", +// Input: request.Input.(string), +// } +// } + +// func (a *Adaptor) GetModelList() []string { +// return ModelList +// } + +// func (a *Adaptor) GetChannelName() string { +// return "zhipu" +// } diff --git a/relay/channel/zhipu/constants.go b/relay/adaptor/zhipu/constants.go similarity index 100% rename from relay/channel/zhipu/constants.go rename to relay/adaptor/zhipu/constants.go diff --git a/relay/channel/zhipu/main.go b/relay/adaptor/zhipu/main.go similarity index 100% rename from relay/channel/zhipu/main.go rename to relay/adaptor/zhipu/main.go diff --git a/relay/channel/zhipu/model.go b/relay/adaptor/zhipu/model.go similarity index 100% rename from relay/channel/zhipu/model.go rename to relay/adaptor/zhipu/model.go diff --git a/relay/apitype/define.go b/relay/apitype/define.go new file mode 100644 index 00000000..82d32a50 --- /dev/null +++ b/relay/apitype/define.go @@ -0,0 +1,17 @@ +package apitype + +const ( + OpenAI = iota + Anthropic + PaLM + Baidu + Zhipu + Ali + Xunfei + AIProxyLibrary + Tencent + Gemini + Ollama + + Dummy // this one is only for count, do not add any channel after this +) diff --git a/relay/billing/billing.go b/relay/billing/billing.go new file mode 100644 index 00000000..a99d37ee --- /dev/null +++ b/relay/billing/billing.go @@ -0,0 +1,42 @@ +package billing + +import ( + "context" + "fmt" + "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/model" +) + +func ReturnPreConsumedQuota(ctx context.Context, preConsumedQuota int64, tokenId int) { + if preConsumedQuota != 0 { + go func(ctx context.Context) { + // return pre-consumed quota + err := model.PostConsumeTokenQuota(tokenId, -preConsumedQuota) + if err != nil { + logger.Error(ctx, "error return pre-consumed quota: "+err.Error()) + } + }(ctx) + } +} + +func PostConsumeQuota(ctx context.Context, tokenId int, quotaDelta int64, totalQuota int64, userId int, channelId int, modelRatio float64, groupRatio float64, modelName string, tokenName string) { + // quotaDelta is remaining quota to be consumed + err := model.PostConsumeTokenQuota(tokenId, quotaDelta) + if err != nil { + logger.SysError("error consuming token remain quota: " + err.Error()) + } + err = model.CacheUpdateUserQuota(ctx, userId) + if err != nil { + logger.SysError("error update user quota cache: " + err.Error()) + } + // totalQuota is total quota consumed + if totalQuota != 0 { + logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) + model.RecordConsumeLog(ctx, userId, channelId, int(totalQuota), 0, modelName, tokenName, totalQuota, logContent) + model.UpdateUserUsedQuotaAndRequestCount(userId, totalQuota) + model.UpdateChannelUsedQuota(channelId, totalQuota) + } + if totalQuota <= 0 { + logger.Error(ctx, fmt.Sprintf("totalQuota consumed is %d, something is wrong", totalQuota)) + } +} diff --git a/common/group-ratio.go b/relay/billing/ratio/group.go similarity index 97% rename from common/group-ratio.go rename to relay/billing/ratio/group.go index 2de6e810..8e9c5b73 100644 --- a/common/group-ratio.go +++ b/relay/billing/ratio/group.go @@ -1,4 +1,4 @@ -package common +package ratio import ( "encoding/json" diff --git a/relay/billing/ratio/image.go b/relay/billing/ratio/image.go new file mode 100644 index 00000000..5a29cddc --- /dev/null +++ b/relay/billing/ratio/image.go @@ -0,0 +1,51 @@ +package ratio + +var ImageSizeRatios = map[string]map[string]float64{ + "dall-e-2": { + "256x256": 1, + "512x512": 1.125, + "1024x1024": 1.25, + }, + "dall-e-3": { + "1024x1024": 1, + "1024x1792": 2, + "1792x1024": 2, + }, + "ali-stable-diffusion-xl": { + "512x1024": 1, + "1024x768": 1, + "1024x1024": 1, + "576x1024": 1, + "1024x576": 1, + }, + "ali-stable-diffusion-v1.5": { + "512x1024": 1, + "1024x768": 1, + "1024x1024": 1, + "576x1024": 1, + "1024x576": 1, + }, + "wanx-v1": { + "1024x1024": 1, + "720x1280": 1, + "1280x720": 1, + }, +} + +var ImageGenerationAmounts = map[string][2]int{ + "dall-e-2": {1, 10}, + "dall-e-3": {1, 1}, // OpenAI allows n=1 currently. + "ali-stable-diffusion-xl": {1, 4}, // Ali + "ali-stable-diffusion-v1.5": {1, 4}, // Ali + "wanx-v1": {1, 4}, // Ali + "cogview-3": {1, 1}, +} + +var ImagePromptLengthLimitations = map[string]int{ + "dall-e-2": 1000, + "dall-e-3": 4000, + "ali-stable-diffusion-xl": 4000, + "ali-stable-diffusion-v1.5": 4000, + "wanx-v1": 4000, + "cogview-3": 833, +} diff --git a/common/model-ratio.go b/relay/billing/ratio/model.go similarity index 85% rename from common/model-ratio.go rename to relay/billing/ratio/model.go index 4f6acb7b..1f7daef6 100644 --- a/common/model-ratio.go +++ b/relay/billing/ratio/model.go @@ -1,4 +1,4 @@ -package common +package ratio import ( "encoding/json" @@ -63,8 +63,8 @@ var ModelRatio = map[string]float64{ "text-search-ada-doc-001": 10, "text-moderation-stable": 0.1, "text-moderation-latest": 0.1, - "dall-e-2": 8, // $0.016 - $0.020 / image - "dall-e-3": 20, // $0.040 - $0.120 / image + "dall-e-2": 0.02 * USD, // $0.016 - $0.020 / image + "dall-e-3": 0.04 * USD, // $0.040 - $0.120 / image // https://www.anthropic.com/api#pricing "claude-instant-1.2": 0.8 / 1000 * USD, "claude-2.0": 8.0 / 1000 * USD, @@ -73,14 +73,22 @@ var ModelRatio = map[string]float64{ "claude-3-sonnet-20240229": 3.0 / 1000 * USD, "claude-3-opus-20240229": 15.0 / 1000 * USD, // https://cloud.baidu.com/doc/WENXINWORKSHOP/s/hlrk4akp7 - "ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens - "ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens - "ERNIE-Bot-4": 0.12 * RMB, // ¥0.12 / 1k tokens - "ERNIE-Bot-8K": 0.024 * RMB, - "Embedding-V1": 0.1429, // ¥0.002 / 1k tokens - "bge-large-zh": 0.002 * RMB, - "bge-large-en": 0.002 * RMB, - "bge-large-8k": 0.002 * RMB, + "ERNIE-4.0-8K": 0.120 * RMB, + "ERNIE-3.5-8K": 0.012 * RMB, + "ERNIE-3.5-8K-0205": 0.024 * RMB, + "ERNIE-3.5-8K-1222": 0.012 * RMB, + "ERNIE-Bot-8K": 0.024 * RMB, + "ERNIE-3.5-4K-0205": 0.012 * RMB, + "ERNIE-Speed-8K": 0.004 * RMB, + "ERNIE-Speed-128K": 0.004 * RMB, + "ERNIE-Lite-8K-0922": 0.008 * RMB, + "ERNIE-Lite-8K-0308": 0.003 * RMB, + "ERNIE-Tiny-8K": 0.001 * RMB, + "BLOOMZ-7B": 0.004 * RMB, + "Embedding-V1": 0.002 * RMB, + "bge-large-zh": 0.002 * RMB, + "bge-large-en": 0.002 * RMB, + "tao-8k": 0.002 * RMB, // https://ai.google.dev/pricing "PaLM-2": 1, "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens @@ -89,18 +97,24 @@ var ModelRatio = map[string]float64{ "gemini-1.0-pro-001": 1, "gemini-1.5-pro": 1, // https://open.bigmodel.cn/pricing - "glm-4": 0.1 * RMB, - "glm-4v": 0.1 * RMB, - "glm-3-turbo": 0.005 * RMB, - "chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens - "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens - "chatglm_std": 0.3572, // ¥0.005 / 1k tokens - "chatglm_lite": 0.1429, // ¥0.002 / 1k tokens - "qwen-turbo": 0.5715, // ¥0.008 / 1k tokens // https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-thousand-questions-metering-and-billing + "glm-4": 0.1 * RMB, + "glm-4v": 0.1 * RMB, + "glm-3-turbo": 0.005 * RMB, + "embedding-2": 0.0005 * RMB, + "chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens + "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens + "chatglm_std": 0.3572, // ¥0.005 / 1k tokens + "chatglm_lite": 0.1429, // ¥0.002 / 1k tokens + "cogview-3": 0.25 * RMB, + // https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-thousand-questions-metering-and-billing + "qwen-turbo": 0.5715, // ¥0.008 / 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 + "ali-stable-diffusion-xl": 8, + "ali-stable-diffusion-v1.5": 8, + "wanx-v1": 8, "SparkDesk": 1.2858, // ¥0.018 / 1k tokens "SparkDesk-v1.1": 1.2858, // ¥0.018 / 1k tokens "SparkDesk-v2.1": 1.2858, // ¥0.018 / 1k tokens diff --git a/relay/channel/ali/adaptor.go b/relay/channel/ali/adaptor.go deleted file mode 100644 index b312934b..00000000 --- a/relay/channel/ali/adaptor.go +++ /dev/null @@ -1,83 +0,0 @@ -package ali - -// import ( -// "github.com/Laisky/errors/v2" -// "fmt" -// "github.com/gin-gonic/gin" -// "github.com/songquanpeng/one-api/common" -// "github.com/songquanpeng/one-api/relay/channel" -// "github.com/songquanpeng/one-api/relay/constant" -// "github.com/songquanpeng/one-api/relay/model" -// "github.com/songquanpeng/one-api/relay/util" -// "io" -// "net/http" -// ) - -// // https://help.aliyun.com/zh/dashscope/developer-reference/api-details - -// type Adaptor struct { -// } - -// func (a *Adaptor) Init(meta *util.RelayMeta) { - -// } - -// func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { -// fullRequestURL := fmt.Sprintf("%s/api/v1/services/aigc/text-generation/generation", meta.BaseURL) -// if meta.Mode == constant.RelayModeEmbeddings { -// fullRequestURL = fmt.Sprintf("%s/api/v1/services/embeddings/text-embedding/text-embedding", meta.BaseURL) -// } -// return fullRequestURL, nil -// } - -// func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { -// channel.SetupCommonRequestHeader(c, req, meta) -// req.Header.Set("Authorization", "Bearer "+meta.APIKey) -// if meta.IsStream { -// req.Header.Set("X-DashScope-SSE", "enable") -// } -// if c.GetString(common.ConfigKeyPlugin) != "" { -// req.Header.Set("X-DashScope-Plugin", c.GetString(common.ConfigKeyPlugin)) -// } -// 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") -// } -// switch relayMode { -// case constant.RelayModeEmbeddings: -// baiduEmbeddingRequest := ConvertEmbeddingRequest(*request) -// return baiduEmbeddingRequest, nil -// default: -// baiduRequest := ConvertRequest(*request) -// return baiduRequest, nil -// } -// } - -// func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { -// return channel.DoRequestHelper(a, c, meta, requestBody) -// } - -// func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { -// if meta.IsStream { -// err, usage = StreamHandler(c, resp) -// } else { -// switch meta.Mode { -// case constant.RelayModeEmbeddings: -// err, usage = EmbeddingHandler(c, resp) -// default: -// err, usage = Handler(c, resp) -// } -// } -// return -// } - -// func (a *Adaptor) GetModelList() []string { -// return ModelList -// } - -// func (a *Adaptor) GetChannelName() string { -// return "ali" -// } diff --git a/relay/channel/ali/model.go b/relay/channel/ali/model.go deleted file mode 100644 index d0cd341c..00000000 --- a/relay/channel/ali/model.go +++ /dev/null @@ -1,71 +0,0 @@ -package ali - -// type Message struct { -// Content string `json:"content"` -// Role string `json:"role"` -// } - -// type Input struct { -// //Prompt string `json:"prompt"` -// Messages []Message `json:"messages"` -// } - -// type Parameters struct { -// TopP float64 `json:"top_p,omitempty"` -// TopK int `json:"top_k,omitempty"` -// Seed uint64 `json:"seed,omitempty"` -// EnableSearch bool `json:"enable_search,omitempty"` -// IncrementalOutput bool `json:"incremental_output,omitempty"` -// } - -// type ChatRequest struct { -// Model string `json:"model"` -// Input Input `json:"input"` -// Parameters Parameters `json:"parameters,omitempty"` -// } - -// type EmbeddingRequest struct { -// Model string `json:"model"` -// Input struct { -// Texts []string `json:"texts"` -// } `json:"input"` -// Parameters *struct { -// TextType string `json:"text_type,omitempty"` -// } `json:"parameters,omitempty"` -// } - -// type Embedding struct { -// Embedding []float64 `json:"embedding"` -// TextIndex int `json:"text_index"` -// } - -// type EmbeddingResponse struct { -// Output struct { -// Embeddings []Embedding `json:"embeddings"` -// } `json:"output"` -// Usage Usage `json:"usage"` -// Error -// } - -// type Error struct { -// Code string `json:"code"` -// Message string `json:"message"` -// RequestId string `json:"request_id"` -// } - -// type Usage struct { -// InputTokens int `json:"input_tokens"` -// OutputTokens int `json:"output_tokens"` -// TotalTokens int `json:"total_tokens"` -// } - -// type Output struct { -// Text string `json:"text"` -// FinishReason string `json:"finish_reason"` -// } - -// type ChatResponse struct { -// Output Output `json:"output"` -// Usage Usage `json:"usage"` -// Error -// } diff --git a/relay/channel/baidu/constants.go b/relay/channel/baidu/constants.go deleted file mode 100644 index 45a4e901..00000000 --- a/relay/channel/baidu/constants.go +++ /dev/null @@ -1,13 +0,0 @@ -package baidu - -var ModelList = []string{ - "ERNIE-Bot-4", - "ERNIE-Bot-8K", - "ERNIE-Bot", - "ERNIE-Speed", - "ERNIE-Bot-turbo", - "Embedding-V1", - "bge-large-zh", - "bge-large-en", - "tao-8k", -} diff --git a/relay/channel/interface.go b/relay/channel/interface.go deleted file mode 100644 index e25db83f..00000000 --- a/relay/channel/interface.go +++ /dev/null @@ -1,20 +0,0 @@ -package channel - -import ( - "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" - "io" - "net/http" -) - -type Adaptor interface { - Init(meta *util.RelayMeta) - GetRequestURL(meta *util.RelayMeta) (string, error) - SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error - ConvertRequest(c *gin.Context, relayMode int, request *model.GeneralOpenAIRequest) (any, error) - DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) - DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) - GetModelList() []string - GetChannelName() string -} diff --git a/relay/channel/minimax/main.go b/relay/channel/minimax/main.go deleted file mode 100644 index a3cd0f14..00000000 --- a/relay/channel/minimax/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package minimax - -import ( - "fmt" - - "github.com/Laisky/errors/v2" - "github.com/songquanpeng/one-api/relay/constant" - "github.com/songquanpeng/one-api/relay/util" -) - -func GetRequestURL(meta *util.RelayMeta) (string, error) { - if meta.Mode == constant.RelayModeChatCompletions { - return fmt.Sprintf("%s/v1/text/chatcompletion_v2", meta.BaseURL), nil - } - return "", errors.Errorf("unsupported relay mode %d for minimax", meta.Mode) -} diff --git a/relay/channel/openai/compatible.go b/relay/channel/openai/compatible.go deleted file mode 100644 index e4951a34..00000000 --- a/relay/channel/openai/compatible.go +++ /dev/null @@ -1,46 +0,0 @@ -package openai - -import ( - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/relay/channel/ai360" - "github.com/songquanpeng/one-api/relay/channel/baichuan" - "github.com/songquanpeng/one-api/relay/channel/groq" - "github.com/songquanpeng/one-api/relay/channel/lingyiwanwu" - "github.com/songquanpeng/one-api/relay/channel/minimax" - "github.com/songquanpeng/one-api/relay/channel/mistral" - "github.com/songquanpeng/one-api/relay/channel/moonshot" -) - -var CompatibleChannels = []int{ - common.ChannelTypeAzure, - common.ChannelType360, - common.ChannelTypeMoonshot, - common.ChannelTypeBaichuan, - common.ChannelTypeMinimax, - common.ChannelTypeMistral, - common.ChannelTypeGroq, - common.ChannelTypeLingYiWanWu, -} - -func GetCompatibleChannelMeta(channelType int) (string, []string) { - switch channelType { - case common.ChannelTypeAzure: - return "azure", ModelList - case common.ChannelType360: - return "360", ai360.ModelList - case common.ChannelTypeMoonshot: - return "moonshot", moonshot.ModelList - case common.ChannelTypeBaichuan: - return "baichuan", baichuan.ModelList - case common.ChannelTypeMinimax: - return "minimax", minimax.ModelList - case common.ChannelTypeMistral: - return "mistralai", mistral.ModelList - case common.ChannelTypeGroq: - return "groq", groq.ModelList - case common.ChannelTypeLingYiWanWu: - return "lingyiwanwu", lingyiwanwu.ModelList - default: - return "openai", ModelList - } -} diff --git a/relay/channel/openai/helper.go b/relay/channel/openai/helper.go deleted file mode 100644 index 9bca8cab..00000000 --- a/relay/channel/openai/helper.go +++ /dev/null @@ -1,11 +0,0 @@ -package openai - -import "github.com/songquanpeng/one-api/relay/model" - -func ResponseText2Usage(responseText string, modeName string, promptTokens int) *model.Usage { - usage := &model.Usage{} - usage.PromptTokens = promptTokens - usage.CompletionTokens = CountTokenText(responseText, modeName) - usage.TotalTokens = usage.PromptTokens + usage.CompletionTokens - return usage -} diff --git a/relay/channel/zhipu/adaptor.go b/relay/channel/zhipu/adaptor.go deleted file mode 100644 index 9c7bdd36..00000000 --- a/relay/channel/zhipu/adaptor.go +++ /dev/null @@ -1,62 +0,0 @@ -package zhipu - -// import ( -// "github.com/Laisky/errors/v2" -// "fmt" -// "github.com/gin-gonic/gin" -// "github.com/songquanpeng/one-api/relay/channel" -// "github.com/songquanpeng/one-api/relay/model" -// "github.com/songquanpeng/one-api/relay/util" -// "io" -// "net/http" -// ) - -// type Adaptor struct { -// } - -// func (a *Adaptor) Init(meta *util.RelayMeta) { - -// } - -// func (a *Adaptor) GetRequestURL(meta *util.RelayMeta) (string, error) { -// method := "invoke" -// if meta.IsStream { -// method = "sse-invoke" -// } -// return fmt.Sprintf("%s/api/paas/v3/model-api/%s/%s", meta.BaseURL, meta.ActualModelName, method), nil -// } - -// func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *util.RelayMeta) error { -// channel.SetupCommonRequestHeader(c, req, meta) -// token := GetToken(meta.APIKey) -// req.Header.Set("Authorization", token) -// 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") -// } -// return ConvertRequest(*request), nil -// } - -// func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io.Reader) (*http.Response, error) { -// return channel.DoRequestHelper(a, c, meta, requestBody) -// } - -// func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { -// if meta.IsStream { -// err, usage = StreamHandler(c, resp) -// } else { -// err, usage = Handler(c, resp) -// } -// return -// } - -// func (a *Adaptor) GetModelList() []string { -// return ModelList -// } - -// func (a *Adaptor) GetChannelName() string { -// return "zhipu" -// } diff --git a/relay/channeltype/define.go b/relay/channeltype/define.go new file mode 100644 index 00000000..80027a80 --- /dev/null +++ b/relay/channeltype/define.go @@ -0,0 +1,39 @@ +package channeltype + +const ( + Unknown = iota + OpenAI + API2D + Azure + CloseAI + OpenAISB + OpenAIMax + OhMyGPT + Custom + Ails + AIProxy + PaLM + API2GPT + AIGC2D + Anthropic + Baidu + Zhipu + Ali + Xunfei + AI360 + OpenRouter + AIProxyLibrary + FastGPT + Tencent + Gemini + Moonshot + Baichuan + Minimax + Mistral + Groq + Ollama + LingYiWanWu + StepFun + + Dummy +) diff --git a/relay/channeltype/helper.go b/relay/channeltype/helper.go new file mode 100644 index 00000000..01c2918c --- /dev/null +++ b/relay/channeltype/helper.go @@ -0,0 +1,30 @@ +package channeltype + +import "github.com/songquanpeng/one-api/relay/apitype" + +func ToAPIType(channelType int) int { + apiType := apitype.OpenAI + switch channelType { + case Anthropic: + apiType = apitype.Anthropic + case Baidu: + apiType = apitype.Baidu + case PaLM: + apiType = apitype.PaLM + case Zhipu: + apiType = apitype.Zhipu + case Ali: + apiType = apitype.Ali + case Xunfei: + apiType = apitype.Xunfei + case AIProxyLibrary: + apiType = apitype.AIProxyLibrary + case Tencent: + apiType = apitype.Tencent + case Gemini: + apiType = apitype.Gemini + case Ollama: + apiType = apitype.Ollama + } + return apiType +} diff --git a/relay/channeltype/url.go b/relay/channeltype/url.go new file mode 100644 index 00000000..eec59116 --- /dev/null +++ b/relay/channeltype/url.go @@ -0,0 +1,43 @@ +package channeltype + +var ChannelBaseURLs = []string{ + "", // 0 + "https://api.openai.com", // 1 + "https://oa.api2d.net", // 2 + "", // 3 + "https://api.closeai-proxy.xyz", // 4 + "https://api.openai-sb.com", // 5 + "https://api.openaimax.com", // 6 + "https://api.ohmygpt.com", // 7 + "", // 8 + "https://api.caipacity.com", // 9 + "https://api.aiproxy.io", // 10 + "https://generativelanguage.googleapis.com", // 11 + "https://api.api2gpt.com", // 12 + "https://api.aigc2d.com", // 13 + "https://api.anthropic.com", // 14 + "https://aip.baidubce.com", // 15 + "https://open.bigmodel.cn", // 16 + "https://dashscope.aliyuncs.com", // 17 + "", // 18 + "https://ai.360.cn", // 19 + "https://openrouter.ai/api", // 20 + "https://api.aiproxy.io", // 21 + "https://fastgpt.run/api/openapi", // 22 + "https://hunyuan.cloud.tencent.com", // 23 + "https://generativelanguage.googleapis.com", // 24 + "https://api.moonshot.cn", // 25 + "https://api.baichuan-ai.com", // 26 + "https://api.minimax.chat", // 27 + "https://api.mistral.ai", // 28 + "https://api.groq.com/openai", // 29 + "http://localhost:11434", // 30 + "https://api.lingyiwanwu.com", // 31 + "https://api.stepfun.com", // 32 +} + +func init() { + if len(ChannelBaseURLs) != Dummy { + panic("channel base urls length not match") + } +} diff --git a/relay/util/init.go b/relay/client/init.go similarity index 97% rename from relay/util/init.go rename to relay/client/init.go index b303de0c..73108700 100644 --- a/relay/util/init.go +++ b/relay/client/init.go @@ -1,4 +1,4 @@ -package util +package client import ( "net/http" diff --git a/relay/constant/api_type.go b/relay/constant/api_type.go deleted file mode 100644 index b249f6a2..00000000 --- a/relay/constant/api_type.go +++ /dev/null @@ -1,48 +0,0 @@ -package constant - -import ( - "github.com/songquanpeng/one-api/common" -) - -const ( - APITypeOpenAI = iota - APITypeAnthropic - APITypePaLM - APITypeBaidu - APITypeZhipu - APITypeAli - APITypeXunfei - APITypeAIProxyLibrary - APITypeTencent - APITypeGemini - APITypeOllama - - APITypeDummy // this one is only for count, do not add any channel after this -) - -func ChannelType2APIType(channelType int) int { - apiType := APITypeOpenAI - switch channelType { - case common.ChannelTypeAnthropic: - apiType = APITypeAnthropic - case common.ChannelTypeBaidu: - apiType = APITypeBaidu - case common.ChannelTypePaLM: - apiType = APITypePaLM - case common.ChannelTypeZhipu: - apiType = APITypeZhipu - case common.ChannelTypeAli: - apiType = APITypeAli - case common.ChannelTypeXunfei: - apiType = APITypeXunfei - case common.ChannelTypeAIProxyLibrary: - apiType = APITypeAIProxyLibrary - case common.ChannelTypeTencent: - apiType = APITypeTencent - case common.ChannelTypeGemini: - apiType = APITypeGemini - case common.ChannelTypeOllama: - apiType = APITypeOllama - } - return apiType -} diff --git a/relay/constant/image.go b/relay/constant/image.go deleted file mode 100644 index 5e04895f..00000000 --- a/relay/constant/image.go +++ /dev/null @@ -1,24 +0,0 @@ -package constant - -var DalleSizeRatios = map[string]map[string]float64{ - "dall-e-2": { - "256x256": 1, - "512x512": 1.125, - "1024x1024": 1.25, - }, - "dall-e-3": { - "1024x1024": 1, - "1024x1792": 2, - "1792x1024": 2, - }, -} - -var DalleGenerationImageAmounts = map[string][2]int{ - "dall-e-2": {1, 10}, - "dall-e-3": {1, 1}, // OpenAI allows n=1 currently. -} - -var DalleImagePromptLengthLimitations = map[string]int{ - "dall-e-2": 1000, - "dall-e-3": 4000, -} diff --git a/relay/constant/relay_mode.go b/relay/constant/relay_mode.go deleted file mode 100644 index 5e2fe574..00000000 --- a/relay/constant/relay_mode.go +++ /dev/null @@ -1,42 +0,0 @@ -package constant - -import "strings" - -const ( - RelayModeUnknown = iota - RelayModeChatCompletions - RelayModeCompletions - RelayModeEmbeddings - RelayModeModerations - RelayModeImagesGenerations - RelayModeEdits - RelayModeAudioSpeech - RelayModeAudioTranscription - RelayModeAudioTranslation -) - -func Path2RelayMode(path string) int { - relayMode := RelayModeUnknown - if strings.HasPrefix(path, "/v1/chat/completions") { - relayMode = RelayModeChatCompletions - } else if strings.HasPrefix(path, "/v1/completions") { - relayMode = RelayModeCompletions - } else if strings.HasPrefix(path, "/v1/embeddings") { - relayMode = RelayModeEmbeddings - } else if strings.HasSuffix(path, "embeddings") { - relayMode = RelayModeEmbeddings - } else if strings.HasPrefix(path, "/v1/moderations") { - relayMode = RelayModeModerations - } else if strings.HasPrefix(path, "/v1/images/generations") { - relayMode = RelayModeImagesGenerations - } else if strings.HasPrefix(path, "/v1/edits") { - relayMode = RelayModeEdits - } else if strings.HasPrefix(path, "/v1/audio/speech") { - relayMode = RelayModeAudioSpeech - } else if strings.HasPrefix(path, "/v1/audio/transcriptions") { - relayMode = RelayModeAudioTranscription - } else if strings.HasPrefix(path, "/v1/audio/translations") { - relayMode = RelayModeAudioTranslation - } - return relayMode -} diff --git a/relay/controller/audio.go b/relay/controller/audio.go index 85599b1f..58094c22 100644 --- a/relay/controller/audio.go +++ b/relay/controller/audio.go @@ -6,20 +6,23 @@ import ( "context" "encoding/json" "fmt" - "io" - "net/http" - "strings" - "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/model" - "github.com/songquanpeng/one-api/relay/channel/openai" - "github.com/songquanpeng/one-api/relay/constant" + "github.com/songquanpeng/one-api/relay/adaptor/azure" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + "github.com/songquanpeng/one-api/relay/billing" + billingratio "github.com/songquanpeng/one-api/relay/billing/ratio" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/client" relaymodel "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" + "github.com/songquanpeng/one-api/relay/relaymode" + "io" + "net/http" + "strings" ) func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatusCode { @@ -34,7 +37,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus tokenName := c.GetString("token_name") var ttsRequest openai.TextToSpeechRequest - if relayMode == constant.RelayModeAudioSpeech { + if relayMode == relaymode.AudioSpeech { // Read JSON err := common.UnmarshalBodyReusable(c, &ttsRequest) // Check if JSON is valid @@ -48,14 +51,15 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus } } - modelRatio := common.GetModelRatio(audioModel) - // groupRatio := common.GetGroupRatio(group) - groupRatio := c.GetFloat64("channel_ratio") + modelRatio := billingratio.GetModelRatio(audioModel) + // groupRatio := billingratio.GetGroupRatio(group) + groupRatio := c.GetFloat64("channel_ratio") // get minimal ratio from multiple groups + ratio := modelRatio * groupRatio var quota int64 var preConsumedQuota int64 switch relayMode { - case constant.RelayModeAudioSpeech: + case relaymode.AudioSpeech: preConsumedQuota = int64(float64(len(ttsRequest.Input)) * ratio) quota = preConsumedQuota default: @@ -117,19 +121,19 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus } } - baseURL := common.ChannelBaseURLs[channelType] + baseURL := channeltype.ChannelBaseURLs[channelType] requestURL := c.Request.URL.String() if c.GetString("base_url") != "" { baseURL = c.GetString("base_url") } - fullRequestURL := util.GetFullRequestURL(baseURL, requestURL, channelType) - if channelType == common.ChannelTypeAzure { - apiVersion := util.GetAzureAPIVersion(c) - if relayMode == constant.RelayModeAudioTranscription { + fullRequestURL := openai.GetFullRequestURL(baseURL, requestURL, channelType) + if channelType == channeltype.Azure { + apiVersion := azure.GetAPIVersion(c) + if relayMode == relaymode.AudioTranscription { // https://learn.microsoft.com/en-us/azure/ai-services/openai/whisper-quickstart?tabs=command-line#rest-api fullRequestURL = fmt.Sprintf("%s/openai/deployments/%s/audio/transcriptions?api-version=%s", baseURL, audioModel, apiVersion) - } else if relayMode == constant.RelayModeAudioSpeech { + } else if relayMode == relaymode.AudioSpeech { // https://learn.microsoft.com/en-us/azure/ai-services/openai/text-to-speech-quickstart?tabs=command-line#rest-api fullRequestURL = fmt.Sprintf("%s/openai/deployments/%s/audio/speech?api-version=%s", baseURL, audioModel, apiVersion) } @@ -148,7 +152,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus return openai.ErrorWrapper(err, "new_request_failed", http.StatusInternalServerError) } - if (relayMode == constant.RelayModeAudioTranscription || relayMode == constant.RelayModeAudioSpeech) && channelType == common.ChannelTypeAzure { + if (relayMode == relaymode.AudioTranscription || relayMode == relaymode.AudioSpeech) && channelType == channeltype.Azure { // https://learn.microsoft.com/en-us/azure/ai-services/openai/whisper-quickstart?tabs=command-line#rest-api apiKey := c.Request.Header.Get("Authorization") apiKey = strings.TrimPrefix(apiKey, "Bearer ") @@ -160,7 +164,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) req.Header.Set("Accept", c.Request.Header.Get("Accept")) - resp, err := util.HTTPClient.Do(req) + resp, err := client.HTTPClient.Do(req) if err != nil { return openai.ErrorWrapper(err, "do_request_failed", http.StatusInternalServerError) } @@ -174,7 +178,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus return openai.ErrorWrapper(err, "close_request_body_failed", http.StatusInternalServerError) } - if relayMode != constant.RelayModeAudioSpeech { + if relayMode != relaymode.AudioSpeech { responseBody, err := io.ReadAll(resp.Body) if err != nil { return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError) @@ -213,12 +217,12 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus resp.Body = io.NopCloser(bytes.NewBuffer(responseBody)) } if resp.StatusCode != http.StatusOK { - return util.RelayErrorHandler(resp) + return RelayErrorHandler(resp) } succeed = true quotaDelta := quota - preConsumedQuota defer func(ctx context.Context) { - go util.PostConsumeQuota(ctx, tokenId, quotaDelta, quota, userId, channelId, modelRatio, groupRatio, audioModel, tokenName) + go billing.PostConsumeQuota(ctx, tokenId, quotaDelta, quota, userId, channelId, modelRatio, groupRatio, audioModel, tokenName) }(c.Request.Context()) for k, v := range resp.Header { diff --git a/relay/controller/error.go b/relay/controller/error.go new file mode 100644 index 00000000..69ece3ec --- /dev/null +++ b/relay/controller/error.go @@ -0,0 +1,91 @@ +package controller + +import ( + "encoding/json" + "fmt" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/common/logger" + "github.com/songquanpeng/one-api/relay/model" + "io" + "net/http" + "strconv" +) + +type GeneralErrorResponse struct { + Error model.Error `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) (ErrorWithStatusCode *model.ErrorWithStatusCode) { + ErrorWithStatusCode = &model.ErrorWithStatusCode{ + StatusCode: resp.StatusCode, + Error: model.Error{ + Message: "", + Type: "upstream_error", + Code: "bad_response_status_code", + Param: strconv.Itoa(resp.StatusCode), + }, + } + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return + } + if config.DebugEnabled { + logger.SysLog(fmt.Sprintf("error happened, status code: %d, response: \n%s", resp.StatusCode, string(responseBody))) + } + err = resp.Body.Close() + if err != nil { + return + } + var errResponse GeneralErrorResponse + err = json.Unmarshal(responseBody, &errResponse) + if err != nil { + return + } + if errResponse.Error.Message != "" { + // OpenAI format error, so we override the default one + ErrorWithStatusCode.Error = errResponse.Error + } else { + ErrorWithStatusCode.Error.Message = errResponse.ToMessage() + } + if ErrorWithStatusCode.Error.Message == "" { + ErrorWithStatusCode.Error.Message = fmt.Sprintf("bad response status code %d", resp.StatusCode) + } + return +} diff --git a/relay/controller/helper.go b/relay/controller/helper.go index 71dd653e..e07aba89 100644 --- a/relay/controller/helper.go +++ b/relay/controller/helper.go @@ -9,10 +9,13 @@ import ( "github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/model" - "github.com/songquanpeng/one-api/relay/channel/openai" - "github.com/songquanpeng/one-api/relay/constant" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + billingratio "github.com/songquanpeng/one-api/relay/billing/ratio" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/controller/validator" + "github.com/songquanpeng/one-api/relay/meta" relaymodel "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" + "github.com/songquanpeng/one-api/relay/relaymode" "math" "net/http" ) @@ -23,21 +26,21 @@ func getAndValidateTextRequest(c *gin.Context, relayMode int) (*relaymodel.Gener if err != nil { return nil, err } - if relayMode == constant.RelayModeModerations && textRequest.Model == "" { + if relayMode == relaymode.Moderations && textRequest.Model == "" { textRequest.Model = "text-moderation-latest" } - if relayMode == constant.RelayModeEmbeddings && textRequest.Model == "" { + if relayMode == relaymode.Embeddings && textRequest.Model == "" { textRequest.Model = c.Param("model") } - err = util.ValidateTextRequest(textRequest, relayMode) + err = validator.ValidateTextRequest(textRequest, relayMode) if err != nil { return nil, err } return textRequest, nil } -func getImageRequest(c *gin.Context, relayMode int) (*openai.ImageRequest, error) { - imageRequest := &openai.ImageRequest{} +func getImageRequest(c *gin.Context, relayMode int) (*relaymodel.ImageRequest, error) { + imageRequest := &relaymodel.ImageRequest{} err := common.UnmarshalBodyReusable(c, imageRequest) if err != nil { return nil, err @@ -54,9 +57,25 @@ func getImageRequest(c *gin.Context, relayMode int) (*openai.ImageRequest, error return imageRequest, nil } -func validateImageRequest(imageRequest *openai.ImageRequest, meta *util.RelayMeta) *relaymodel.ErrorWithStatusCode { +func isValidImageSize(model string, size string) bool { + if model == "cogview-3" { + return true + } + _, ok := billingratio.ImageSizeRatios[model][size] + return ok +} + +func getImageSizeRatio(model string, size string) float64 { + ratio, ok := billingratio.ImageSizeRatios[model][size] + if !ok { + return 1 + } + return ratio +} + +func validateImageRequest(imageRequest *relaymodel.ImageRequest, meta *meta.Meta) *relaymodel.ErrorWithStatusCode { // model validation - _, hasValidSize := constant.DalleSizeRatios[imageRequest.Model][imageRequest.Size] + hasValidSize := isValidImageSize(imageRequest.Model, imageRequest.Size) if !hasValidSize { return openai.ErrorWrapper(errors.New("size not supported for this image model"), "size_not_supported", http.StatusBadRequest) } @@ -64,27 +83,24 @@ func validateImageRequest(imageRequest *openai.ImageRequest, meta *util.RelayMet if imageRequest.Prompt == "" { return openai.ErrorWrapper(errors.New("prompt is required"), "prompt_missing", http.StatusBadRequest) } - if len(imageRequest.Prompt) > constant.DalleImagePromptLengthLimitations[imageRequest.Model] { + if len(imageRequest.Prompt) > billingratio.ImagePromptLengthLimitations[imageRequest.Model] { return openai.ErrorWrapper(errors.New("prompt is too long"), "prompt_too_long", http.StatusBadRequest) } // Number of generated images validation if !isWithinRange(imageRequest.Model, imageRequest.N) { // channel not azure - if meta.ChannelType != common.ChannelTypeAzure { + if meta.ChannelType != channeltype.Azure { return openai.ErrorWrapper(errors.New("invalid value of n"), "n_not_within_range", http.StatusBadRequest) } } return nil } -func getImageCostRatio(imageRequest *openai.ImageRequest) (float64, error) { +func getImageCostRatio(imageRequest *relaymodel.ImageRequest) (float64, error) { if imageRequest == nil { return 0, errors.New("imageRequest is nil") } - imageCostRatio, hasValidSize := constant.DalleSizeRatios[imageRequest.Model][imageRequest.Size] - if !hasValidSize { - return 0, errors.Errorf("size not supported for this image model: %s", imageRequest.Size) - } + imageCostRatio := getImageSizeRatio(imageRequest.Model, imageRequest.Size) if imageRequest.Quality == "hd" && imageRequest.Model == "dall-e-3" { if imageRequest.Size == "1024x1024" { imageCostRatio *= 2 @@ -97,11 +113,11 @@ func getImageCostRatio(imageRequest *openai.ImageRequest) (float64, error) { func getPromptTokens(textRequest *relaymodel.GeneralOpenAIRequest, relayMode int) int { switch relayMode { - case constant.RelayModeChatCompletions: + case relaymode.ChatCompletions: return openai.CountTokenMessages(textRequest.Messages, textRequest.Model) - case constant.RelayModeCompletions: + case relaymode.Completions: return openai.CountTokenInput(textRequest.Prompt, textRequest.Model) - case constant.RelayModeModerations: + case relaymode.Moderations: return openai.CountTokenInput(textRequest.Input, textRequest.Model) } return 0 @@ -115,7 +131,7 @@ func getPreConsumedQuota(textRequest *relaymodel.GeneralOpenAIRequest, promptTok return int64(float64(preConsumedTokens) * ratio) } -func preConsumeQuota(ctx context.Context, textRequest *relaymodel.GeneralOpenAIRequest, promptTokens int, ratio float64, meta *util.RelayMeta) (int64, *relaymodel.ErrorWithStatusCode) { +func preConsumeQuota(ctx context.Context, textRequest *relaymodel.GeneralOpenAIRequest, promptTokens int, ratio float64, meta *meta.Meta) (int64, *relaymodel.ErrorWithStatusCode) { preConsumedQuota := getPreConsumedQuota(textRequest, promptTokens, ratio) userQuota, err := model.CacheGetUserQuota(ctx, meta.UserId) @@ -144,13 +160,13 @@ func preConsumeQuota(ctx context.Context, textRequest *relaymodel.GeneralOpenAIR return preConsumedQuota, nil } -func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *util.RelayMeta, textRequest *relaymodel.GeneralOpenAIRequest, ratio float64, preConsumedQuota int64, modelRatio float64, groupRatio float64) { +func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *meta.Meta, textRequest *relaymodel.GeneralOpenAIRequest, ratio float64, preConsumedQuota int64, modelRatio float64, groupRatio float64) { if usage == nil { logger.Error(ctx, "usage is nil, which is unexpected") return } var quota int64 - completionRatio := common.GetCompletionRatio(textRequest.Model) + completionRatio := billingratio.GetCompletionRatio(textRequest.Model) promptTokens := usage.PromptTokens completionTokens := usage.CompletionTokens quota = int64(math.Ceil((float64(promptTokens) + float64(completionTokens)*completionRatio) * ratio)) @@ -178,3 +194,14 @@ func postConsumeQuota(ctx context.Context, usage *relaymodel.Usage, meta *util.R model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota) model.UpdateChannelUsedQuota(meta.ChannelId, quota) } + +func getMappedModelName(modelName string, mapping map[string]string) (string, bool) { + if mapping == nil { + return modelName, false + } + mappedModelName := mapping[modelName] + if mappedModelName != "" { + return mappedModelName, true + } + return modelName, false +} diff --git a/relay/controller/image.go b/relay/controller/image.go index d88dc271..ea3e32a0 100644 --- a/relay/controller/image.go +++ b/relay/controller/image.go @@ -5,35 +5,33 @@ import ( "context" "encoding/json" "fmt" - "github.com/Laisky/errors/v2" "io" "net/http" - "strings" - "github.com/songquanpeng/one-api/common" + "github.com/Laisky/errors/v2" + "github.com/gin-gonic/gin" "github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/model" - "github.com/songquanpeng/one-api/relay/channel/openai" - "github.com/songquanpeng/one-api/relay/constant" + "github.com/songquanpeng/one-api/relay" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + billingratio "github.com/songquanpeng/one-api/relay/billing/ratio" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/meta" relaymodel "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" - - "github.com/gin-gonic/gin" ) func isWithinRange(element string, value int) bool { - if _, ok := constant.DalleGenerationImageAmounts[element]; !ok { + if _, ok := billingratio.ImageGenerationAmounts[element]; !ok { return false } - min := constant.DalleGenerationImageAmounts[element][0] - max := constant.DalleGenerationImageAmounts[element][1] - + min := billingratio.ImageGenerationAmounts[element][0] + max := billingratio.ImageGenerationAmounts[element][1] return value >= min && value <= max } func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatusCode { ctx := c.Request.Context() - meta := util.GetRelayMeta(c) + meta := meta.GetByContext(c) imageRequest, err := getImageRequest(c, meta.Mode) if err != nil { logger.Errorf(ctx, "getImageRequest failed: %s", err.Error()) @@ -43,7 +41,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus // map model name var isModelMapped bool meta.OriginModelName = imageRequest.Model - imageRequest.Model, isModelMapped = util.GetMappedModelName(imageRequest.Model, meta.ModelMapping) + imageRequest.Model, isModelMapped = getMappedModelName(imageRequest.Model, meta.ModelMapping) meta.ActualModelName = imageRequest.Model // model validation @@ -57,17 +55,8 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus return openai.ErrorWrapper(err, "get_image_cost_ratio_failed", http.StatusInternalServerError) } - requestURL := c.Request.URL.String() - fullRequestURL := util.GetFullRequestURL(meta.BaseURL, requestURL, meta.ChannelType) - if meta.ChannelType == common.ChannelTypeAzure { - // https://learn.microsoft.com/en-us/azure/ai-services/openai/dall-e-quickstart?tabs=dalle3%2Ccommand-line&pivots=rest-api - apiVersion := util.GetAzureAPIVersion(c) - // https://{resource_name}.openai.azure.com/openai/deployments/dall-e-3/images/generations?api-version=2024-03-01-preview - fullRequestURL = fmt.Sprintf("%s/openai/deployments/%s/images/generations?api-version=%s", meta.BaseURL, imageRequest.Model, apiVersion) - } - var requestBody io.Reader - if isModelMapped || meta.ChannelType == common.ChannelTypeAzure { // make Azure channel request body + if isModelMapped || meta.ChannelType == channeltype.Azure { // make Azure channel request body jsonStr, err := json.Marshal(imageRequest) if err != nil { return openai.ErrorWrapper(err, "marshal_image_request_failed", http.StatusInternalServerError) @@ -77,9 +66,32 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus requestBody = c.Request.Body } - modelRatio := common.GetModelRatio(imageRequest.Model) - // groupRatio := common.GetGroupRatio(meta.Group) + adaptor := relay.GetAdaptor(meta.APIType) + if adaptor == nil { + return openai.ErrorWrapper(fmt.Errorf("invalid api type: %d", meta.APIType), "invalid_api_type", http.StatusBadRequest) + } + + switch meta.ChannelType { + case channeltype.Ali: + fallthrough + case channeltype.Baidu: + fallthrough + case channeltype.Zhipu: + finalRequest, err := adaptor.ConvertImageRequest(imageRequest) + if err != nil { + return openai.ErrorWrapper(err, "convert_image_request_failed", http.StatusInternalServerError) + } + jsonStr, err := json.Marshal(finalRequest) + if err != nil { + return openai.ErrorWrapper(err, "marshal_image_request_failed", http.StatusInternalServerError) + } + requestBody = bytes.NewBuffer(jsonStr) + } + + modelRatio := billingratio.GetModelRatio(imageRequest.Model) + // groupRatio := billingratio.GetGroupRatio(meta.Group) groupRatio := c.GetFloat64("channel_ratio") // pre-selected cheapest channel ratio + ratio := modelRatio * groupRatio userQuota, err := model.CacheGetUserQuota(ctx, meta.UserId) @@ -89,36 +101,13 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus return openai.ErrorWrapper(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden) } - req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody) - if err != nil { - return openai.ErrorWrapper(err, "new_request_failed", http.StatusInternalServerError) - } - token := c.Request.Header.Get("Authorization") - if meta.ChannelType == common.ChannelTypeAzure { // Azure authentication - token = strings.TrimPrefix(token, "Bearer ") - req.Header.Set("api-key", token) - } else { - req.Header.Set("Authorization", token) - } - - req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) - req.Header.Set("Accept", c.Request.Header.Get("Accept")) - - resp, err := util.HTTPClient.Do(req) + // do request + resp, err := adaptor.DoRequest(c, meta, requestBody) if err != nil { + logger.Errorf(ctx, "DoRequest failed: %s", err.Error()) return openai.ErrorWrapper(err, "do_request_failed", http.StatusInternalServerError) } - err = req.Body.Close() - if err != nil { - return openai.ErrorWrapper(err, "close_request_body_failed", http.StatusInternalServerError) - } - err = c.Request.Body.Close() - if err != nil { - return openai.ErrorWrapper(err, "close_request_body_failed", http.StatusInternalServerError) - } - var imageResponse openai.ImageResponse - defer func(ctx context.Context) { if resp.StatusCode != http.StatusOK { return @@ -141,34 +130,12 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus } }(c.Request.Context()) - 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) - } - err = json.Unmarshal(responseBody, &imageResponse) - if err != nil { - return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError) + // do response + _, respErr := adaptor.DoResponse(c, resp, meta) + if respErr != nil { + logger.Errorf(ctx, "respErr is not nil: %+v", respErr) + return respErr } - resp.Body = io.NopCloser(bytes.NewBuffer(responseBody)) - - for k, v := range resp.Header { - c.Writer.Header().Set(k, v[0]) - } - c.Writer.WriteHeader(resp.StatusCode) - - _, err = io.Copy(c.Writer, resp.Body) - if err != nil { - return openai.ErrorWrapper(err, "copy_response_body_failed", http.StatusInternalServerError) - } - err = resp.Body.Close() - if err != nil { - return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError) - } return nil } diff --git a/relay/controller/text.go b/relay/controller/text.go index 282e8f25..beda2822 100644 --- a/relay/controller/text.go +++ b/relay/controller/text.go @@ -10,18 +10,20 @@ import ( "github.com/Laisky/errors/v2" "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/relay/channel/openai" - "github.com/songquanpeng/one-api/relay/constant" - "github.com/songquanpeng/one-api/relay/helper" + "github.com/songquanpeng/one-api/relay" + "github.com/songquanpeng/one-api/relay/adaptor/openai" + "github.com/songquanpeng/one-api/relay/apitype" + "github.com/songquanpeng/one-api/relay/billing" + billingratio "github.com/songquanpeng/one-api/relay/billing/ratio" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/model" - "github.com/songquanpeng/one-api/relay/util" ) func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { ctx := c.Request.Context() - meta := util.GetRelayMeta(c) + meta := meta.GetByContext(c) // get & validate textRequest textRequest, err := getAndValidateTextRequest(c, meta.Mode) if err != nil { @@ -33,12 +35,13 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { // map model name var isModelMapped bool meta.OriginModelName = textRequest.Model - textRequest.Model, isModelMapped = util.GetMappedModelName(textRequest.Model, meta.ModelMapping) + textRequest.Model, isModelMapped = getMappedModelName(textRequest.Model, meta.ModelMapping) meta.ActualModelName = textRequest.Model // get model ratio & group ratio - modelRatio := common.GetModelRatio(textRequest.Model) - // groupRatio := common.GetGroupRatio(meta.Group) + modelRatio := billingratio.GetModelRatio(textRequest.Model) + // groupRatio := billingratio.GetGroupRatio(meta.Group) groupRatio := meta.ChannelRatio + ratio := modelRatio * groupRatio // pre-consume quota promptTokens := getPromptTokens(textRequest, meta.Mode) @@ -49,16 +52,16 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { return bizErr } - adaptor := helper.GetAdaptor(meta.APIType) + adaptor := relay.GetAdaptor(meta.APIType) if adaptor == nil { return openai.ErrorWrapper(errors.Errorf("invalid api type: %d", meta.APIType), "invalid_api_type", http.StatusBadRequest) } // get request body var requestBody io.Reader - if meta.APIType == constant.APITypeOpenAI { + if meta.APIType == apitype.OpenAI { // no need to convert request for openai - shouldResetRequestBody := isModelMapped || meta.ChannelType == common.ChannelTypeBaichuan // frequency_penalty 0 is not acceptable for baichuan + shouldResetRequestBody := isModelMapped || meta.ChannelType == channeltype.Baichuan // frequency_penalty 0 is not acceptable for baichuan if shouldResetRequestBody { jsonStr, err := json.Marshal(textRequest) if err != nil { @@ -93,10 +96,10 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { } errorHappened := (resp.StatusCode != http.StatusOK) || (meta.IsStream && resp.Header.Get("Content-Type") == "application/json") if errorHappened { - util.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId) + billing.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId) logger.Error(ctx, fmt.Sprintf("relay text [%d] <- %q %q", resp.StatusCode, resp.Request.URL.String(), string(requestBodyBytes))) - return util.RelayErrorHandler(resp) + return RelayErrorHandler(resp) } meta.IsStream = meta.IsStream || strings.HasPrefix(resp.Header.Get("Content-Type"), "text/event-stream") @@ -104,7 +107,7 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode { usage, respErr := adaptor.DoResponse(c, resp, meta) if respErr != nil { logger.Errorf(ctx, "respErr is not nil: %+v", respErr) - util.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId) + billing.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId) return respErr } // post-consume quota diff --git a/relay/util/validation.go b/relay/controller/validator/validation.go similarity index 76% rename from relay/util/validation.go rename to relay/controller/validator/validation.go index b9d25c2a..3aab6ac8 100644 --- a/relay/util/validation.go +++ b/relay/controller/validator/validation.go @@ -1,10 +1,11 @@ -package util +package validator import ( - "github.com/Laisky/errors/v2" - "github.com/songquanpeng/one-api/relay/constant" - "github.com/songquanpeng/one-api/relay/model" "math" + + "github.com/Laisky/errors/v2" + "github.com/songquanpeng/one-api/relay/model" + "github.com/songquanpeng/one-api/relay/relaymode" ) func ValidateTextRequest(textRequest *model.GeneralOpenAIRequest, relayMode int) error { @@ -15,20 +16,20 @@ func ValidateTextRequest(textRequest *model.GeneralOpenAIRequest, relayMode int) return errors.New("model is required") } switch relayMode { - case constant.RelayModeCompletions: + case relaymode.Completions: if textRequest.Prompt == "" { return errors.New("field prompt is required") } - case constant.RelayModeChatCompletions: + case relaymode.ChatCompletions: if textRequest.Messages == nil || len(textRequest.Messages) == 0 { return errors.New("field messages is required") } - case constant.RelayModeEmbeddings: - case constant.RelayModeModerations: + case relaymode.Embeddings: + case relaymode.Moderations: if textRequest.Input == "" { return errors.New("field input is required") } - case constant.RelayModeEdits: + case relaymode.Edits: if textRequest.Instruction == "" { return errors.New("field instruction is required") } diff --git a/relay/helper/main.go b/relay/helper/main.go deleted file mode 100644 index 18bbe51a..00000000 --- a/relay/helper/main.go +++ /dev/null @@ -1,40 +0,0 @@ -package helper - -import ( - "github.com/songquanpeng/one-api/relay/channel" - "github.com/songquanpeng/one-api/relay/channel/aiproxy" - "github.com/songquanpeng/one-api/relay/channel/anthropic" - "github.com/songquanpeng/one-api/relay/channel/gemini" - "github.com/songquanpeng/one-api/relay/channel/ollama" - "github.com/songquanpeng/one-api/relay/channel/openai" - "github.com/songquanpeng/one-api/relay/channel/palm" - "github.com/songquanpeng/one-api/relay/constant" -) - -func GetAdaptor(apiType int) channel.Adaptor { - switch apiType { - case constant.APITypeAIProxyLibrary: - return &aiproxy.Adaptor{} - // case constant.APITypeAli: - // return &ali.Adaptor{} - case constant.APITypeAnthropic: - return &anthropic.Adaptor{} - // case constant.APITypeBaidu: - // return &baidu.Adaptor{} - case constant.APITypeGemini: - return &gemini.Adaptor{} - case constant.APITypeOpenAI: - return &openai.Adaptor{} - case constant.APITypePaLM: - return &palm.Adaptor{} - // case constant.APITypeTencent: - // return &tencent.Adaptor{} - // case constant.APITypeXunfei: - // return &xunfei.Adaptor{} - // case constant.APITypeZhipu: - // return &zhipu.Adaptor{} - case constant.APITypeOllama: - return &ollama.Adaptor{} - } - return nil -} diff --git a/relay/util/relay_meta.go b/relay/meta/relay_meta.go similarity index 64% rename from relay/util/relay_meta.go rename to relay/meta/relay_meta.go index 17135816..a17aa0f0 100644 --- a/relay/util/relay_meta.go +++ b/relay/meta/relay_meta.go @@ -1,13 +1,15 @@ -package util +package meta import ( "github.com/gin-gonic/gin" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/relay/constant" + "github.com/songquanpeng/one-api/common/config" + "github.com/songquanpeng/one-api/relay/adaptor/azure" + "github.com/songquanpeng/one-api/relay/channeltype" + "github.com/songquanpeng/one-api/relay/relaymode" "strings" ) -type RelayMeta struct { +type Meta struct { Mode int ChannelType int ChannelId int @@ -29,9 +31,9 @@ type RelayMeta struct { ChannelRatio float64 } -func GetRelayMeta(c *gin.Context) *RelayMeta { - meta := RelayMeta{ - Mode: constant.Path2RelayMode(c.Request.URL.Path), +func GetByContext(c *gin.Context) *Meta { + meta := Meta{ + Mode: relaymode.GetByPath(c.Request.URL.Path), ChannelType: c.GetInt("channel"), ChannelId: c.GetInt("channel_id"), TokenId: c.GetInt("token_id"), @@ -40,18 +42,18 @@ func GetRelayMeta(c *gin.Context) *RelayMeta { Group: c.GetString("group"), ModelMapping: c.GetStringMapString("model_mapping"), BaseURL: c.GetString("base_url"), - APIVersion: c.GetString(common.ConfigKeyAPIVersion), + APIVersion: c.GetString(config.KeyAPIVersion), APIKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "), Config: nil, RequestURLPath: c.Request.URL.String(), ChannelRatio: c.GetFloat64("channel_ratio"), } - if meta.ChannelType == common.ChannelTypeAzure { - meta.APIVersion = GetAzureAPIVersion(c) + if meta.ChannelType == channeltype.Azure { + meta.APIVersion = azure.GetAPIVersion(c) } if meta.BaseURL == "" { - meta.BaseURL = common.ChannelBaseURLs[meta.ChannelType] + meta.BaseURL = channeltype.ChannelBaseURLs[meta.ChannelType] } - meta.APIType = constant.ChannelType2APIType(meta.ChannelType) + meta.APIType = channeltype.ToAPIType(meta.ChannelType) return &meta } diff --git a/relay/model/image.go b/relay/model/image.go new file mode 100644 index 00000000..bab84256 --- /dev/null +++ b/relay/model/image.go @@ -0,0 +1,12 @@ +package model + +type ImageRequest struct { + Model string `json:"model"` + Prompt string `json:"prompt" binding:"required"` + N int `json:"n,omitempty"` + Size string `json:"size,omitempty"` + Quality string `json:"quality,omitempty"` + ResponseFormat string `json:"response_format,omitempty"` + Style string `json:"style,omitempty"` + User string `json:"user,omitempty"` +} diff --git a/relay/relaymode/define.go b/relay/relaymode/define.go new file mode 100644 index 00000000..96d09438 --- /dev/null +++ b/relay/relaymode/define.go @@ -0,0 +1,14 @@ +package relaymode + +const ( + Unknown = iota + ChatCompletions + Completions + Embeddings + Moderations + ImagesGenerations + Edits + AudioSpeech + AudioTranscription + AudioTranslation +) diff --git a/relay/relaymode/helper.go b/relay/relaymode/helper.go new file mode 100644 index 00000000..926dd42e --- /dev/null +++ b/relay/relaymode/helper.go @@ -0,0 +1,29 @@ +package relaymode + +import "strings" + +func GetByPath(path string) int { + relayMode := Unknown + if strings.HasPrefix(path, "/v1/chat/completions") { + relayMode = ChatCompletions + } else if strings.HasPrefix(path, "/v1/completions") { + relayMode = Completions + } else if strings.HasPrefix(path, "/v1/embeddings") { + relayMode = Embeddings + } else if strings.HasSuffix(path, "embeddings") { + relayMode = Embeddings + } else if strings.HasPrefix(path, "/v1/moderations") { + relayMode = Moderations + } else if strings.HasPrefix(path, "/v1/images/generations") { + relayMode = ImagesGenerations + } else if strings.HasPrefix(path, "/v1/edits") { + relayMode = Edits + } else if strings.HasPrefix(path, "/v1/audio/speech") { + relayMode = AudioSpeech + } else if strings.HasPrefix(path, "/v1/audio/transcriptions") { + relayMode = AudioTranscription + } else if strings.HasPrefix(path, "/v1/audio/translations") { + relayMode = AudioTranslation + } + return relayMode +} diff --git a/relay/util/billing.go b/relay/util/billing.go deleted file mode 100644 index 495d011e..00000000 --- a/relay/util/billing.go +++ /dev/null @@ -1,19 +0,0 @@ -package util - -import ( - "context" - "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/model" -) - -func ReturnPreConsumedQuota(ctx context.Context, preConsumedQuota int64, tokenId int) { - if preConsumedQuota != 0 { - go func(ctx context.Context) { - // return pre-consumed quota - err := model.PostConsumeTokenQuota(tokenId, -preConsumedQuota) - if err != nil { - logger.Error(ctx, "error return pre-consumed quota: "+err.Error()) - } - }(ctx) - } -} diff --git a/relay/util/common.go b/relay/util/common.go deleted file mode 100644 index 518d0b00..00000000 --- a/relay/util/common.go +++ /dev/null @@ -1,188 +0,0 @@ -package util - -import ( - "context" - "encoding/json" - "fmt" - "github.com/songquanpeng/one-api/common" - "github.com/songquanpeng/one-api/common/config" - "github.com/songquanpeng/one-api/common/logger" - "github.com/songquanpeng/one-api/model" - relaymodel "github.com/songquanpeng/one-api/relay/model" - "io" - "net/http" - "strconv" - "strings" - - "github.com/gin-gonic/gin" -) - -func ShouldDisableChannel(err *relaymodel.Error, statusCode int) bool { - if !config.AutomaticDisableChannelEnabled { - return false - } - if err == nil { - return false - } - if statusCode == http.StatusUnauthorized { - return true - } - switch err.Type { - case "insufficient_quota": - return true - // https://docs.anthropic.com/claude/reference/errors - case "authentication_error": - return true - case "permission_error": - return true - case "forbidden": - return true - } - if err.Code == "invalid_api_key" || err.Code == "account_deactivated" { - return true - } - if strings.HasPrefix(err.Message, "Your credit balance is too low") { // anthropic - return true - } else if strings.HasPrefix(err.Message, "This organization has been disabled.") { - return true - } - return false -} - -func ShouldEnableChannel(err error, openAIErr *relaymodel.Error) bool { - if !config.AutomaticEnableChannelEnabled { - return false - } - if err != nil { - return false - } - if openAIErr != nil { - return false - } - return true -} - -type GeneralErrorResponse struct { - Error relaymodel.Error `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) (ErrorWithStatusCode *relaymodel.ErrorWithStatusCode) { - ErrorWithStatusCode = &relaymodel.ErrorWithStatusCode{ - StatusCode: resp.StatusCode, - Error: relaymodel.Error{ - Message: "", - Type: "upstream_error", - Code: "bad_response_status_code", - Param: strconv.Itoa(resp.StatusCode), - }, - } - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return - } - if config.DebugEnabled { - logger.SysLog(fmt.Sprintf("error happened, status code: %d, response: \n%s", resp.StatusCode, string(responseBody))) - } - err = resp.Body.Close() - if err != nil { - return - } - var errResponse GeneralErrorResponse - err = json.Unmarshal(responseBody, &errResponse) - if err != nil { - return - } - if errResponse.Error.Message != "" { - // OpenAI format error, so we override the default one - ErrorWithStatusCode.Error = errResponse.Error - } else { - ErrorWithStatusCode.Error.Message = errResponse.ToMessage() - } - if ErrorWithStatusCode.Error.Message == "" { - ErrorWithStatusCode.Error.Message = fmt.Sprintf("bad response status code %d", resp.StatusCode) - } - return -} - -func GetFullRequestURL(baseURL string, requestURL string, channelType int) string { - fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL) - - if strings.HasPrefix(baseURL, "https://gateway.ai.cloudflare.com") { - switch channelType { - case common.ChannelTypeOpenAI: - fullRequestURL = fmt.Sprintf("%s%s", baseURL, strings.TrimPrefix(requestURL, "/v1")) - case common.ChannelTypeAzure: - fullRequestURL = fmt.Sprintf("%s%s", baseURL, strings.TrimPrefix(requestURL, "/openai/deployments")) - } - } - return fullRequestURL -} - -func PostConsumeQuota(ctx context.Context, tokenId int, quotaDelta int64, totalQuota int64, userId int, channelId int, modelRatio float64, groupRatio float64, modelName string, tokenName string) { - // quotaDelta is remaining quota to be consumed - err := model.PostConsumeTokenQuota(tokenId, quotaDelta) - if err != nil { - logger.SysError("error consuming token remain quota: " + err.Error()) - } - err = model.CacheUpdateUserQuota(ctx, userId) - if err != nil { - logger.SysError("error update user quota cache: " + err.Error()) - } - // totalQuota is total quota consumed - if totalQuota >= 0 { - logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) - model.RecordConsumeLog(ctx, userId, channelId, int(totalQuota), 0, modelName, tokenName, totalQuota, logContent) - model.UpdateUserUsedQuotaAndRequestCount(userId, totalQuota) - model.UpdateChannelUsedQuota(channelId, totalQuota) - } - - if totalQuota < 0 { - logger.Error(ctx, fmt.Sprintf("totalQuota consumed is %d, something is wrong", totalQuota)) - } -} - -func GetAzureAPIVersion(c *gin.Context) string { - query := c.Request.URL.Query() - apiVersion := query.Get("api-version") - if apiVersion == "" { - apiVersion = c.GetString(common.ConfigKeyAPIVersion) - } - return apiVersion -} diff --git a/relay/util/model_mapping.go b/relay/util/model_mapping.go deleted file mode 100644 index 39e062a1..00000000 --- a/relay/util/model_mapping.go +++ /dev/null @@ -1,12 +0,0 @@ -package util - -func GetMappedModelName(modelName string, mapping map[string]string) (string, bool) { - if mapping == nil { - return modelName, false - } - mappedModelName := mapping[modelName] - if mappedModelName != "" { - return mappedModelName, true - } - return modelName, false -} diff --git a/router/api-router.go b/router/api.go similarity index 92% rename from router/api-router.go rename to router/api.go index 7af4511a..b9e5de38 100644 --- a/router/api-router.go +++ b/router/api.go @@ -2,6 +2,7 @@ package router import ( "github.com/songquanpeng/one-api/controller" + "github.com/songquanpeng/one-api/controller/auth" "github.com/songquanpeng/one-api/middleware" "github.com/gin-contrib/gzip" @@ -22,11 +23,13 @@ func SetApiRouter(router *gin.Engine) { apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.GET("/user/get-by-token", middleware.TokenAuth(), controller.GetSelfByToken) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) - apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth) - apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode) - apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth) - apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind) + apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), auth.GitHubOAuth) + apiRouter.GET("/oauth/lark", middleware.CriticalRateLimit(), auth.LarkOAuth) + apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), auth.GenerateOAuthCode) + apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), auth.WeChatAuth) + apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), auth.WeChatBind) apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.EmailBind) + apiRouter.POST("/topup", middleware.AdminAuth(), controller.AdminTopUp) userRoute := apiRouter.Group("/user") { @@ -44,6 +47,7 @@ func SetApiRouter(router *gin.Engine) { selfRoute.GET("/token", controller.GenerateAccessToken) selfRoute.GET("/aff", controller.GetAffCode) selfRoute.POST("/topup", controller.TopUp) + selfRoute.GET("/available_models", controller.GetUserAvailableModels) } adminRoute := userRoute.Group("/") @@ -69,7 +73,7 @@ func SetApiRouter(router *gin.Engine) { { channelRoute.GET("/", controller.GetAllChannels) channelRoute.GET("/search", controller.SearchChannels) - channelRoute.GET("/models", controller.ListModels) + channelRoute.GET("/models", controller.ListAllModels) channelRoute.GET("/:id", controller.GetChannel) channelRoute.GET("/test", controller.TestChannels) channelRoute.GET("/test/:id", controller.TestChannel) diff --git a/router/relay-router.go b/router/relay.go similarity index 100% rename from router/relay-router.go rename to router/relay.go diff --git a/router/web-router.go b/router/web.go similarity index 100% rename from router/web-router.go rename to router/web.go diff --git a/web/README.md b/web/README.md index 29f4713e..829271e2 100644 --- a/web/README.md +++ b/web/README.md @@ -2,6 +2,9 @@ > 每个文件夹代表一个主题,欢迎提交你的主题 +> [!WARNING] +> 不是每一个主题都及时同步了所有功能,由于精力有限,优先更新默认主题,其他主题欢迎 & 期待 PR + ## 提交新的主题 > 欢迎在页面底部保留你和 One API 的版权信息以及指向链接 diff --git a/web/berry/src/constants/ChannelConstants.js b/web/berry/src/constants/ChannelConstants.js index ec049f7d..b74c58c7 100644 --- a/web/berry/src/constants/ChannelConstants.js +++ b/web/berry/src/constants/ChannelConstants.js @@ -107,6 +107,12 @@ export const CHANNEL_OPTIONS = { value: 31, color: 'primary' }, + 32: { + key: 32, + text: '阶跃星辰', + value: 32, + color: 'primary' + }, 8: { key: 8, text: '自定义渠道', diff --git a/web/berry/src/constants/SnackbarConstants.js b/web/berry/src/constants/SnackbarConstants.js index a05c6652..19523da1 100644 --- a/web/berry/src/constants/SnackbarConstants.js +++ b/web/berry/src/constants/SnackbarConstants.js @@ -18,7 +18,7 @@ export const snackbarConstants = { }, NOTICE: { variant: 'info', - autoHideDuration: 20000 + autoHideDuration: 7000 } }, Mobile: { diff --git a/web/berry/src/utils/common.js b/web/berry/src/utils/common.js index aa4b8c37..8925e542 100644 --- a/web/berry/src/utils/common.js +++ b/web/berry/src/utils/common.js @@ -51,9 +51,9 @@ export function showError(error) { export function showNotice(message, isHTML = false) { if (isHTML) { - enqueueSnackbar(, getSnackbarOptions('INFO')); + enqueueSnackbar(, getSnackbarOptions('NOTICE')); } else { - enqueueSnackbar(message, getSnackbarOptions('INFO')); + enqueueSnackbar(message, getSnackbarOptions('NOTICE')); } } diff --git a/web/berry/src/views/Channel/component/EditModal.js b/web/berry/src/views/Channel/component/EditModal.js index 07111c97..cbf411b9 100644 --- a/web/berry/src/views/Channel/component/EditModal.js +++ b/web/berry/src/views/Channel/component/EditModal.js @@ -340,7 +340,9 @@ const EditModal = ({ open, channelId, onCancel, onOk }) => { }, }} > - {Object.values(CHANNEL_OPTIONS).map((option) => { + {Object.values(CHANNEL_OPTIONS).sort((a, b) => { + return a.text.localeCompare(b.text) + }).map((option) => { return ( {option.text} diff --git a/web/berry/src/views/Token/component/EditModal.js b/web/berry/src/views/Token/component/EditModal.js index cf65a96a..a5e08be1 100644 --- a/web/berry/src/views/Token/component/EditModal.js +++ b/web/berry/src/views/Token/component/EditModal.js @@ -103,7 +103,7 @@ const EditModal = ({ open, tokenId, onCancel, onOk }) => { fontSize: "1.125rem", }} > - {tokenId ? "编辑Token" : "新建Token"} + {tokenId ? "编辑令牌" : "新建令牌"} diff --git a/web/default/src/App.js b/web/default/src/App.js index 13c884dc..4ece4eeb 100644 --- a/web/default/src/App.js +++ b/web/default/src/App.js @@ -24,6 +24,7 @@ import EditRedemption from './pages/Redemption/EditRedemption'; import TopUp from './pages/TopUp'; import Log from './pages/Log'; import Chat from './pages/Chat'; +import LarkOAuth from './components/LarkOAuth'; const Home = lazy(() => import('./pages/Home')); const About = lazy(() => import('./pages/About')); @@ -239,6 +240,14 @@ function App() { } /> + }> + + + } + /> { + const [searchParams, setSearchParams] = useSearchParams(); + + const [userState, userDispatch] = useContext(UserContext); + const [prompt, setPrompt] = useState('处理中...'); + const [processing, setProcessing] = useState(true); + + let navigate = useNavigate(); + + const sendCode = async (code, state, count) => { + const res = await API.get(`/api/oauth/lark?code=${code}&state=${state}`); + const { success, message, data } = res.data; + if (success) { + if (message === 'bind') { + showSuccess('绑定成功!'); + navigate('/setting'); + } else { + userDispatch({ type: 'login', payload: data }); + localStorage.setItem('user', JSON.stringify(data)); + showSuccess('登录成功!'); + navigate('/'); + } + } else { + showError(message); + if (count === 0) { + setPrompt(`操作失败,重定向至登录界面中...`); + navigate('/setting'); // in case this is failed to bind lark + return; + } + count++; + setPrompt(`出现错误,第 ${count} 次重试中...`); + await new Promise((resolve) => setTimeout(resolve, count * 2000)); + await sendCode(code, state, count); + } + }; + + useEffect(() => { + let code = searchParams.get('code'); + let state = searchParams.get('state'); + sendCode(code, state, 0).then(); + }, []); + + return ( + + + {prompt} + + + ); +}; + +export default LarkOAuth; diff --git a/web/default/src/components/LoginForm.js b/web/default/src/components/LoginForm.js index b48f64c4..01408f56 100644 --- a/web/default/src/components/LoginForm.js +++ b/web/default/src/components/LoginForm.js @@ -3,7 +3,8 @@ import { Button, Divider, Form, Grid, Header, Image, Message, Modal, Segment } f import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { UserContext } from '../context/User'; import { API, getLogo, showError, showSuccess, showWarning } from '../helpers'; -import { onGitHubOAuthClicked } from './utils'; +import { onGitHubOAuthClicked, onLarkOAuthClicked } from './utils'; +import larkIcon from '../images/lark.svg'; const LoginForm = () => { const [inputs, setInputs] = useState({ @@ -124,7 +125,7 @@ const LoginForm = () => { 点击注册 - {status.github_oauth || status.wechat_login ? ( + {status.github_oauth || status.wechat_login || status.lark_client_id ? ( <> Or {status.github_oauth ? ( @@ -137,6 +138,18 @@ const LoginForm = () => { ) : ( <> )} + {status.lark_client_id ? ( + + ) : ( + <> + )} {status.wechat_login ? ( ) } + { + status.lark_client_id && ( + + ) + } {/* { {/* updateChannelBalance(channel.id, channel.name, idx);*/} {/* }}*/} {/*>*/} - {/* 更新余额*/} + {/* Update balance*/} {/**/} - 删除 + Delete } on='click' @@ -559,7 +559,7 @@ const ChannelsTable = () => { manageChannel(channel.id, 'delete', idx); }} > - 删除渠道 {channel.name} + Delete channel {channel.name} @@ -592,20 +592,20 @@ const ChannelsTable = () => { {/**/} + {/* loading={loading || updatingBalance}>Update the balance of enabled channels*/} - 删除禁用渠道 + DeleteDisableChannel } on='click' @@ -613,7 +613,7 @@ const ChannelsTable = () => { hoverable > { (channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0) } /> - - + + diff --git a/web/default/src/components/GitHubOAuth.js b/web/default/src/components/GitHubOAuth.js index c43ed2a1..c0f3ac65 100644 --- a/web/default/src/components/GitHubOAuth.js +++ b/web/default/src/components/GitHubOAuth.js @@ -8,7 +8,7 @@ const GitHubOAuth = () => { const [searchParams, setSearchParams] = useSearchParams(); const [userState, userDispatch] = useContext(UserContext); - const [prompt, setPrompt] = useState('处理中...'); + const [prompt, setPrompt] = useState('Processing...'); const [processing, setProcessing] = useState(true); let navigate = useNavigate(); @@ -18,23 +18,23 @@ const GitHubOAuth = () => { const { success, message, data } = res.data; if (success) { if (message === 'bind') { - showSuccess('绑定成功!'); + showSuccess('Binding successful!'); navigate('/setting'); } else { userDispatch({ type: 'login', payload: data }); localStorage.setItem('user', JSON.stringify(data)); - showSuccess('登录成功!'); + showSuccess('Login successful!'); navigate('/'); } } else { showError(message); if (count === 0) { - setPrompt(`操作失败,重定向至登录界面中...`); + setPrompt(`Operation failed, redirecting to login screen...`); navigate('/setting'); // in case this is failed to bind GitHub return; } count++; - setPrompt(`出现错误,第 ${count} 次重试中...`); + setPrompt(`An error occurred, retrying ${count}...`); await new Promise((resolve) => setTimeout(resolve, count * 2000)); await sendCode(code, state, count); } diff --git a/web/default/src/components/Header.js b/web/default/src/components/Header.js index 21ebcab6..091a4b5e 100644 --- a/web/default/src/components/Header.js +++ b/web/default/src/components/Header.js @@ -9,50 +9,50 @@ import '../index.css'; // Header Buttons let headerButtons = [ { - name: '首页', + name: 'Home', to: '/', icon: 'home' }, { - name: '渠道', + name: 'Channel', to: '/channel', icon: 'sitemap', admin: true }, { - name: '令牌', + name: 'API Keys', to: '/token', icon: 'key' }, { - name: '兑换', + name: 'Redeem', to: '/redemption', icon: 'dollar sign', admin: true }, { - name: '充值', + name: 'Recharge', to: '/topup', icon: 'cart' }, { - name: '用户', + name: 'Users', to: '/user', icon: 'user', admin: true }, { - name: '日志', + name: 'Logs', to: '/log', icon: 'book' }, { - name: '设置', + name: 'Settings', to: '/setting', icon: 'setting' }, { - name: '关于', + name: 'About', to: '/about', icon: 'info circle' } @@ -60,7 +60,7 @@ let headerButtons = [ if (localStorage.getItem('chat_link')) { headerButtons.splice(1, 0, { - name: '聊天', + name: 'Chat', to: '/chat', icon: 'comments' }); @@ -77,7 +77,7 @@ const Header = () => { async function logout() { setShowSidebar(false); await API.get('/api/user/logout'); - showSuccess('注销成功!'); + showSuccess('Logout successful!'); userDispatch({ type: 'logout' }); localStorage.removeItem('user'); navigate('/login'); @@ -152,7 +152,7 @@ const Header = () => { {renderButtons(true)} {userState.user ? ( - + ) : ( <> )} @@ -202,12 +202,12 @@ const Header = () => { className='link item' > - 注销 + Log out
) : ( { const [searchParams, setSearchParams] = useSearchParams(); const [userState, userDispatch] = useContext(UserContext); - const [prompt, setPrompt] = useState('处理中...'); + const [prompt, setPrompt] = useState('Processing...'); const [processing, setProcessing] = useState(true); let navigate = useNavigate(); @@ -18,23 +18,23 @@ const LarkOAuth = () => { const { success, message, data } = res.data; if (success) { if (message === 'bind') { - showSuccess('绑定成功!'); + showSuccess('Binding successful!'); navigate('/setting'); } else { userDispatch({ type: 'login', payload: data }); localStorage.setItem('user', JSON.stringify(data)); - showSuccess('登录成功!'); + showSuccess('Login successful!'); navigate('/'); } } else { showError(message); if (count === 0) { - setPrompt(`操作失败,重定向至登录界面中...`); + setPrompt(`Operation failed, redirecting to login screen...`); navigate('/setting'); // in case this is failed to bind lark return; } count++; - setPrompt(`出现错误,第 ${count} 次重试中...`); + setPrompt(`An error occurred, retrying ${count}...`); await new Promise((resolve) => setTimeout(resolve, count * 2000)); await sendCode(code, state, count); } diff --git a/web/default/src/components/Loading.js b/web/default/src/components/Loading.js index 1210a56f..b2cb728d 100644 --- a/web/default/src/components/Loading.js +++ b/web/default/src/components/Loading.js @@ -5,7 +5,7 @@ const Loading = ({ prompt: name = 'page' }) => { return ( - 加载{name}中... + Loading {name}... ); diff --git a/web/default/src/components/LoginForm.js b/web/default/src/components/LoginForm.js index 71566ef8..68a19581 100644 --- a/web/default/src/components/LoginForm.js +++ b/web/default/src/components/LoginForm.js @@ -22,7 +22,7 @@ const LoginForm = () => { useEffect(() => { if (searchParams.get('expired')) { - showError('未登录或登录已过期,请重新登录!'); + showError('Not logged in or login has expired, please log in again!'); } let status = localStorage.getItem('status'); if (status) { @@ -46,7 +46,7 @@ const LoginForm = () => { userDispatch({ type: 'login', payload: data }); localStorage.setItem('user', JSON.stringify(data)); navigate('/'); - showSuccess('登录成功!'); + showSuccess('Login successful!'); setShowWeChatLoginModal(false); } else { showError(message); @@ -71,11 +71,11 @@ const LoginForm = () => { localStorage.setItem('user', JSON.stringify(data)); if (username === 'root' && password === '123456') { navigate('/user/edit'); - showSuccess('登录成功!'); - showWarning('请立刻修改默认密码!'); + showSuccess('Login successful!'); + showWarning('Please change the default password immediately!'); } else { navigate('/token'); - showSuccess('登录成功!'); + showSuccess('Login successful!'); } } else { showError(message); @@ -87,7 +87,7 @@ const LoginForm = () => {
- 用户登录 + User login
@@ -95,7 +95,7 @@ const LoginForm = () => { fluid icon='user' iconPosition='left' - placeholder='用户名 / 邮箱地址' + placeholder='Username / Email address' name='username' value={username} onChange={handleChange} @@ -104,25 +104,25 @@ const LoginForm = () => { fluid icon='lock' iconPosition='left' - placeholder='密码' + placeholder='Password' name='password' type='password' value={password} onChange={handleChange} />
- 忘记密码? + Forget password? - 点击重置 + Click to reset - ; 没有账户? + ; No account? - 点击注册 + Click to register {status.github_oauth || status.wechat_login || status.lark_client_id ? ( @@ -186,13 +186,13 @@ const LoginForm = () => {

- 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效) + Scan the QR code with WeChat, follow the official account and enter 'verification code' to get the verification code (valid within three minutes)

{ size='large' onClick={onSubmitWeChatVerificationCode} > - 登录 + Log in diff --git a/web/default/src/components/LogsTable.js b/web/default/src/components/LogsTable.js index 308aff98..813e62fe 100644 --- a/web/default/src/components/LogsTable.js +++ b/web/default/src/components/LogsTable.js @@ -14,30 +14,30 @@ function renderTimestamp(timestamp) { } const MODE_OPTIONS = [ - { key: 'all', text: '全部用户', value: 'all' }, - { key: 'self', text: '当前用户', value: 'self' } + { key: 'all', text: 'All users', value: 'all' }, + { key: 'self', text: 'Current user', value: 'self' } ]; const LOG_OPTIONS = [ - { key: '0', text: '全部', value: 0 }, - { key: '1', text: '充值', value: 1 }, - { key: '2', text: '消费', value: 2 }, - { key: '3', text: '管理', value: 3 }, - { key: '4', text: '系统', value: 4 } + { key: '0', text: 'All', value: 0 }, + { key: '1', text: 'Recharge', value: 1 }, + { key: '2', text: 'Consumption', value: 2 }, + { key: '3', text: 'Management', value: 3 }, + { key: '4', text: 'System', value: 4 } ]; function renderType(type) { switch (type) { case 1: - return ; + return ; case 2: - return ; + return ; case 3: - return ; + return ; case 4: - return ; + return ; default: - return ; + return ; } } @@ -199,34 +199,34 @@ const LogsTable = () => { <>
- 使用明细(总消耗额度: + Usages(Total consumption limit: {showStat && renderQuota(stat.quota)} - {!showStat && 点击查看} + {!showStat && click to view} )
- - + - - - 查询 + Query { isAdminUser && <> - - @@ -243,7 +243,7 @@ const LogsTable = () => { }} width={3} > - 时间 + Time { isAdminUser && { }} width={1} > - 渠道 + Channel } { @@ -264,7 +264,7 @@ const LogsTable = () => { }} width={1} > - 用户 + Users } { }} width={1} > - 令牌 + API Keys { }} width={1} > - 类型 + Type { }} width={2} > - 模型 + Model { }} width={1} > - 提示 + Prompt { }} width={1} > - 补全 + Completion { }} width={isAdminUser ? 4 : 6} > - 详情 + Details @@ -370,7 +370,7 @@ const LogsTable = () => { 填入 } - placeholder='输入自定义模型名称' + placeholder='EnterCustomModel name' value={customModel} onChange={(e, { value }) => { setCustomModel(value); @@ -429,8 +429,8 @@ const EditChannel = () => { inputs.type !== 43 && (<> { { label='User ID' name='user_id' required - placeholder={'生成该密钥的用户 ID'} + placeholder={'生成该Key的Users ID'} onChange={handleConfigChange} value={config.user_id} autoComplete='' @@ -533,10 +533,10 @@ const EditChannel = () => { { inputs.type !== 33 && inputs.type !== 42 && (batch ? { /> : { label='Account ID' name='user_id' required - placeholder={'请输入 Account ID,例如:d8d7c61dbc334c32d3ced580e4bf42b4'} + placeholder={'请Enter Account ID,For example:d8d7c61dbc334c32d3ced580e4bf42b4'} onChange={handleConfigChange} value={config.user_id} autoComplete='' @@ -573,7 +573,7 @@ const EditChannel = () => { inputs.type !== 33 && !isEdit && ( setBatch(!batch)} /> @@ -583,9 +583,9 @@ const EditChannel = () => { inputs.type !== 3 && inputs.type !== 33 && inputs.type !== 8 && inputs.type !== 22 && ( { { ) } - - + +
diff --git a/web/default/src/pages/Channel/index.js b/web/default/src/pages/Channel/index.js index edf668b3..5b2f47c6 100644 --- a/web/default/src/pages/Channel/index.js +++ b/web/default/src/pages/Channel/index.js @@ -5,7 +5,7 @@ import ChannelsTable from '../../components/ChannelsTable'; const Channel = () => ( <> -
管理渠道
+
Manage Channels
diff --git a/web/default/src/pages/Home/index.js b/web/default/src/pages/Home/index.js index 63d6d77a..3dd57392 100644 --- a/web/default/src/pages/Home/index.js +++ b/web/default/src/pages/Home/index.js @@ -37,7 +37,7 @@ const Home = () => { localStorage.setItem('home_page_content', content); } else { showError(message); - setHomePageContent('加载首页内容失败...'); + setHomePageContent('Failed to load homepage content...'); } setHomePageContentLoaded(true); }; @@ -56,18 +56,18 @@ const Home = () => { { homePageContentLoaded && homePageContent === '' ? <> -
系统状况
+
System status
- 系统信息 - 系统信息总览 + System information + System information overview -

名称:{statusState?.status?.system_name}

-

版本:{statusState?.status?.version ? statusState?.status?.version : "unknown"}

+

Name:{statusState?.status?.system_name}

+

Version:{statusState?.status?.version ? statusState?.status?.version : "unknown"}

- 源码: + Source code: { https://github.com/songquanpeng/one-api

-

启动时间:{getStartTimeString()}

+

Startup time:{getStartTimeString()}

@@ -83,32 +83,32 @@ const Home = () => { - 系统配置 - 系统配置总览 + System configuration + System configuration overview

- 邮箱验证: + Email verification: {statusState?.status?.email_verification === true - ? '已启用' - : '未启用'} + ? 'Enabled' + : 'Not enabled'}

- GitHub 身份验证: + GitHub Authentication: {statusState?.status?.github_oauth === true - ? '已启用' - : '未启用'} + ? 'Enabled' + : 'Not enabled'}

- 微信身份验证: + WeChat Authentication: {statusState?.status?.wechat_login === true - ? '已启用' - : '未启用'} + ? 'Enabled' + : 'Not enabled'}

- Turnstile 用户校验: + Turnstile user verification: {statusState?.status?.turnstile_check === true - ? '已启用' - : '未启用'} + ? 'Enabled' + : 'Not enabled'}

diff --git a/web/default/src/pages/NotFound/index.js b/web/default/src/pages/NotFound/index.js index f92dbc90..013bf6ce 100644 --- a/web/default/src/pages/NotFound/index.js +++ b/web/default/src/pages/NotFound/index.js @@ -4,8 +4,8 @@ import { Message } from 'semantic-ui-react'; const NotFound = () => ( <> - 页面不存在 -

请检查你的浏览器地址是否正确

+ Page does not exist +

Please check if your browser address is correct

); diff --git a/web/default/src/pages/Redemption/EditRedemption.js b/web/default/src/pages/Redemption/EditRedemption.js index 7a33f770..31db9109 100644 --- a/web/default/src/pages/Redemption/EditRedemption.js +++ b/web/default/src/pages/Redemption/EditRedemption.js @@ -58,9 +58,9 @@ const EditRedemption = () => { const { success, message, data } = res.data; if (success) { if (isEdit) { - showSuccess('兑换码更新成功!'); + showSuccess('Redemption code updated successfully!'); } else { - showSuccess('兑换码创建成功!'); + showSuccess('Redemption code created successfully!'); setInputs(originInputs); } } else { @@ -78,13 +78,13 @@ const EditRedemption = () => { return ( <> -
{isEdit ? '更新兑换码信息' : '创建新的兑换码'}
+
{isEdit ? 'Update redemption code information' : 'Create a new redemption code'}
{ { !isEdit && <> { } - - + +
diff --git a/web/default/src/pages/Redemption/index.js b/web/default/src/pages/Redemption/index.js index c0649412..879584c2 100644 --- a/web/default/src/pages/Redemption/index.js +++ b/web/default/src/pages/Redemption/index.js @@ -5,7 +5,7 @@ import RedemptionsTable from '../../components/RedemptionsTable'; const Redemption = () => ( <> -
管理兑换码
+
Manage Redeem Codes
diff --git a/web/default/src/pages/Setting/index.js b/web/default/src/pages/Setting/index.js index 30d0ef28..e77bb472 100644 --- a/web/default/src/pages/Setting/index.js +++ b/web/default/src/pages/Setting/index.js @@ -9,7 +9,7 @@ import OperationSetting from '../../components/OperationSetting'; const Setting = () => { let panes = [ { - menuItem: '个人设置', + menuItem: 'Personal settings', render: () => ( @@ -20,7 +20,7 @@ const Setting = () => { if (isRoot()) { panes.push({ - menuItem: '运营设置', + menuItem: 'Operations settings', render: () => ( @@ -28,7 +28,7 @@ const Setting = () => { ) }); panes.push({ - menuItem: '系统设置', + menuItem: 'System settings', render: () => ( @@ -36,7 +36,7 @@ const Setting = () => { ) }); panes.push({ - menuItem: '其他设置', + menuItem: 'Other settings', render: () => ( diff --git a/web/default/src/pages/Token/EditToken.js b/web/default/src/pages/Token/EditToken.js index 684b7e92..f13a8a69 100644 --- a/web/default/src/pages/Token/EditToken.js +++ b/web/default/src/pages/Token/EditToken.js @@ -95,7 +95,7 @@ const EditToken = () => { if (localInputs.expired_time !== -1) { let time = Date.parse(localInputs.expired_time); if (isNaN(time)) { - showError('过期时间格式错误!'); + showError('Expiration time format error!'); return; } localInputs.expired_time = Math.ceil(time / 1000); @@ -110,9 +110,9 @@ const EditToken = () => { const { success, message } = res.data; if (success) { if (isEdit) { - showSuccess('令牌更新成功!'); + showSuccess('Token updated successfully!'); } else { - showSuccess('令牌创建成功,请在列表页面点击复制获取令牌!'); + showSuccess('Token created successfully, please click copy on the list page to get the token!'); setInputs(originInputs); } } else { @@ -123,13 +123,13 @@ const EditToken = () => { return ( <> -
{isEdit ? '更新令牌信息' : '创建新的令牌'}
+
{isEdit ? 'Update key information' : 'Create a new key'}
{ { { {
+ }}>Never expires + }}>Expires after one month + }}>Expires after one day + }}>Expires after one hour + }}>Expires after one minute
- 注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。 + Note that the quota of the token is only used to limit the maximum quota usage of the token itself, and the actual usage is limited by the remaining quota of the account. { - - + }}>{unlimited_quota ? 'Cancel unlimited quota' : 'Set to unlimited quota'} + +
diff --git a/web/default/src/pages/Token/index.js b/web/default/src/pages/Token/index.js index c995131e..0e3e9385 100644 --- a/web/default/src/pages/Token/index.js +++ b/web/default/src/pages/Token/index.js @@ -5,7 +5,7 @@ import TokensTable from '../../components/TokensTable'; const Token = () => ( <> -
我的令牌
+
My keys
diff --git a/web/default/src/pages/TopUp/index.js b/web/default/src/pages/TopUp/index.js index 2fcf0eae..ffaa129e 100644 --- a/web/default/src/pages/TopUp/index.js +++ b/web/default/src/pages/TopUp/index.js @@ -12,7 +12,7 @@ const TopUp = () => { const topUp = async () => { if (redemptionCode === '') { - showInfo('请输入充值码!') + showInfo('Please enter the recharge code!') return; } setIsSubmitting(true); @@ -22,7 +22,7 @@ const TopUp = () => { }); const { success, message, data } = res.data; if (success) { - showSuccess('充值成功!'); + showSuccess('Recharge successful!'); setUserQuota((quota) => { return quota + data; }); @@ -31,7 +31,7 @@ const TopUp = () => { showError(message); } } catch (err) { - showError('请求失败'); + showError('Request failed'); } finally { setIsSubmitting(false); } @@ -39,7 +39,7 @@ const TopUp = () => { const openTopUpLink = () => { if (!topUpLink) { - showError('超级管理员未设置充值链接!'); + showError('The super administrator did not set a recharge link!'); return; } let url = new URL(topUpLink); @@ -76,12 +76,12 @@ const TopUp = () => { return ( -
充值额度
+
Recharge quota
{ @@ -89,10 +89,10 @@ const TopUp = () => { }} />
@@ -100,7 +100,7 @@ const TopUp = () => { {renderQuota(userQuota)} - 剩余额度 + Remaining quota
diff --git a/web/default/src/pages/User/AddUser.js b/web/default/src/pages/User/AddUser.js index f9f4bc18..4fa7b72b 100644 --- a/web/default/src/pages/User/AddUser.js +++ b/web/default/src/pages/User/AddUser.js @@ -20,7 +20,7 @@ const AddUser = () => { const res = await API.post(`/api/user/`, inputs); const { success, message } = res.data; if (success) { - showSuccess('用户账户创建成功!'); + showSuccess('User account created successfully!'); setInputs(originInputs); } else { showError(message); @@ -30,13 +30,13 @@ const AddUser = () => { return ( <> -
创建新用户账户
+
Create new user account
{ { { />
diff --git a/web/default/src/pages/User/EditUser.js b/web/default/src/pages/User/EditUser.js index 8ae0e556..6556407c 100644 --- a/web/default/src/pages/User/EditUser.js +++ b/web/default/src/pages/User/EditUser.js @@ -76,7 +76,7 @@ const EditUser = () => { } const { success, message } = res.data; if (success) { - showSuccess('用户信息更新成功!'); + showSuccess('User information updated successfully!'); } else { showError(message); } @@ -85,13 +85,13 @@ const EditUser = () => { return ( <> -
更新用户信息
+
Update user information
{ { { userId && <> { { } - - + +
diff --git a/web/default/src/pages/User/index.js b/web/default/src/pages/User/index.js index 29f7437a..dfdf7a85 100644 --- a/web/default/src/pages/User/index.js +++ b/web/default/src/pages/User/index.js @@ -5,7 +5,7 @@ import UsersTable from '../../components/UsersTable'; const User = () => ( <> -
管理用户
+
Manage Users
From a9310deb650f9ee4201c8301b975fe733f5bece1 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Wed, 22 Jan 2025 03:27:22 +0000 Subject: [PATCH 170/170] feat: add support for new Gemini model version 'gemini-2.0-flash-thinking-exp-01-21' --- relay/adaptor/gemini/adaptor.go | 3 ++- relay/adaptor/gemini/constants.go | 2 +- relay/adaptor/vertexai/gemini/adapter.go | 3 ++- relay/billing/ratio/model.go | 19 ++++++++++--------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/relay/adaptor/gemini/adaptor.go b/relay/adaptor/gemini/adaptor.go index 33733de3..67a1b72e 100644 --- a/relay/adaptor/gemini/adaptor.go +++ b/relay/adaptor/gemini/adaptor.go @@ -27,7 +27,8 @@ func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { var defaultVersion string switch meta.ActualModelName { case "gemini-2.0-flash-exp", - "gemini-2.0-flash-thinking-exp": + "gemini-2.0-flash-thinking-exp", + "gemini-2.0-flash-thinking-exp-01-21": defaultVersion = "v1beta" default: defaultVersion = config.GeminiVersion diff --git a/relay/adaptor/gemini/constants.go b/relay/adaptor/gemini/constants.go index 9d1cbc4a..381d0c12 100644 --- a/relay/adaptor/gemini/constants.go +++ b/relay/adaptor/gemini/constants.go @@ -7,5 +7,5 @@ var ModelList = []string{ "gemini-1.5-flash", "gemini-1.5-pro", "text-embedding-004", "aqa", "gemini-2.0-flash-exp", - "gemini-2.0-flash-thinking-exp", + "gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp-01-21", } diff --git a/relay/adaptor/vertexai/gemini/adapter.go b/relay/adaptor/vertexai/gemini/adapter.go index 871a616f..36f983b4 100644 --- a/relay/adaptor/vertexai/gemini/adapter.go +++ b/relay/adaptor/vertexai/gemini/adapter.go @@ -18,7 +18,8 @@ var ModelList = []string{ "gemini-pro", "gemini-pro-vision", "gemini-1.5-pro-001", "gemini-1.5-flash-001", "gemini-1.5-pro-002", "gemini-1.5-flash-002", - "gemini-2.0-flash-exp", "gemini-2.0-flash-thinking-exp", + "gemini-2.0-flash-exp", + "gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp-01-21", } type Adaptor struct { diff --git a/relay/billing/ratio/model.go b/relay/billing/ratio/model.go index cdcbc9d2..49316dc4 100644 --- a/relay/billing/ratio/model.go +++ b/relay/billing/ratio/model.go @@ -117,15 +117,16 @@ var ModelRatio = map[string]float64{ "bge-large-en": 0.002 * RMB, "tao-8k": 0.002 * RMB, // https://ai.google.dev/pricing - "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens - "gemini-1.0-pro": 1, - "gemini-1.5-pro": 1, - "gemini-1.5-pro-001": 1, - "gemini-1.5-flash": 1, - "gemini-1.5-flash-001": 1, - "gemini-2.0-flash-exp": 1, - "gemini-2.0-flash-thinking-exp": 1, - "aqa": 1, + "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens + "gemini-1.0-pro": 1, + "gemini-1.5-pro": 1, + "gemini-1.5-pro-001": 1, + "gemini-1.5-flash": 1, + "gemini-1.5-flash-001": 1, + "gemini-2.0-flash-exp": 1, + "gemini-2.0-flash-thinking-exp": 1, + "gemini-2.0-flash-thinking-exp-01-21": 1, + "aqa": 1, // https://open.bigmodel.cn/pricing "glm-4": 0.1 * RMB, "glm-4v": 0.1 * RMB,