diff --git a/common/config/key.go b/common/config/key.go
deleted file mode 100644
index d2bab7d2..00000000
--- a/common/config/key.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package config
-
-const (
- KeyPrefix = "cfg_"
-
- KeyAPIVersion = KeyPrefix + "api_version"
- KeyLibraryID = KeyPrefix + "library_id"
- KeyPlugin = KeyPrefix + "plugin"
- KeySK = KeyPrefix + "sk"
- KeyAK = KeyPrefix + "ak"
- KeyRegion = KeyPrefix + "region"
-)
diff --git a/common/constants.go b/common/constants.go
index cdcec59b..87221b61 100644
--- a/common/constants.go
+++ b/common/constants.go
@@ -4,12 +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
-
-var (
- // CtxKeyChannel is the key to store the channel in the context
- CtxKeyChannel string = "channel_docu"
- CtxKeyRequestModel string = "request_model"
- CtxKeyRawRequest string = "raw_request"
- CtxKeyConvertedRequest string = "converted_request"
- CtxKeyOriginModel string = "origin_model"
-)
diff --git a/common/ctxkey/config.go b/common/ctxkey/config.go
new file mode 100644
index 00000000..69e8a27a
--- /dev/null
+++ b/common/ctxkey/config.go
@@ -0,0 +1,13 @@
+package ctxkey
+
+const (
+ ConfigPrefix = "cfg_"
+
+ ConfigAPIVersion = ConfigPrefix + "api_version"
+ ConfigLibraryID = ConfigPrefix + "library_id"
+ ConfigPlugin = ConfigPrefix + "plugin"
+ ConfigSK = ConfigPrefix + "sk"
+ ConfigAK = ConfigPrefix + "ak"
+ ConfigRegion = ConfigPrefix + "region"
+ ConfigUserID = ConfigPrefix + "user_id"
+)
diff --git a/common/ctxkey/key.go b/common/ctxkey/key.go
index 6f1002bd..5e22d282 100644
--- a/common/ctxkey/key.go
+++ b/common/ctxkey/key.go
@@ -1,7 +1,23 @@
package ctxkey
-var (
- RequestModel = "request_model"
- ConvertedRequest = "converted_request"
- OriginalModel = "original_model"
+const (
+ Id = "id"
+ Username = "username"
+ Role = "role"
+ Status = "status"
+ ChannelModel = "channel_model"
+ ChannelRatio = "channel_ratio"
+ Channel = "channel"
+ ChannelId = "channel_id"
+ SpecificChannelId = "specific_channel_id"
+ RequestModel = "request_model"
+ ConvertedRequest = "converted_request"
+ OriginalModel = "original_model"
+ Group = "group"
+ ModelMapping = "model_mapping"
+ ChannelName = "channel_name"
+ TokenId = "token_id"
+ TokenName = "token_name"
+ BaseURL = "base_url"
+ AvailableModels = "available_models"
)
diff --git a/common/logger/logger.go b/common/logger/logger.go
index a0c4f3ac..bd30246b 100644
--- a/common/logger/logger.go
+++ b/common/logger/logger.go
@@ -3,15 +3,16 @@ package logger
import (
"context"
"fmt"
- "github.com/Laisky/one-api/common/config"
- "github.com/Laisky/one-api/common/helper"
- "github.com/gin-gonic/gin"
"io"
"log"
"os"
"path/filepath"
"sync"
"time"
+
+ "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/helper"
+ "github.com/gin-gonic/gin"
)
const (
@@ -21,28 +22,20 @@ const (
loggerError = "ERR"
)
-var setupLogLock sync.Mutex
-var setupLogWorking bool
+var setupLogOnce sync.Once
func SetupLogger() {
- if LogDir != "" {
- ok := setupLogLock.TryLock()
- if !ok {
- log.Println("setup log is already working")
- return
+ setupLogOnce.Do(func() {
+ if LogDir != "" {
+ logPath := filepath.Join(LogDir, fmt.Sprintf("oneapi-%s.log", time.Now().Format("20060102")))
+ fd, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ log.Fatal("failed to open log file")
+ }
+ gin.DefaultWriter = io.MultiWriter(os.Stdout, fd)
+ gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, fd)
}
- defer func() {
- setupLogLock.Unlock()
- setupLogWorking = false
- }()
- logPath := filepath.Join(LogDir, fmt.Sprintf("oneapi-%s.log", time.Now().Format("20060102")))
- fd, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
- if err != nil {
- log.Fatal("failed to open log file")
- }
- gin.DefaultWriter = io.MultiWriter(os.Stdout, fd)
- gin.DefaultErrorWriter = io.MultiWriter(os.Stderr, fd)
- }
+ })
}
func SysLog(s string) {
@@ -100,12 +93,7 @@ func logHelper(ctx context.Context, level string, msg string) {
}
now := time.Now()
_, _ = fmt.Fprintf(writer, "[%s] %v | %s | %s \n", level, now.Format("2006/01/02 - 15:04:05"), id, msg)
- if !setupLogWorking {
- setupLogWorking = true
- go func() {
- SetupLogger()
- }()
- }
+ SetupLogger()
}
func FatalLog(v ...any) {
diff --git a/controller/auth/wechat.go b/controller/auth/wechat.go
index 9ed66b35..6fbc0967 100644
--- a/controller/auth/wechat.go
+++ b/controller/auth/wechat.go
@@ -3,14 +3,16 @@ package auth
import (
"encoding/json"
"fmt"
- "github.com/Laisky/errors/v2"
- "github.com/Laisky/one-api/common/config"
- "github.com/Laisky/one-api/controller"
- "github.com/Laisky/one-api/model"
- "github.com/gin-gonic/gin"
"net/http"
"strconv"
"time"
+
+ "github.com/Laisky/errors/v2"
+ "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
+ "github.com/Laisky/one-api/controller"
+ "github.com/Laisky/one-api/model"
+ "github.com/gin-gonic/gin"
)
type wechatLoginResponse struct {
@@ -136,7 +138,7 @@ func WeChatBind(c *gin.Context) {
})
return
}
- id := c.GetInt("id")
+ id := c.GetInt(ctxkey.Id)
user := model.User{
Id: id,
}
diff --git a/controller/billing.go b/controller/billing.go
index 245f6922..abca1eef 100644
--- a/controller/billing.go
+++ b/controller/billing.go
@@ -2,6 +2,7 @@ package controller
import (
"github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/model"
relaymodel "github.com/Laisky/one-api/relay/model"
"github.com/gin-gonic/gin"
@@ -14,13 +15,13 @@ func GetSubscription(c *gin.Context) {
var token *model.Token
var expiredTime int64
if config.DisplayTokenStatEnabled {
- tokenId := c.GetInt("token_id")
+ tokenId := c.GetInt(ctxkey.TokenId)
token, err = model.GetTokenById(tokenId)
expiredTime = token.ExpiredTime
remainQuota = token.RemainQuota
usedQuota = token.UsedQuota
} else {
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
remainQuota, err = model.GetUserQuota(userId)
if err != nil {
usedQuota, err = model.GetUserUsedQuota(userId)
@@ -64,11 +65,11 @@ func GetUsage(c *gin.Context) {
var err error
var token *model.Token
if config.DisplayTokenStatEnabled {
- tokenId := c.GetInt("token_id")
+ tokenId := c.GetInt(ctxkey.TokenId)
token, err = model.GetTokenById(tokenId)
quota = token.UsedQuota
} else {
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
quota, err = model.GetUserUsedQuota(userId)
}
if err != nil {
diff --git a/controller/channel-test.go b/controller/channel-test.go
index 346c2a01..7dcb5b36 100644
--- a/controller/channel-test.go
+++ b/controller/channel-test.go
@@ -15,6 +15,7 @@ import (
"github.com/Laisky/errors/v2"
"github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/logger"
"github.com/Laisky/one-api/common/message"
"github.com/Laisky/one-api/middleware"
@@ -26,7 +27,6 @@ import (
"github.com/Laisky/one-api/relay/meta"
relaymodel "github.com/Laisky/one-api/relay/model"
"github.com/Laisky/one-api/relay/relaymode"
-
"github.com/gin-gonic/gin"
)
@@ -55,8 +55,8 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error
}
c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
c.Request.Header.Set("Content-Type", "application/json")
- c.Set("channel", channel.Type)
- c.Set("base_url", channel.GetBaseURL())
+ c.Set(ctxkey.Channel, channel.Type)
+ c.Set(ctxkey.BaseURL, channel.GetBaseURL())
middleware.SetupContextForSelectedChannel(c, channel, "")
meta := meta.GetByContext(c)
apiType := channeltype.ToAPIType(channel.Type)
@@ -65,8 +65,12 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error
return errors.Errorf("invalid api type: %d, adaptor is nil", apiType), nil
}
adaptor.Init(meta)
- modelName := adaptor.GetModelList()[0]
- if !strings.Contains(channel.Models, modelName) {
+ var modelName string
+ modelList := adaptor.GetModelList()
+ if len(modelList) != 0 {
+ modelName = modelList[0]
+ }
+ if modelName == "" || !strings.Contains(channel.Models, modelName) {
modelNames := strings.Split(channel.Models, ",")
if len(modelNames) > 0 {
modelName = modelNames[0]
@@ -83,6 +87,7 @@ func testChannel(channel *model.Channel) (err error, openaiErr *relaymodel.Error
if err != nil {
return err, nil
}
+ logger.SysLog(string(jsonData))
requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(requestBody)
resp, err := adaptor.DoRequest(c, meta, requestBody)
diff --git a/controller/log.go b/controller/log.go
index d30ee85c..eb4921be 100644
--- a/controller/log.go
+++ b/controller/log.go
@@ -1,11 +1,13 @@
package controller
import (
- "github.com/Laisky/one-api/common/config"
- "github.com/Laisky/one-api/model"
- "github.com/gin-gonic/gin"
"net/http"
"strconv"
+
+ "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
+ "github.com/Laisky/one-api/model"
+ "github.com/gin-gonic/gin"
)
func GetAllLogs(c *gin.Context) {
@@ -41,7 +43,7 @@ func GetUserLogs(c *gin.Context) {
if p < 0 {
p = 0
}
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
logType, _ := strconv.Atoi(c.Query("type"))
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
@@ -83,7 +85,7 @@ func SearchAllLogs(c *gin.Context) {
func SearchUserLogs(c *gin.Context) {
keyword := c.Query("keyword")
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
logs, err := model.SearchUserLogs(userId, keyword)
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -122,7 +124,7 @@ func GetLogsStat(c *gin.Context) {
}
func GetLogsSelfStat(c *gin.Context) {
- username := c.GetString("username")
+ username := c.GetString(ctxkey.Username)
logType, _ := strconv.Atoi(c.Query("type"))
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
diff --git a/controller/model.go b/controller/model.go
index 75986db1..39ed2b17 100644
--- a/controller/model.go
+++ b/controller/model.go
@@ -2,6 +2,10 @@ package controller
import (
"fmt"
+ "net/http"
+ "strings"
+
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/model"
relay "github.com/Laisky/one-api/relay"
"github.com/Laisky/one-api/relay/adaptor/openai"
@@ -10,8 +14,6 @@ import (
"github.com/Laisky/one-api/relay/meta"
relaymodel "github.com/Laisky/one-api/relay/model"
"github.com/gin-gonic/gin"
- "net/http"
- "strings"
)
// https://platform.openai.com/docs/api-reference/models/list
@@ -139,10 +141,10 @@ func ListAllModels(c *gin.Context) {
func ListModels(c *gin.Context) {
ctx := c.Request.Context()
var availableModels []string
- if c.GetString("available_models") != "" {
- availableModels = strings.Split(c.GetString("available_models"), ",")
+ if c.GetString(ctxkey.AvailableModels) != "" {
+ availableModels = strings.Split(c.GetString(ctxkey.AvailableModels), ",")
} else {
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
userGroup, _ := model.CacheGetUserGroup(userId)
availableModels, _ = model.CacheGetGroupModels(ctx, userGroup)
}
@@ -194,7 +196,7 @@ func RetrieveModel(c *gin.Context) {
func GetUserAvailableModels(c *gin.Context) {
ctx := c.Request.Context()
- id := c.GetInt("id")
+ id := c.GetInt(ctxkey.Id)
userGroup, err := model.CacheGetUserGroup(id)
if err != nil {
c.JSON(http.StatusOK, gin.H{
diff --git a/controller/redemption.go b/controller/redemption.go
index dcf744b2..979265a4 100644
--- a/controller/redemption.go
+++ b/controller/redemption.go
@@ -1,13 +1,15 @@
package controller
import (
+ "net/http"
+ "strconv"
+
"github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/helper"
"github.com/Laisky/one-api/common/random"
"github.com/Laisky/one-api/model"
"github.com/gin-gonic/gin"
- "net/http"
- "strconv"
)
func GetAllRedemptions(c *gin.Context) {
@@ -109,7 +111,7 @@ func AddRedemption(c *gin.Context) {
for i := 0; i < redemption.Count; i++ {
key := random.GetUUID()
cleanRedemption := model.Redemption{
- UserId: c.GetInt("id"),
+ UserId: c.GetInt(ctxkey.Id),
Name: redemption.Name,
Key: key,
CreatedTime: helper.GetTimestamp(),
diff --git a/controller/relay.go b/controller/relay.go
index 10aa1c3c..81906691 100644
--- a/controller/relay.go
+++ b/controller/relay.go
@@ -48,15 +48,15 @@ func Relay(c *gin.Context) {
requestBody, _ := common.GetRequestBody(c)
logger.Debugf(ctx, "request body: %s", string(requestBody))
}
- channelId := c.GetInt("channel_id")
+ channelId := c.GetInt(ctxkey.ChannelId)
bizErr := relayHelper(c, relayMode)
if bizErr == nil {
monitor.Emit(channelId, true)
return
}
lastFailedChannelId := channelId
- channelName := c.GetString("channel_name")
- group := c.GetString("group")
+ channelName := c.GetString(ctxkey.ChannelName)
+ group := c.GetString(ctxkey.Group)
originalModel := c.GetString(ctxkey.OriginalModel)
go processChannelRelayError(ctx, channelId, channelName, bizErr)
requestId := c.GetString(logger.RequestIdKey)
@@ -82,9 +82,9 @@ func Relay(c *gin.Context) {
if bizErr == nil {
return
}
- channelId := c.GetInt("channel_id")
+ channelId := c.GetInt(ctxkey.ChannelId)
lastFailedChannelId = channelId
- channelName := c.GetString("channel_name")
+ channelName := c.GetString(ctxkey.ChannelName)
go processChannelRelayError(ctx, channelId, channelName, bizErr)
}
if bizErr != nil {
@@ -100,14 +100,14 @@ 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 {
+ if v, ok := c.Get(ctxkey.SpecificChannelId); ok {
return errors.Errorf("specific channel = %v", v)
}
- if statusCode == http.StatusBadRequest {
+ if statusCode == http.StatusTooManyRequests {
return errors.Errorf("status code = %d", statusCode)
}
- if statusCode/100 == 2 {
+ if statusCode/100 == 5 {
return errors.Errorf("status code = %d", statusCode)
}
diff --git a/controller/token.go b/controller/token.go
index ff3a63ba..80620394 100644
--- a/controller/token.go
+++ b/controller/token.go
@@ -7,6 +7,7 @@ import (
"github.com/Laisky/one-api/common"
"github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/helper"
"github.com/Laisky/one-api/common/network"
"github.com/Laisky/one-api/common/random"
@@ -16,7 +17,7 @@ import (
)
func GetAllTokens(c *gin.Context) {
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
p, _ := strconv.Atoi(c.Query("p"))
if p < 0 {
p = 0
@@ -41,7 +42,7 @@ func GetAllTokens(c *gin.Context) {
}
func SearchTokens(c *gin.Context) {
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
keyword := c.Query("keyword")
tokens, err := model.SearchUserTokens(userId, keyword)
if err != nil {
@@ -61,7 +62,7 @@ func SearchTokens(c *gin.Context) {
func GetToken(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -86,8 +87,8 @@ func GetToken(c *gin.Context) {
}
func GetTokenStatus(c *gin.Context) {
- tokenId := c.GetInt("token_id")
- userId := c.GetInt("id")
+ tokenId := c.GetInt(ctxkey.TokenId)
+ userId := c.GetInt(ctxkey.Id)
token, err := model.GetTokenByIds(tokenId, userId)
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -145,7 +146,7 @@ func AddToken(c *gin.Context) {
}
cleanToken := model.Token{
- UserId: c.GetInt("id"),
+ UserId: c.GetInt(ctxkey.Id),
Name: token.Name,
Key: random.GenerateKey(),
CreatedTime: helper.GetTimestamp(),
@@ -174,7 +175,7 @@ func AddToken(c *gin.Context) {
func DeleteToken(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
err := model.DeleteTokenById(id, userId)
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -198,7 +199,7 @@ type updateTokenDto struct {
}
func UpdateToken(c *gin.Context) {
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
statusOnly := c.Query("status_only")
tokenPatch := new(updateTokenDto)
err := c.ShouldBindJSON(tokenPatch)
diff --git a/controller/user.go b/controller/user.go
index eb2a6f18..5c4b8fdf 100644
--- a/controller/user.go
+++ b/controller/user.go
@@ -3,14 +3,15 @@ package controller
import (
"encoding/json"
"fmt"
- "github.com/Laisky/one-api/common"
- "github.com/Laisky/one-api/common/config"
- "github.com/Laisky/one-api/common/random"
- "github.com/Laisky/one-api/model"
"net/http"
"strconv"
"time"
+ "github.com/Laisky/one-api/common"
+ "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
+ "github.com/Laisky/one-api/common/random"
+ "github.com/Laisky/one-api/model"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
@@ -244,7 +245,7 @@ func GetUser(c *gin.Context) {
})
return
}
- myRole := c.GetInt("role")
+ myRole := c.GetInt(ctxkey.Role)
if myRole <= user.Role && myRole != model.RoleRootUser {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -261,7 +262,7 @@ func GetUser(c *gin.Context) {
}
func GetUserDashboard(c *gin.Context) {
- id := c.GetInt("id")
+ id := c.GetInt(ctxkey.Id)
now := time.Now()
startOfDay := now.Truncate(24*time.Hour).AddDate(0, 0, -6).Unix()
endOfDay := now.Truncate(24 * time.Hour).Add(24*time.Hour - time.Second).Unix()
@@ -284,7 +285,7 @@ func GetUserDashboard(c *gin.Context) {
}
func GenerateAccessToken(c *gin.Context) {
- id := c.GetInt("id")
+ id := c.GetInt(ctxkey.Id)
user, err := model.GetUserById(id, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -320,7 +321,7 @@ func GenerateAccessToken(c *gin.Context) {
}
func GetAffCode(c *gin.Context) {
- id := c.GetInt("id")
+ id := c.GetInt(ctxkey.Id)
user, err := model.GetUserById(id, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -358,7 +359,7 @@ func GetSelfByToken(c *gin.Context) {
}
func GetSelf(c *gin.Context) {
- id := c.GetInt("id")
+ id := c.GetInt(ctxkey.Id)
user, err := model.GetUserById(id, false)
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -403,7 +404,7 @@ func UpdateUser(c *gin.Context) {
})
return
}
- myRole := c.GetInt("role")
+ myRole := c.GetInt(ctxkey.Role)
if myRole <= originUser.Role && myRole != model.RoleRootUser {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -461,7 +462,7 @@ func UpdateSelf(c *gin.Context) {
}
cleanUser := model.User{
- Id: c.GetInt("id"),
+ Id: c.GetInt(ctxkey.Id),
Username: user.Username,
Password: user.Password,
DisplayName: user.DisplayName,
diff --git a/main.go b/main.go
index f5e5e7e6..6c3883d2 100644
--- a/main.go
+++ b/main.go
@@ -73,7 +73,7 @@ func main() {
}
if config.MemoryCacheEnabled {
logger.SysLog("memory cache enabled")
- logger.SysError(fmt.Sprintf("sync frequency: %d seconds", config.SyncFrequency))
+ logger.SysLog(fmt.Sprintf("sync frequency: %d seconds", config.SyncFrequency))
model.InitChannelCache()
}
if config.MemoryCacheEnabled {
diff --git a/middleware/auth.go b/middleware/auth.go
index ca6cb1b0..4508af29 100644
--- a/middleware/auth.go
+++ b/middleware/auth.go
@@ -2,14 +2,16 @@ package middleware
import (
"fmt"
+ "net/http"
+ "strings"
+
"github.com/Laisky/one-api/common/blacklist"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/logger"
"github.com/Laisky/one-api/common/network"
"github.com/Laisky/one-api/model"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
- "net/http"
- "strings"
)
func authHelper(c *gin.Context, minRole int) {
@@ -123,20 +125,20 @@ func TokenAuth() func(c *gin.Context) {
abortWithMessage(c, http.StatusBadRequest, err.Error())
return
}
- c.Set("request_model", requestModel)
+ c.Set(ctxkey.RequestModel, requestModel)
if token.Models != nil && *token.Models != "" {
- c.Set("available_models", *token.Models)
+ c.Set(ctxkey.AvailableModels, *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)
+ c.Set(ctxkey.Id, token.UserId)
+ c.Set(ctxkey.TokenId, token.Id)
+ c.Set(ctxkey.TokenName, token.Name)
if len(parts) > 1 {
if model.IsAdmin(token.UserId) {
- c.Set("specific_channel_id", parts[1])
+ c.Set(ctxkey.SpecificChannelId, parts[1])
} else {
abortWithMessage(c, http.StatusForbidden, "普通用户不支持指定渠道")
return
diff --git a/middleware/distributor.go b/middleware/distributor.go
index 68cfd33b..b3c99316 100644
--- a/middleware/distributor.go
+++ b/middleware/distributor.go
@@ -6,8 +6,6 @@ import (
"strconv"
"strings"
- "github.com/Laisky/one-api/common"
- "github.com/Laisky/one-api/common/config"
"github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/logger"
"github.com/Laisky/one-api/model"
@@ -22,12 +20,12 @@ type ModelRequest struct {
func Distribute() func(c *gin.Context) {
return func(c *gin.Context) {
- userId := c.GetInt("id")
+ userId := c.GetInt(ctxkey.Id)
userGroup, _ := model.CacheGetUserGroup(userId)
- c.Set("group", userGroup)
+ c.Set(ctxkey.Group, userGroup)
var requestModel string
var channel *model.Channel
- channelId, ok := c.Get("specific_channel_id")
+ channelId, ok := c.Get(ctxkey.SpecificChannelId)
if ok {
id, err := strconv.Atoi(channelId.(string))
if err != nil {
@@ -44,7 +42,7 @@ func Distribute() func(c *gin.Context) {
return
}
} else {
- requestModel = c.GetString("request_model")
+ requestModel = c.GetString(ctxkey.RequestModel)
var err error
channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, requestModel, false)
if err != nil {
@@ -74,30 +72,31 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
}
}
logger.Info(c.Request.Context(), fmt.Sprintf("set channel %s ratio to %f", channel.Name, minimalRatio))
- c.Set("channel_ratio", minimalRatio)
- c.Set(common.CtxKeyChannel, channel)
- c.Set("channel", channel.Type)
- c.Set("channel_id", channel.Id)
- c.Set("channel_name", channel.Name)
- c.Set("model_mapping", channel.GetModelMapping())
+ c.Set(ctxkey.ChannelRatio, minimalRatio)
+ c.Set(ctxkey.ChannelModel, channel)
+
+ c.Set(ctxkey.Channel, channel.Type)
+ c.Set(ctxkey.ChannelId, channel.Id)
+ c.Set(ctxkey.ChannelName, channel.Name)
+ c.Set(ctxkey.ModelMapping, channel.GetModelMapping())
c.Set(ctxkey.OriginalModel, modelName) // for retry
c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", channel.Key))
- c.Set("base_url", channel.GetBaseURL())
+ c.Set(ctxkey.BaseURL, channel.GetBaseURL())
// this is for backward compatibility
switch channel.Type {
case channeltype.Azure:
- c.Set(config.KeyAPIVersion, channel.Other)
+ c.Set(ctxkey.ConfigAPIVersion, channel.Other)
case channeltype.Xunfei:
- c.Set(config.KeyAPIVersion, channel.Other)
+ c.Set(ctxkey.ConfigAPIVersion, channel.Other)
case channeltype.Gemini:
- c.Set(config.KeyAPIVersion, channel.Other)
+ c.Set(ctxkey.ConfigAPIVersion, channel.Other)
case channeltype.AIProxyLibrary:
- c.Set(config.KeyLibraryID, channel.Other)
+ c.Set(ctxkey.ConfigLibraryID, channel.Other)
case channeltype.Ali:
- c.Set(config.KeyPlugin, channel.Other)
+ c.Set(ctxkey.ConfigPlugin, channel.Other)
}
cfg, _ := channel.LoadConfig()
for k, v := range cfg {
- c.Set(config.KeyPrefix+k, v)
+ c.Set(ctxkey.ConfigPrefix+k, v)
}
}
diff --git a/relay/adaptor.go b/relay/adaptor.go
index 1519b40e..a5a305d1 100644
--- a/relay/adaptor.go
+++ b/relay/adaptor.go
@@ -7,6 +7,7 @@ import (
"github.com/Laisky/one-api/relay/adaptor/anthropic"
"github.com/Laisky/one-api/relay/adaptor/aws"
"github.com/Laisky/one-api/relay/adaptor/baidu"
+ "github.com/Laisky/one-api/relay/adaptor/coze"
"github.com/Laisky/one-api/relay/adaptor/gemini"
"github.com/Laisky/one-api/relay/adaptor/ollama"
"github.com/Laisky/one-api/relay/adaptor/openai"
@@ -43,6 +44,8 @@ func GetAdaptor(apiType int) adaptor.Adaptor {
return &zhipu.Adaptor{}
case apitype.Ollama:
return &ollama.Adaptor{}
+ case apitype.Coze:
+ return &coze.Adaptor{}
}
return nil
diff --git a/relay/adaptor/aiproxy/adaptor.go b/relay/adaptor/aiproxy/adaptor.go
index ae7a8290..e209d38c 100644
--- a/relay/adaptor/aiproxy/adaptor.go
+++ b/relay/adaptor/aiproxy/adaptor.go
@@ -2,14 +2,15 @@ package aiproxy
import (
"fmt"
+ "io"
+ "net/http"
+
"github.com/Laisky/errors/v2"
- "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/relay/adaptor"
"github.com/Laisky/one-api/relay/meta"
"github.com/Laisky/one-api/relay/model"
"github.com/gin-gonic/gin"
- "io"
- "net/http"
)
type Adaptor struct {
@@ -34,7 +35,7 @@ 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(config.KeyLibraryID)
+ aiProxyLibraryRequest.LibraryId = c.GetString(ctxkey.ConfigLibraryID)
return aiProxyLibraryRequest, nil
}
diff --git a/relay/adaptor/ali/adaptor.go b/relay/adaptor/ali/adaptor.go
index f12eae8c..948f9def 100644
--- a/relay/adaptor/ali/adaptor.go
+++ b/relay/adaptor/ali/adaptor.go
@@ -6,7 +6,7 @@ import (
"io"
"net/http"
- "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/relay/adaptor"
"github.com/Laisky/one-api/relay/meta"
"github.com/Laisky/one-api/relay/model"
@@ -48,8 +48,8 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *me
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))
+ if c.GetString(ctxkey.ConfigPlugin) != "" {
+ req.Header.Set("X-DashScope-Plugin", c.GetString(ctxkey.ConfigPlugin))
}
return nil
}
diff --git a/relay/adaptor/aws/main.go b/relay/adaptor/aws/main.go
index dc94572d..cb56d38f 100644
--- a/relay/adaptor/aws/main.go
+++ b/relay/adaptor/aws/main.go
@@ -9,7 +9,6 @@ import (
"net/http"
"github.com/Laisky/one-api/common"
- "github.com/Laisky/one-api/common/config"
"github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/helper"
"github.com/Laisky/one-api/common/logger"
@@ -25,9 +24,9 @@ import (
)
func newAwsClient(c *gin.Context) (*bedrockruntime.Client, error) {
- ak := c.GetString(config.KeyAK)
- sk := c.GetString(config.KeySK)
- region := c.GetString(config.KeyRegion)
+ ak := c.GetString(ctxkey.ConfigAK)
+ sk := c.GetString(ctxkey.ConfigSK)
+ region := c.GetString(ctxkey.ConfigRegion)
client := bedrockruntime.New(bedrockruntime.Options{
Region: region,
Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(ak, sk, "")),
diff --git a/relay/adaptor/azure/helper.go b/relay/adaptor/azure/helper.go
index b4b54379..847fd93e 100644
--- a/relay/adaptor/azure/helper.go
+++ b/relay/adaptor/azure/helper.go
@@ -1,7 +1,7 @@
package azure
import (
- "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/gin-gonic/gin"
)
@@ -9,7 +9,7 @@ func GetAPIVersion(c *gin.Context) string {
query := c.Request.URL.Query()
apiVersion := query.Get("api-version")
if apiVersion == "" {
- apiVersion = c.GetString(config.KeyAPIVersion)
+ apiVersion = c.GetString(ctxkey.ConfigAPIVersion)
}
return apiVersion
}
diff --git a/relay/adaptor/coze/adaptor.go b/relay/adaptor/coze/adaptor.go
new file mode 100644
index 00000000..cb349639
--- /dev/null
+++ b/relay/adaptor/coze/adaptor.go
@@ -0,0 +1,76 @@
+package coze
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/Laisky/one-api/common/ctxkey"
+ "github.com/Laisky/one-api/relay/adaptor"
+ "github.com/Laisky/one-api/relay/adaptor/openai"
+ "github.com/Laisky/one-api/relay/meta"
+ "github.com/Laisky/one-api/relay/model"
+ "github.com/gin-gonic/gin"
+)
+
+type Adaptor struct {
+}
+
+func (a *Adaptor) Init(meta *meta.Meta) {
+
+}
+
+func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
+ return fmt.Sprintf("%s/open_api/v2/chat", meta.BaseURL), nil
+}
+
+func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error {
+ adaptor.SetupCommonRequestHeader(c, req, meta)
+ req.Header.Set("Authorization", "Bearer "+meta.APIKey)
+ return nil
+}
+
+func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.GeneralOpenAIRequest) (any, error) {
+ if request == nil {
+ return nil, errors.New("request is nil")
+ }
+ request.User = c.GetString(ctxkey.ConfigUserID)
+ return ConvertRequest(*request), nil
+}
+
+func (a *Adaptor) ConvertImageRequest(request *model.ImageRequest) (any, error) {
+ if request == nil {
+ return nil, errors.New("request is nil")
+ }
+ return request, nil
+}
+
+func (a *Adaptor) DoRequest(c *gin.Context, meta *meta.Meta, requestBody io.Reader) (*http.Response, error) {
+ return adaptor.DoRequestHelper(a, c, meta, requestBody)
+}
+
+func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Meta) (usage *model.Usage, err *model.ErrorWithStatusCode) {
+ var responseText *string
+ if meta.IsStream {
+ err, responseText = StreamHandler(c, resp)
+ } else {
+ err, responseText = Handler(c, resp, meta.PromptTokens, meta.ActualModelName)
+ }
+ if responseText != nil {
+ usage = openai.ResponseText2Usage(*responseText, meta.ActualModelName, meta.PromptTokens)
+ } else {
+ usage = &model.Usage{}
+ }
+ usage.PromptTokens = meta.PromptTokens
+ usage.TotalTokens = usage.PromptTokens + usage.CompletionTokens
+ return
+}
+
+func (a *Adaptor) GetModelList() []string {
+ return ModelList
+}
+
+func (a *Adaptor) GetChannelName() string {
+ return "coze"
+}
diff --git a/relay/adaptor/coze/constant/contenttype/define.go b/relay/adaptor/coze/constant/contenttype/define.go
new file mode 100644
index 00000000..69c876bc
--- /dev/null
+++ b/relay/adaptor/coze/constant/contenttype/define.go
@@ -0,0 +1,5 @@
+package contenttype
+
+const (
+ Text = "text"
+)
diff --git a/relay/adaptor/coze/constant/event/define.go b/relay/adaptor/coze/constant/event/define.go
new file mode 100644
index 00000000..c03e8c17
--- /dev/null
+++ b/relay/adaptor/coze/constant/event/define.go
@@ -0,0 +1,7 @@
+package event
+
+const (
+ Message = "message"
+ Done = "done"
+ Error = "error"
+)
diff --git a/relay/adaptor/coze/constant/messagetype/define.go b/relay/adaptor/coze/constant/messagetype/define.go
new file mode 100644
index 00000000..6c1c25db
--- /dev/null
+++ b/relay/adaptor/coze/constant/messagetype/define.go
@@ -0,0 +1,6 @@
+package messagetype
+
+const (
+ Answer = "answer"
+ FollowUp = "follow_up"
+)
diff --git a/relay/adaptor/coze/constants.go b/relay/adaptor/coze/constants.go
new file mode 100644
index 00000000..d20fd875
--- /dev/null
+++ b/relay/adaptor/coze/constants.go
@@ -0,0 +1,3 @@
+package coze
+
+var ModelList = []string{}
diff --git a/relay/adaptor/coze/helper.go b/relay/adaptor/coze/helper.go
new file mode 100644
index 00000000..e0b34f2c
--- /dev/null
+++ b/relay/adaptor/coze/helper.go
@@ -0,0 +1,10 @@
+package coze
+
+import "github.com/Laisky/one-api/relay/adaptor/coze/constant/event"
+
+func event2StopReason(e *string) string {
+ if e == nil || *e == event.Message {
+ return ""
+ }
+ return "stop"
+}
diff --git a/relay/adaptor/coze/main.go b/relay/adaptor/coze/main.go
new file mode 100644
index 00000000..b17d1354
--- /dev/null
+++ b/relay/adaptor/coze/main.go
@@ -0,0 +1,215 @@
+package coze
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "github.com/Laisky/one-api/common"
+ "github.com/Laisky/one-api/common/conv"
+ "github.com/Laisky/one-api/common/helper"
+ "github.com/Laisky/one-api/common/logger"
+ "github.com/Laisky/one-api/relay/adaptor/coze/constant/messagetype"
+ "github.com/Laisky/one-api/relay/adaptor/openai"
+ "github.com/Laisky/one-api/relay/model"
+ "github.com/gin-gonic/gin"
+ "io"
+ "net/http"
+ "strings"
+)
+
+// https://www.coze.com/open
+
+func stopReasonCoze2OpenAI(reason *string) string {
+ if reason == nil {
+ return ""
+ }
+ switch *reason {
+ case "end_turn":
+ return "stop"
+ case "stop_sequence":
+ return "stop"
+ case "max_tokens":
+ return "length"
+ default:
+ return *reason
+ }
+}
+
+func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
+ cozeRequest := Request{
+ Stream: textRequest.Stream,
+ User: textRequest.User,
+ BotId: strings.TrimPrefix(textRequest.Model, "bot-"),
+ }
+ for i, message := range textRequest.Messages {
+ if i == len(textRequest.Messages)-1 {
+ cozeRequest.Query = message.StringContent()
+ continue
+ }
+ cozeMessage := Message{
+ Role: message.Role,
+ Content: message.StringContent(),
+ }
+ cozeRequest.ChatHistory = append(cozeRequest.ChatHistory, cozeMessage)
+ }
+ return &cozeRequest
+}
+
+func StreamResponseCoze2OpenAI(cozeResponse *StreamResponse) (*openai.ChatCompletionsStreamResponse, *Response) {
+ var response *Response
+ var stopReason string
+ var choice openai.ChatCompletionsStreamResponseChoice
+
+ if cozeResponse.Message != nil {
+ if cozeResponse.Message.Type != messagetype.Answer {
+ return nil, nil
+ }
+ choice.Delta.Content = cozeResponse.Message.Content
+ }
+ choice.Delta.Role = "assistant"
+ finishReason := stopReasonCoze2OpenAI(&stopReason)
+ if finishReason != "null" {
+ choice.FinishReason = &finishReason
+ }
+ var openaiResponse openai.ChatCompletionsStreamResponse
+ openaiResponse.Object = "chat.completion.chunk"
+ openaiResponse.Choices = []openai.ChatCompletionsStreamResponseChoice{choice}
+ openaiResponse.Id = cozeResponse.ConversationId
+ return &openaiResponse, response
+}
+
+func ResponseCoze2OpenAI(cozeResponse *Response) *openai.TextResponse {
+ var responseText string
+ for _, message := range cozeResponse.Messages {
+ if message.Type == messagetype.Answer {
+ responseText = message.Content
+ break
+ }
+ }
+ choice := openai.TextResponseChoice{
+ Index: 0,
+ Message: model.Message{
+ Role: "assistant",
+ Content: responseText,
+ Name: nil,
+ },
+ FinishReason: "stop",
+ }
+ fullTextResponse := openai.TextResponse{
+ Id: fmt.Sprintf("chatcmpl-%s", cozeResponse.ConversationId),
+ Model: "coze-bot",
+ Object: "chat.completion",
+ Created: helper.GetTimestamp(),
+ Choices: []openai.TextResponseChoice{choice},
+ }
+ return &fullTextResponse
+}
+
+func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *string) {
+ var responseText string
+ 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), "\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 {
+ continue
+ }
+ if !strings.HasPrefix(data, "data:") {
+ continue
+ }
+ data = strings.TrimPrefix(data, "data:")
+ dataChan <- data
+ }
+ stopChan <- true
+ }()
+ common.SetEventStreamHeaders(c)
+ var modelName string
+ c.Stream(func(w io.Writer) bool {
+ select {
+ case data := <-dataChan:
+ // some implementations may add \r at the end of data
+ data = strings.TrimSuffix(data, "\r")
+ var cozeResponse StreamResponse
+ err := json.Unmarshal([]byte(data), &cozeResponse)
+ if err != nil {
+ logger.SysError("error unmarshalling stream response: " + err.Error())
+ return true
+ }
+ response, _ := StreamResponseCoze2OpenAI(&cozeResponse)
+ if response == nil {
+ return true
+ }
+ for _, choice := range response.Choices {
+ responseText += conv.AsString(choice.Delta.Content)
+ }
+ response.Model = modelName
+ response.Created = createdTime
+ jsonStr, 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(jsonStr)})
+ return true
+ case <-stopChan:
+ c.Render(-1, common.CustomEvent{Data: "data: [DONE]"})
+ return false
+ }
+ })
+ _ = resp.Body.Close()
+ return nil, &responseText
+}
+
+func Handler(c *gin.Context, resp *http.Response, promptTokens int, modelName string) (*model.ErrorWithStatusCode, *string) {
+ 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
+ }
+ var cozeResponse Response
+ err = json.Unmarshal(responseBody, &cozeResponse)
+ if err != nil {
+ return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
+ }
+ if cozeResponse.Code != 0 {
+ return &model.ErrorWithStatusCode{
+ Error: model.Error{
+ Message: cozeResponse.Msg,
+ Code: cozeResponse.Code,
+ },
+ StatusCode: resp.StatusCode,
+ }, nil
+ }
+ fullTextResponse := ResponseCoze2OpenAI(&cozeResponse)
+ fullTextResponse.Model = modelName
+ 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)
+ var responseText string
+ if len(fullTextResponse.Choices) > 0 {
+ responseText = fullTextResponse.Choices[0].Message.StringContent()
+ }
+ return nil, &responseText
+}
diff --git a/relay/adaptor/coze/model.go b/relay/adaptor/coze/model.go
new file mode 100644
index 00000000..d0afecfe
--- /dev/null
+++ b/relay/adaptor/coze/model.go
@@ -0,0 +1,38 @@
+package coze
+
+type Message struct {
+ Role string `json:"role"`
+ Type string `json:"type"`
+ Content string `json:"content"`
+ ContentType string `json:"content_type"`
+}
+
+type ErrorInformation struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+}
+
+type Request struct {
+ ConversationId string `json:"conversation_id,omitempty"`
+ BotId string `json:"bot_id"`
+ User string `json:"user"`
+ Query string `json:"query"`
+ ChatHistory []Message `json:"chat_history,omitempty"`
+ Stream bool `json:"stream"`
+}
+
+type Response struct {
+ ConversationId string `json:"conversation_id,omitempty"`
+ Messages []Message `json:"messages,omitempty"`
+ Code int `json:"code,omitempty"`
+ Msg string `json:"msg,omitempty"`
+}
+
+type StreamResponse struct {
+ Event string `json:"event,omitempty"`
+ Message *Message `json:"message,omitempty"`
+ IsFinish bool `json:"is_finish,omitempty"`
+ Index int `json:"index,omitempty"`
+ ConversationId string `json:"conversation_id,omitempty"`
+ ErrorInformation *ErrorInformation `json:"error_information,omitempty"`
+}
diff --git a/relay/adaptor/groq/constants.go b/relay/adaptor/groq/constants.go
index fc9a9ebd..1aa2574b 100644
--- a/relay/adaptor/groq/constants.go
+++ b/relay/adaptor/groq/constants.go
@@ -7,4 +7,6 @@ var ModelList = []string{
"llama2-7b-2048",
"llama2-70b-4096",
"mixtral-8x7b-32768",
+ "llama3-8b-8192",
+ "llama3-70b-8192",
}
diff --git a/relay/adaptor/openai/main.go b/relay/adaptor/openai/main.go
index adec386d..8072acd3 100644
--- a/relay/adaptor/openai/main.go
+++ b/relay/adaptor/openai/main.go
@@ -16,6 +16,12 @@ import (
"github.com/gin-gonic/gin"
)
+const (
+ dataPrefix = "data: "
+ done = "[DONE]"
+ dataPrefixLength = len(dataPrefix)
+)
+
func StreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*model.ErrorWithStatusCode, string, *model.Usage) {
responseText := ""
scanner := bufio.NewScanner(resp.Body)
@@ -37,39 +43,46 @@ func StreamHandler(c *gin.Context, resp *http.Response, relayMode int) (*model.E
go func() {
for scanner.Scan() {
data := scanner.Text()
- if len(data) < 6 { // ignore blank line or wrong format
+ if len(data) < dataPrefixLength { // ignore blank line or wrong format
continue
}
- if data[:6] != "data: " && data[:6] != "[DONE]" {
+ if data[:dataPrefixLength] != dataPrefix && data[:dataPrefixLength] != done {
continue
}
- dataChan <- data
- data = data[6:]
- if !strings.HasPrefix(data, "[DONE]") {
- switch relayMode {
- case relaymode.ChatCompletions:
- var streamResponse ChatCompletionsStreamResponse
- err := json.Unmarshal([]byte(data), &streamResponse)
- if err != nil {
- logger.SysError("error unmarshalling stream response: " + err.Error())
- continue // just ignore the error
- }
- for _, choice := range streamResponse.Choices {
- responseText += conv.AsString(choice.Delta.Content)
- }
- if streamResponse.Usage != nil {
- usage = streamResponse.Usage
- }
- case relaymode.Completions:
- var streamResponse CompletionsStreamResponse
- err := json.Unmarshal([]byte(data), &streamResponse)
- if err != nil {
- logger.SysError("error unmarshalling stream response: " + err.Error())
- continue
- }
- for _, choice := range streamResponse.Choices {
- responseText += choice.Text
- }
+ if strings.HasPrefix(data[dataPrefixLength:], done) {
+ dataChan <- data
+ continue
+ }
+ switch relayMode {
+ case relaymode.ChatCompletions:
+ var streamResponse ChatCompletionsStreamResponse
+ err := json.Unmarshal([]byte(data[dataPrefixLength:]), &streamResponse)
+ if err != nil {
+ logger.SysError("error unmarshalling stream response: " + err.Error())
+ dataChan <- data // if error happened, pass the data to client
+ continue // just ignore the error
+ }
+ if len(streamResponse.Choices) == 0 {
+ // but for empty choice, we should not pass it to client, this is for azure
+ continue // just ignore empty choice
+ }
+ dataChan <- data
+ for _, choice := range streamResponse.Choices {
+ responseText += conv.AsString(choice.Delta.Content)
+ }
+ if streamResponse.Usage != nil {
+ usage = streamResponse.Usage
+ }
+ case relaymode.Completions:
+ dataChan <- data
+ var streamResponse CompletionsStreamResponse
+ err := json.Unmarshal([]byte(data[dataPrefixLength:]), &streamResponse)
+ if err != nil {
+ logger.SysError("error unmarshalling stream response: " + err.Error())
+ continue
+ }
+ for _, choice := range streamResponse.Choices {
+ responseText += choice.Text
}
}
}
diff --git a/relay/adaptor/xunfei/main.go b/relay/adaptor/xunfei/main.go
index 9a5d12cd..6a34c0e2 100644
--- a/relay/adaptor/xunfei/main.go
+++ b/relay/adaptor/xunfei/main.go
@@ -13,7 +13,7 @@ import (
"time"
"github.com/Laisky/one-api/common"
- "github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/helper"
"github.com/Laisky/one-api/common/logger"
"github.com/Laisky/one-api/common/random"
@@ -281,7 +281,7 @@ func getAPIVersion(c *gin.Context, modelName string) string {
return apiVersion
}
- apiVersion = c.GetString(config.KeyAPIVersion)
+ apiVersion = c.GetString(ctxkey.ConfigAPIVersion)
if apiVersion != "" {
return apiVersion
}
diff --git a/relay/apitype/define.go b/relay/apitype/define.go
index 3760ba00..a3f2b98c 100644
--- a/relay/apitype/define.go
+++ b/relay/apitype/define.go
@@ -13,6 +13,7 @@ const (
Gemini
Ollama
AwsClaude
+ Coze
Dummy // this one is only for count, do not add any channel after this
)
diff --git a/relay/billing/ratio/model.go b/relay/billing/ratio/model.go
index 3b7bc3c5..d874ecf2 100644
--- a/relay/billing/ratio/model.go
+++ b/relay/billing/ratio/model.go
@@ -148,11 +148,13 @@ var ModelRatio = map[string]float64{
"mistral-medium-latest": 2.7 / 1000 * USD,
"mistral-large-latest": 8.0 / 1000 * USD,
"mistral-embed": 0.1 / 1000 * USD,
- // https://wow.groq.com/
- "llama2-70b-4096": 0.7 / 1000 * USD,
- "llama2-7b-2048": 0.1 / 1000 * USD,
+ // https://wow.groq.com/#:~:text=inquiries%C2%A0here.-,Model,-Current%20Speed
+ "llama3-70b-8192": 0.59 / 1000 * USD,
"mixtral-8x7b-32768": 0.27 / 1000 * USD,
+ "llama3-8b-8192": 0.05 / 1000 * USD,
"gemma-7b-it": 0.1 / 1000 * USD,
+ "llama2-70b-4096": 0.64 / 1000 * USD,
+ "llama2-7b-2048": 0.1 / 1000 * USD,
// https://platform.lingyiwanwu.com/docs#-计费单元
"yi-34b-chat-0205": 2.5 / 1000 * RMB,
"yi-34b-chat-200k": 12.0 / 1000 * RMB,
@@ -262,7 +264,7 @@ func GetCompletionRatio(name string) float64 {
return 4.0 / 3.0
}
if strings.HasPrefix(name, "gpt-4") {
- if strings.HasPrefix(name, "gpt-4-turbo") {
+ if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") {
return 3
}
return 2
@@ -281,7 +283,11 @@ func GetCompletionRatio(name string) float64 {
}
switch name {
case "llama2-70b-4096":
- return 0.8 / 0.7
+ return 0.8 / 0.64
+ case "llama3-8b-8192":
+ return 2
+ case "llama3-70b-8192":
+ return 0.79 / 0.59
}
return 1
}
diff --git a/relay/channeltype/define.go b/relay/channeltype/define.go
index faa0d443..6975e492 100644
--- a/relay/channeltype/define.go
+++ b/relay/channeltype/define.go
@@ -35,6 +35,7 @@ const (
LingYiWanWu
StepFun
AwsClaude
+ Coze
Dummy
)
diff --git a/relay/channeltype/helper.go b/relay/channeltype/helper.go
index 50b6b524..bea2d60e 100644
--- a/relay/channeltype/helper.go
+++ b/relay/channeltype/helper.go
@@ -27,6 +27,8 @@ func ToAPIType(channelType int) int {
apiType = apitype.Ollama
case AwsClaude:
apiType = apitype.AwsClaude
+ case Coze:
+ apiType = apitype.Coze
}
return apiType
diff --git a/relay/channeltype/url.go b/relay/channeltype/url.go
index 9ac29f30..1f15dfe3 100644
--- a/relay/channeltype/url.go
+++ b/relay/channeltype/url.go
@@ -35,6 +35,7 @@ var ChannelBaseURLs = []string{
"https://api.lingyiwanwu.com", // 31
"https://api.stepfun.com", // 32
"", // 33
+ "https://api.coze.com", // 34
}
func init() {
diff --git a/relay/controller/audio.go b/relay/controller/audio.go
index 9f7970be..40e844df 100644
--- a/relay/controller/audio.go
+++ b/relay/controller/audio.go
@@ -6,9 +6,14 @@ import (
"context"
"encoding/json"
"fmt"
+ "io"
+ "net/http"
+ "strings"
+
"github.com/Laisky/errors/v2"
"github.com/Laisky/one-api/common"
"github.com/Laisky/one-api/common/config"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/logger"
"github.com/Laisky/one-api/model"
"github.com/Laisky/one-api/relay/adaptor/azure"
@@ -20,21 +25,18 @@ import (
relaymodel "github.com/Laisky/one-api/relay/model"
"github.com/Laisky/one-api/relay/relaymode"
"github.com/gin-gonic/gin"
- "io"
- "net/http"
- "strings"
)
func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatusCode {
ctx := c.Request.Context()
audioModel := "whisper-1"
- tokenId := c.GetInt("token_id")
- channelType := c.GetInt("channel")
- channelId := c.GetInt("channel_id")
- userId := c.GetInt("id")
- // group := c.GetString("group")
- tokenName := c.GetString("token_name")
+ tokenId := c.GetInt(ctxkey.TokenId)
+ channelType := c.GetInt(ctxkey.Channel)
+ channelId := c.GetInt(ctxkey.ChannelId)
+ userId := c.GetInt(ctxkey.Id)
+ // group := c.GetString(ctxkey.Group)
+ tokenName := c.GetString(ctxkey.TokenName)
var ttsRequest openai.TextToSpeechRequest
if relayMode == relaymode.AudioSpeech {
@@ -53,7 +55,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus
modelRatio := billingratio.GetModelRatio(audioModel)
// groupRatio := billingratio.GetGroupRatio(group)
- groupRatio := c.GetFloat64("channel_ratio") // get minimal ratio from multiple groups
+ groupRatio := c.GetFloat64(ctxkey.ChannelRatio) // get minimal ratio from multiple groups
ratio := modelRatio * groupRatio
var quota int64
@@ -109,7 +111,7 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus
}()
// map model name
- modelMapping := c.GetString("model_mapping")
+ modelMapping := c.GetString(ctxkey.ModelMapping)
if modelMapping != "" {
modelMap := make(map[string]string)
err := json.Unmarshal([]byte(modelMapping), &modelMap)
@@ -123,8 +125,8 @@ func RelayAudioHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus
baseURL := channeltype.ChannelBaseURLs[channelType]
requestURL := c.Request.URL.String()
- if c.GetString("base_url") != "" {
- baseURL = c.GetString("base_url")
+ if c.GetString(ctxkey.BaseURL) != "" {
+ baseURL = c.GetString(ctxkey.BaseURL)
}
fullRequestURL := openai.GetFullRequestURL(baseURL, requestURL, channelType)
diff --git a/relay/controller/image.go b/relay/controller/image.go
index 822b8557..74ab5c41 100644
--- a/relay/controller/image.go
+++ b/relay/controller/image.go
@@ -9,6 +9,7 @@ import (
"net/http"
"github.com/Laisky/errors/v2"
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/common/logger"
"github.com/Laisky/one-api/model"
"github.com/Laisky/one-api/relay"
@@ -90,7 +91,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus
modelRatio := billingratio.GetModelRatio(imageRequest.Model)
// groupRatio := billingratio.GetGroupRatio(meta.Group)
- groupRatio := c.GetFloat64("channel_ratio") // pre-selected cheapest channel ratio
+ groupRatio := c.GetFloat64(ctxkey.ChannelRatio) // pre-selected cheapest channel ratio
ratio := modelRatio * groupRatio
userQuota, err := model.CacheGetUserQuota(ctx, meta.UserId)
@@ -122,11 +123,11 @@ func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatus
logger.SysError("error update user quota cache: " + err.Error())
}
if quota >= 0 {
- tokenName := c.GetString("token_name")
+ tokenName := c.GetString(ctxkey.TokenName)
logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
model.RecordConsumeLog(ctx, meta.UserId, meta.ChannelId, 0, 0, imageRequest.Model, tokenName, quota, logContent)
model.UpdateUserUsedQuotaAndRequestCount(meta.UserId, quota)
- channelId := c.GetInt("channel_id")
+ channelId := c.GetInt(ctxkey.ChannelId)
model.UpdateChannelUsedQuota(channelId, quota)
}
}(c.Request.Context())
diff --git a/relay/meta/relay_meta.go b/relay/meta/relay_meta.go
index 788f6768..76f05b03 100644
--- a/relay/meta/relay_meta.go
+++ b/relay/meta/relay_meta.go
@@ -1,12 +1,13 @@
package meta
import (
- "github.com/Laisky/one-api/common/config"
+ "strings"
+
+ "github.com/Laisky/one-api/common/ctxkey"
"github.com/Laisky/one-api/relay/adaptor/azure"
"github.com/Laisky/one-api/relay/channeltype"
"github.com/Laisky/one-api/relay/relaymode"
"github.com/gin-gonic/gin"
- "strings"
)
type Meta struct {
@@ -34,19 +35,19 @@ type Meta struct {
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"),
- TokenName: c.GetString("token_name"),
- UserId: c.GetInt("id"),
- Group: c.GetString("group"),
- ModelMapping: c.GetStringMapString("model_mapping"),
- BaseURL: c.GetString("base_url"),
- APIVersion: c.GetString(config.KeyAPIVersion),
+ ChannelType: c.GetInt(ctxkey.Channel),
+ ChannelId: c.GetInt(ctxkey.ChannelId),
+ TokenId: c.GetInt(ctxkey.TokenId),
+ TokenName: c.GetString(ctxkey.TokenName),
+ UserId: c.GetInt(ctxkey.Id),
+ Group: c.GetString(ctxkey.Group),
+ ModelMapping: c.GetStringMapString(ctxkey.ModelMapping),
+ BaseURL: c.GetString(ctxkey.BaseURL),
+ APIVersion: c.GetString(ctxkey.ConfigAPIVersion),
APIKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
Config: nil,
RequestURLPath: c.Request.URL.String(),
- ChannelRatio: c.GetFloat64("channel_ratio"),
+ ChannelRatio: c.GetFloat64(ctxkey.ChannelRatio),
}
if meta.ChannelType == channeltype.Azure {
meta.APIVersion = azure.GetAPIVersion(c)
diff --git a/web/default/src/components/ChannelsTable.js b/web/default/src/components/ChannelsTable.js
index 5280fd2b..1258ca5a 100644
--- a/web/default/src/components/ChannelsTable.js
+++ b/web/default/src/components/ChannelsTable.js
@@ -33,7 +33,7 @@ function renderType(type) {
}
type2label[0] = { value: 0, text: '未知类型', color: 'grey' };
}
- return ;
+ return ;
}
function renderBalance(type, balance) {
diff --git a/web/default/src/components/TokensTable.js b/web/default/src/components/TokensTable.js
index 6483cce1..53c25804 100644
--- a/web/default/src/components/TokensTable.js
+++ b/web/default/src/components/TokensTable.js
@@ -12,6 +12,7 @@ const COPY_OPTIONS = [
];
const OPEN_LINK_OPTIONS = [
+ { key: 'next', text: 'ChatGPT Next Web', value: 'next' },
{ key: 'ama', text: 'BotGem', value: 'ama' },
{ key: 'opencat', text: 'OpenCat', value: 'opencat' },
];
diff --git a/web/default/src/constants/channel.constants.js b/web/default/src/constants/channel.constants.js
index 82fc7d44..0c1f4822 100644
--- a/web/default/src/constants/channel.constants.js
+++ b/web/default/src/constants/channel.constants.js
@@ -19,6 +19,7 @@ export const CHANNEL_OPTIONS = [
{ key: 30, text: 'Ollama', value: 30, color: 'black' },
{ key: 31, text: '零一万物', value: 31, color: 'green' },
{ key: 32, text: '阶跃星辰', value: 32, color: 'blue' },
+ { key: 34, text: 'Coze', value: 34, color: 'blue' },
{ key: 8, text: '自定义渠道', value: 8, color: 'pink' },
{ key: 22, text: '知识库:FastGPT', value: 22, color: 'blue' },
{ key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple' },
diff --git a/web/default/src/pages/Channel/EditChannel.js b/web/default/src/pages/Channel/EditChannel.js
index bcf65ffa..769dab8a 100644
--- a/web/default/src/pages/Channel/EditChannel.js
+++ b/web/default/src/pages/Channel/EditChannel.js
@@ -57,7 +57,8 @@ const EditChannel = () => {
const [config, setConfig] = useState({
region: '',
sk: '',
- ak: ''
+ ak: '',
+ user_id: ''
});
const handleInputChange = (e, { name, value }) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
@@ -156,13 +157,11 @@ const EditChannel = () => {
}, []);
const submit = async () => {
- // some provider as AWS need both AK and SK rather than a single key,
- // so we need to combine them into a single key to achieve the best compatibility.
- if (inputs.ak && inputs.sk) {
- console.log(`combine ak ${inputs.ak} and sk ${inputs.sk}`, inputs.ak, inputs.sk);
- inputs.key = `${inputs.ak}\n${inputs.sk}`;
+ if (inputs.key === '') {
+ if (config.ak !== '' && config.sk !== '' && config.region !== '') {
+ inputs.key = `${config.ak}|${config.sk}|${config.region}`;
+ }
}
-
if (!isEdit && (inputs.name === '' || inputs.key === '')) {
showInfo('请填写渠道名称和渠道密钥!');
return;
@@ -356,6 +355,13 @@ const EditChannel = () => {
)
}
+ {
+ inputs.type === 34 && (
+