Compare commits

...

14 Commits

Author SHA1 Message Date
ybyang
55fefc8f00 Merge aacd752850 into 36c8f4f15c 2024-12-21 08:25:07 +08:00
JustSong
36c8f4f15c docs: update readme
Some checks are pending
CI / Unit tests (push) Waiting to run
CI / commit_lint (push) Waiting to run
2024-12-21 00:33:20 +08:00
JustSong
45b51ea0ee feat: update feishu oauth login 2024-12-20 23:27:00 +08:00
ybyang
aacd752850 Merge branch 'main' of github.com:whybeyoung/one-api 2024-12-01 05:38:00 +08:00
ybyang
924d0d8487 stream error reuturn 2024-12-01 05:34:44 +08:00
ybyang
49d4089d62 feat: support stream usage for xunfei stream adaptor 2024-11-28 19:19:38 +08:00
ybyang
bb22dc0661 support stream usage 2024-11-28 19:14:20 +08:00
ybyang
59a835ede7 support stream usage 2024-11-28 19:13:47 +08:00
ybyang
e916af9c56 讯飞响应 设置sid 2024-11-25 17:05:33 +08:00
ybyang
4c395352ff 讯飞响应 sid 错误码提示返回 2024-11-25 16:58:08 +08:00
ybyang
e7cc50d8cb 讯飞响应 sid 错误码提示返回 2024-11-25 15:49:52 +08:00
ybyang
ff24d0fab8 支持LoraID , 通过OpenAI Client设置 ExtraHeader 并在讯飞实现中支持 2024-11-25 14:27:03 +08:00
ybyang
62ab7f47ad support maas for xunfei 2024-11-25 13:36:43 +08:00
ybyang
cae8f6a948 support maas for xunfei 2024-11-25 11:40:10 +08:00
8 changed files with 55 additions and 16 deletions

View File

