Skip to content

Commit 65b2d9e

Browse files
committed
feat: support aliyun qwen3 model
1 parent 4489a4d commit 65b2d9e

5 files changed

Lines changed: 241 additions & 59 deletions

File tree

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
github.com/sashabaranov/go-openai v1.40.5
2222
github.com/sirupsen/logrus v1.9.3
2323
github.com/spf13/viper v1.20.1
24+
github.com/stretchr/testify v1.11.1
2425
github.com/tencentyun/cos-go-sdk-v5 v0.7.65
2526
github.com/yanyiwu/gojieba v1.4.5
2627
go.opentelemetry.io/otel v1.37.0
@@ -45,6 +46,7 @@ require (
4546
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4647
github.com/clbanning/mxj v1.8.4 // indirect
4748
github.com/cloudwego/base64x v0.1.5 // indirect
49+
github.com/davecgh/go-spew v1.1.1 // indirect
4850
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
4951
github.com/dustin/go-humanize v1.0.1 // indirect
5052
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
@@ -81,6 +83,7 @@ require (
8183
github.com/olekukonko/tablewriter v0.0.5 // indirect
8284
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
8385
github.com/pierrec/lz4/v4 v4.1.21 // indirect
86+
github.com/pmezard/go-difflib v1.0.0 // indirect
8487
github.com/rivo/uniseg v0.4.7 // indirect
8588
github.com/robfig/cron/v3 v3.0.1 // indirect
8689
github.com/rs/xid v1.6.0 // indirect

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
192192
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
193193
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
194194
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
195-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
196195
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
196+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
197+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
197198
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
198199
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
199200
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=

internal/handler/initialization.go

Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,8 @@ func (h *InitializationHandler) checkRemoteModelConnection(ctx context.Context,
13611361
"content": "test",
13621362
},
13631363
},
1364-
"max_tokens": 1,
1364+
"max_tokens": 1,
1365+
"enable_thinking": false, // for dashscope.aliyuncs qwen3-32b
13651366
}
13661367

13671368
jsonData, err := json.Marshal(testRequest)
@@ -1386,6 +1387,11 @@ func (h *InitializationHandler) checkRemoteModelConnection(ctx context.Context,
13861387
}
13871388
defer resp.Body.Close()
13881389

1390+
body, err := io.ReadAll(resp.Body)
1391+
if err == nil {
1392+
logger.Infof(ctx, "Response body: %s", string(body))
1393+
}
1394+
13891395
// 检查响应状态
13901396
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
13911397
// 连接成功,模型可用
@@ -1401,58 +1407,6 @@ func (h *InitializationHandler) checkRemoteModelConnection(ctx context.Context,
14011407
}
14021408
}
14031409

1404-
// checkModelExistence 检查指定模型是否在模型列表中存在
1405-
func (h *InitializationHandler) checkModelExistence(ctx context.Context,
1406-
resp *http.Response, modelName string) (bool, string) {
1407-
body, err := io.ReadAll(resp.Body)
1408-
if err != nil {
1409-
return true, "连接正常,但无法验证模型列表"
1410-
}
1411-
1412-
var modelsResp struct {
1413-
Data []struct {
1414-
ID string `json:"id"`
1415-
Object string `json:"object"`
1416-
} `json:"data"`
1417-
Object string `json:"object"`
1418-
}
1419-
1420-
// 尝试解析模型列表响应
1421-
if err := json.Unmarshal(body, &modelsResp); err != nil {
1422-
// 如果无法解析,可能是非标准API,只要连接成功就认为可用
1423-
return true, "连接正常"
1424-
}
1425-
1426-
// 检查模型是否在列表中
1427-
for _, model := range modelsResp.Data {
1428-
if model.ID == modelName {
1429-
return true, "连接正常,模型存在"
1430-
}
1431-
}
1432-
1433-
// 模型不在列表中,返回可用的模型建议
1434-
if len(modelsResp.Data) > 0 {
1435-
availableModels := make([]string, 0, min(3, len(modelsResp.Data)))
1436-
for i, model := range modelsResp.Data {
1437-
if i >= 3 {
1438-
break
1439-
}
1440-
availableModels = append(availableModels, model.ID)
1441-
}
1442-
return false, fmt.Sprintf("模型 '%s' 不存在,可用模型: %s", modelName, strings.Join(availableModels, ", "))
1443-
}
1444-
1445-
return false, fmt.Sprintf("模型 '%s' 不存在", modelName)
1446-
}
1447-
1448-
// min returns the minimum of two integers
1449-
func min(a, b int) int {
1450-
if a < b {
1451-
return a
1452-
}
1453-
return b
1454-
}
1455-
14561410
// checkRerankModelConnection 检查Rerank模型连接和功能的内部方法
14571411
func (h *InitializationHandler) checkRerankModelConnection(ctx context.Context,
14581412
modelName, baseURL, apiKey string) (bool, string) {

internal/models/chat/remote_api.go

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package chat
22

33
import (
4+
"bytes"
45
"context"
6+
"encoding/json"
57
"fmt"
8+
"net/http"
9+
"strings"
610

711
"github.com/Tencent/WeKnora/internal/types"
812
"github.com/sashabaranov/go-openai"
@@ -13,6 +17,14 @@ type RemoteAPIChat struct {
1317
modelName string
1418
client *openai.Client
1519
modelID string
20+
baseURL string
21+
apiKey string
22+
}
23+
24+
// QwenChatCompletionRequest 用于 qwen 模型的自定义请求结构体
25+
type QwenChatCompletionRequest struct {
26+
openai.ChatCompletionRequest
27+
EnableThinking *bool `json:"enable_thinking,omitempty"` // qwen 模型专用字段
1628
}
1729

1830
// NewRemoteAPIChat 调用远程API 聊天实例
@@ -26,6 +38,8 @@ func NewRemoteAPIChat(chatConfig *ChatConfig) (*RemoteAPIChat, error) {
2638
modelName: chatConfig.ModelName,
2739
client: openai.NewClientWithConfig(config),
2840
modelID: chatConfig.ModelID,
41+
baseURL: chatConfig.BaseURL,
42+
apiKey: apiKey,
2943
}, nil
3044
}
3145

@@ -41,6 +55,27 @@ func (c *RemoteAPIChat) convertMessages(messages []Message) []openai.ChatComplet
4155
return openaiMessages
4256
}
4357

