Compare commits

...

9 Commits

Author SHA1 Message Date
lihangfu
279caf82dc feat: support tencent v3 api (#1542)
Co-authored-by: lihangfu <hfli8@iflytek.com>
2024-06-20 00:23:08 +08:00
Wei Tingjiang
b1520b308b Try to fix Gemini streaming return being truncated by FinishReason. (#1477)
1
2024-06-14 00:30:47 +08:00
JustSong
ed717211aa chore: adjust default rate limit config 2024-06-13 00:35:37 +08:00
JustSong
6ccf3f3cfc chore: add logger.SysLogf function 2024-06-13 00:28:56 +08:00
jinjianming
f74577141c fix: fix default token not created in some cases (#1510)
* 修复git、微信等用户注册不会创建默认令牌问题

修复git、微信等用户注册不会创建默认令牌问题

* 修复git、微信等用户注册不会创建默认令牌问题

删除普通用户注册代码

* fix: do not block if error happened

---------

Co-authored-by: JustSong <songquanpeng@foxmail.com>
2024-06-13 00:20:48 +08:00
Buer
6aafb7a99e fix: channel edit settings key error (#1496) 2024-06-13 00:08:49 +08:00
Zhong Liu
c1971870fa fix: support for Spark Lite model (#1526)
* fix: Support for Spark Lite model

* fix: fix panic

* fix: fix xunfei version config

---------

Co-authored-by: JustSong <39998050+songquanpeng@users.noreply.github.com>
Co-authored-by: JustSong <songquanpeng@foxmail.com>
2024-06-13 00:07:26 +08:00
wagxuebing
f83894c83f fix: xunfei interface call 4001 error (#1499)
Co-authored-by: lynnssb <lynntobing@gmail.com>
2024-06-12 23:12:58 +08:00
fxsome
e9981fff36 feat: post all messages for cloudflare (#1515) 2024-06-08 13:34:23 +08:00
19 changed files with 245 additions and 186 deletions

View File

@@ -117,10 +117,10 @@ var ValidThemes = map[string]bool{
// All duration's unit is seconds // All duration's unit is seconds
// Shouldn't larger then RateLimitKeyExpirationDuration // Shouldn't larger then RateLimitKeyExpirationDuration
var ( var (
GlobalApiRateLimitNum = env.Int("GLOBAL_API_RATE_LIMIT", 180) GlobalApiRateLimitNum = env.Int("GLOBAL_API_RATE_LIMIT", 240)
GlobalApiRateLimitDuration int64 = 3 * 60 GlobalApiRateLimitDuration int64 = 3 * 60
GlobalWebRateLimitNum = env.Int("GLOBAL_WEB_RATE_LIMIT", 60) GlobalWebRateLimitNum = env.Int("GLOBAL_WEB_RATE_LIMIT", 120)
GlobalWebRateLimitDuration int64 = 3 * 60 GlobalWebRateLimitDuration int64 = 3 * 60
UploadRateLimitNum = 10 UploadRateLimitNum = 10

View File

@@ -43,11 +43,19 @@ func SysLog(s string) {
_, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s) _, _ = fmt.Fprintf(gin.DefaultWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
} }
func SysLogf(format string, a ...any) {
SysLog(fmt.Sprintf(format, a...))
}
func SysError(s string) { func SysError(s string) {
t := time.Now() t := time.Now()
_, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s) _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[SYS] %v | %s \n", t.Format("2006/01/02 - 15:04:05"), s)
} }
func SysErrorf(format string, a ...any) {
SysError(fmt.Sprintf(format, a...))
}
func Debug(ctx context.Context, msg string) { func Debug(ctx context.Context, msg string) {
if config.DebugEnabled { if config.DebugEnabled {
logHelper(ctx, loggerDEBUG, msg) logHelper(ctx, loggerDEBUG, msg)

View File

@@ -6,8 +6,6 @@ import (
"github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/ctxkey" "github.com/songquanpeng/one-api/common/ctxkey"
"github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/common/random" "github.com/songquanpeng/one-api/common/random"
"github.com/songquanpeng/one-api/model" "github.com/songquanpeng/one-api/model"
"net/http" "net/http"
@@ -111,7 +109,6 @@ func Logout(c *gin.Context) {
} }
func Register(c *gin.Context) { func Register(c *gin.Context) {
ctx := c.Request.Context()
if !config.RegisterEnabled { if !config.RegisterEnabled {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"message": "管理员关闭了新用户注册", "message": "管理员关闭了新用户注册",
@@ -176,28 +173,7 @@ func Register(c *gin.Context) {
}) })
return return
} }
go func() {
err := user.ValidateAndFill()
if err != nil {
logger.Errorf(ctx, "user.ValidateAndFill failed: %w", err)
return
}
cleanToken := model.Token{
UserId: user.Id,
Name: "default",
Key: random.GenerateKey(),
CreatedTime: helper.GetTimestamp(),
AccessedTime: helper.GetTimestamp(),
ExpiredTime: -1,
RemainQuota: -1,
UnlimitedQuota: true,
}
err = cleanToken.Insert()
if err != nil {
logger.Errorf(ctx, "cleanToken.Insert failed: %w", err)
return
}
}()
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",

View File

@@ -24,7 +24,7 @@ var buildFS embed.FS
func main() { func main() {
logger.SetupLogger() logger.SetupLogger()
logger.SysLog(fmt.Sprintf("One API %s started", common.Version)) logger.SysLogf("One API %s started", common.Version)
if os.Getenv("GIN_MODE") != "debug" { if os.Getenv("GIN_MODE") != "debug" {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }

View File

@@ -67,26 +67,28 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
c.Set(ctxkey.BaseURL, channel.GetBaseURL()) c.Set(ctxkey.BaseURL, channel.GetBaseURL())
cfg, _ := channel.LoadConfig() cfg, _ := channel.LoadConfig()
// this is for backward compatibility // this is for backward compatibility
switch channel.Type { if channel.Other != nil {
case channeltype.Azure: switch channel.Type {
if cfg.APIVersion == "" { case channeltype.Azure:
cfg.APIVersion = channel.Other if cfg.APIVersion == "" {
} cfg.APIVersion = *channel.Other
case channeltype.Xunfei: }
if cfg.APIVersion == "" { case channeltype.Xunfei:
cfg.APIVersion = channel.Other if cfg.APIVersion == "" {
} cfg.APIVersion = *channel.Other
case channeltype.Gemini: }
if cfg.APIVersion == "" { case channeltype.Gemini:
cfg.APIVersion = channel.Other if cfg.APIVersion == "" {
} cfg.APIVersion = *channel.Other
case channeltype.AIProxyLibrary: }
if cfg.LibraryID == "" { case channeltype.AIProxyLibrary:
cfg.LibraryID = channel.Other if cfg.LibraryID == "" {
} cfg.LibraryID = *channel.Other
case channeltype.Ali: }
if cfg.Plugin == "" { case channeltype.Ali:
cfg.Plugin = channel.Other if cfg.Plugin == "" {
cfg.Plugin = *channel.Other
}
} }
} }
c.Set(ctxkey.Config, cfg) c.Set(ctxkey.Config, cfg)

View File

@@ -27,7 +27,7 @@ type Channel struct {
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
BaseURL *string `json:"base_url" gorm:"column:base_url;default:''"` BaseURL *string `json:"base_url" gorm:"column:base_url;default:''"`
Other string `json:"other"` // DEPRECATED: please save config to field Config Other *string `json:"other"` // DEPRECATED: please save config to field Config
Balance float64 `json:"balance"` // in USD Balance float64 `json:"balance"` // in USD
BalanceUpdatedTime int64 `json:"balance_updated_time" gorm:"bigint"` BalanceUpdatedTime int64 `json:"balance_updated_time" gorm:"bigint"`
Models string `json:"models"` Models string `json:"models"`

View File

@@ -6,6 +6,7 @@ import (
"github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/common/blacklist" "github.com/songquanpeng/one-api/common/blacklist"
"github.com/songquanpeng/one-api/common/config" "github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/common/random" "github.com/songquanpeng/one-api/common/random"
"gorm.io/gorm" "gorm.io/gorm"
@@ -140,6 +141,22 @@ func (user *User) Insert(inviterId int) error {
RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("邀请用户赠送 %s", common.LogQuota(config.QuotaForInviter))) RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("邀请用户赠送 %s", common.LogQuota(config.QuotaForInviter)))
} }
} }
// create default token
cleanToken := Token{
UserId: user.Id,
Name: "default",
Key: random.GenerateKey(),
CreatedTime: helper.GetTimestamp(),
AccessedTime: helper.GetTimestamp(),
ExpiredTime: -1,
RemainQuota: -1,
UnlimitedQuota: true,
}
result.Error = cleanToken.Insert()
if result.Error != nil {
// do not block
logger.SysError(fmt.Sprintf("create default token for user %d failed: %s", user.Id, result.Error.Error()))
}
return nil return nil
} }

View File

@@ -17,15 +17,21 @@ import (
) )
func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request { func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
lastMessage := textRequest.Messages[len(textRequest.Messages)-1] var promptBuilder strings.Builder
return &Request{ for _, message := range textRequest.Messages {
MaxTokens: textRequest.MaxTokens, promptBuilder.WriteString(message.StringContent())
Prompt: lastMessage.StringContent(), promptBuilder.WriteString("\n") // 添加换行符来分隔每个消息
Stream: textRequest.Stream, }
Temperature: textRequest.Temperature,
} return &Request{
MaxTokens: textRequest.MaxTokens,
Prompt: promptBuilder.String(),
Stream: textRequest.Stream,
Temperature: textRequest.Temperature,
}
} }
func ResponseCloudflare2OpenAI(cloudflareResponse *Response) *openai.TextResponse { func ResponseCloudflare2OpenAI(cloudflareResponse *Response) *openai.TextResponse {
choice := openai.TextResponseChoice{ choice := openai.TextResponseChoice{
Index: 0, Index: 0,

View File

@@ -245,8 +245,10 @@ func responseGeminiChat2OpenAI(response *ChatResponse) *openai.TextResponse {
func streamResponseGeminiChat2OpenAI(geminiResponse *ChatResponse) *openai.ChatCompletionsStreamResponse { func streamResponseGeminiChat2OpenAI(geminiResponse *ChatResponse) *openai.ChatCompletionsStreamResponse {
var choice openai.ChatCompletionsStreamResponseChoice var choice openai.ChatCompletionsStreamResponseChoice
choice.Delta.Content = geminiResponse.GetResponseText() choice.Delta.Content = geminiResponse.GetResponseText()
choice.FinishReason = &constant.StopFinishReason //choice.FinishReason = &constant.StopFinishReason
var response openai.ChatCompletionsStreamResponse var response openai.ChatCompletionsStreamResponse
response.Id = fmt.Sprintf("chatcmpl-%s", random.GetUUID())
response.Created = helper.GetTimestamp()
response.Object = "chat.completion.chunk" response.Object = "chat.completion.chunk"
response.Model = "gemini" response.Model = "gemini"
response.Choices = []openai.ChatCompletionsStreamResponseChoice{choice} response.Choices = []openai.ChatCompletionsStreamResponseChoice{choice}

View File

@@ -2,35 +2,43 @@ package tencent
import ( import (
"errors" "errors"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common/helper"
"github.com/songquanpeng/one-api/relay/adaptor" "github.com/songquanpeng/one-api/relay/adaptor"
"github.com/songquanpeng/one-api/relay/adaptor/openai" "github.com/songquanpeng/one-api/relay/adaptor/openai"
"github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/meta"
"github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/model"
"io" "io"
"net/http" "net/http"
"strconv"
"strings" "strings"
) )
// https://cloud.tencent.com/document/api/1729/101837 // https://cloud.tencent.com/document/api/1729/101837
type Adaptor struct { type Adaptor struct {
Sign string Sign string
Action string
Version string
Timestamp int64
} }
func (a *Adaptor) Init(meta *meta.Meta) { func (a *Adaptor) Init(meta *meta.Meta) {
a.Action = "ChatCompletions"
a.Version = "2023-09-01"
a.Timestamp = helper.GetTimestamp()
} }
func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) { func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
return fmt.Sprintf("%s/hyllm/v1/chat/completions", meta.BaseURL), nil return meta.BaseURL + "/", nil
} }
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error {
adaptor.SetupCommonRequestHeader(c, req, meta) adaptor.SetupCommonRequestHeader(c, req, meta)
req.Header.Set("Authorization", a.Sign) req.Header.Set("Authorization", a.Sign)
req.Header.Set("X-TC-Action", meta.ActualModelName) req.Header.Set("X-TC-Action", a.Action)
req.Header.Set("X-TC-Version", a.Version)
req.Header.Set("X-TC-Timestamp", strconv.FormatInt(a.Timestamp, 10))
return nil return nil
} }
@@ -40,15 +48,13 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *model.G
} }
apiKey := c.Request.Header.Get("Authorization") apiKey := c.Request.Header.Get("Authorization")
apiKey = strings.TrimPrefix(apiKey, "Bearer ") apiKey = strings.TrimPrefix(apiKey, "Bearer ")
appId, secretId, secretKey, err := ParseConfig(apiKey) _, secretId, secretKey, err := ParseConfig(apiKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tencentRequest := ConvertRequest(*request) tencentRequest := ConvertRequest(*request)
tencentRequest.AppId = appId
tencentRequest.SecretId = secretId
// we have to calculate the sign here // we have to calculate the sign here
a.Sign = GetSign(*tencentRequest, secretKey) a.Sign = GetSign(*tencentRequest, a, secretId, secretKey)
return tencentRequest, nil return tencentRequest, nil
} }

View File

@@ -1,7 +1,8 @@
package tencent package tencent
var ModelList = []string{ var ModelList = []string{
"ChatPro", "hunyuan-lite",
"ChatStd", "hunyuan-standard",
"hunyuan", "hunyuan-standard-256K",
"hunyuan-pro",
} }

View File

@@ -3,8 +3,8 @@ package tencent
import ( import (
"bufio" "bufio"
"crypto/hmac" "crypto/hmac"
"crypto/sha1" "crypto/sha256"
"encoding/base64" "encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -19,34 +19,26 @@ import (
"github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/model"
"io" "io"
"net/http" "net/http"
"sort"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// https://cloud.tencent.com/document/product/1729/97732
func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest { func ConvertRequest(request model.GeneralOpenAIRequest) *ChatRequest {
messages := make([]Message, 0, len(request.Messages)) messages := make([]*Message, 0, len(request.Messages))
for i := 0; i < len(request.Messages); i++ { for i := 0; i < len(request.Messages); i++ {
message := request.Messages[i] message := request.Messages[i]
messages = append(messages, Message{ messages = append(messages, &Message{
Content: message.StringContent(), Content: message.StringContent(),
Role: message.Role, Role: message.Role,
}) })
} }
stream := 0
if request.Stream {
stream = 1
}
return &ChatRequest{ return &ChatRequest{
Timestamp: helper.GetTimestamp(), Model: &request.Model,
Expired: helper.GetTimestamp() + 24*60*60, Stream: &request.Stream,
QueryID: random.GetUUID(),
Temperature: request.Temperature,
TopP: request.TopP,
Stream: stream,
Messages: messages, Messages: messages,
TopP: &request.TopP,
Temperature: &request.Temperature,
} }
} }
@@ -54,7 +46,11 @@ func responseTencent2OpenAI(response *ChatResponse) *openai.TextResponse {
fullTextResponse := openai.TextResponse{ fullTextResponse := openai.TextResponse{
Object: "chat.completion", Object: "chat.completion",
Created: helper.GetTimestamp(), Created: helper.GetTimestamp(),
Usage: response.Usage, Usage: model.Usage{
PromptTokens: response.Usage.PromptTokens,
CompletionTokens: response.Usage.CompletionTokens,
TotalTokens: response.Usage.TotalTokens,
},
} }
if len(response.Choices) > 0 { if len(response.Choices) > 0 {
choice := openai.TextResponseChoice{ choice := openai.TextResponseChoice{
@@ -154,6 +150,7 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) { func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) {
var TencentResponse ChatResponse var TencentResponse ChatResponse
var responseP ChatResponseP
responseBody, err := io.ReadAll(resp.Body) responseBody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil return openai.ErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
@@ -162,10 +159,11 @@ func Handler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *
if err != nil { if err != nil {
return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
} }
err = json.Unmarshal(responseBody, &TencentResponse) err = json.Unmarshal(responseBody, &responseP)
if err != nil { if err != nil {
return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil return openai.ErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
} }
TencentResponse = responseP.Response
if TencentResponse.Error.Code != 0 { if TencentResponse.Error.Code != 0 {
return &model.ErrorWithStatusCode{ return &model.ErrorWithStatusCode{
Error: model.Error{ Error: model.Error{
@@ -202,29 +200,62 @@ func ParseConfig(config string) (appId int64, secretId string, secretKey string,
return return
} }
func GetSign(req ChatRequest, secretKey string) string { func sha256hex(s string) string {
params := make([]string, 0) b := sha256.Sum256([]byte(s))
params = append(params, "app_id="+strconv.FormatInt(req.AppId, 10)) return hex.EncodeToString(b[:])
params = append(params, "secret_id="+req.SecretId) }
params = append(params, "timestamp="+strconv.FormatInt(req.Timestamp, 10))
params = append(params, "query_id="+req.QueryID) func hmacSha256(s, key string) string {
params = append(params, "temperature="+strconv.FormatFloat(req.Temperature, 'f', -1, 64)) hashed := hmac.New(sha256.New, []byte(key))
params = append(params, "top_p="+strconv.FormatFloat(req.TopP, 'f', -1, 64)) hashed.Write([]byte(s))
params = append(params, "stream="+strconv.Itoa(req.Stream)) return string(hashed.Sum(nil))
params = append(params, "expired="+strconv.FormatInt(req.Expired, 10)) }
var messageStr string func GetSign(req ChatRequest, adaptor *Adaptor, secId, secKey string) string {
for _, msg := range req.Messages { // build canonical request string
messageStr += fmt.Sprintf(`{"role":"%s","content":"%s"},`, msg.Role, msg.Content) host := "hunyuan.tencentcloudapi.com"
} httpRequestMethod := "POST"
messageStr = strings.TrimSuffix(messageStr, ",") canonicalURI := "/"
params = append(params, "messages=["+messageStr+"]") canonicalQueryString := ""
canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\nx-tc-action:%s\n",
sort.Strings(params) "application/json", host, strings.ToLower(adaptor.Action))
url := "hunyuan.cloud.tencent.com/hyllm/v1/chat/completions?" + strings.Join(params, "&") signedHeaders := "content-type;host;x-tc-action"
mac := hmac.New(sha1.New, []byte(secretKey)) payload, _ := json.Marshal(req)
signURL := url hashedRequestPayload := sha256hex(string(payload))
mac.Write([]byte(signURL)) canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
sign := mac.Sum([]byte(nil)) httpRequestMethod,
return base64.StdEncoding.EncodeToString(sign) canonicalURI,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
hashedRequestPayload)
// build string to sign
algorithm := "TC3-HMAC-SHA256"
requestTimestamp := strconv.FormatInt(adaptor.Timestamp, 10)
timestamp, _ := strconv.ParseInt(requestTimestamp, 10, 64)
t := time.Unix(timestamp, 0).UTC()
// must be the format 2006-01-02, ref to package time for more info
date := t.Format("2006-01-02")
credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, "hunyuan")
hashedCanonicalRequest := sha256hex(canonicalRequest)
string2sign := fmt.Sprintf("%s\n%s\n%s\n%s",
algorithm,
requestTimestamp,
credentialScope,
hashedCanonicalRequest)
// sign string
secretDate := hmacSha256(date, "TC3"+secKey)
secretService := hmacSha256("hunyuan", secretDate)
secretKey := hmacSha256("tc3_request", secretService)
signature := hex.EncodeToString([]byte(hmacSha256(string2sign, secretKey)))
// build authorization
authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
algorithm,
secId,
credentialScope,
signedHeaders,
signature)
return authorization
} }

View File

@@ -1,63 +1,75 @@
package tencent package tencent
import (
"github.com/songquanpeng/one-api/relay/model"
)
type Message struct { type Message struct {
Role string `json:"role"` Role string `json:"Role"`
Content string `json:"content"` Content string `json:"Content"`
} }
type ChatRequest struct { type ChatRequest struct {
AppId int64 `json:"app_id"` // 腾讯云账号的 APPID // 模型名称,可选值包括 hunyuan-lite、hunyuan-standard、hunyuan-standard-256K、hunyuan-pro。
SecretId string `json:"secret_id"` // 官网 SecretId // 各模型介绍请阅读 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 中的说明。
// Timestamp当前 UNIX 时间戳,单位为秒,可记录发起 API 请求的时间。 //
// 例如1529223702如果与当前时间相差过大会引起签名过期错误 // 注意:
Timestamp int64 `json:"timestamp"` // 不同的模型计费不同,请根据 [购买指南](https://cloud.tencent.com/document/product/1729/97731) 按需调用。
// Expired 签名的有效期,是一个符合 UNIX Epoch 时间戳规范的数值, Model *string `json:"Model"`
// 单位为秒Expired 必须大于 Timestamp 且 Expired-Timestamp 小于90天 // 聊天上下文信息。
Expired int64 `json:"expired"` // 说明:
QueryID string `json:"query_id"` //请求 Id用于问题排查 // 1. 长度最多为 40按对话时间从旧到新在数组中排列。
// Temperature 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 // 2. Message.Role 可选值system、user、assistant。
// 默认 1.0,取值区间为[0.0,2.0],非必要不建议使用,不合理的取值会影响效果 // 其中system 角色可选如存在则必须位于列表的最开始。user 和 assistant 需交替出现(一问一答),以 user 提问开始和结束,且 Content 不能为空。Role 的顺序示例:[system可选 user assistant user assistant user ...]。
// 建议该参数和 top_p 只设置1个不要同时更改 top_p // 3. Messages 中 Content 总长度不能超过模型输入长度上限(可参考 [产品概述](https://cloud.tencent.com/document/product/1729/104753) 文档),超过则会截断最前面的内容,只保留尾部内容。
Temperature float64 `json:"temperature"` Messages []*Message `json:"Messages"`
// TopP 影响输出文本的多样性,取值越大,生成文本的多样性越强 // 流式调用开关。
// 默认1.0,取值区间为[0.0, 1.0],非必要不建议使用, 不合理的取值会影响效果 // 说明:
// 建议该参数和 temperature 只设置1个不要同时更改 // 1. 未传值时默认为非流式调用false
TopP float64 `json:"top_p"` // 2. 流式调用时以 SSE 协议增量返回结果(返回值取 Choices[n].Delta 中的值,需要拼接增量数据才能获得完整结果)。
// Stream 0同步1流式 默认协议SSE) // 3. 非流式调用时:
// 同步请求超时60s如果内容较长建议使用流式 // 调用方式与普通 HTTP 请求无异。
Stream int `json:"stream"` // 接口响应耗时较长,**如需更低时延建议设置为 true**。
// Messages 会话内容, 长度最多为40, 按对话时间从旧到新在数组中排列 // 只返回一次最终结果(返回值取 Choices[n].Message 中的值)。
// 输入 content 总数最大支持 3000 token。 //
Messages []Message `json:"messages"` // 注意:
// 通过 SDK 调用时,流式和非流式调用需用**不同的方式**获取返回值,具体参考 SDK 中的注释或示例(在各语言 SDK 代码仓库的 examples/hunyuan/v20230901/ 目录中)。
Stream *bool `json:"Stream"`
// 说明:
// 1. 影响输出文本的多样性,取值越大,生成文本的多样性越强。
// 2. 取值区间为 [0.0, 1.0],未传值时使用各模型推荐值。
// 3. 非必要不建议使用,不合理的取值会影响效果。
TopP *float64 `json:"TopP"`
// 说明:
// 1. 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。
// 2. 取值区间为 [0.0, 2.0],未传值时使用各模型推荐值。
// 3. 非必要不建议使用,不合理的取值会影响效果。
Temperature *float64 `json:"Temperature"`
} }
type Error struct { type Error struct {
Code int `json:"code"` Code int `json:"Code"`
Message string `json:"message"` Message string `json:"Message"`
} }
type Usage struct { type Usage struct {
InputTokens int `json:"input_tokens"` PromptTokens int `json:"PromptTokens"`
OutputTokens int `json:"output_tokens"` CompletionTokens int `json:"CompletionTokens"`
TotalTokens int `json:"total_tokens"` TotalTokens int `json:"TotalTokens"`
} }
type ResponseChoices struct { type ResponseChoices struct {
FinishReason string `json:"finish_reason,omitempty"` // 流式结束标志位,为 stop 则表示尾包 FinishReason string `json:"FinishReason,omitempty"` // 流式结束标志位,为 stop 则表示尾包
Messages Message `json:"messages,omitempty"` // 内容,同步模式返回内容,流模式为 null 输出 content 内容总数最多支持 1024token。 Messages Message `json:"Message,omitempty"` // 内容,同步模式返回内容,流模式为 null 输出 content 内容总数最多支持 1024token。
Delta Message `json:"delta,omitempty"` // 内容,流模式返回内容,同步模式为 null 输出 content 内容总数最多支持 1024token。 Delta Message `json:"Delta,omitempty"` // 内容,流模式返回内容,同步模式为 null 输出 content 内容总数最多支持 1024token。
} }
type ChatResponse struct { type ChatResponse struct {
Choices []ResponseChoices `json:"choices,omitempty"` // 结果 Choices []ResponseChoices `json:"Choices,omitempty"` // 结果
Created string `json:"created,omitempty"` // unix 时间戳的字符串 Created int64 `json:"Created,omitempty"` // unix 时间戳的字符串
Id string `json:"id,omitempty"` // 会话 id Id string `json:"Id,omitempty"` // 会话 id
Usage model.Usage `json:"usage,omitempty"` // token 数量 Usage Usage `json:"Usage,omitempty"` // token 数量
Error Error `json:"error,omitempty"` // 错误信息 注意:此字段可能返回 null表示取不到有效值 Error Error `json:"Error,omitempty"` // 错误信息 注意:此字段可能返回 null表示取不到有效值
Note string `json:"note,omitempty"` // 注释 Note string `json:"Note,omitempty"` // 注释
ReqID string `json:"req_id,omitempty"` // 唯一请求 Id每次请求都会返回。用于反馈接口入参 ReqID string `json:"Req_id,omitempty"` // 唯一请求 Id每次请求都会返回。用于反馈接口入参
}
type ChatResponseP struct {
Response ChatResponse `json:"Response,omitempty"`
} }

View File

@@ -27,14 +27,6 @@ func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error { func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *meta.Meta) error {
adaptor.SetupCommonRequestHeader(c, req, meta) adaptor.SetupCommonRequestHeader(c, req, meta)
version := parseAPIVersionByModelName(meta.ActualModelName)
if version == "" {
version = a.meta.Config.APIVersion
}
if version == "" {
version = "v1.1"
}
a.meta.Config.APIVersion = version
// check DoResponse for auth part // check DoResponse for auth part
return nil return nil
} }
@@ -69,6 +61,14 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *meta.Met
if a.request == nil { if a.request == nil {
return nil, openai.ErrorWrapper(errors.New("request is nil"), "request_is_nil", http.StatusBadRequest) return nil, openai.ErrorWrapper(errors.New("request is nil"), "request_is_nil", http.StatusBadRequest)
} }
version := parseAPIVersionByModelName(meta.ActualModelName)
if version == "" {
version = a.meta.Config.APIVersion
}
if version == "" {
version = "v1.1"
}
a.meta.Config.APIVersion = version
if meta.IsStream { if meta.IsStream {
err, usage = StreamHandler(c, meta, *a.request, splits[0], splits[1], splits[2]) err, usage = StreamHandler(c, meta, *a.request, splits[0], splits[1], splits[2])
} else { } else {

View File

@@ -5,7 +5,14 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/songquanpeng/one-api/common" "github.com/songquanpeng/one-api/common"
@@ -16,11 +23,6 @@ import (
"github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/constant"
"github.com/songquanpeng/one-api/relay/meta" "github.com/songquanpeng/one-api/relay/meta"
"github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/model"
"io"
"net/http"
"net/url"
"strings"
"time"
) )
// https://console.xfyun.cn/services/cbm // https://console.xfyun.cn/services/cbm
@@ -28,11 +30,7 @@ import (
func requestOpenAI2Xunfei(request model.GeneralOpenAIRequest, xunfeiAppId string, domain string) *ChatRequest { func requestOpenAI2Xunfei(request model.GeneralOpenAIRequest, xunfeiAppId string, domain string) *ChatRequest {
messages := make([]Message, 0, len(request.Messages)) messages := make([]Message, 0, len(request.Messages))
var lastToolCalls []model.Tool
for _, message := range request.Messages { for _, message := range request.Messages {
if message.ToolCalls != nil {
lastToolCalls = message.ToolCalls
}
messages = append(messages, Message{ messages = append(messages, Message{
Role: message.Role, Role: message.Role,
Content: message.StringContent(), Content: message.StringContent(),
@@ -45,9 +43,10 @@ func requestOpenAI2Xunfei(request model.GeneralOpenAIRequest, xunfeiAppId string
xunfeiRequest.Parameter.Chat.TopK = request.N xunfeiRequest.Parameter.Chat.TopK = request.N
xunfeiRequest.Parameter.Chat.MaxTokens = request.MaxTokens xunfeiRequest.Parameter.Chat.MaxTokens = request.MaxTokens
xunfeiRequest.Payload.Message.Text = messages xunfeiRequest.Payload.Message.Text = messages
if len(lastToolCalls) != 0 {
for _, toolCall := range lastToolCalls { if strings.HasPrefix(domain, "generalv3") {
xunfeiRequest.Payload.Functions.Text = append(xunfeiRequest.Payload.Functions.Text, toolCall.Function) xunfeiRequest.Payload.Functions = &Functions{
Text: request.Tools,
} }
} }
@@ -203,7 +202,7 @@ func Handler(c *gin.Context, meta *meta.Meta, textRequest model.GeneralOpenAIReq
} }
} }
if len(xunfeiResponse.Payload.Choices.Text) == 0 { if len(xunfeiResponse.Payload.Choices.Text) == 0 {
return openai.ErrorWrapper(err, "xunfei_empty_response_detected", http.StatusInternalServerError), nil return openai.ErrorWrapper(errors.New("xunfei empty response detected"), "xunfei_empty_response_detected", http.StatusInternalServerError), nil
} }
xunfeiResponse.Payload.Choices.Text[0].Content = content xunfeiResponse.Payload.Choices.Text[0].Content = content

View File

@@ -9,6 +9,10 @@ type Message struct {
Content string `json:"content"` Content string `json:"content"`
} }
type Functions struct {
Text []model.Tool `json:"text,omitempty"`
}
type ChatRequest struct { type ChatRequest struct {
Header struct { Header struct {
AppId string `json:"app_id"` AppId string `json:"app_id"`
@@ -26,9 +30,7 @@ type ChatRequest struct {
Message struct { Message struct {
Text []Message `json:"text"` Text []Message `json:"text"`
} `json:"message"` } `json:"message"`
Functions struct { Functions *Functions `json:"functions,omitempty"`
Text []model.Function `json:"text,omitempty"`
} `json:"functions,omitempty"`
} `json:"payload"` } `json:"payload"`
} }

View File

@@ -24,7 +24,7 @@ var ChannelBaseURLs = []string{
"https://openrouter.ai/api", // 20 "https://openrouter.ai/api", // 20
"https://api.aiproxy.io", // 21 "https://api.aiproxy.io", // 21
"https://fastgpt.run/api/openapi", // 22 "https://fastgpt.run/api/openapi", // 22
"https://hunyuan.cloud.tencent.com", // 23 "https://hunyuan.tencentcloudapi.com", // 23
"https://generativelanguage.googleapis.com", // 24 "https://generativelanguage.googleapis.com", // 24
"https://api.moonshot.cn", // 25 "https://api.moonshot.cn", // 25
"https://api.baichuan-ai.com", // 26 "https://api.baichuan-ai.com", // 26

View File

@@ -163,7 +163,7 @@ const EditModal = ({ open, channelId, onCancel, onOk }) => {
values.other = 'v2.1'; values.other = 'v2.1';
} }
if (values.key === '') { if (values.key === '') {
if (values.config.ak !== '' && values.config.sk !== '' && values.config.region !== '') { if (values.config.ak && values.config.sk && values.config.region) {
values.key = `${values.config.ak}|${values.config.sk}|${values.config.region}`; values.key = `${values.config.ak}|${values.config.sk}|${values.config.region}`;
} }
} }

View File

@@ -181,9 +181,6 @@ const EditChannel = () => {
if (localInputs.type === 3 && localInputs.other === '') { if (localInputs.type === 3 && localInputs.other === '') {
localInputs.other = '2024-03-01-preview'; localInputs.other = '2024-03-01-preview';
} }
if (localInputs.type === 18 && localInputs.other === '') {
localInputs.other = 'v2.1';
}
let res; let res;
localInputs.models = localInputs.models.join(','); localInputs.models = localInputs.models.join(',');
localInputs.group = localInputs.groups.join(','); localInputs.group = localInputs.groups.join(',');