Compare commits

...

4 Commits

Author SHA1 Message Date
JustSong
4139a7036f chore: make subscription api compatible with official api 2023-07-15 23:01:54 +08:00
JustSong
02da0b51f8 docs: update README 2023-07-15 19:07:38 +08:00
JustSong
35cfebee12 feat: retry on failed (close #112) 2023-07-15 19:06:51 +08:00
JustSong
0e088f7c3e feat: support ChatGLM2 (close #274) 2023-07-15 17:07:05 +08:00
8 changed files with 73 additions and 19 deletions

View File

@@ -81,17 +81,19 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
12. 支持以美元为单位显示额度。 12. 支持以美元为单位显示额度。
13. 支持发布公告,设置充值链接,设置新用户初始额度。 13. 支持发布公告,设置充值链接,设置新用户初始额度。
14. 支持模型映射,重定向用户的请求模型。 14. 支持模型映射,重定向用户的请求模型。
15. 支持绘图接口 15. 支持失败自动重试
16. 支持丰富的**自定义**设置, 16. 支持绘图接口。
17. 支持丰富的**自定义**设置,
1. 支持自定义系统名称logo 以及页脚。 1. 支持自定义系统名称logo 以及页脚。
2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。 2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
17. 支持通过系统访问令牌访问管理 API。 18. 支持通过系统访问令牌访问管理 API。
18. 支持 Cloudflare Turnstile 用户校验。 19. 支持 Cloudflare Turnstile 用户校验。
19. 支持用户管理,支持**多种用户登录注册方式** 20. 支持用户管理,支持**多种用户登录注册方式**
+ 邮箱登录注册以及通过邮箱进行密码重置。 + 邮箱登录注册以及通过邮箱进行密码重置。
+ [GitHub 开放授权](https://github.com/settings/applications/new)。 + [GitHub 开放授权](https://github.com/settings/applications/new)。
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。 + 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
20. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式 21. 支持 [ChatGLM](https://github.com/THUDM/ChatGLM2-6B)
22. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
## 部署 ## 部署
### 基于 Docker 进行部署 ### 基于 Docker 进行部署

View File

@@ -68,6 +68,7 @@ var AutomaticDisableChannelEnabled = false
var QuotaRemindThreshold = 1000 var QuotaRemindThreshold = 1000
var PreConsumedQuota = 500 var PreConsumedQuota = 500
var ApproximateTokenEnabled = false var ApproximateTokenEnabled = false
var RetryTimes = 0
var RootUserEmail = "" var RootUserEmail = ""

View File

@@ -7,16 +7,19 @@ import (
) )
func GetSubscription(c *gin.Context) { func GetSubscription(c *gin.Context) {
var quota int var remainQuota int
var usedQuota int
var err error var err error
var token *model.Token var token *model.Token
if common.DisplayTokenStatEnabled { if common.DisplayTokenStatEnabled {
tokenId := c.GetInt("token_id") tokenId := c.GetInt("token_id")
token, err = model.GetTokenById(tokenId) token, err = model.GetTokenById(tokenId)
quota = token.RemainQuota remainQuota = token.RemainQuota
usedQuota = token.UsedQuota
} else { } else {
userId := c.GetInt("id") userId := c.GetInt("id")
quota, err = model.GetUserQuota(userId) remainQuota, err = model.GetUserQuota(userId)
usedQuota, err = model.GetUserUsedQuota(userId)
} }
if err != nil { if err != nil {
openAIError := OpenAIError{ openAIError := OpenAIError{
@@ -28,6 +31,7 @@ func GetSubscription(c *gin.Context) {
}) })
return return
} }
quota := remainQuota + usedQuota
amount := float64(quota) amount := float64(quota)
if common.DisplayInCurrencyEnabled { if common.DisplayInCurrencyEnabled {
amount /= common.QuotaPerUnit amount /= common.QuotaPerUnit

View File

@@ -252,6 +252,24 @@ func init() {
Root: "code-davinci-edit-001", Root: "code-davinci-edit-001",
Parent: nil, Parent: nil,
}, },
{
Id: "ChatGLM",
Object: "model",
Created: 1677649963,
OwnedBy: "thudm",
Permission: permission,
Root: "ChatGLM",
Parent: nil,
},
{
Id: "ChatGLM2",
Object: "model",
Created: 1677649963,
OwnedBy: "thudm",
Permission: permission,
Root: "ChatGLM2",
Parent: nil,
},
} }
openAIModelsMap = make(map[string]OpenAIModels) openAIModelsMap = make(map[string]OpenAIModels)
for _, model := range openAIModels { for _, model := range openAIModels {

View File

@@ -227,8 +227,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
return 0, nil, nil return 0, nil, nil
} }
if i := strings.Index(string(data), "\n\n"); i >= 0 { if i := strings.Index(string(data), "\n"); i >= 0 {
return i + 2, data[0:i], nil return i + 1, data[0:i], nil
} }
if atEOF { if atEOF {
@@ -242,8 +242,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
go func() { go func() {
for scanner.Scan() { for scanner.Scan() {
data := scanner.Text() data := scanner.Text()
if len(data) < 6 { // must be something wrong! if len(data) < 6 { // ignore blank line or wrong format
common.SysError("invalid stream response: " + data)
continue continue
} }
dataChan <- data dataChan <- data
@@ -286,6 +285,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
if strings.HasPrefix(data, "data: [DONE]") { if strings.HasPrefix(data, "data: [DONE]") {
data = data[:12] data = data[:12]
} }
// some implementations may add \r at the end of data
data = strings.TrimSuffix(data, "\r")
c.Render(-1, common.CustomEvent{Data: data}) c.Render(-1, common.CustomEvent{Data: data})
return true return true
case <-stopChan: case <-stopChan:

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"one-api/common" "one-api/common"
"strconv"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -132,12 +133,21 @@ func Relay(c *gin.Context) {
err = relayTextHelper(c, relayMode) err = relayTextHelper(c, relayMode)
} }
if err != nil { if err != nil {
if err.StatusCode == http.StatusTooManyRequests { retryTimesStr := c.Query("retry")
err.OpenAIError.Message = "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。" retryTimes, _ := strconv.Atoi(retryTimesStr)
if retryTimesStr == "" {
retryTimes = common.RetryTimes
}
if retryTimes > 0 {
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s?retry=%d", c.Request.URL.Path, retryTimes-1))
} else {
if err.StatusCode == http.StatusTooManyRequests {
err.OpenAIError.Message = "当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。"
}
c.JSON(err.StatusCode, gin.H{
"error": err.OpenAIError,
})
} }
c.JSON(err.StatusCode, gin.H{
"error": err.OpenAIError,
})
channelId := c.GetInt("channel_id") 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 // https://platform.openai.com/docs/guides/error-codes/api-errors

View File

@@ -68,6 +68,7 @@ func InitOptionMap() {
common.OptionMap["TopUpLink"] = common.TopUpLink common.OptionMap["TopUpLink"] = common.TopUpLink
common.OptionMap["ChatLink"] = common.ChatLink common.OptionMap["ChatLink"] = common.ChatLink
common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64) common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
common.OptionMapRWMutex.Unlock() common.OptionMapRWMutex.Unlock()
loadOptionsFromDatabase() loadOptionsFromDatabase()
} }
@@ -196,6 +197,8 @@ func updateOptionMap(key string, value string) (err error) {
common.QuotaRemindThreshold, _ = strconv.Atoi(value) common.QuotaRemindThreshold, _ = strconv.Atoi(value)
case "PreConsumedQuota": case "PreConsumedQuota":
common.PreConsumedQuota, _ = strconv.Atoi(value) common.PreConsumedQuota, _ = strconv.Atoi(value)
case "RetryTimes":
common.RetryTimes, _ = strconv.Atoi(value)
case "ModelRatio": case "ModelRatio":
err = common.UpdateModelRatioByJSONString(value) err = common.UpdateModelRatioByJSONString(value)
case "GroupRatio": case "GroupRatio":

View File

@@ -20,6 +20,7 @@ const OperationSetting = () => {
DisplayInCurrencyEnabled: '', DisplayInCurrencyEnabled: '',
DisplayTokenStatEnabled: '', DisplayTokenStatEnabled: '',
ApproximateTokenEnabled: '', ApproximateTokenEnabled: '',
RetryTimes: 0,
}); });
const [originInputs, setOriginInputs] = useState({}); const [originInputs, setOriginInputs] = useState({});
let [loading, setLoading] = useState(false); let [loading, setLoading] = useState(false);
@@ -122,6 +123,9 @@ const OperationSetting = () => {
if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) { if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) {
await updateOption('QuotaPerUnit', inputs.QuotaPerUnit); await updateOption('QuotaPerUnit', inputs.QuotaPerUnit);
} }
if (originInputs['RetryTimes'] !== inputs.RetryTimes) {
await updateOption('RetryTimes', inputs.RetryTimes);
}
break; break;
} }
}; };
@@ -133,7 +137,7 @@ const OperationSetting = () => {
<Header as='h3'> <Header as='h3'>
通用设置 通用设置
</Header> </Header>
<Form.Group widths={3}> <Form.Group widths={4}>
<Form.Input <Form.Input
label='充值链接' label='充值链接'
name='TopUpLink' name='TopUpLink'
@@ -162,6 +166,17 @@ const OperationSetting = () => {
step='0.01' step='0.01'
placeholder='一单位货币能兑换的额度' placeholder='一单位货币能兑换的额度'
/> />
<Form.Input
label='失败重试次数'
name='RetryTimes'
type={'number'}
step='1'
min='0'
onChange={handleInputChange}
autoComplete='new-password'
value={inputs.RetryTimes}
placeholder='失败重试次数'
/>
</Form.Group> </Form.Group>
<Form.Group inline> <Form.Group inline>
<Form.Checkbox <Form.Checkbox