From bc8ceca7428f5b39196883862177a4a65d2b8237 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 29 Sep 2023 12:46:26 +0000 Subject: [PATCH 001/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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/169] 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 50e0638313409ac18c8f1ee58257f011a12d33c4 Mon Sep 17 00:00:00 2001 From: "Laisky.Cai" Date: Fri, 12 Jan 2024 05:44:19 +0000 Subject: [PATCH 038/169] 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 039/169] 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 040/169] 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 041/169] 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 042/169] 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 043/169] 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 044/169] 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 045/169] 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 046/169] 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 047/169] 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 048/169] 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 049/169] 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 050/169] 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 051/169] 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 052/169] 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 053/169] 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 054/169] 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 055/169] 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 056/169] 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 057/169] 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 058/169] 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 059/169] 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 060/169] 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 061/169] 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 062/169] 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 063/169] 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 064/169] 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 065/169] 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 066/169] 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 067/169] 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 068/169] 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 069/169] 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 070/169] 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 071/169] 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 072/169] 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 073/169] 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 074/169] 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 075/169] 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 076/169] 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 077/169] 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 078/169] 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 079/169] 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 080/169] 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 081/169] 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 082/169] 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 083/169] 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 084/169] 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 085/169] 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 && ( + + ) + }