58+
// isQwenModel 检查是否为 qwen 模型
59+
func (c *RemoteAPIChat) isAliyunQwen3Model() bool {
60+
return strings.HasPrefix(c.modelName, "qwen3-") && c.baseURL == "https://dashscope.aliyuncs.com/compatible-mode/v1"
61+
}
62+
63+
// buildQwenChatCompletionRequest 构建 qwen 模型的聊天请求参数
64+
func (c *RemoteAPIChat) buildQwenChatCompletionRequest(messages []Message,
65+
opts *ChatOptions, isStream bool,
66+
) QwenChatCompletionRequest {
67+
req := QwenChatCompletionRequest{
68+
ChatCompletionRequest: c.buildChatCompletionRequest(messages, opts, isStream),
69+
}
70+
71+
// 对于 qwen 模型,在非流式调用中强制设置 enable_thinking: false
72+
if !isStream {
73+
enableThinking := false
74+
req.EnableThinking = &enableThinking
75+
}
76+
return req
77+
}
78+
4479
// buildChatCompletionRequest 构建聊天请求参数
4580
func (c *RemoteAPIChat) buildChatCompletionRequest(messages []Message,
4681
opts *ChatOptions, isStream bool,
@@ -71,18 +106,18 @@ func (c *RemoteAPIChat) buildChatCompletionRequest(messages []Message,
71106
if opts.PresencePenalty > 0 {
72107
req.PresencePenalty = float32(opts.PresencePenalty)
73108
}
74-
if opts.Thinking != nil {
75-
req.ChatTemplateKwargs = map[string]any{
76-
"enable_thinking": *opts.Thinking,
77-
}
78-
}
79109
}
80110

81111
return req
82112
}
83113

84114
// Chat 进行非流式聊天
85115
func (c *RemoteAPIChat) Chat(ctx context.Context, messages []Message, opts *ChatOptions) (*types.ChatResponse, error) {
116+
// 如果是 qwen 模型,使用自定义请求
117+
if c.isAliyunQwen3Model() {
118+
return c.chatWithQwen(ctx, messages, opts)
119+
}
120+
86121
// 构建请求参数
87122
req := c.buildChatCompletionRequest(messages, opts, false)
88123

@@ -111,6 +146,68 @@ func (c *RemoteAPIChat) Chat(ctx context.Context, messages []Message, opts *Chat
111146
}, nil
112147
}
113148

149+
// chatWithQwen 使用自定义请求处理 qwen 模型
150+
func (c *RemoteAPIChat) chatWithQwen(ctx context.Context, messages []Message, opts *ChatOptions) (*types.ChatResponse, error) {
151+
// 构建 qwen 请求参数
152+
req := c.buildQwenChatCompletionRequest(messages, opts, false)
153+
154+
// 序列化请求
155+
jsonData, err := json.Marshal(req)
156+
if err != nil {
157+
return nil, fmt.Errorf("marshal request: %w", err)
158+
}
159+
160+
// 构建 URL
161+
endpoint := c.baseURL + "/chat/completions"
162+
163+
// 创建 HTTP 请求
164+
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(jsonData))
165+
if err != nil {
166+
return nil, fmt.Errorf("create request: %w", err)
167+
}
168+
169+
// 设置请求头
170+
httpReq.Header.Set("Content-Type", "application/json")
171+
httpReq.Header.Set("Authorization", "Bearer "+c.apiKey)
172+
173+
// 发送请求
174+
client := &http.Client{}
175+
resp, err := client.Do(httpReq)
176+
if err != nil {
177+
return nil, fmt.Errorf("send request: %w", err)
178+
}
179+
defer resp.Body.Close()
180+
181+
// 检查响应状态
182+
if resp.StatusCode != http.StatusOK {
183+
return nil, fmt.Errorf("API request failed with status: %d", resp.StatusCode)
184+
}
185+
186+
// 解析响应
187+
var chatResp openai.ChatCompletionResponse
188+
if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil {
189+
return nil, fmt.Errorf("decode response: %w", err)
190+
}
191+
192+
if len(chatResp.Choices) == 0 {
193+
return nil, fmt.Errorf("no response from API")
194+
}
195+
196+
// 转换响应格式
197+
return &types.ChatResponse{
198+
Content: chatResp.Choices[0].Message.Content,
199+
Usage: struct {
200+
PromptTokens int `json:"prompt_tokens"`
201+
CompletionTokens int `json:"completion_tokens"`
202+
TotalTokens int `json:"total_tokens"`
203+
}{
204+
PromptTokens: chatResp.Usage.PromptTokens,
205+
CompletionTokens: chatResp.Usage.CompletionTokens,
206+
TotalTokens: chatResp.Usage.TotalTokens,
207+
},
208+
}, nil
209+
}
210+
114211
// ChatStream 进行流式聊天
115212
func (c *RemoteAPIChat) ChatStream(ctx context.Context,
116213
messages []Message, opts *ChatOptions,

0 commit comments

Comments
 (0)