WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// AliyunChatBaseURL 阿里云 DashScope Chat/Embedding 的默认 BaseURL
|
||||
AliyunChatBaseURL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
// AliyunRerankBaseURL 阿里云 DashScope Rerank 的默认 BaseURL
|
||||
AliyunRerankBaseURL = "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank"
|
||||
)
|
||||
|
||||
// AliyunProvider 实现阿里云 DashScope 的 Provider 接口
|
||||
type AliyunProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&AliyunProvider{})
|
||||
}
|
||||
|
||||
// Info 返回阿里云 provider 的元数据
|
||||
func (p *AliyunProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderAliyun,
|
||||
DisplayName: "阿里云 DashScope",
|
||||
Description: "qwen-plus, tongyi-embedding-vision-plus, qwen3-rerank, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: AliyunChatBaseURL,
|
||||
types.ModelTypeEmbedding: AliyunChatBaseURL,
|
||||
types.ModelTypeRerank: AliyunRerankBaseURL,
|
||||
types.ModelTypeVLLM: AliyunChatBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证阿里云 provider 配置
|
||||
func (p *AliyunProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Aliyun DashScope")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsQwenThinkingModel 检查模型名是否为支持思维链的 Qwen 模型
|
||||
// 支持思维链的模型需要特殊处理 enable_thinking 参数
|
||||
func IsQwenThinkingModel(modelName string) bool {
|
||||
lowerName := strings.ToLower(modelName)
|
||||
return strings.HasPrefix(lowerName, "qwen3") ||
|
||||
strings.HasPrefix(lowerName, "qwen-plus") ||
|
||||
strings.HasPrefix(lowerName, "qwen-max") ||
|
||||
strings.HasPrefix(lowerName, "qwen-turbo")
|
||||
}
|
||||
|
||||
// IsQwen3Model checks whether the model belongs to the Qwen3 family only.
|
||||
func IsQwen3Model(modelName string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(modelName), "qwen3")
|
||||
}
|
||||
|
||||
// IsDeepSeekModel 检查模型名是否为 DeepSeek 模型
|
||||
// DeepSeek 模型不支持 tool_choice 参数
|
||||
func IsDeepSeekModel(modelName string) bool {
|
||||
return strings.Contains(strings.ToLower(modelName), "deepseek")
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const AnthropicBaseURL = "https://api.anthropic.com/v1"
|
||||
|
||||
// AnthropicProvider implements native Anthropic Messages API metadata.
|
||||
type AnthropicProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&AnthropicProvider{})
|
||||
}
|
||||
|
||||
func (p *AnthropicProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderAnthropic,
|
||||
DisplayName: "Anthropic",
|
||||
Description: "Claude models via native Anthropic Messages API",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: AnthropicBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AnthropicProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Anthropic provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
// AzureOpenAIProvider 实现 Azure OpenAI 的 Provider 接口
|
||||
type AzureOpenAIProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&AzureOpenAIProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 Azure OpenAI provider 的元数据
|
||||
func (p *AzureOpenAIProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderAzureOpenAI,
|
||||
DisplayName: "Azure OpenAI",
|
||||
Description: "gpt-4o, gpt-4, text-embedding-ada-002, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: "https://{resource}.openai.azure.com",
|
||||
types.ModelTypeEmbedding: "https://{resource}.openai.azure.com",
|
||||
types.ModelTypeRerank: "https://{resource}.openai.azure.com",
|
||||
types.ModelTypeVLLM: "https://{resource}.openai.azure.com",
|
||||
types.ModelTypeASR: "https://{resource}.openai.azure.com",
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeVLLM,
|
||||
types.ModelTypeASR,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
ExtraFields: []ExtraFieldConfig{
|
||||
{
|
||||
Key: "api_version",
|
||||
Label: "API Version",
|
||||
Type: "string",
|
||||
Required: false,
|
||||
Default: "2024-10-21",
|
||||
Placeholder: "e.g. 2024-10-21",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 Azure OpenAI provider 配置
|
||||
func (p *AzureOpenAIProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Azure OpenAI provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("deployment name (model name) is required")
|
||||
}
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("Azure resource endpoint (base URL) is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DeepSeekBaseURL DeepSeek 官方 API BaseURL
|
||||
DeepSeekBaseURL = "https://api.deepseek.com/v1"
|
||||
)
|
||||
|
||||
// DeepSeekProvider 实现 DeepSeek 的 Provider 接口
|
||||
type DeepSeekProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&DeepSeekProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 DeepSeek provider 的元数据
|
||||
func (p *DeepSeekProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderDeepSeek,
|
||||
DisplayName: "DeepSeek",
|
||||
Description: "deepseek-chat, deepseek-reasoner, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: DeepSeekBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 DeepSeek provider 配置
|
||||
func (p *DeepSeekProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for DeepSeek provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// GeminiBaseURL Google Gemini API BaseURL
|
||||
GeminiBaseURL = "https://generativelanguage.googleapis.com/v1beta"
|
||||
// GeminiOpenAICompatBaseURL Gemini OpenAI 兼容模式 BaseURL
|
||||
GeminiOpenAICompatBaseURL = "https://generativelanguage.googleapis.com/v1beta/openai"
|
||||
)
|
||||
|
||||
// GeminiProvider 实现 Google Gemini 的 Provider 接口
|
||||
type GeminiProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&GeminiProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 Gemini provider 的元数据
|
||||
func (p *GeminiProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderGemini,
|
||||
DisplayName: "Google Gemini",
|
||||
Description: "gemini-3-flash-preview, gemini-2.5-pro, gemini-embedding-2, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: GeminiOpenAICompatBaseURL,
|
||||
types.ModelTypeEmbedding: GeminiBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 Gemini provider 配置
|
||||
func (p *GeminiProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Google Gemini provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
// GenericProvider 实现通用 OpenAI 兼容的 Provider 接口
|
||||
type GenericProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&GenericProvider{})
|
||||
}
|
||||
|
||||
// Info 返回通用 provider 的元数据
|
||||
func (p *GenericProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderGeneric,
|
||||
DisplayName: "自定义 (OpenAI兼容接口)",
|
||||
Description: "Generic API endpoint (OpenAI-compatible)",
|
||||
DefaultURLs: map[types.ModelType]string{}, // 需要用户自行配置填写
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
types.ModelTypeASR,
|
||||
},
|
||||
RequiresAuth: false, // 可能需要也可能不需要
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证通用 provider 配置
|
||||
func (p *GenericProvider) ValidateConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("base URL is required for generic provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// GPUStackBaseURL GPUStack API BaseURL (OpenAI 兼容模式)
|
||||
GPUStackBaseURL = "http://your_gpustack_server_url/v1-openai"
|
||||
// GPUStackRerankBaseURL GPUStack Rerank API 虽然兼容OpenAI,但路径不同 (/v1/rerank 而非 /v1-openai/rerank)
|
||||
GPUStackRerankBaseURL = "http://your_gpustack_server_url/v1"
|
||||
)
|
||||
|
||||
// GPUStackProvider 实现 GPUStack 的 Provider 接口
|
||||
type GPUStackProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&GPUStackProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 GPUStack provider 的元数据
|
||||
func (p *GPUStackProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderGPUStack,
|
||||
DisplayName: "GPUStack",
|
||||
Description: "Choose your deployed model on GPUStack",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: GPUStackBaseURL,
|
||||
types.ModelTypeEmbedding: GPUStackBaseURL,
|
||||
types.ModelTypeRerank: GPUStackRerankBaseURL,
|
||||
types.ModelTypeVLLM: GPUStackBaseURL,
|
||||
types.ModelTypeASR: GPUStackBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
types.ModelTypeASR,
|
||||
},
|
||||
RequiresAuth: true, // GPUStack 需要 API Key
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 GPUStack provider 配置
|
||||
func (p *GPUStackProvider) ValidateConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("base URL is required for GPUStack provider")
|
||||
}
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for GPUStack provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// HunyuanBaseURL 腾讯混元 API BaseURL (OpenAI 兼容模式)
|
||||
HunyuanBaseURL = "https://api.hunyuan.cloud.tencent.com/v1"
|
||||
)
|
||||
|
||||
// HunyuanProvider 实现腾讯混元的 Provider 接口
|
||||
type HunyuanProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&HunyuanProvider{})
|
||||
}
|
||||
|
||||
// Info 返回腾讯混元 provider 的元数据
|
||||
func (p *HunyuanProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderHunyuan,
|
||||
DisplayName: "腾讯混元 Hunyuan",
|
||||
Description: "hunyuan-pro, hunyuan-standard, hunyuan-embedding, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: HunyuanBaseURL,
|
||||
types.ModelTypeEmbedding: HunyuanBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证腾讯混元 provider 配置
|
||||
func (p *HunyuanProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Hunyuan provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
JinaBaseURL = "https://api.jina.ai/v1"
|
||||
)
|
||||
|
||||
// JinaProvider 实现 Jina AI 的 Provider 接口
|
||||
type JinaProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&JinaProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 Jina AI provider 的元数据
|
||||
func (p *JinaProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderJina,
|
||||
DisplayName: "Jina",
|
||||
Description: "jina-clip-v1, jina-embeddings-v2-base-zh, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeEmbedding: JinaBaseURL,
|
||||
types.ModelTypeRerank: JinaBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 Jina AI provider 配置
|
||||
func (p *JinaProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Jina AI provider")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// LiteLLMBaseURL is a placeholder the user must replace with a reachable
|
||||
// LiteLLM proxy. Loopback defaults (localhost:4000) are rejected by SSRF
|
||||
// unless the host is added to SSRF_WHITELIST; Docker also cannot reach a
|
||||
// proxy on the host via localhost. The hostname contains "litellm" so
|
||||
// DetectProvider recognises the catalog default.
|
||||
LiteLLMBaseURL = "http://your_litellm_proxy/v1"
|
||||
)
|
||||
|
||||
// LiteLLMProvider implements the Provider interface for LiteLLM
|
||||
// (https://github.com/BerriAI/litellm). LiteLLM exposes a single
|
||||
// OpenAI-compatible endpoint that routes to 100+ providers (OpenAI, Anthropic,
|
||||
// Gemini, Bedrock, Vertex, Azure, ...) behind one base URL and key, so it plugs
|
||||
// into WeKnora's OpenAI-compatible transport like the other gateway providers.
|
||||
type LiteLLMProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&LiteLLMProvider{})
|
||||
}
|
||||
|
||||
// Info returns the metadata for the LiteLLM provider.
|
||||
func (p *LiteLLMProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderLiteLLM,
|
||||
DisplayName: "LiteLLM",
|
||||
Description: "Self-hosted LiteLLM proxy: one OpenAI-compatible endpoint to 100+ providers.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: LiteLLMBaseURL,
|
||||
types.ModelTypeEmbedding: LiteLLMBaseURL,
|
||||
types.ModelTypeVLLM: LiteLLMBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig validates the LiteLLM provider configuration.
|
||||
func (p *LiteLLMProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for LiteLLM provider")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// LKEAPBaseURL 腾讯云知识引擎原子能力 (LKEAP) 兼容 OpenAI 协议的 BaseURL
|
||||
LKEAPBaseURL = "https://api.lkeap.cloud.tencent.com/v1"
|
||||
// LKEAPRerankBaseURL 腾讯云知识引擎原子能力 Rerank API 域名(TC3 签名)
|
||||
LKEAPRerankBaseURL = "https://lkeap.tencentcloudapi.com"
|
||||
)
|
||||
|
||||
// LKEAPProvider 实现腾讯云 LKEAP 的 Provider 接口
|
||||
// 支持 DeepSeek-R1, DeepSeek-V3 系列模型,具备思维链能力
|
||||
type LKEAPProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&LKEAPProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 LKEAP provider 的元数据
|
||||
func (p *LKEAPProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderLKEAP,
|
||||
DisplayName: "腾讯云 LKEAP",
|
||||
Description: "DeepSeek-R1, DeepSeek-V3, lke-reranker-base 等",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: LKEAPBaseURL,
|
||||
types.ModelTypeRerank: LKEAPRerankBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeRerank,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 LKEAP provider 配置
|
||||
func (p *LKEAPProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for LKEAP provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsLKEAPDeepSeekV3Model 检查是否为 DeepSeek V3.x 系列模型
|
||||
// V3.x 系列支持通过 Thinking 参数控制思维链开关
|
||||
func IsLKEAPDeepSeekV3Model(modelName string) bool {
|
||||
return strings.Contains(strings.ToLower(modelName), "deepseek-v3")
|
||||
}
|
||||
|
||||
// IsLKEAPDeepSeekR1Model 检查是否为 DeepSeek R1 系列模型
|
||||
// R1 系列默认开启思维链
|
||||
func IsLKEAPDeepSeekR1Model(modelName string) bool {
|
||||
return strings.Contains(strings.ToLower(modelName), "deepseek-r1")
|
||||
}
|
||||
|
||||
// IsLKEAPThinkingModel 检查是否为支持思维链的 LKEAP 模型
|
||||
func IsLKEAPThinkingModel(modelName string) bool {
|
||||
return IsLKEAPDeepSeekR1Model(modelName) || IsLKEAPDeepSeekV3Model(modelName)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
LongCatBaseURL = "https://api.longcat.chat/openai/v1"
|
||||
)
|
||||
|
||||
// LongCatProvider 实现 LongCat AI 的 Provider 接口
|
||||
type LongCatProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&LongCatProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 LongCat provider 的元数据
|
||||
func (p *LongCatProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderLongCat,
|
||||
DisplayName: "LongCat AI",
|
||||
Description: "LongCat-Flash-Chat, LongCat-Flash-Thinking, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: LongCatBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 LongCat provider 配置
|
||||
func (p *LongCatProvider) ValidateConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("base URL is required for LongCat provider")
|
||||
}
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for LongCat provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// MimoBaseURL 小米 Mimo API BaseURL
|
||||
MimoBaseURL = "https://api.xiaomimimo.com/v1"
|
||||
)
|
||||
|
||||
// MimoProvider 实现小米 Mimo 的 Provider 接口
|
||||
type MimoProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&MimoProvider{})
|
||||
}
|
||||
|
||||
// Info 返回小米 Mimo provider 的元数据
|
||||
func (p *MimoProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderMimo,
|
||||
DisplayName: "小米 MiMo",
|
||||
Description: "mimo-v2-flash",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: MimoBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证小米 Mimo provider 配置
|
||||
func (p *MimoProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Mimo provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// MiniMaxBaseURL MiniMax 国际版 API BaseURL
|
||||
MiniMaxBaseURL = "https://api.minimax.io/v1"
|
||||
// MiniMaxCNBaseURL MiniMax 国内版 API BaseURL
|
||||
MiniMaxCNBaseURL = "https://api.minimaxi.com/v1"
|
||||
)
|
||||
|
||||
// MiniMaxProvider 实现 MiniMax 的 Provider 接口
|
||||
type MiniMaxProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&MiniMaxProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 MiniMax provider 的元数据
|
||||
func (p *MiniMaxProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderMiniMax,
|
||||
DisplayName: "MiniMax",
|
||||
Description: "MiniMax-M3, MiniMax-M2.7, MiniMax-M2.7-highspeed, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: MiniMaxCNBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 MiniMax provider 配置
|
||||
func (p *MiniMaxProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for MiniMax provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModelScopeBaseURL ModelScope API BaseURL (OpenAI 兼容模式)
|
||||
ModelScopeBaseURL = "https://api-inference.modelscope.cn/v1"
|
||||
)
|
||||
|
||||
// ModelScopeProvider 实现 ModelScope (魔搭) 的 Provider 接口
|
||||
type ModelScopeProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&ModelScopeProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 ModelScope provider 的元数据
|
||||
func (p *ModelScopeProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderModelScope,
|
||||
DisplayName: "魔搭 ModelScope",
|
||||
Description: "Qwen/Qwen3-8B, Qwen/Qwen3-Embedding-8B, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: ModelScopeBaseURL,
|
||||
types.ModelTypeEmbedding: ModelScopeBaseURL,
|
||||
types.ModelTypeVLLM: ModelScopeBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 ModelScope provider 配置
|
||||
func (p *ModelScopeProvider) ValidateConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("base URL is required for ModelScope provider")
|
||||
}
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for ModelScope provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
MoonshotBaseURL = "https://api.moonshot.ai/v1"
|
||||
)
|
||||
|
||||
// IsMoonshotFixedTempModel reports whether the given Moonshot/Kimi model
|
||||
// only accepts temperature=1. The following models reject any temperature
|
||||
// value other than 1:
|
||||
// - moonshot-v1 series (moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k)
|
||||
// - kimi-k2.5 and kimi-k2.6 (no temperature param in API docs)
|
||||
//
|
||||
// kimi-k2 / kimi-k2-turbo / kimi-k2-thinking models accept the full [0,1]
|
||||
// range and are NOT affected.
|
||||
func IsMoonshotFixedTempModel(modelName string) bool {
|
||||
name := strings.ToLower(strings.TrimSpace(modelName))
|
||||
if strings.HasPrefix(name, "moonshot-v1") {
|
||||
return true
|
||||
}
|
||||
// kimi-k2.5, kimi-k2.6 — no temperature parameter supported
|
||||
if name == "kimi-k2.5" || name == "kimi-k2.6" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MoonshotProvider 实现 Moonshot AI (Kimi) 的 Provider 接口
|
||||
type MoonshotProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&MoonshotProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 Moonshot provider 的元数据
|
||||
func (p *MoonshotProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderMoonshot,
|
||||
DisplayName: "月之暗面 Moonshot",
|
||||
Description: "kimi-k2-turbo-preview, moonshot-v1-8k-vision-preview, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: MoonshotBaseURL,
|
||||
types.ModelTypeVLLM: MoonshotBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 Moonshot provider 配置
|
||||
func (p *MoonshotProvider) ValidateConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("base URL is required for Moonshot provider")
|
||||
}
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Moonshot provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// NovitaOpenAIBaseURL Novita OpenAI-compatible API BaseURL
|
||||
NovitaOpenAIBaseURL = "https://api.novita.ai/openai/v1"
|
||||
)
|
||||
|
||||
// NovitaProvider 实现 Novita AI 的 Provider 接口
|
||||
type NovitaProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&NovitaProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 Novita provider 的元数据
|
||||
func (p *NovitaProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderNovita,
|
||||
DisplayName: "Novita AI",
|
||||
Description: "moonshotai/kimi-k2.5, zai-org/glm-5, minimax/minimax-m2.7, qwen/qwen3-embedding-0.6b, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: NovitaOpenAIBaseURL,
|
||||
types.ModelTypeEmbedding: NovitaOpenAIBaseURL,
|
||||
types.ModelTypeVLLM: NovitaOpenAIBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 Novita provider 配置
|
||||
func (p *NovitaProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Novita provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// NvidiaChatBaseURL NVIDIA Chat 的默认 BaseURL
|
||||
NvidiaChatBaseURL = "https://integrate.api.nvidia.com/v1"
|
||||
// NvidiaRerankBaseURL NVIDIA Rerank 的默认 BaseURL
|
||||
NvidiaRerankBaseURL = "https://ai.api.nvidia.com/v1/retrieval/nvidia/reranking"
|
||||
)
|
||||
|
||||
// NvidiaProvider 实现NVIDIA AI 的 Provider 接口
|
||||
type NvidiaProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&NvidiaProvider{})
|
||||
}
|
||||
|
||||
// Info 返回NVIDIA provider 的元数据
|
||||
func (p *NvidiaProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderNvidia,
|
||||
DisplayName: "NVIDIA",
|
||||
Description: "deepseek-ai-deepseek-v3_1, nv-embed-v1, rerank-qa-mistral-4b, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: NvidiaChatBaseURL,
|
||||
types.ModelTypeEmbedding: NvidiaChatBaseURL,
|
||||
types.ModelTypeRerank: NvidiaRerankBaseURL,
|
||||
types.ModelTypeVLLM: NvidiaChatBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证NVIDIA provider 配置
|
||||
func (p *NvidiaProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for NVIDIA")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
OpenAIBaseURL = "https://api.openai.com/v1"
|
||||
)
|
||||
|
||||
// OpenAIProvider 实现 OpenAI 的 Provider 接口
|
||||
type OpenAIProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&OpenAIProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 OpenAI provider 的元数据
|
||||
func (p *OpenAIProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderOpenAI,
|
||||
DisplayName: "OpenAI",
|
||||
Description: "gpt-5.2, gpt-5-mini, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: OpenAIBaseURL,
|
||||
types.ModelTypeEmbedding: OpenAIBaseURL,
|
||||
types.ModelTypeRerank: OpenAIBaseURL,
|
||||
types.ModelTypeVLLM: OpenAIBaseURL,
|
||||
types.ModelTypeASR: OpenAIBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
types.ModelTypeASR,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 OpenAI provider 配置
|
||||
func (p *OpenAIProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for OpenAI provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsOpenAIReasoningOrGPT5Model 判断模型是否为 OpenAI / Azure OpenAI 的
|
||||
// 推理类(o-series)或 GPT-5 系列模型。
|
||||
//
|
||||
// 这些模型在 OpenAI Chat Completions API 中:
|
||||
// - 不再支持 `max_tokens`,必须使用 `max_completion_tokens`;
|
||||
// - 仅支持默认的 `temperature=1`、`top_p=1`,且不支持 `frequency_penalty` /
|
||||
// `presence_penalty` 等采样参数(传非默认值会被拒绝)。
|
||||
//
|
||||
// 参考:
|
||||
// - https://platform.openai.com/docs/api-reference/chat
|
||||
// - https://learn.microsoft.com/azure/ai-services/openai/how-to/reasoning
|
||||
//
|
||||
// 仅基于模型名做启发式匹配;对于 Azure OpenAI,因为模型名实际上是 deployment 名,
|
||||
// 用户若用了自定义部署名我们无法识别,此时仍会按普通模型处理(保持原行为)。
|
||||
func IsOpenAIReasoningOrGPT5Model(modelName string) bool {
|
||||
name := strings.ToLower(strings.TrimSpace(modelName))
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(name, "gpt-5") {
|
||||
return true
|
||||
}
|
||||
// o1 / o1-mini / o1-preview / o3 / o3-mini / o4-mini ...
|
||||
// 必须精确匹配,避免误命中 "openai-..." 之类。
|
||||
for _, prefix := range []string{"o1", "o3", "o4"} {
|
||||
if name == prefix || strings.HasPrefix(name, prefix+"-") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package provider
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestIsOpenAIReasoningOrGPT5Model 验证 GPT-5 / o-series 模型识别逻辑。
|
||||
// 见 issue #1283:这些模型必须使用 max_completion_tokens 替代 max_tokens。
|
||||
func TestIsOpenAIReasoningOrGPT5Model(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{"empty", "", false},
|
||||
|
||||
{"gpt-5", "gpt-5", true},
|
||||
{"gpt-5-mini", "gpt-5-mini", true},
|
||||
{"gpt-5.2", "gpt-5.2", true},
|
||||
{"gpt-5.5-pro", "gpt-5.5-pro", true},
|
||||
{"gpt-5 mixed case", "GPT-5.4-Mini", true},
|
||||
|
||||
{"o1", "o1", true},
|
||||
{"o1-mini", "o1-mini", true},
|
||||
{"o1-preview", "o1-preview", true},
|
||||
{"o3", "o3", true},
|
||||
{"o3-mini", "o3-mini", true},
|
||||
{"o4-mini", "o4-mini", true},
|
||||
|
||||
{"gpt-4", "gpt-4", false},
|
||||
{"gpt-4o", "gpt-4o", false},
|
||||
{"gpt-4o-mini", "gpt-4o-mini", false},
|
||||
{"gpt-3.5-turbo", "gpt-3.5-turbo", false},
|
||||
|
||||
{"name starting with 'o1' but not o-series", "olympus-1", false},
|
||||
{"name starting with 'openai-'", "openai-gpt-4", false},
|
||||
{"name starting with 'o3' but not o-series", "o3xtra", false},
|
||||
{"random", "qwen-max", false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := IsOpenAIReasoningOrGPT5Model(tc.input)
|
||||
if got != tc.want {
|
||||
t.Errorf("IsOpenAIReasoningOrGPT5Model(%q) = %v, want %v", tc.input, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
OpenRouterBaseURL = "https://openrouter.ai/api/v1"
|
||||
)
|
||||
|
||||
// OpenRouterProvider 实现 OpenRouter 的 Provider 接口
|
||||
type OpenRouterProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&OpenRouterProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 OpenRouter provider 的元数据
|
||||
func (p *OpenRouterProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderOpenRouter,
|
||||
DisplayName: "OpenRouter",
|
||||
Description: "openai/gpt-5.2-chat, google/gemini-3-flash-preview, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: OpenRouterBaseURL,
|
||||
types.ModelTypeEmbedding: OpenRouterBaseURL,
|
||||
types.ModelTypeVLLM: OpenRouterBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 OpenRouter provider 配置
|
||||
func (p *OpenRouterProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for OpenRouter provider")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// Package provider defines the unified interface and registry for multi-vendor model API adapters.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
// ProviderName 模型服务商名称
|
||||
type ProviderName string
|
||||
|
||||
const (
|
||||
// OpenAI
|
||||
ProviderOpenAI ProviderName = "openai"
|
||||
// Anthropic Claude
|
||||
ProviderAnthropic ProviderName = "anthropic"
|
||||
// 阿里云 DashScope
|
||||
ProviderAliyun ProviderName = "aliyun"
|
||||
// 智谱AI (GLM 系列)
|
||||
ProviderZhipu ProviderName = "zhipu"
|
||||
// OpenRouter
|
||||
ProviderOpenRouter ProviderName = "openrouter"
|
||||
// ProviderLiteLLM is the LiteLLM self-hosted proxy (OpenAI-compatible gateway to 100+ providers).
|
||||
ProviderLiteLLM ProviderName = "litellm"
|
||||
// Requesty
|
||||
ProviderRequesty ProviderName = "requesty"
|
||||
// 硅基流动
|
||||
ProviderSiliconFlow ProviderName = "siliconflow"
|
||||
// Jina AI (Embedding and Rerank)
|
||||
ProviderJina ProviderName = "jina"
|
||||
// Generic 兼容OpenAI (自定义部署)
|
||||
ProviderGeneric ProviderName = "generic"
|
||||
// DeepSeek
|
||||
ProviderDeepSeek ProviderName = "deepseek"
|
||||
// Google Gemini
|
||||
ProviderGemini ProviderName = "gemini"
|
||||
// 火山引擎 Ark
|
||||
ProviderVolcengine ProviderName = "volcengine"
|
||||
// 腾讯混元
|
||||
ProviderHunyuan ProviderName = "hunyuan"
|
||||
// MiniMax
|
||||
ProviderMiniMax ProviderName = "minimax"
|
||||
// 小米 Mimo
|
||||
ProviderMimo ProviderName = "mimo"
|
||||
// GPUStack (私有化部署)
|
||||
ProviderGPUStack ProviderName = "gpustack"
|
||||
// 月之暗面 Moonshot (Kimi)
|
||||
ProviderMoonshot ProviderName = "moonshot"
|
||||
// 魔搭 ModelScope
|
||||
ProviderModelScope ProviderName = "modelscope"
|
||||
// 百度千帆
|
||||
ProviderQianfan ProviderName = "qianfan"
|
||||
// 七牛云
|
||||
ProviderQiniu ProviderName = "qiniu"
|
||||
// 美团 LongCat AI
|
||||
ProviderLongCat ProviderName = "longcat"
|
||||
// 腾讯云 LKEAP (知识引擎原子能力)
|
||||
ProviderLKEAP ProviderName = "lkeap"
|
||||
// NVIDIA
|
||||
ProviderNvidia ProviderName = "nvidia"
|
||||
// Novita AI
|
||||
ProviderNovita ProviderName = "novita"
|
||||
// Azure OpenAI
|
||||
ProviderAzureOpenAI ProviderName = "azure_openai"
|
||||
)
|
||||
|
||||
// AllProviders 返回所有注册的提供者名称
|
||||
func AllProviders() []ProviderName {
|
||||
return []ProviderName{
|
||||
ProviderGeneric,
|
||||
ProviderWeKnoraCloud,
|
||||
ProviderAliyun,
|
||||
ProviderZhipu,
|
||||
ProviderVolcengine,
|
||||
ProviderHunyuan,
|
||||
ProviderSiliconFlow,
|
||||
ProviderDeepSeek,
|
||||
ProviderMiniMax,
|
||||
ProviderMoonshot,
|
||||
ProviderModelScope,
|
||||
ProviderQianfan,
|
||||
ProviderQiniu,
|
||||
ProviderOpenAI,
|
||||
ProviderAnthropic,
|
||||
ProviderGemini,
|
||||
ProviderOpenRouter,
|
||||
ProviderLiteLLM,
|
||||
ProviderRequesty,
|
||||
ProviderJina,
|
||||
ProviderMimo,
|
||||
ProviderLongCat,
|
||||
ProviderLKEAP,
|
||||
ProviderGPUStack,
|
||||
ProviderNvidia,
|
||||
ProviderNovita,
|
||||
ProviderAzureOpenAI,
|
||||
}
|
||||
}
|
||||
|
||||
// ProviderInfo 包含提供者的元数据
|
||||
type ProviderInfo struct {
|
||||
Name ProviderName // 提供者标识
|
||||
DisplayName string // 可读名称
|
||||
Description string // 提供者描述
|
||||
DefaultURLs map[types.ModelType]string // 按模型类型区分的默认 BaseURL
|
||||
ModelTypes []types.ModelType // 支持的模型类型
|
||||
RequiresAuth bool // 是否需要 API key
|
||||
ExtraFields []ExtraFieldConfig // 额外配置字段
|
||||
}
|
||||
|
||||
// GetDefaultURL 获取指定模型类型的默认 URL
|
||||
func (p ProviderInfo) GetDefaultURL(modelType types.ModelType) string {
|
||||
if url, ok := p.DefaultURLs[modelType]; ok {
|
||||
return url
|
||||
}
|
||||
// 回退到 Chat URL
|
||||
if url, ok := p.DefaultURLs[types.ModelTypeKnowledgeQA]; ok {
|
||||
return url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtraFieldConfig 定义提供者的额外配置字段
|
||||
type ExtraFieldConfig struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"` // "string", "number", "boolean", "select"
|
||||
Required bool `json:"required"`
|
||||
Default string `json:"default"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Options []struct {
|
||||
Label string `json:"label"`
|
||||
Value string `json:"value"`
|
||||
} `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
// Config 表示模型提供者的配置
|
||||
type Config struct {
|
||||
Provider ProviderName `json:"provider"`
|
||||
BaseURL string `json:"base_url"`
|
||||
APIKey string `json:"api_key"`
|
||||
ModelName string `json:"model_name"`
|
||||
ModelID string `json:"model_id"`
|
||||
Extra map[string]any `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
// Info 返回服务商的元数据
|
||||
Info() ProviderInfo
|
||||
|
||||
// ValidateConfig 验证服务商的配置
|
||||
ValidateConfig(config *Config) error
|
||||
}
|
||||
|
||||
// registry 存储所有注册的提供者
|
||||
var (
|
||||
registryMu sync.RWMutex
|
||||
registry = make(map[ProviderName]Provider)
|
||||
)
|
||||
|
||||
// Register 添加一个提供者到全局注册表
|
||||
func Register(p Provider) {
|
||||
registryMu.Lock()
|
||||
defer registryMu.Unlock()
|
||||
registry[p.Info().Name] = p
|
||||
}
|
||||
|
||||
// Get 通过名称从注册表中获取提供者
|
||||
func Get(name ProviderName) (Provider, bool) {
|
||||
registryMu.RLock()
|
||||
defer registryMu.RUnlock()
|
||||
p, ok := registry[name]
|
||||
return p, ok
|
||||
}
|
||||
|
||||
// GetOrDefault 通过名称从注册表中获取提供者,如果未找到则返回默认提供者
|
||||
func GetOrDefault(name ProviderName) Provider {
|
||||
p, ok := Get(name)
|
||||
if ok {
|
||||
return p
|
||||
}
|
||||
// 如果未找到则返回默认提供者
|
||||
p, _ = Get(ProviderGeneric)
|
||||
return p
|
||||
}
|
||||
|
||||
// List 返回所有注册的提供者(按 AllProviders 定义的顺序)
|
||||
func List() []ProviderInfo {
|
||||
registryMu.RLock()
|
||||
defer registryMu.RUnlock()
|
||||
|
||||
result := make([]ProviderInfo, 0, len(registry))
|
||||
for _, name := range AllProviders() {
|
||||
if p, ok := registry[name]; ok {
|
||||
result = append(result, p.Info())
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ListByModelType 返回所有支持指定模型类型的提供者(按 AllProviders 定义的顺序)
|
||||
func ListByModelType(modelType types.ModelType) []ProviderInfo {
|
||||
registryMu.RLock()
|
||||
defer registryMu.RUnlock()
|
||||
|
||||
result := make([]ProviderInfo, 0)
|
||||
for _, name := range AllProviders() {
|
||||
if p, ok := registry[name]; ok {
|
||||
info := p.Info()
|
||||
for _, t := range info.ModelTypes {
|
||||
if t == modelType {
|
||||
result = append(result, info)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DetectProvider 通过 BaseURL 检测服务商
|
||||
func DetectProvider(baseURL string) ProviderName {
|
||||
switch {
|
||||
case containsAny(baseURL, "dashscope.aliyuncs.com"):
|
||||
return ProviderAliyun
|
||||
case containsAny(baseURL, "open.bigmodel.cn", "zhipu"):
|
||||
return ProviderZhipu
|
||||
case containsAny(baseURL, "openrouter.ai"):
|
||||
return ProviderOpenRouter
|
||||
// Hostname/path containing "litellm" (including the catalog placeholder
|
||||
// your_litellm_proxy). Loopback URLs such as localhost:4000 stay generic
|
||||
// because they are SSRF-blocked unless explicitly whitelisted.
|
||||
case containsAny(baseURL, "litellm"):
|
||||
return ProviderLiteLLM
|
||||
case containsAny(baseURL, "router.requesty.ai", "requesty.ai"):
|
||||
return ProviderRequesty
|
||||
case containsAny(baseURL, "siliconflow.cn"):
|
||||
return ProviderSiliconFlow
|
||||
case containsAny(baseURL, "api.jina.ai"):
|
||||
return ProviderJina
|
||||
case containsAny(baseURL, "openai.azure.com"):
|
||||
return ProviderAzureOpenAI
|
||||
case containsAny(baseURL, "api.openai.com"):
|
||||
return ProviderOpenAI
|
||||
case containsAny(baseURL, "api.anthropic.com"):
|
||||
return ProviderAnthropic
|
||||
case containsAny(baseURL, "api.deepseek.com"):
|
||||
return ProviderDeepSeek
|
||||
case containsAny(baseURL, "generativelanguage.googleapis.com"):
|
||||
return ProviderGemini
|
||||
case containsAny(baseURL, "volces.com", "volcengine"):
|
||||
return ProviderVolcengine
|
||||
case containsAny(baseURL, "hunyuan.cloud.tencent.com"):
|
||||
return ProviderHunyuan
|
||||
case containsAny(baseURL, "minimax.io", "minimaxi.com"):
|
||||
return ProviderMiniMax
|
||||
case containsAny(baseURL, "xiaomimimo.com"):
|
||||
return ProviderMimo
|
||||
case containsAny(baseURL, "gpustack"):
|
||||
return ProviderGPUStack
|
||||
case containsAny(baseURL, "modelscope.cn"):
|
||||
return ProviderModelScope
|
||||
case containsAny(baseURL, "qiniuapi.com", "qiniu"):
|
||||
return ProviderQiniu
|
||||
case containsAny(baseURL, "moonshot.ai"):
|
||||
return ProviderMoonshot
|
||||
case containsAny(baseURL, "qianfan.baidubce.com", "baidubce.com"):
|
||||
return ProviderQianfan
|
||||
case containsAny(baseURL, "longcat.chat"):
|
||||
return ProviderLongCat
|
||||
case containsAny(baseURL, "lkeap.cloud.tencent.com", "api.lkeap", "lkeap.tencentcloudapi.com"):
|
||||
return ProviderLKEAP
|
||||
case containsAny(baseURL, "nvidia.com"):
|
||||
return ProviderNvidia
|
||||
case containsAny(baseURL, "api.novita.ai", "novita.ai"):
|
||||
return ProviderNovita
|
||||
case containsAny(baseURL, "weknora.weixin.qq.com"):
|
||||
return ProviderWeKnoraCloud
|
||||
default:
|
||||
return ProviderGeneric
|
||||
}
|
||||
}
|
||||
|
||||
func containsAny(s string, substrs ...string) bool {
|
||||
for _, sub := range substrs {
|
||||
if strings.Contains(s, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewConfigFromModel(model *types.Model) (*Config, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("model is nil")
|
||||
}
|
||||
|
||||
providerName := ProviderName(model.Parameters.Provider)
|
||||
if providerName == "" {
|
||||
providerName = DetectProvider(model.Parameters.BaseURL)
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Provider: providerName,
|
||||
BaseURL: model.Parameters.BaseURL,
|
||||
APIKey: model.Parameters.APIKey,
|
||||
ModelName: model.Name,
|
||||
ModelID: model.ID,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProviderRegistry(t *testing.T) {
|
||||
// Test that all default providers are registered
|
||||
t.Run("default providers registered", func(t *testing.T) {
|
||||
providers := List()
|
||||
assert.NotEmpty(t, providers, "should have registered providers")
|
||||
|
||||
// Check specific providers exist
|
||||
for _, name := range []ProviderName{ProviderOpenAI, ProviderAliyun, ProviderZhipu, ProviderGeneric} {
|
||||
p, ok := Get(name)
|
||||
assert.True(t, ok, "provider %s should be registered", name)
|
||||
assert.NotNil(t, p, "provider %s should not be nil", name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetOrDefault fallback", func(t *testing.T) {
|
||||
// Non-existent provider should fall back to generic
|
||||
p := GetOrDefault("nonexistent")
|
||||
require.NotNil(t, p)
|
||||
assert.Equal(t, ProviderGeneric, p.Info().Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDetectProvider(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
expected ProviderName
|
||||
}{
|
||||
{"https://api.openai.com/v1", ProviderOpenAI},
|
||||
{"https://api.anthropic.com/v1", ProviderAnthropic},
|
||||
{"https://openrouter.ai/api/v1", ProviderOpenRouter},
|
||||
{"https://litellm.example.com/v1", ProviderLiteLLM},
|
||||
{LiteLLMBaseURL, ProviderLiteLLM},
|
||||
{"http://localhost:4000/v1", ProviderGeneric},
|
||||
{"https://router.requesty.ai/v1", ProviderRequesty},
|
||||
{"https://dashscope.aliyuncs.com/compatible-mode/v1", ProviderAliyun},
|
||||
{"https://open.bigmodel.cn/api/paas/v4", ProviderZhipu},
|
||||
{"https://api.deepseek.com/v1", ProviderDeepSeek},
|
||||
{"https://generativelanguage.googleapis.com/v1beta/openai", ProviderGemini},
|
||||
{"https://ark.cn-beijing.volces.com/api/v3", ProviderVolcengine},
|
||||
{"https://api.hunyuan.cloud.tencent.com/v1", ProviderHunyuan},
|
||||
{"https://api.minimaxi.com/v1", ProviderMiniMax},
|
||||
{"https://api.minimax.io/v1", ProviderMiniMax},
|
||||
{"https://api.xiaomimimo.com/v1", ProviderMimo},
|
||||
{"https://custom-endpoint.example.com/v1", ProviderGeneric},
|
||||
{"http://localhost:11434/v1", ProviderGeneric},
|
||||
{"https://integrate.api.nvidia.com/v1", ProviderNvidia},
|
||||
{"https://ai.api.nvidia.com/v1/retrieval/nvidia/reranking", ProviderNvidia},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.url, func(t *testing.T) {
|
||||
result := DetectProvider(tt.url)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnthropicProviderValidation(t *testing.T) {
|
||||
p := &AnthropicProvider{}
|
||||
|
||||
t.Run("valid config", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "sk-ant-test",
|
||||
ModelName: "claude-sonnet-4-5",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("missing API key", func(t *testing.T) {
|
||||
config := &Config{
|
||||
ModelName: "claude-sonnet-4-5",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "API key")
|
||||
})
|
||||
|
||||
t.Run("info", func(t *testing.T) {
|
||||
info := p.Info()
|
||||
assert.Equal(t, ProviderAnthropic, info.Name)
|
||||
assert.Equal(t, AnthropicBaseURL, info.GetDefaultURL(types.ModelTypeKnowledgeQA))
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeKnowledgeQA)
|
||||
assert.True(t, info.RequiresAuth)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenAIProviderValidation(t *testing.T) {
|
||||
p := &OpenAIProvider{}
|
||||
|
||||
t.Run("valid config", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "sk-test",
|
||||
ModelName: "gpt-4",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("missing API key", func(t *testing.T) {
|
||||
config := &Config{
|
||||
ModelName: "gpt-4",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "API key")
|
||||
})
|
||||
|
||||
t.Run("missing model name", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "sk-test",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "model name")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAliyunProviderValidation(t *testing.T) {
|
||||
p := &AliyunProvider{}
|
||||
|
||||
t.Run("valid config", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "sk-test",
|
||||
ModelName: "qwen-max",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("info", func(t *testing.T) {
|
||||
info := p.Info()
|
||||
assert.Equal(t, ProviderAliyun, info.Name)
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeKnowledgeQA)
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeEmbedding)
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeRerank)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAliyunModelDetection(t *testing.T) {
|
||||
t.Run("Qwen3 model detection", func(t *testing.T) {
|
||||
assert.True(t, IsQwen3Model("qwen3-32b"))
|
||||
assert.True(t, IsQwen3Model("qwen3-72b"))
|
||||
assert.False(t, IsQwen3Model("qwen-max"))
|
||||
assert.False(t, IsQwen3Model("qwen2.5-72b"))
|
||||
})
|
||||
|
||||
t.Run("DeepSeek model detection", func(t *testing.T) {
|
||||
assert.True(t, IsDeepSeekModel("deepseek-chat"))
|
||||
assert.True(t, IsDeepSeekModel("deepseek-v3.1"))
|
||||
assert.True(t, IsDeepSeekModel("DeepSeek-Chat"))
|
||||
assert.False(t, IsDeepSeekModel("qwen-max"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMiniMaxProviderValidation(t *testing.T) {
|
||||
p := &MiniMaxProvider{}
|
||||
|
||||
t.Run("valid config", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "test-key",
|
||||
ModelName: "MiniMax-M2.7",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("missing API key", func(t *testing.T) {
|
||||
config := &Config{
|
||||
ModelName: "MiniMax-M2.7",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "API key")
|
||||
})
|
||||
|
||||
t.Run("missing model name", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "test-key",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "model name")
|
||||
})
|
||||
|
||||
t.Run("info", func(t *testing.T) {
|
||||
info := p.Info()
|
||||
assert.Equal(t, ProviderMiniMax, info.Name)
|
||||
assert.Equal(t, "MiniMax", info.DisplayName)
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeKnowledgeQA)
|
||||
assert.True(t, info.RequiresAuth)
|
||||
assert.Contains(t, info.Description, "M2.7")
|
||||
})
|
||||
}
|
||||
|
||||
func TestZhipuProviderValidation(t *testing.T) {
|
||||
p := &ZhipuProvider{}
|
||||
|
||||
t.Run("valid config", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "test-key",
|
||||
ModelName: "glm-4",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("info", func(t *testing.T) {
|
||||
info := p.Info()
|
||||
assert.Equal(t, ProviderZhipu, info.Name)
|
||||
assert.Equal(t, ZhipuChatBaseURL, info.GetDefaultURL(types.ModelTypeKnowledgeQA))
|
||||
assert.Equal(t, ZhipuEmbeddingBaseURL, info.GetDefaultURL(types.ModelTypeEmbedding))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRequestyProviderValidation(t *testing.T) {
|
||||
p := &RequestyProvider{}
|
||||
|
||||
t.Run("valid config", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "test-key",
|
||||
ModelName: "openai/gpt-4o-mini",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("missing API key", func(t *testing.T) {
|
||||
config := &Config{
|
||||
ModelName: "openai/gpt-4o-mini",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "API key")
|
||||
})
|
||||
|
||||
t.Run("info", func(t *testing.T) {
|
||||
info := p.Info()
|
||||
assert.Equal(t, ProviderRequesty, info.Name)
|
||||
assert.Equal(t, "Requesty", info.DisplayName)
|
||||
assert.Equal(t, RequestyBaseURL, info.GetDefaultURL(types.ModelTypeKnowledgeQA))
|
||||
assert.Equal(t, RequestyBaseURL, info.GetDefaultURL(types.ModelTypeEmbedding))
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeKnowledgeQA)
|
||||
assert.True(t, info.RequiresAuth)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListByModelType(t *testing.T) {
|
||||
t.Run("chat models", func(t *testing.T) {
|
||||
providers := ListByModelType(types.ModelTypeKnowledgeQA)
|
||||
assert.NotEmpty(t, providers)
|
||||
// Multiple providers support chat
|
||||
assert.GreaterOrEqual(t, len(providers), 9)
|
||||
})
|
||||
|
||||
t.Run("rerank models", func(t *testing.T) {
|
||||
providers := ListByModelType(types.ModelTypeRerank)
|
||||
assert.NotEmpty(t, providers)
|
||||
// Check that Aliyun supports rerank
|
||||
foundAliyun := false
|
||||
foundLKEAP := false
|
||||
foundVolcengine := false
|
||||
for _, p := range providers {
|
||||
if p.Name == ProviderAliyun {
|
||||
foundAliyun = true
|
||||
}
|
||||
if p.Name == ProviderLKEAP {
|
||||
foundLKEAP = true
|
||||
assert.Equal(t, LKEAPRerankBaseURL, p.GetDefaultURL(types.ModelTypeRerank))
|
||||
}
|
||||
if p.Name == ProviderVolcengine {
|
||||
foundVolcengine = true
|
||||
assert.Equal(t, VolcengineRerankBaseURL, p.GetDefaultURL(types.ModelTypeRerank))
|
||||
}
|
||||
}
|
||||
assert.True(t, foundAliyun, "Aliyun should support rerank")
|
||||
assert.True(t, foundLKEAP, "LKEAP should support rerank")
|
||||
assert.True(t, foundVolcengine, "Volcengine should support rerank")
|
||||
})
|
||||
|
||||
t.Run("embedding models include openrouter", func(t *testing.T) {
|
||||
providers := ListByModelType(types.ModelTypeEmbedding)
|
||||
assert.NotEmpty(t, providers)
|
||||
|
||||
found := false
|
||||
for _, p := range providers {
|
||||
if p.Name == ProviderOpenRouter {
|
||||
found = true
|
||||
assert.Equal(t, OpenRouterBaseURL, p.GetDefaultURL(types.ModelTypeEmbedding))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found, "OpenRouter should support embedding")
|
||||
})
|
||||
|
||||
t.Run("embedding models include gemini", func(t *testing.T) {
|
||||
providers := ListByModelType(types.ModelTypeEmbedding)
|
||||
assert.NotEmpty(t, providers)
|
||||
|
||||
found := false
|
||||
for _, p := range providers {
|
||||
if p.Name == ProviderGemini {
|
||||
found = true
|
||||
assert.Equal(t, GeminiBaseURL, p.GetDefaultURL(types.ModelTypeEmbedding))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, found, "Gemini should support embedding via the native Gemini API")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLiteLLMProviderValidation(t *testing.T) {
|
||||
p := &LiteLLMProvider{}
|
||||
|
||||
t.Run("valid config", func(t *testing.T) {
|
||||
config := &Config{
|
||||
APIKey: "test-key",
|
||||
ModelName: "gpt-4.1-mini",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("missing API key", func(t *testing.T) {
|
||||
config := &Config{
|
||||
ModelName: "gpt-4.1-mini",
|
||||
}
|
||||
err := p.ValidateConfig(config)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "API key")
|
||||
})
|
||||
|
||||
t.Run("info", func(t *testing.T) {
|
||||
info := p.Info()
|
||||
assert.Equal(t, ProviderLiteLLM, info.Name)
|
||||
assert.Equal(t, "LiteLLM", info.DisplayName)
|
||||
assert.True(t, info.RequiresAuth)
|
||||
assert.Equal(t, LiteLLMBaseURL, info.DefaultURLs[types.ModelTypeKnowledgeQA])
|
||||
assert.Equal(t, LiteLLMBaseURL, info.DefaultURLs[types.ModelTypeEmbedding])
|
||||
assert.Equal(t, LiteLLMBaseURL, info.DefaultURLs[types.ModelTypeVLLM])
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeKnowledgeQA)
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeEmbedding)
|
||||
assert.Contains(t, info.ModelTypes, types.ModelTypeVLLM)
|
||||
})
|
||||
|
||||
t.Run("registered and listed", func(t *testing.T) {
|
||||
got, ok := Get(ProviderLiteLLM)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, got)
|
||||
|
||||
foundChat := false
|
||||
for _, info := range ListByModelType(types.ModelTypeKnowledgeQA) {
|
||||
if info.Name == ProviderLiteLLM {
|
||||
foundChat = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, foundChat, "LiteLLM should appear for chat models")
|
||||
|
||||
foundEmbed := false
|
||||
for _, info := range ListByModelType(types.ModelTypeEmbedding) {
|
||||
if info.Name == ProviderLiteLLM {
|
||||
foundEmbed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, foundEmbed, "LiteLLM should appear for embedding models")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
QianfanBaseURL = "https://qianfan.baidubce.com/v2"
|
||||
)
|
||||
|
||||
// QianfanProvider 实现百度千帆的 Provider 接口
|
||||
type QianfanProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&QianfanProvider{})
|
||||
}
|
||||
|
||||
// Info 返回百度千帆 provider 的元数据
|
||||
func (p *QianfanProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderQianfan,
|
||||
DisplayName: "百度千帆 Baidu Cloud",
|
||||
Description: "ernie-5.0-thinking-preview, embedding-v1, bce-reranker-base, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: QianfanBaseURL,
|
||||
types.ModelTypeEmbedding: QianfanBaseURL,
|
||||
types.ModelTypeRerank: QianfanBaseURL,
|
||||
types.ModelTypeVLLM: QianfanBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证百度千帆 provider 配置
|
||||
func (p *QianfanProvider) ValidateConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("base URL is required for Qianfan provider")
|
||||
}
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Qianfan provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// QiniuBaseURL 七牛云 API BaseURL (OpenAI 兼容模式)
|
||||
QiniuBaseURL = "https://api.qnaigc.com/v1"
|
||||
)
|
||||
|
||||
// QiniuProvider 实现七牛云的 Provider 接口
|
||||
type QiniuProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&QiniuProvider{})
|
||||
}
|
||||
|
||||
// Info 返回七牛云 provider 的元数据
|
||||
func (p *QiniuProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderQiniu,
|
||||
DisplayName: "七牛云 Qiniu",
|
||||
Description: "deepseek/deepseek-v3.2-251201, z-ai/glm-4.7, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: QiniuBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证七牛云 provider 配置
|
||||
func (p *QiniuProvider) ValidateConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return fmt.Errorf("base URL is required for Qiniu provider")
|
||||
}
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Qiniu provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestyBaseURL = "https://router.requesty.ai/v1"
|
||||
)
|
||||
|
||||
// RequestyProvider 实现 Requesty 的 Provider 接口
|
||||
type RequestyProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&RequestyProvider{})
|
||||
}
|
||||
|
||||
// Info 返回 Requesty provider 的元数据
|
||||
func (p *RequestyProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderRequesty,
|
||||
DisplayName: "Requesty",
|
||||
Description: "openai/gpt-4o-mini, anthropic/claude-sonnet-4-5, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: RequestyBaseURL,
|
||||
types.ModelTypeEmbedding: RequestyBaseURL,
|
||||
types.ModelTypeVLLM: RequestyBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证 Requesty provider 配置
|
||||
func (p *RequestyProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Requesty provider")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
SiliconFlowBaseURL = "https://api.siliconflow.cn/v1"
|
||||
)
|
||||
|
||||
// SiliconFlowProvider 实现硅基流动的 Provider 接口
|
||||
type SiliconFlowProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&SiliconFlowProvider{})
|
||||
}
|
||||
|
||||
// Info 返回硅基流动 provider 的元数据
|
||||
func (p *SiliconFlowProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderSiliconFlow,
|
||||
DisplayName: "硅基流动 SiliconFlow",
|
||||
Description: "deepseek-ai/DeepSeek-V3.1, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: SiliconFlowBaseURL,
|
||||
types.ModelTypeEmbedding: SiliconFlowBaseURL,
|
||||
types.ModelTypeRerank: SiliconFlowBaseURL,
|
||||
types.ModelTypeVLLM: SiliconFlowBaseURL,
|
||||
types.ModelTypeASR: SiliconFlowBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
types.ModelTypeASR,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证硅基流动 provider 配置
|
||||
func (p *SiliconFlowProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for SiliconFlow provider")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// VolcengineChatBaseURL 火山引擎 Ark Chat API BaseURL (OpenAI 兼容模式)
|
||||
VolcengineChatBaseURL = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
// VolcengineEmbeddingBaseURL 火山引擎 Ark Multimodal Embedding API BaseURL
|
||||
VolcengineEmbeddingBaseURL = "https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal"
|
||||
// VolcengineRerankBaseURL 火山引擎知识库托管 Rerank API BaseURL
|
||||
VolcengineRerankBaseURL = "https://api-knowledgebase.mlp.cn-beijing.volces.com"
|
||||
)
|
||||
|
||||
// VolcengineProvider 实现火山引擎 Ark 的 Provider 接口
|
||||
type VolcengineProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&VolcengineProvider{})
|
||||
}
|
||||
|
||||
// Info 返回火山引擎 provider 的元数据
|
||||
func (p *VolcengineProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderVolcengine,
|
||||
DisplayName: "火山引擎 Volcengine",
|
||||
Description: "doubao-1-5-pro-32k-250115, doubao-embedding-vision-250615, doubao-seed-rerank, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: VolcengineChatBaseURL,
|
||||
types.ModelTypeEmbedding: VolcengineEmbeddingBaseURL,
|
||||
types.ModelTypeRerank: VolcengineRerankBaseURL,
|
||||
types.ModelTypeVLLM: VolcengineChatBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证火山引擎 provider 配置
|
||||
func (p *VolcengineProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Volcengine Ark provider")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package provider
|
||||
|
||||
import "github.com/Tencent/WeKnora/internal/types"
|
||||
|
||||
const (
|
||||
ProviderWeKnoraCloud ProviderName = "weknoracloud"
|
||||
|
||||
// WeKnoraCloudBaseURL WeKnoraCloud 服务硬编码 Base URL(统一入口,路径由各实现拼接)
|
||||
WeKnoraCloudBaseURL = "https://weknora.weixin.qq.com"
|
||||
)
|
||||
|
||||
type WeKnoraCloudProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&WeKnoraCloudProvider{})
|
||||
}
|
||||
|
||||
func (p *WeKnoraCloudProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderWeKnoraCloud,
|
||||
DisplayName: "WeKnoraCloud",
|
||||
Description: "WeKnora云服务,模型:chat, embedding, rerank, vlm",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: WeKnoraCloudBaseURL,
|
||||
types.ModelTypeEmbedding: WeKnoraCloudBaseURL,
|
||||
types.ModelTypeRerank: WeKnoraCloudBaseURL,
|
||||
types.ModelTypeVLLM: WeKnoraCloudBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *WeKnoraCloudProvider) ValidateConfig(config *Config) error {
|
||||
// AppID/AppSecret 通过专用初始化接口写入,此处仅做结构校验。
|
||||
// 其中 AppSecret 字段当前实际承载上游 API Key。
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ZhipuChatBaseURL 智谱 AI Chat 的默认 BaseURL
|
||||
ZhipuChatBaseURL = "https://open.bigmodel.cn/api/paas/v4"
|
||||
// ZhipuEmbeddingBaseURL 智谱 AI Embedding 的默认 BaseURL
|
||||
ZhipuEmbeddingBaseURL = "https://open.bigmodel.cn/api/paas/v4"
|
||||
// ZhipuRerankBaseURL 智谱 AI Rerank 的默认 BaseURL
|
||||
ZhipuRerankBaseURL = "https://open.bigmodel.cn/api/paas/v4/rerank"
|
||||
)
|
||||
|
||||
// ZhipuProvider 实现智谱 AI 的 Provider 接口
|
||||
type ZhipuProvider struct{}
|
||||
|
||||
func init() {
|
||||
Register(&ZhipuProvider{})
|
||||
}
|
||||
|
||||
// Info 返回智谱 AI provider 的元数据
|
||||
func (p *ZhipuProvider) Info() ProviderInfo {
|
||||
return ProviderInfo{
|
||||
Name: ProviderZhipu,
|
||||
DisplayName: "智谱 BigModel",
|
||||
Description: "glm-4.7, embedding-3, rerank, etc.",
|
||||
DefaultURLs: map[types.ModelType]string{
|
||||
types.ModelTypeKnowledgeQA: ZhipuChatBaseURL,
|
||||
types.ModelTypeEmbedding: ZhipuEmbeddingBaseURL,
|
||||
types.ModelTypeRerank: ZhipuRerankBaseURL,
|
||||
types.ModelTypeVLLM: ZhipuChatBaseURL,
|
||||
},
|
||||
ModelTypes: []types.ModelType{
|
||||
types.ModelTypeKnowledgeQA,
|
||||
types.ModelTypeEmbedding,
|
||||
types.ModelTypeRerank,
|
||||
types.ModelTypeVLLM,
|
||||
},
|
||||
RequiresAuth: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证智谱 AI provider 配置
|
||||
func (p *ZhipuProvider) ValidateConfig(config *Config) error {
|
||||
if config.APIKey == "" {
|
||||
return fmt.Errorf("API key is required for Zhipu AI")
|
||||
}
|
||||
if config.ModelName == "" {
|
||||
return fmt.Errorf("model name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user