Compare commits

...

18 Commits

Author SHA1 Message Date
mrhaoji
f0dc7f3f06 fix: InitChannelCache does not filter disabled channels (#201)
* chore: Show the HTTP status code in the test_time script to determine the success or failure of the request.

* fix: InitChannelCache does not filter disabled channels

* chore: do not hardcode

---------

Co-authored-by: JustSong <songquanpeng@foxmail.com>
2023-06-25 23:14:15 +08:00
mrhaoji
99fed1f850 chore: show the HTTP status code in the test_time script to determine the success or failure of the request (#200) 2023-06-25 22:58:16 +08:00
JustSong
4dc5388a80 chore: do not show completion ratio anymore 2023-06-25 20:29:42 +08:00
JustSong
f81f4c60b2 docs: update README 2023-06-25 15:14:52 +08:00
JustSong
c613d8b6b2 docs: update README 2023-06-25 15:14:09 +08:00
JustSong
7adac1c09c chore: update default ratio for text-embedding-ada-002 2023-06-25 12:07:42 +08:00
mrhaoji
6f05128368 chore: show equivalent amount next to remaining quota in the user editing page (#198) 2023-06-25 11:54:05 +08:00
JustSong
9b178a28a3 feat: support /v1/edits now (close #196) 2023-06-25 11:46:23 +08:00
JustSong
4a6a7f4635 chore: update the number that representing the unlimited quota 2023-06-25 10:52:46 +08:00
JustSong
6b1a24d650 fix: check if token is nil before using it 2023-06-25 10:40:54 +08:00
JustSong
94ba3dd024 chore: billing api now will return a large number if unlimited quota is set 2023-06-25 10:39:22 +08:00
JustSong
f6eb4e5628 perf: validate the request first before send to OpenAI's server 2023-06-25 10:25:33 +08:00
JustSong
57bd907f83 fix: do not record if used quota is zero 2023-06-25 09:59:58 +08:00
JustSong
dd8e8d5ee8 fix: do not charge the user if the amount of tokens used was zero 2023-06-25 09:56:03 +08:00
JustSong
1ca1aa0cdc fix: fix usage is not correct 2023-06-25 09:36:39 +08:00
quzard
f2ba0c0300 fix: fix log sorting (#195) 2023-06-24 21:34:20 +08:00
JustSong
f5c1fcd3c3 fix: do not reuse state variable directly 2023-06-24 19:45:18 +08:00
JustSong
5fdf670a19 fix: reset page number after query 2023-06-24 19:32:46 +08:00
11 changed files with 97 additions and 25 deletions

View File

@@ -10,7 +10,7 @@
# One API
_✨ The all-in-one OpenAI interface, integrates various API access methods, ready to use ✨_
_✨ An OpenAI key management & redistribution system, easy to deploy & use ✨_
</div>

View File

@@ -12,14 +12,16 @@ total_time=0
times=()
for ((i=1; i<=count; i++)); do
result=$(curl -o /dev/null -s -w %{time_total}\\n \
result=$(curl -o /dev/null -s -w "%{http_code} %{time_total}\\n" \
https://"$domain"/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $key" \
-d '{"messages": [{"content": "echo hi", "role": "user"}], "model": "gpt-3.5-turbo", "stream": false, "max_tokens": 1}')
echo "$result"
total_time=$(bc <<< "$total_time + $result")
times+=("$result")
http_code=$(echo "$result" | awk '{print $1}')
time=$(echo "$result" | awk '{print $2}')
echo "HTTP status code: $http_code, Time taken: $time"
total_time=$(bc <<< "$total_time + $time")
times+=("$time")
done
average_time=$(echo "scale=4; $total_time / $count" | bc)

View File

@@ -31,7 +31,7 @@ var ModelRatio = map[string]float64{
"curie": 10,
"babbage": 10,
"ada": 10,
"text-embedding-ada-002": 0.2,
"text-embedding-ada-002": 0.05,
"text-search-ada-doc-001": 10,
"text-moderation-stable": 0.1,
"text-moderation-latest": 0.1,

View File

@@ -32,6 +32,9 @@ func GetSubscription(c *gin.Context) {
if common.DisplayInCurrencyEnabled {
amount /= common.QuotaPerUnit
}
if token != nil && token.UnlimitedQuota {
amount = 100000000
}
subscription := OpenAISubscriptionResponse{
Object: "billing_subscription",
HasPaymentMethod: true,
@@ -71,7 +74,7 @@ func GetUsage(c *gin.Context) {
}
usage := OpenAIUsageResponse{
Object: "list",
TotalUsage: amount,
TotalUsage: amount * 100,
}
c.JSON(200, usage)
return

View File

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

View File

@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"io"
@@ -26,9 +27,32 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
return errorWrapper(err, "bind_request_body_failed", http.StatusBadRequest)
}
}
if relayMode == RelayModeModeration && textRequest.Model == "" {
if relayMode == RelayModeModerations && textRequest.Model == "" {
textRequest.Model = "text-moderation-latest"
}
// request validation
if textRequest.Model == "" {
return errorWrapper(errors.New("model is required"), "required_field_missing", http.StatusBadRequest)
}
switch relayMode {
case RelayModeCompletions:
if textRequest.Prompt == "" {
return errorWrapper(errors.New("field prompt is required"), "required_field_missing", http.StatusBadRequest)
}
case RelayModeChatCompletions:
if textRequest.Messages == nil || len(textRequest.Messages) == 0 {
return errorWrapper(errors.New("field messages is required"), "required_field_missing", http.StatusBadRequest)
}
case RelayModeEmbeddings:
case RelayModeModerations:
if textRequest.Input == "" {
return errorWrapper(errors.New("field input is required"), "required_field_missing", http.StatusBadRequest)
}
case RelayModeEdits:
if textRequest.Instruction == "" {
return errorWrapper(errors.New("field instruction is required"), "required_field_missing", http.StatusBadRequest)
}
}
baseURL := common.ChannelBaseURLs[channelType]
requestURL := c.Request.URL.String()
if c.GetString("base_url") != "" {
@@ -64,7 +88,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model)
case RelayModeCompletions:
promptTokens = countTokenInput(textRequest.Prompt, textRequest.Model)
case RelayModeModeration:
case RelayModeModerations:
promptTokens = countTokenInput(textRequest.Input, textRequest.Model)
}
preConsumedTokens := common.PreConsumedQuota
@@ -124,7 +148,10 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
defer func() {
if consumeQuota {
quota := 0
completionRatio := 1.333333 // default for gpt-3
completionRatio := 1.0
if strings.HasPrefix(textRequest.Model, "gpt-3.5") {
completionRatio = 1.333333
}
if strings.HasPrefix(textRequest.Model, "gpt-4") {
completionRatio = 2
}
@@ -139,17 +166,25 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
if ratio != 0 && quota <= 0 {
quota = 1
}
totalTokens := promptTokens + completionTokens
if totalTokens == 0 {
// in this case, must be some error happened
// we cannot just return, because we may have to return the pre-consumed quota
quota = 0
}
quotaDelta := quota - preConsumedQuota
err := model.PostConsumeTokenQuota(tokenId, quotaDelta)
if err != nil {
common.SysError("error consuming token remain quota: " + err.Error())
}
tokenName := c.GetString("token_name")
logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
model.RecordConsumeLog(userId, promptTokens, completionTokens, textRequest.Model, tokenName, quota, logContent)
model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
channelId := c.GetInt("channel_id")
model.UpdateChannelUsedQuota(channelId, quota)
if quota != 0 {
tokenName := c.GetString("token_name")
logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
model.RecordConsumeLog(userId, promptTokens, completionTokens, textRequest.Model, tokenName, quota, logContent)
model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
channelId := c.GetInt("channel_id")
model.UpdateChannelUsedQuota(channelId, quota)
}
}
}()

View File

@@ -19,8 +19,9 @@ const (
RelayModeChatCompletions
RelayModeCompletions
RelayModeEmbeddings
RelayModeModeration
RelayModeModerations
RelayModeImagesGenerations
RelayModeEdits
)
// https://platform.openai.com/docs/api-reference/chat
@@ -35,6 +36,7 @@ type GeneralOpenAIRequest struct {
TopP float64 `json:"top_p"`
N int `json:"n"`
Input any `json:"input"`
Instruction string `json:"instruction"`
}
type ChatRequest struct {
@@ -99,9 +101,11 @@ func Relay(c *gin.Context) {
} else if strings.HasPrefix(c.Request.URL.Path, "/v1/embeddings") {
relayMode = RelayModeEmbeddings
} else if strings.HasPrefix(c.Request.URL.Path, "/v1/moderations") {
relayMode = RelayModeModeration
relayMode = RelayModeModerations
} else if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") {
relayMode = RelayModeImagesGenerations
} else if strings.HasPrefix(c.Request.URL.Path, "/v1/edits") {
relayMode = RelayModeEdits
}
var err *OpenAIErrorWithStatusCode
switch relayMode {

View File

@@ -108,7 +108,7 @@ var channelSyncLock sync.RWMutex
func InitChannelCache() {
newChannelId2channel := make(map[int]*Channel)
var channels []*Channel
DB.Find(&channels)
DB.Where("status = ?", common.ChannelStatusEnabled).Find(&channels)
for _, channel := range channels {
newChannelId2channel[channel.Id] = channel
}

View File

@@ -19,7 +19,7 @@ func SetRelayRouter(router *gin.Engine) {
{
relayV1Router.POST("/completions", controller.Relay)
relayV1Router.POST("/chat/completions", controller.Relay)
relayV1Router.POST("/edits", controller.RelayNotImplemented)
relayV1Router.POST("/edits", controller.Relay)
relayV1Router.POST("/images/generations", controller.RelayNotImplemented)
relayV1Router.POST("/images/edits", controller.RelayNotImplemented)
relayV1Router.POST("/images/variations", controller.RelayNotImplemented)

View File

@@ -107,7 +107,7 @@ const LogsTable = () => {
if (startIdx === 0) {
setLogs(data);
} else {
let newLogs = logs;
let newLogs = [...logs];
newLogs.push(...data);
setLogs(newLogs);
}
@@ -129,6 +129,7 @@ const LogsTable = () => {
const refresh = async () => {
setLoading(true);
setActivePage(1)
await loadLogs(0);
if (isAdminUser) {
getLogStat().then();
@@ -168,9 +169,17 @@ const LogsTable = () => {
if (logs.length === 0) return;
setLoading(true);
let sortedLogs = [...logs];
sortedLogs.sort((a, b) => {
return ('' + a[key]).localeCompare(b[key]);
});
if (typeof sortedLogs[0][key] === 'string'){
sortedLogs.sort((a, b) => {
return ('' + a[key]).localeCompare(b[key]);
});
} else {
sortedLogs.sort((a, b) => {
if (a[key] === b[key]) return 0;
if (a[key] > b[key]) return -1;
if (a[key] < b[key]) return 1;
});
}
if (sortedLogs[0].id === logs[0].id) {
sortedLogs.reverse();
}

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Button, Form, Header, Segment } from 'semantic-ui-react';
import { useParams } from 'react-router-dom';
import { API, showError, showSuccess } from '../../helpers';
import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render';
const EditUser = () => {
const params = useParams();
@@ -134,7 +135,7 @@ const EditUser = () => {
</Form.Field>
<Form.Field>
<Form.Input
label='剩余额度'
label={`剩余额度${renderQuotaWithPrompt(quota)}`}
name='quota'
placeholder={'请输入新的剩余额度'}
onChange={handleInputChange}