feat: able to configure ratio for more models now (close #53)

This commit is contained in:
JustSong 2023-05-11 20:59:35 +08:00
parent 241ade2fae
commit d9db16e999
6 changed files with 95 additions and 74 deletions

View File

@ -49,11 +49,6 @@ var TurnstileSecretKey = ""
var QuotaForNewUser = 100 var QuotaForNewUser = 100
// https://platform.openai.com/docs/models/model-endpoint-compatibility
var RatioGPT3dot5 float64 = 2
var RatioGPT4 float64 = 30
var RatioGPT4_32k float64 = 60
const ( const (
RoleGuestUser = 0 RoleGuestUser = 0
RoleCommonUser = 1 RoleCommonUser = 1

52
common/model-ratio.go Normal file
View File

@ -0,0 +1,52 @@
package common
import "encoding/json"
// https://platform.openai.com/docs/models/model-endpoint-compatibility
// https://openai.com/pricing
// TODO: when a new api is enabled, check the pricing here
var ModelRatio = map[string]float64{
"gpt-4": 15,
"gpt-4-0314": 15,
"gpt-4-32k": 30,
"gpt-4-32k-0314": 30,
"gpt-3.5-turbo": 1,
"gpt-3.5-turbo-0301": 1,
"text-ada-001": 0.2,
"text-babbage-001": 0.25,
"text-curie-001": 1,
"text-davinci-002": 10,
"text-davinci-003": 10,
"text-davinci-edit-001": 10,
"code-davinci-edit-001": 10,
"whisper-1": 10,
"davinci": 10,
"curie": 10,
"babbage": 10,
"ada": 10,
"text-embedding-ada-002": 0.25,
"text-search-ada-doc-001": 10,
"text-moderation-stable": 10,
"text-moderation-latest": 10,
}
func ModelRatio2JSONString() string {
jsonBytes, err := json.Marshal(ModelRatio)
if err != nil {
SysError("Error marshalling model ratio: " + err.Error())
}
return string(jsonBytes)
}
func UpdateModelRatioByJSONString(jsonStr string) error {
return json.Unmarshal([]byte(jsonStr), &ModelRatio)
}
func GetModelRatio(name string) float64 {
ratio, ok := ModelRatio[name]
if !ok {
SysError("Model ratio not found: " + name)
return 1
}
return ratio
}

View File

@ -118,24 +118,22 @@ func relayHelper(c *gin.Context) error {
defer func() { defer func() {
if consumeQuota { if consumeQuota {
quota := 0 quota := 0
usingGPT4 := strings.HasPrefix(textRequest.Model, "gpt-4")
completionRatio := 1
if usingGPT4 {
completionRatio = 2
}
if isStream { if isStream {
var text string var promptText string
for _, message := range textRequest.Messages { for _, message := range textRequest.Messages {
text += fmt.Sprintf("%s: %s\n", message.Role, message.Content) promptText += fmt.Sprintf("%s: %s\n", message.Role, message.Content)
} }
text += fmt.Sprintf("%s: %s\n", "assistant", streamResponseText) completionText := fmt.Sprintf("%s: %s\n", "assistant", streamResponseText)
quota = countToken(text) + 3 quota = countToken(promptText) + countToken(completionText)*completionRatio + 3
} else { } else {
quota = textResponse.Usage.TotalTokens quota = textResponse.Usage.PromptTokens + textResponse.Usage.CompletionTokens*completionRatio
}
ratio := common.RatioGPT3dot5
if strings.HasPrefix(textRequest.Model, "gpt-4-32k") {
ratio = common.RatioGPT4_32k
} else if strings.HasPrefix(textRequest.Model, "gpt-4") {
ratio = common.RatioGPT4
} else {
ratio = common.RatioGPT3dot5
} }
ratio := common.GetModelRatio(textRequest.Model)
quota = int(float64(quota) * ratio) quota = int(float64(quota) * ratio)
err := model.DecreaseTokenQuota(tokenId, quota) err := model.DecreaseTokenQuota(tokenId, quota)
if err != nil { if err != nil {

View File

@ -47,9 +47,7 @@ func InitOptionMap() {
common.OptionMap["TurnstileSiteKey"] = "" common.OptionMap["TurnstileSiteKey"] = ""
common.OptionMap["TurnstileSecretKey"] = "" common.OptionMap["TurnstileSecretKey"] = ""
common.OptionMap["QuotaForNewUser"] = strconv.Itoa(common.QuotaForNewUser) common.OptionMap["QuotaForNewUser"] = strconv.Itoa(common.QuotaForNewUser)
common.OptionMap["RatioGPT3dot5"] = strconv.FormatFloat(common.RatioGPT3dot5, 'f', -1, 64) common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
common.OptionMap["RatioGPT4"] = strconv.FormatFloat(common.RatioGPT4, 'f', -1, 64)
common.OptionMap["RatioGPT4_32k"] = strconv.FormatFloat(common.RatioGPT4_32k, 'f', -1, 64)
common.OptionMap["TopUpLink"] = common.TopUpLink common.OptionMap["TopUpLink"] = common.TopUpLink
common.OptionMapRWMutex.Unlock() common.OptionMapRWMutex.Unlock()
options, _ := AllOption() options, _ := AllOption()
@ -75,7 +73,7 @@ func UpdateOption(key string, value string) error {
return nil return nil
} }
func updateOptionMap(key string, value string) { func updateOptionMap(key string, value string) (err error) {
common.OptionMapRWMutex.Lock() common.OptionMapRWMutex.Lock()
defer common.OptionMapRWMutex.Unlock() defer common.OptionMapRWMutex.Unlock()
common.OptionMap[key] = value common.OptionMap[key] = value
@ -138,13 +136,10 @@ func updateOptionMap(key string, value string) {
common.TurnstileSecretKey = value common.TurnstileSecretKey = value
case "QuotaForNewUser": case "QuotaForNewUser":
common.QuotaForNewUser, _ = strconv.Atoi(value) common.QuotaForNewUser, _ = strconv.Atoi(value)
case "RatioGPT3dot5": case "ModelRatio":
common.RatioGPT3dot5, _ = strconv.ParseFloat(value, 64) err = common.UpdateModelRatioByJSONString(value)
case "RatioGPT4":
common.RatioGPT4, _ = strconv.ParseFloat(value, 64)
case "RatioGPT4_32k":
common.RatioGPT4_32k, _ = strconv.ParseFloat(value, 64)
case "TopUpLink": case "TopUpLink":
common.TopUpLink = value common.TopUpLink = value
} }
return err
} }

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Divider, Form, Grid, Header, Message } from 'semantic-ui-react'; import { Divider, Form, Grid, Header, Message } from 'semantic-ui-react';
import { API, removeTrailingSlash, showError } from '../helpers'; import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers';
const SystemSetting = () => { const SystemSetting = () => {
let [inputs, setInputs] = useState({ let [inputs, setInputs] = useState({
@ -25,9 +25,7 @@ const SystemSetting = () => {
TurnstileSecretKey: '', TurnstileSecretKey: '',
RegisterEnabled: '', RegisterEnabled: '',
QuotaForNewUser: 0, QuotaForNewUser: 0,
RatioGPT3dot5: 2, ModelRatio: '',
RatioGPT4: 30,
RatioGPT4_32k: 60,
TopUpLink: '' TopUpLink: ''
}); });
let originInputs = {}; let originInputs = {};
@ -93,7 +91,7 @@ const SystemSetting = () => {
name === 'TurnstileSiteKey' || name === 'TurnstileSiteKey' ||
name === 'TurnstileSecretKey' || name === 'TurnstileSecretKey' ||
name === 'QuotaForNewUser' || name === 'QuotaForNewUser' ||
name.startsWith('Ratio') || name === 'ModelRatio' ||
name === 'TopUpLink' name === 'TopUpLink'
) { ) {
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
@ -111,19 +109,17 @@ const SystemSetting = () => {
if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) { if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
await updateOption('QuotaForNewUser', inputs.QuotaForNewUser); await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
} }
if (originInputs['RatioGPT3dot5'] !== inputs.RatioGPT3dot5) { if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
await updateOption('RatioGPT3dot5', inputs.RatioGPT3dot5); if (!verifyJSON(inputs.ModelRatio)) {
} showError('模型倍率不是合法的 JSON 字符串');
if (originInputs['RatioGPT4'] !== inputs.RatioGPT4) { return;
await updateOption('RatioGPT4', inputs.RatioGPT4); }
} await updateOption('ModelRatio', inputs.ModelRatio);
if (originInputs['RatioGPT4_32k'] !== inputs.RatioGPT4_32k) {
await updateOption('RatioGPT4_32k', inputs.RatioGPT4_32k);
} }
if (originInputs['TopUpLink'] !== inputs.TopUpLink) { if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
await updateOption('TopUpLink', inputs.TopUpLink); await updateOption('TopUpLink', inputs.TopUpLink);
} }
} };
const submitSMTP = async () => { const submitSMTP = async () => {
if (originInputs['SMTPServer'] !== inputs.SMTPServer) { if (originInputs['SMTPServer'] !== inputs.SMTPServer) {
@ -278,39 +274,15 @@ const SystemSetting = () => {
placeholder='例如发卡网站的购买链接' placeholder='例如发卡网站的购买链接'
/> />
</Form.Group> </Form.Group>
<Form.Group widths={3}> <Form.Group widths='equal'>
<Form.Input <Form.TextArea
label='GPT-3.5 系列模型倍率' label='模型倍率'
name='RatioGPT3dot5' name='ModelRatio'
onChange={handleInputChange} onChange={handleInputChange}
style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
autoComplete='off' autoComplete='off'
value={inputs.RatioGPT3dot5} value={inputs.ModelRatio}
type='number' placeholder='为一个 JSON 文本,键为模型名称,值为倍率'
step='0.01'
min='0'
placeholder='例如2'
/>
<Form.Input
label='GPT-4 系列模型倍率'
name='RatioGPT4'
onChange={handleInputChange}
autoComplete='off'
value={inputs.RatioGPT4}
type='number'
step='0.01'
min='0'
placeholder='例如30'
/>
<Form.Input
label='GPT-4 32k 系列模型倍率'
name='RatioGPT4_32k'
onChange={handleInputChange}
autoComplete='off'
value={inputs.RatioGPT4_32k}
type='number'
step='0.01'
min='0'
placeholder='例如60'
/> />
</Form.Group> </Form.Group>
<Form.Button onClick={submitOperationConfig}>保存运营设置</Form.Button> <Form.Button onClick={submitOperationConfig}>保存运营设置</Form.Button>

View File

@ -152,4 +152,13 @@ export function downloadTextAsFile(text, filename) {
a.href = url; a.href = url;
a.download = filename; a.download = filename;
a.click(); a.click();
} }
export const verifyJSON = (str) => {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
};