mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-07 17:23:42 +08:00
Compare commits
5 Commits
v0.5.6-alp
...
v0.5.6-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47c08c72ce | ||
|
|
53b2cace0b | ||
|
|
f0fc991b44 | ||
|
|
594f06e7b0 | ||
|
|
197d1d7a9d |
@@ -59,6 +59,9 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
|
|||||||
> **Warning**
|
> **Warning**
|
||||||
> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。
|
> 使用 Docker 拉取的最新镜像可能是 `alpha` 版本,如果追求稳定性请手动指定版本。
|
||||||
|
|
||||||
|
> **Warning**
|
||||||
|
> 使用 root 用户初次登录系统后,务必修改默认密码 `123456`!
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
1. 支持多种大模型:
|
1. 支持多种大模型:
|
||||||
+ [x] [OpenAI ChatGPT 系列模型](https://platform.openai.com/docs/guides/gpt/chat-completions-api)(支持 [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference))
|
+ [x] [OpenAI ChatGPT 系列模型](https://platform.openai.com/docs/guides/gpt/chat-completions-api)(支持 [Azure OpenAI API](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -31,6 +32,9 @@ func relayAudioHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError)
|
return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
if userQuota-preConsumedQuota < 0 {
|
||||||
|
return errorWrapper(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden)
|
||||||
|
}
|
||||||
err = model.CacheDecreaseUserQuota(userId, preConsumedQuota)
|
err = model.CacheDecreaseUserQuota(userId, preConsumedQuota)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError)
|
return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func relayImageHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode
|
|||||||
quota := int(ratio*sizeRatio*1000) * imageRequest.N
|
quota := int(ratio*sizeRatio*1000) * imageRequest.N
|
||||||
|
|
||||||
if consumeQuota && userQuota-quota < 0 {
|
if consumeQuota && userQuota-quota < 0 {
|
||||||
return errorWrapper(err, "insufficient_user_quota", http.StatusForbidden)
|
return errorWrapper(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody)
|
req, err := http.NewRequest(c.Request.Method, fullRequestURL, requestBody)
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError)
|
return errorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
if userQuota-preConsumedQuota < 0 {
|
||||||
|
return errorWrapper(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden)
|
||||||
|
}
|
||||||
err = model.CacheDecreaseUserQuota(userId, preConsumedQuota)
|
err = model.CacheDecreaseUserQuota(userId, preConsumedQuota)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError)
|
return errorWrapper(err, "decrease_user_quota_failed", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -9,44 +9,53 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stopFinishReason = "stop"
|
var stopFinishReason = "stop"
|
||||||
|
|
||||||
|
// tokenEncoderMap won't grow after initialization
|
||||||
var tokenEncoderMap = map[string]*tiktoken.Tiktoken{}
|
var tokenEncoderMap = map[string]*tiktoken.Tiktoken{}
|
||||||
|
var defaultTokenEncoder *tiktoken.Tiktoken
|
||||||
|
|
||||||
func InitTokenEncoders() {
|
func InitTokenEncoders() {
|
||||||
common.SysLog("initializing token encoders")
|
common.SysLog("initializing token encoders")
|
||||||
fallbackTokenEncoder, err := tiktoken.EncodingForModel("gpt-3.5-turbo")
|
gpt35TokenEncoder, err := tiktoken.EncodingForModel("gpt-3.5-turbo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.FatalLog(fmt.Sprintf("failed to get fallback token encoder: %s", err.Error()))
|
common.FatalLog(fmt.Sprintf("failed to get gpt-3.5-turbo token encoder: %s", err.Error()))
|
||||||
|
}
|
||||||
|
defaultTokenEncoder = gpt35TokenEncoder
|
||||||
|
gpt4TokenEncoder, err := tiktoken.EncodingForModel("gpt-4")
|
||||||
|
if err != nil {
|
||||||
|
common.FatalLog(fmt.Sprintf("failed to get gpt-4 token encoder: %s", err.Error()))
|
||||||
}
|
}
|
||||||
for model, _ := range common.ModelRatio {
|
for model, _ := range common.ModelRatio {
|
||||||
tokenEncoder, err := tiktoken.EncodingForModel(model)
|
if strings.HasPrefix(model, "gpt-3.5") {
|
||||||
if err != nil {
|
tokenEncoderMap[model] = gpt35TokenEncoder
|
||||||
common.SysError(fmt.Sprintf("using fallback encoder for model %s", model))
|
} else if strings.HasPrefix(model, "gpt-4") {
|
||||||
tokenEncoderMap[model] = fallbackTokenEncoder
|
tokenEncoderMap[model] = gpt4TokenEncoder
|
||||||
continue
|
} else {
|
||||||
|
tokenEncoderMap[model] = nil
|
||||||
}
|
}
|
||||||
tokenEncoderMap[model] = tokenEncoder
|
|
||||||
}
|
}
|
||||||
common.SysLog("token encoders initialized")
|
common.SysLog("token encoders initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTokenEncoder(model string) *tiktoken.Tiktoken {
|
func getTokenEncoder(model string) *tiktoken.Tiktoken {
|
||||||
if tokenEncoder, ok := tokenEncoderMap[model]; ok {
|
tokenEncoder, ok := tokenEncoderMap[model]
|
||||||
|
if ok && tokenEncoder != nil {
|
||||||
return tokenEncoder
|
return tokenEncoder
|
||||||
}
|
}
|
||||||
tokenEncoder, err := tiktoken.EncodingForModel(model)
|
if ok {
|
||||||
if err != nil {
|
tokenEncoder, err := tiktoken.EncodingForModel(model)
|
||||||
common.SysError(fmt.Sprintf("failed to get token encoder for model %s: %s, using encoder for gpt-3.5-turbo", model, err.Error()))
|
|
||||||
tokenEncoder, err = tiktoken.EncodingForModel("gpt-3.5-turbo")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.FatalLog(fmt.Sprintf("failed to get token encoder for model gpt-3.5-turbo: %s", err.Error()))
|
common.SysError(fmt.Sprintf("failed to get token encoder for model %s: %s, using encoder for gpt-3.5-turbo", model, err.Error()))
|
||||||
|
tokenEncoder = defaultTokenEncoder
|
||||||
}
|
}
|
||||||
|
tokenEncoderMap[model] = tokenEncoder
|
||||||
|
return tokenEncoder
|
||||||
}
|
}
|
||||||
tokenEncoderMap[model] = tokenEncoder
|
return defaultTokenEncoder
|
||||||
return tokenEncoder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTokenNum(tokenEncoder *tiktoken.Tiktoken, text string) int {
|
func getTokenNum(tokenEncoder *tiktoken.Tiktoken, text string) int {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type Channel struct {
|
|||||||
Key string `json:"key" gorm:"not null;index"`
|
Key string `json:"key" gorm:"not null;index"`
|
||||||
Status int `json:"status" gorm:"default:1"`
|
Status int `json:"status" gorm:"default:1"`
|
||||||
Name string `json:"name" gorm:"index"`
|
Name string `json:"name" gorm:"index"`
|
||||||
Weight int `json:"weight"`
|
Weight *uint `json:"weight" gorm:"default:0"`
|
||||||
CreatedTime int64 `json:"created_time" gorm:"bigint"`
|
CreatedTime int64 `json:"created_time" gorm:"bigint"`
|
||||||
TestTime int64 `json:"test_time" gorm:"bigint"`
|
TestTime int64 `json:"test_time" gorm:"bigint"`
|
||||||
ResponseTime int `json:"response_time"` // in milliseconds
|
ResponseTime int `json:"response_time"` // in milliseconds
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id" gorm:"index"`
|
||||||
CreatedAt int64 `json:"created_at" gorm:"bigint;index"`
|
CreatedAt int64 `json:"created_at" gorm:"bigint;index"`
|
||||||
Type int `json:"type" gorm:"index"`
|
Type int `json:"type" gorm:"index"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
@@ -19,7 +19,7 @@ type Log struct {
|
|||||||
Quota int `json:"quota" gorm:"default:0"`
|
Quota int `json:"quota" gorm:"default:0"`
|
||||||
PromptTokens int `json:"prompt_tokens" gorm:"default:0"`
|
PromptTokens int `json:"prompt_tokens" gorm:"default:0"`
|
||||||
CompletionTokens int `json:"completion_tokens" gorm:"default:0"`
|
CompletionTokens int `json:"completion_tokens" gorm:"default:0"`
|
||||||
Channel int `json:"channel" gorm:"default:0"`
|
ChannelId int `json:"channel" gorm:"index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -47,7 +47,6 @@ func RecordLog(userId int, logType int, content string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string) {
|
func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string) {
|
||||||
common.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content))
|
common.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content))
|
||||||
if !common.LogConsumeEnabled {
|
if !common.LogConsumeEnabled {
|
||||||
@@ -64,7 +63,7 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
|
|||||||
TokenName: tokenName,
|
TokenName: tokenName,
|
||||||
ModelName: modelName,
|
ModelName: modelName,
|
||||||
Quota: quota,
|
Quota: quota,
|
||||||
Channel: channelId,
|
ChannelId: channelId,
|
||||||
}
|
}
|
||||||
err := DB.Create(log).Error
|
err := DB.Create(log).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React, { useContext, useEffect, useState } from 'react';
|
|||||||
import { Button, Divider, Form, Grid, Header, Image, Message, Modal, Segment } from 'semantic-ui-react';
|
import { Button, Divider, Form, Grid, Header, Image, Message, Modal, Segment } from 'semantic-ui-react';
|
||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { UserContext } from '../context/User';
|
import { UserContext } from '../context/User';
|
||||||
import { API, getLogo, showError, showSuccess } from '../helpers';
|
import { API, getLogo, showError, showSuccess, showWarning } from '../helpers';
|
||||||
import { getOAuthState, onGitHubOAuthClicked } from './utils';
|
import { onGitHubOAuthClicked } from './utils';
|
||||||
|
|
||||||
const LoginForm = () => {
|
const LoginForm = () => {
|
||||||
const [inputs, setInputs] = useState({
|
const [inputs, setInputs] = useState({
|
||||||
@@ -68,8 +68,14 @@ const LoginForm = () => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
userDispatch({ type: 'login', payload: data });
|
userDispatch({ type: 'login', payload: data });
|
||||||
localStorage.setItem('user', JSON.stringify(data));
|
localStorage.setItem('user', JSON.stringify(data));
|
||||||
navigate('/');
|
if (username === 'root' && password === '123456') {
|
||||||
showSuccess('登录成功!');
|
navigate('/user/edit');
|
||||||
|
showSuccess('登录成功!');
|
||||||
|
showWarning('请立刻修改默认密码!');
|
||||||
|
} else {
|
||||||
|
navigate('/token');
|
||||||
|
showSuccess('登录成功!');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
@@ -126,7 +132,7 @@ const LoginForm = () => {
|
|||||||
circular
|
circular
|
||||||
color='black'
|
color='black'
|
||||||
icon='github'
|
icon='github'
|
||||||
onClick={()=>onGitHubOAuthClicked(status.github_client_id)}
|
onClick={() => onGitHubOAuthClicked(status.github_client_id)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ const EditUser = () => {
|
|||||||
label='密码'
|
label='密码'
|
||||||
name='password'
|
name='password'
|
||||||
type={'password'}
|
type={'password'}
|
||||||
placeholder={'请输入新的密码'}
|
placeholder={'请输入新的密码,最短 8 位'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={password}
|
value={password}
|
||||||
autoComplete='new-password'
|
autoComplete='new-password'
|
||||||
|
|||||||
Reference in New Issue
Block a user