Merge remote-tracking branch 'songquanpeng/main'
# Conflicts: # controller/relay-text.go
This commit is contained in:
commit
b66bd7bf75
@ -86,7 +86,7 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
||||
|
||||
## 部署
|
||||
### 基于 Docker 进行部署
|
||||
部署命令:`docker run --name one-api -d --restart always -p 3000:3000 -v /home/ubuntu/data/one-api:/data justsong/one-api`
|
||||
部署命令:`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`
|
||||
|
||||
更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR`
|
||||
|
||||
@ -266,8 +266,8 @@ https://openai.justsong.cn
|
||||
|
||||
## 常见问题
|
||||
1. 额度是什么?怎么计算的?One API 的额度计算有问题?
|
||||
+ 额度 = token * 倍率
|
||||
+ 倍率包括分组的倍率,以及补全的倍率。
|
||||
+ 额度 = 分组倍率 * 模型倍率 * (提示 token 数 + 补全 token 数 * 补全倍率)
|
||||
+ 其中补全倍率对于 GPT3.5 固定为 1.33,GPT4 为 2,与官方保持一致。
|
||||
+ 如果是非流模式,官方接口会返回消耗的总 token,但是你要注意提示和补全的消耗倍率不一样。
|
||||
2. 账户额度足够为什么提示额度不足?
|
||||
+ 请检查你的令牌额度是否足够,这个和账户额度是分开的。
|
||||
|
@ -11,7 +11,7 @@ var GroupRatio = map[string]float64{
|
||||
func GroupRatio2JSONString() string {
|
||||
jsonBytes, err := json.Marshal(GroupRatio)
|
||||
if err != nil {
|
||||
SysError("Error marshalling model ratio: " + err.Error())
|
||||
SysError("error marshalling model ratio: " + err.Error())
|
||||
}
|
||||
return string(jsonBytes)
|
||||
}
|
||||
@ -24,7 +24,7 @@ func UpdateGroupRatioByJSONString(jsonStr string) error {
|
||||
func GetGroupRatio(name string) float64 {
|
||||
ratio, ok := GroupRatio[name]
|
||||
if !ok {
|
||||
SysError("Group ratio not found: " + name)
|
||||
SysError("group ratio not found: " + name)
|
||||
return 1
|
||||
}
|
||||
return ratio
|
||||
|
@ -40,7 +40,7 @@ var ModelRatio = map[string]float64{
|
||||
func ModelRatio2JSONString() string {
|
||||
jsonBytes, err := json.Marshal(ModelRatio)
|
||||
if err != nil {
|
||||
SysError("Error marshalling model ratio: " + err.Error())
|
||||
SysError("error marshalling model ratio: " + err.Error())
|
||||
}
|
||||
return string(jsonBytes)
|
||||
}
|
||||
@ -53,7 +53,7 @@ func UpdateModelRatioByJSONString(jsonStr string) error {
|
||||
func GetModelRatio(name string) float64 {
|
||||
ratio, ok := ModelRatio[name]
|
||||
if !ok {
|
||||
SysError("Model ratio not found: " + name)
|
||||
SysError("model ratio not found: " + name)
|
||||
return 30
|
||||
}
|
||||
return ratio
|
||||
|
@ -20,7 +20,7 @@ func InitRedisClient() (err error) {
|
||||
SysLog("Redis is enabled")
|
||||
opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING"))
|
||||
if err != nil {
|
||||
FatalLog(err)
|
||||
FatalLog("failed to parse Redis connection string: " + err.Error())
|
||||
}
|
||||
RDB = redis.NewClient(opt)
|
||||
|
||||
@ -28,13 +28,16 @@ func InitRedisClient() (err error) {
|
||||
defer cancel()
|
||||
|
||||
_, err = RDB.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
FatalLog("Redis ping test failed: " + err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ParseRedisOption() *redis.Options {
|
||||
opt, err := redis.ParseURL(os.Getenv("REDIS_CONN_STRING"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
FatalLog("failed to parse Redis connection string: " + err.Error())
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func disableChannel(channelId int, channelName string, reason string) {
|
||||
content := fmt.Sprintf("通道「%s」(#%d)已被禁用,原因:%s", channelName, channelId, reason)
|
||||
err := common.SendEmail(subject, common.RootUserEmail, content)
|
||||
if err != nil {
|
||||
common.SysError(fmt.Sprintf("发送邮件失败:%s", err.Error()))
|
||||
common.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ func testAllChannels(c *gin.Context) error {
|
||||
}
|
||||
err := common.SendEmail("通道测试完成", common.RootUserEmail, "通道测试完成,如果没有收到禁用通知,说明所有通道都正常")
|
||||
if err != nil {
|
||||
common.SysError(fmt.Sprintf("发送邮件失败:%s", err.Error()))
|
||||
common.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
|
||||
}
|
||||
testAllChannelsLock.Lock()
|
||||
testAllChannelsRunning = false
|
||||
|
@ -152,7 +152,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
||||
quotaDelta := quota - preConsumedQuota
|
||||
err := model.PostConsumeTokenQuota(tokenId, quotaDelta)
|
||||
if err != nil {
|
||||
common.SysError("Error consuming token remain quota: " + err.Error())
|
||||
common.SysError("error consuming token remain quota: " + err.Error())
|
||||
}
|
||||
//tokenName := c.GetString("token_name")
|
||||
//model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)", tokenName, textRequest.Model, common.LogQuota(quota), modelRatio, groupRatio))
|
||||
@ -186,7 +186,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
||||
for scanner.Scan() {
|
||||
data := scanner.Text()
|
||||
if len(data) < 6 { // must be something wrong!
|
||||
common.SysError("Invalid stream response: " + data)
|
||||
common.SysError("invalid stream response: " + data)
|
||||
continue
|
||||
}
|
||||
dataChan <- data
|
||||
@ -197,7 +197,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
||||
var streamResponse ChatCompletionsStreamResponse
|
||||
err = json.Unmarshal([]byte(data), &streamResponse)
|
||||
if err != nil {
|
||||
common.SysError("Error unmarshalling stream response: " + err.Error())
|
||||
common.SysError("error unmarshalling stream response: " + err.Error())
|
||||
return
|
||||
}
|
||||
for _, choice := range streamResponse.Choices {
|
||||
@ -207,7 +207,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
||||
var streamResponse CompletionsStreamResponse
|
||||
err = json.Unmarshal([]byte(data), &streamResponse)
|
||||
if err != nil {
|
||||
common.SysError("Error unmarshalling stream response: " + err.Error())
|
||||
common.SysError("error unmarshalling stream response: " + err.Error())
|
||||
return
|
||||
}
|
||||
for _, choice := range streamResponse.Choices {
|
||||
|
@ -119,7 +119,7 @@ func Relay(c *gin.Context) {
|
||||
"error": err.OpenAIError,
|
||||
})
|
||||
channelId := c.GetInt("channel_id")
|
||||
common.SysError(fmt.Sprintf("Relay error (channel #%d): %s", channelId, err.Message))
|
||||
common.SysError(fmt.Sprintf("relay error (channel #%d): %s", channelId, err.Message))
|
||||
// https://platform.openai.com/docs/guides/error-codes/api-errors
|
||||
if common.AutomaticDisableChannelEnabled && (err.Type == "insufficient_quota" || err.Code == "invalid_api_key") {
|
||||
channelId := c.GetInt("channel_id")
|
||||
|
@ -2,7 +2,7 @@ version: '3.4'
|
||||
|
||||
services:
|
||||
one-api:
|
||||
image: ghcr.io/songquanpeng/one-api:latest
|
||||
image: justsong/one-api:latest
|
||||
container_name: one-api
|
||||
restart: always
|
||||
command: --log-dir /app/logs
|
||||
@ -11,12 +11,22 @@ services:
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./logs:/app/logs
|
||||
# environment:
|
||||
# REDIS_CONN_STRING: redis://default:redispw@localhost:49153
|
||||
# SESSION_SECRET: random_string
|
||||
# SQL_DSN: root:123456@tcp(localhost:3306)/one-api
|
||||
environment:
|
||||
- SQL_DSN=root:123456@tcp(host.docker.internal:3306)/one-api # 修改此行,或注释掉以使用 SQLite 作为数据库
|
||||
- REDIS_CONN_STRING=redis://redis:6379
|
||||
- SESSION_SECRET=random_string # 修改为随机字符串
|
||||
- TZ=Asia/Shanghai
|
||||
# - SYNC_FREQUENCY=60 # 多机部署时从节点取消注释该行
|
||||
# - FRONTEND_BASE_URL=https://openai.justsong.cn # 多机部署时从节点取消注释该行
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk '{print $2}' | grep 'true'"]
|
||||
test: [ "CMD-SHELL", "curl -s http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk '{print $2}' | grep 'true'" ]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
container_name: redis
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
11
main.go
11
main.go
@ -6,7 +6,6 @@ import (
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-contrib/sessions/redis"
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"one-api/common"
|
||||
"one-api/middleware"
|
||||
"one-api/model"
|
||||
@ -30,19 +29,19 @@ func main() {
|
||||
// Initialize SQL Database
|
||||
err := model.InitDB()
|
||||
if err != nil {
|
||||
common.FatalLog(err)
|
||||
common.FatalLog("failed to initialize database: " + err.Error())
|
||||
}
|
||||
defer func() {
|
||||
err := model.CloseDB()
|
||||
if err != nil {
|
||||
common.FatalLog(err)
|
||||
common.FatalLog("failed to close database: " + err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize Redis
|
||||
err = common.InitRedisClient()
|
||||
if err != nil {
|
||||
common.FatalLog(err)
|
||||
common.FatalLog("failed to initialize Redis: " + err.Error())
|
||||
}
|
||||
|
||||
// Initialize options
|
||||
@ -53,7 +52,7 @@ func main() {
|
||||
if os.Getenv("SYNC_FREQUENCY") != "" {
|
||||
frequency, err := strconv.Atoi(os.Getenv("SYNC_FREQUENCY"))
|
||||
if err != nil {
|
||||
common.FatalLog(err)
|
||||
common.FatalLog("failed to parse SYNC_FREQUENCY: " + err.Error())
|
||||
}
|
||||
go model.SyncOptions(frequency)
|
||||
if common.RedisEnabled {
|
||||
@ -84,6 +83,6 @@ func main() {
|
||||
}
|
||||
err = server.Run(":" + port)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
common.FatalLog("failed to start HTTP server: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -137,13 +137,13 @@ func InitChannelCache() {
|
||||
channelSyncLock.Lock()
|
||||
group2model2channels = newGroup2model2channels
|
||||
channelSyncLock.Unlock()
|
||||
common.SysLog("Channels synced from database")
|
||||
common.SysLog("channels synced from database")
|
||||
}
|
||||
|
||||
func SyncChannelCache(frequency int) {
|
||||
for {
|
||||
time.Sleep(time.Duration(frequency) * time.Second)
|
||||
common.SysLog("Syncing channels from database")
|
||||
common.SysLog("syncing channels from database")
|
||||
InitChannelCache()
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func InitDB() (err error) {
|
||||
PrepareStmt: true, // precompile SQL
|
||||
})
|
||||
}
|
||||
common.SysLog("Database connected")
|
||||
common.SysLog("database connected")
|
||||
if err == nil {
|
||||
DB = db
|
||||
if !common.IsMasterNode {
|
||||
@ -88,7 +88,7 @@ func InitDB() (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
common.SysLog("Database migrated")
|
||||
common.SysLog("database migrated")
|
||||
err = createRootAccountIfNeed()
|
||||
return err
|
||||
} else {
|
||||
|
@ -75,7 +75,7 @@ func loadOptionsFromDatabase() {
|
||||
for _, option := range options {
|
||||
err := updateOptionMap(option.Key, option.Value)
|
||||
if err != nil {
|
||||
common.SysError("Failed to update option map: " + err.Error())
|
||||
common.SysError("failed to update option map: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,7 +83,7 @@ func loadOptionsFromDatabase() {
|
||||
func SyncOptions(frequency int) {
|
||||
for {
|
||||
time.Sleep(time.Duration(frequency) * time.Second)
|
||||
common.SysLog("Syncing options from database")
|
||||
common.SysLog("syncing options from database")
|
||||
loadOptionsFromDatabase()
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func Redeem(key string, userId int) (quota int, err error) {
|
||||
redemption.Status = common.RedemptionCodeStatusUsed
|
||||
err := redemption.SelectUpdate()
|
||||
if err != nil {
|
||||
common.SysError("更新兑换码状态失败:" + err.Error())
|
||||
common.SysError("failed to update redemption status: " + err.Error())
|
||||
}
|
||||
RecordLog(userId, LogTypeTopup, fmt.Sprintf("通过兑换码充值 %s", common.LogQuota(redemption.Quota)))
|
||||
}()
|
||||
|
@ -45,7 +45,7 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
||||
token.Status = common.TokenStatusExpired
|
||||
err := token.SelectUpdate()
|
||||
if err != nil {
|
||||
common.SysError("更新令牌状态失败:" + err.Error())
|
||||
common.SysError("failed to update token status" + err.Error())
|
||||
}
|
||||
return nil, errors.New("该令牌已过期")
|
||||
}
|
||||
@ -53,7 +53,7 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
||||
token.Status = common.TokenStatusExhausted
|
||||
err := token.SelectUpdate()
|
||||
if err != nil {
|
||||
common.SysError("更新令牌状态失败:" + err.Error())
|
||||
common.SysError("failed to update token status" + err.Error())
|
||||
}
|
||||
return nil, errors.New("该令牌额度已用尽")
|
||||
}
|
||||
@ -61,7 +61,7 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
||||
token.AccessedTime = common.GetTimestamp()
|
||||
err := token.SelectUpdate()
|
||||
if err != nil {
|
||||
common.SysError("更新令牌失败:" + err.Error())
|
||||
common.SysError("failed to update token" + err.Error())
|
||||
}
|
||||
}()
|
||||
return token, nil
|
||||
@ -166,7 +166,7 @@ func PreConsumeTokenQuota(tokenId int, quota int) (err error) {
|
||||
go func() {
|
||||
email, err := GetUserEmail(token.UserId)
|
||||
if err != nil {
|
||||
common.SysError("获取用户邮箱失败:" + err.Error())
|
||||
common.SysError("failed to fetch user email: " + err.Error())
|
||||
}
|
||||
prompt := "您的额度即将用尽"
|
||||
if noMoreQuota {
|
||||
@ -177,7 +177,7 @@ func PreConsumeTokenQuota(tokenId int, quota int) (err error) {
|
||||
err = common.SendEmail(prompt, email,
|
||||
fmt.Sprintf("%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。<br/>充值链接:<a href='%s'>%s</a>", prompt, userQuota, topUpLink, topUpLink))
|
||||
if err != nil {
|
||||
common.SysError("发送邮件失败:" + err.Error())
|
||||
common.SysError("failed to send email" + err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -220,7 +220,7 @@ func IsAdmin(userId int) bool {
|
||||
var user User
|
||||
err := DB.Where("id = ?", userId).Select("role").Find(&user).Error
|
||||
if err != nil {
|
||||
common.SysError("No such user " + err.Error())
|
||||
common.SysError("no such user " + err.Error())
|
||||
return false
|
||||
}
|
||||
return user.Role >= common.RoleAdminUser
|
||||
@ -233,7 +233,7 @@ func IsUserEnabled(userId int) bool {
|
||||
var user User
|
||||
err := DB.Where("id = ?", userId).Select("status").Find(&user).Error
|
||||
if err != nil {
|
||||
common.SysError("No such user " + err.Error())
|
||||
common.SysError("no such user " + err.Error())
|
||||
return false
|
||||
}
|
||||
return user.Status == common.UserStatusEnabled
|
||||
@ -300,6 +300,6 @@ func UpdateUserUsedQuotaAndRequestCount(id int, quota int) {
|
||||
},
|
||||
).Error
|
||||
if err != nil {
|
||||
common.SysError("Failed to update user used quota and request count: " + err.Error())
|
||||
common.SysError("failed to update user used quota and request count: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
# File path: /etc/systemd/system/one-api.service
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl start one-api
|
||||
# sudo systemctl enable one-api
|
||||
# sudo systemctl status one-api
|
||||
[Unit]
|
||||
Description=One API Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=yourusername # 守护进程用户名
|
||||
WorkingDirectory=/path/to/One-API # One API运行路径
|
||||
ExecStart=/path/to/One-API/one-api --port 3000 --log-dir /path/to/One-API/logs # 端口
|
||||
User=ubuntu # 注意修改用户名
|
||||
WorkingDirectory=/path/to/one-api # 注意修改路径
|
||||
ExecStart=/path/to/one-api/one-api --port 3000 --log-dir /path/to/one-api/logs # 注意修改路径和端口号
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user