Files
Enterprise-Knowledge-Base/internal/models/chat/chat_provider_spec.go
T

198 lines
5.9 KiB
Go

package chat
import (
"context"
"strings"
"github.com/Tencent/WeKnora/internal/logger"
"github.com/Tencent/WeKnora/internal/models/provider"
"github.com/sashabaranov/go-openai"
)
// ProviderSpec describes provider-specific behavior for chat completions.
// Each spec is registered with a ProviderName and optionally a model matcher.
type ProviderSpec struct {
Provider provider.ProviderName
// ModelMatcher: if non-nil, this spec only applies when the model name matches.
// Used for sub-provider routing (e.g. Qwen3 within Aliyun).
ModelMatcher func(modelName string) bool
// RequestCustomizer: provider-specific request modification.
RequestCustomizer func(req *openai.ChatCompletionRequest, opts *ChatOptions, isStream bool) (any, bool)
// EndpointCustomizer: provider-specific endpoint URL override.
EndpointCustomizer func(baseURL string, modelID string, isStream bool) string
}
// chatProviderSpecs is the ordered list of provider specs.
// Order matters: more specific specs (with ModelMatcher) should come before generic ones.
var chatProviderSpecs = []ProviderSpec{
// Aliyun Qwen Thinking Models (must be before generic Aliyun)
{
Provider: provider.ProviderAliyun,
ModelMatcher: func(name string) bool { return provider.IsQwenThinkingModel(name) },
RequestCustomizer: qwenThinkingRequestCustomizer,
},
// LKEAP
{
Provider: provider.ProviderLKEAP,
RequestCustomizer: lkeapRequestCustomizer,
},
// DeepSeek
{
Provider: provider.ProviderDeepSeek,
RequestCustomizer: deepseekRequestCustomizer,
},
// Generic (vLLM)
{
Provider: provider.ProviderGeneric,
RequestCustomizer: genericRequestCustomizer,
},
// Volcengine (火山引擎 Ark)
{
Provider: provider.ProviderVolcengine,
RequestCustomizer: volcengineRequestCustomizer,
},
// NVIDIA
{
Provider: provider.ProviderNvidia,
RequestCustomizer: genericRequestCustomizer,
},
}
// findProviderSpec finds the matching spec for the given provider and model name.
func findProviderSpec(providerName provider.ProviderName, modelName string) *ProviderSpec {
for i := range chatProviderSpecs {
spec := &chatProviderSpecs[i]
if spec.Provider != providerName {
continue
}
if spec.ModelMatcher != nil && !spec.ModelMatcher(modelName) {
continue
}
return spec
}
return nil
}
// --- Type definitions (moved from wrapper files) ---
// QwenChatCompletionRequest Qwen 模型的自定义请求结构体
type QwenChatCompletionRequest struct {
openai.ChatCompletionRequest
EnableThinking *bool `json:"enable_thinking,omitempty"`
}
// ThinkingConfig 思维链配置(LKEAP / Volcengine 等通用格式)
type ThinkingConfig struct {
Type string `json:"type"` // "enabled" 或 "disabled"
}
// ThinkingChatCompletionRequest 带 thinking 字段的自定义请求结构体
// 适用于 LKEAP、Volcengine 等使用 { "thinking": { "type": "enabled" } } 格式的 provider
type ThinkingChatCompletionRequest struct {
openai.ChatCompletionRequest
Thinking *ThinkingConfig `json:"thinking,omitempty"`
}
// --- Customizer functions ---
// qwenThinkingRequestCustomizer 自定义 Qwen 系列(阿里云)模型的思考请求
func qwenThinkingRequestCustomizer(
req *openai.ChatCompletionRequest, opts *ChatOptions, isStream bool,
) (any, bool) {
if !isStream {
// Qwen3 模型在非流式请求时需要显式禁用 thinking
qwenReq := QwenChatCompletionRequest{
ChatCompletionRequest: *req,
}
enableThinking := false
qwenReq.EnableThinking = &enableThinking
return qwenReq, true
}
// 流式请求:根据 opts.Thinking 启用思考
qwenReq := QwenChatCompletionRequest{
ChatCompletionRequest: *req,
}
thinking := false
if opts != nil && opts.Thinking != nil {
thinking = *opts.Thinking
}
qwenReq.EnableThinking = &thinking
// 必须返回 true 以使用 raw HTTP,否则 SDK 会过滤掉 enable_thinking 字段
return qwenReq, true
}
// lkeapRequestCustomizer 自定义 LKEAP 请求
// 仅对 DeepSeek V3.x 系列模型设置 thinking 参数;R1 系列默认开启思维链
// 参考:https://cloud.tencent.com/document/product/1772/115963
func lkeapRequestCustomizer(
req *openai.ChatCompletionRequest, opts *ChatOptions, _ bool,
) (any, bool) {
modelName := req.Model
if !strings.Contains(strings.ToLower(modelName), "deepseek-v3") || opts == nil || opts.Thinking == nil {
return nil, false
}
lkeapReq := ThinkingChatCompletionRequest{
ChatCompletionRequest: *req,
}
thinkingType := "disabled"
if *opts.Thinking {
thinkingType = "enabled"
}
lkeapReq.Thinking = &ThinkingConfig{Type: thinkingType}
return lkeapReq, true
}
// deepseekRequestCustomizer 自定义 DeepSeek 请求
// DeepSeek 模型不支持 tool_choice 参数,需要清除
func deepseekRequestCustomizer(
req *openai.ChatCompletionRequest, opts *ChatOptions, _ bool,
) (any, bool) {
if opts != nil && opts.ToolChoice != "" {
logger.Infof(context.Background(), "deepseek model, skip tool_choice")
req.ToolChoice = nil
}
return nil, false
}
// genericRequestCustomizer 自定义 Generic 请求
// Generic provider(如 vLLM)使用 ChatTemplateKwargs 传递 thinking 参数
func genericRequestCustomizer(
req *openai.ChatCompletionRequest, opts *ChatOptions, _ bool,
) (any, bool) {
thinking := false
if opts != nil && opts.Thinking != nil {
thinking = *opts.Thinking
}
req.ChatTemplateKwargs = map[string]interface{}{
"enable_thinking": thinking,
}
return req, true
}
// volcengineRequestCustomizer 自定义火山引擎请求
// 火山引擎使用 thinking 参数控制深度思考,格式同 LKEAP: { "type": "enabled"/"disabled" }
func volcengineRequestCustomizer(
req *openai.ChatCompletionRequest, opts *ChatOptions, _ bool,
) (any, bool) {
if opts == nil || opts.Thinking == nil {
return nil, false
}
vcReq := ThinkingChatCompletionRequest{
ChatCompletionRequest: *req,
}
thinkingType := "disabled"
if *opts.Thinking {
thinkingType = "enabled"
}
vcReq.Thinking = &ThinkingConfig{Type: thinkingType}
return vcReq, true
}