@@ -115,8 +115,8 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用
21. 支持 Cloudflare Turnstile 用户校验。
22. 支持用户管理,支持**多种用户登录注册方式**
+ 邮箱登录注册(支持注册邮箱白名单)以及通过邮箱进行密码重置。
+ 支持使用飞书进行授权登录
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
+ 支持[飞书授权登录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/authorize/get)[这里有 One API 的实现细节阐述供参考](https://iamazing.cn/page/feishu-oauth-login)
+ 支持 [GitHub 授权登录](https://github.com/settings/applications/new)。
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
23. 支持主题切换,设置环境变量 `THEME` 即可,默认为 `default`,欢迎 PR 更多主题,具体参考[此处](./web/README.md)。
24. 配合 [Message Pusher](https://github.com/songquanpeng/message-pusher) 可将报警信息推送到多种 App 上。

View File

@@ -21,4 +21,5 @@ const (
AvailableModels = "available_models"
KeyRequestBody = "key_request_body"
SystemPrompt = "system_prompt"
LoraId = "lora_id"
)

View File

@@ -40,7 +40,7 @@ func getLarkUserInfoByCode(code string) (*LarkUser, error) {
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", "https://passport.feishu.cn/suite/passport/oauth/token", bytes.NewBuffer(jsonData))
req, err := http.NewRequest("POST", "https://open.feishu.cn/open-apis/authen/v2/oauth/token", bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}

View File

@@ -10,6 +10,7 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
@@ -28,7 +29,7 @@ import (
// https://console.xfyun.cn/services/cbm
// https://www.xfyun.cn/doc/spark/Web.html
func requestOpenAI2Xunfei(request model.GeneralOpenAIRequest, xunfeiAppId string, domain string) *ChatRequest {
func requestOpenAI2Xunfei(request model.GeneralOpenAIRequest, xunfeiAppId string, domain string, xunfeiPatchId string) *ChatRequest {
messages := make([]Message, 0, len(request.Messages))
for _, message := range request.Messages {
messages = append(messages, Message{
@@ -38,6 +39,9 @@ func requestOpenAI2Xunfei(request model.GeneralOpenAIRequest, xunfeiAppId string
}
xunfeiRequest := ChatRequest{}
xunfeiRequest.Header.AppId = xunfeiAppId
if xunfeiPatchId != "" {
xunfeiRequest.Header.PatchId = []string{xunfeiPatchId}
}
xunfeiRequest.Parameter.Chat.Domain = domain
xunfeiRequest.Parameter.Chat.Temperature = request.Temperature
xunfeiRequest.Parameter.Chat.TopK = request.N
@@ -93,7 +97,7 @@ func responseXunfei2OpenAI(response *ChatResponse) *openai.TextResponse {
FinishReason: constant.StopFinishReason,
}
fullTextResponse := openai.TextResponse{
Id: fmt.Sprintf("chatcmpl-%s", random.GetUUID()),
Id: response.Header.Sid,
Object: "chat.completion",
Created: helper.GetTimestamp(),
Choices: []openai.TextResponseChoice{choice},
@@ -102,7 +106,7 @@ func responseXunfei2OpenAI(response *ChatResponse) *openai.TextResponse {
return &fullTextResponse
}
func streamResponseXunfei2OpenAI(xunfeiResponse *ChatResponse) *openai.ChatCompletionsStreamResponse {
func streamResponseXunfei2OpenAI(xunfeiResponse *ChatResponse, usage *model.Usage) *openai.ChatCompletionsStreamResponse {
if len(xunfeiResponse.Payload.Choices.Text) == 0 {
xunfeiResponse.Payload.Choices.Text = []ChatResponseTextItem{
{
@@ -122,6 +126,7 @@ func streamResponseXunfei2OpenAI(xunfeiResponse *ChatResponse) *openai.ChatCompl
Created: helper.GetTimestamp(),
Model: "SparkDesk",
Choices: []openai.ChatCompletionsStreamResponseChoice{choice},
Usage: usage,
}
return &response
}
@@ -153,11 +158,12 @@ func buildXunfeiAuthUrl(hostUrl string, apiKey, apiSecret string) string {
}
func StreamHandler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*model.ErrorWithStatusCode, *model.Usage) {
domain, authUrl := getXunfeiAuthUrl(meta.Config.APIVersion, apiKey, apiSecret)
dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId)
domain, authUrl := getXunfeiAuthUrl(meta.Config.APIVersion, apiKey, apiSecret, textRequest.Model)
dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId, meta.LoraId)
if err != nil {
return openai.ErrorWrapper(err, "xunfei_request_failed", http.StatusInternalServerError), nil
}
common.SetEventStreamHeaders(c)
var usage model.Usage
c.Stream(func(w io.Writer) bool {
@@ -166,7 +172,23 @@ func StreamHandler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralOpe
usage.PromptTokens += xunfeiResponse.Payload.Usage.Text.PromptTokens
usage.CompletionTokens += xunfeiResponse.Payload.Usage.Text.CompletionTokens
usage.TotalTokens += xunfeiResponse.Payload.Usage.Text.TotalTokens
response := streamResponseXunfei2OpenAI(&xunfeiResponse)
if xunfeiResponse.Header.Code != 0 {
errMessage := fmt.Sprintf("Xunfei request failed with Sid: %s code: %d, msg: %s", xunfeiResponse.Header.Sid, xunfeiResponse.Header.Code, xunfeiResponse.Header.Message)
logger.SysError(errMessage)
mStr, err := json.Marshal(map[string]interface{}{
"error": map[string]interface{}{
"message": errMessage,
"code": xunfeiResponse.Header.Code,
},
})
if err != nil {
logger.SysError("error marshalling stream response: " + err.Error())
return true
}
c.Render(-1, common.CustomEvent{Data: "data: " + string(mStr), Event: "error"})
return false // 停止流式响应
}
response := streamResponseXunfei2OpenAI(&xunfeiResponse, &usage)
jsonResponse, err := json.Marshal(response)
if err != nil {
logger.SysError("error marshalling stream response: " + err.Error())
@@ -183,8 +205,8 @@ func StreamHandler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralOpe
}
func Handler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralOpenAIRequest, appId string, apiSecret string, apiKey string) (*model.ErrorWithStatusCode, *model.Usage) {
domain, authUrl := getXunfeiAuthUrl(meta.Config.APIVersion, apiKey, apiSecret)
dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId)
domain, authUrl := getXunfeiAuthUrl(meta.Config.APIVersion, apiKey, apiSecret, textRequest.Model)
dataChan, stopChan, err := xunfeiMakeRequest(textRequest, domain, authUrl, appId, meta.LoraId)
if err != nil {
return openai.ErrorWrapper(err, "xunfei_request_failed", http.StatusInternalServerError), nil
}
@@ -205,6 +227,10 @@ func Handler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralOpenAIReq
case stop = <-stopChan:
}
}
if xunfeiResponse.Header.Code != 0 {
return openai.ErrorWrapper(errors.New("xunfei response error: sid: "+xunfeiResponse.Header.Sid), strconv.Itoa(xunfeiResponse.Header.Code), http.StatusInternalServerError), nil
}
if len(xunfeiResponse.Payload.Choices.Text) == 0 {
return openai.ErrorWrapper(errors.New("xunfei empty response detected"), "xunfei_empty_response_detected", http.StatusInternalServerError), nil
}
@@ -220,7 +246,7 @@ func Handler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralOpenAIReq
return nil, &usage
}
func xunfeiMakeRequest(textRequest model.GeneralOpenAIRequest, domain, authUrl, appId string) (chan ChatResponse, chan bool, error) {
func xunfeiMakeRequest(textRequest model.GeneralOpenAIRequest, domain, authUrl, appId, patchId string) (chan ChatResponse, chan bool, error) {
d := websocket.Dialer{
HandshakeTimeout: 5 * time.Second,
}
@@ -228,7 +254,7 @@ func xunfeiMakeRequest(textRequest model.GeneralOpenAIRequest, domain, authUrl,
if err != nil || resp.StatusCode != 101 {
return nil, nil, err
}
data := requestOpenAI2Xunfei(textRequest, appId, domain)
data := requestOpenAI2Xunfei(textRequest, appId, domain, patchId)
err = conn.WriteJSON(data)
if err != nil {
return nil, nil, err
@@ -300,7 +326,7 @@ func apiVersion2domain(apiVersion string) string {
return "general" + apiVersion
}
func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string) (string, string) {
func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string, modelName string) (string, string) {
var authUrl string
domain := apiVersion2domain(apiVersion)
switch apiVersion {
@@ -310,6 +336,13 @@ func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string) (strin
case "v3.5-32K":
authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/chat/max-32k"), apiKey, apiSecret)
break
case "maas":
domain = modelName
authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://maas-api.cn-huabei-1.xf-yun.com/v1.1/chat"), apiKey, apiSecret)
case "xingchen":
domain = modelName
authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://xingcheng-api.cn-huabei-1.xf-yun.com/v1.1/chat"), apiKey, apiSecret)
default:
authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion), apiKey, apiSecret)
}

View File

@@ -15,7 +15,8 @@ type Functions struct {
type ChatRequest struct {
Header struct {
AppId string `json:"app_id"`
AppId string `json:"app_id"`
PatchId []string `json:"patch_id,omitempty"`
} `json:"header"`
Parameter struct {
Chat struct {

View File

@@ -31,6 +31,9 @@ type Meta struct {
RequestURLPath string
PromptTokens int // only for DoResponse
SystemPrompt string
//Lora_id
LoraId string
}
func GetByContext(c *gin.Context) *Meta {
@@ -48,6 +51,7 @@ func GetByContext(c *gin.Context) *Meta {
APIKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
RequestURLPath: c.Request.URL.String(),
SystemPrompt: c.GetString(ctxkey.SystemPrompt),
LoraId: c.Request.Header.Get(ctxkey.LoraId),
}
cfg, ok := c.Get(ctxkey.Config)
if ok {

View File

@@ -95,7 +95,7 @@ export async function onLarkOAuthClicked(lark_client_id) {
const state = await getOAuthState();
if (!state) return;
let redirect_uri = `${window.location.origin}/oauth/lark`;
window.open(`https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${redirect_uri}&app_id=${lark_client_id}&state=${state}`);
window.open(`https://accounts.feishu.cn/open-apis/authen/v1/authorize?redirect_uri=${redirect_uri}&client_id=${lark_client_id}&state=${state}`);
}
export async function onOidcClicked(auth_url, client_id, openInNewTab = false) {

0
web/build.sh Normal file → Executable file
View File