fix: 添加DeepSeek支持

This commit is contained in:
npc0-hue
2026-04-24 12:12:28 +08:00
parent d2a7ade1b0
commit 347e48cef6
6 changed files with 54 additions and 64 deletions
+1 -2
View File
@@ -5,6 +5,5 @@ type Gaia struct {
LoginMaxErrorLimit int `mapstructure:"login_max_error_limit" json:"login_max_error_limit" yaml:"login_max_error_limit"`
SuperAdminAccountId string `mapstructure:"SUPER_ADMIN_ACCOUNT_ID" json:"SUPER_ADMIN_ACCOUNT_ID" yaml:"SUPER_ADMIN_ACCOUNT_ID"` // 超级管理员账号
SuperAdminTenantId string `mapstructure:"SUPER_ADMIN_TENANT_ID" json:"SUPER_ADMIN_TENANT_ID" yaml:"SUPER_ADMIN_TENANT_ID"` // 系统默认工作区
StoragePath string `mapstructure:"storage-path" json:"storage-path" yaml:"storage-path"` // Dify storage 目录路径,用于读取私钥
BedrockProxy string `mapstructure:"bedrock_proxy" json:"bedrock_proxy" yaml:"bedrock_proxy"` // 全局 Bedrock 代理地址(host:port 或 http://host:port),凭证未配置 bedrock_proxy_url 时使用
StoragePath string `mapstructure:"storage-path" json:"storage-path" yaml:"storage-path"` // Dify storage 目录路径,用于读取私钥
}
+2 -12
View File
@@ -121,16 +121,6 @@ func overrideRedisFromEnv() {
// overrideAllFromEnv 从环境变量覆盖所有配置
func overrideAllFromEnv() {
overrideDBFromEnv()
overrideRedisFromEnv()
overrideGaiaFromEnv()
}
// overrideGaiaFromEnv 从环境变量覆盖 Gaia 配置
func overrideGaiaFromEnv() {
// BEDROCK_PROXY: 全局 Bedrock 反向代理地址,与 Dify Python 侧 BEDROCK_PROXY 含义一致
if proxy := os.Getenv("BEDROCK_PROXY"); proxy != "" {
global.GVA_CONFIG.Gaia.BedrockProxy = proxy
fmt.Printf("Bedrock proxy overridden from BEDROCK_PROXY environment variable: %s\n", proxy)
}
overrideDBFromEnv()
overrideRedisFromEnv()
}
@@ -9,7 +9,8 @@ const (
ProviderAzure = "azure"
ProviderZhipuai = "zhipuai"
ProviderMinimax = "minimax"
ProviderAWS = "aws" // AWS Bedrock 渠道(用于转发 Claude 等 Anthropic 模型)
ProviderAWS = "aws" // AWS Bedrock 渠道(用于转发 Claude 等 Anthropic 模型)
ProviderDeepSeek = "deepseek" // DeepSeek 渠道
)
// DifyProviderTypeCustom Dify providers 表 provider_type 枚举
@@ -32,15 +33,16 @@ const (
)
// SupportedProviders 列表展示的提供商顺序
var SupportedProviders = []string{ProviderOpenai, ProviderTongyi, ProviderGoogle, ProviderAnthropic, ProviderAWS, ProviderAzure, ProviderZhipuai, ProviderMinimax}
var SupportedProviders = []string{ProviderOpenai, ProviderTongyi, ProviderGoogle, ProviderAnthropic, ProviderAWS, ProviderAzure, ProviderZhipuai, ProviderMinimax, ProviderDeepSeek}
// DefaultChatCompletionsEndpoints 各提供商聊天接口默认完整 URL(兼容旧 ProxyChat
var DefaultChatCompletionsEndpoints = map[string]string{
ProviderOpenai: "https://api.openai.com/v1/chat/completions",
ProviderTongyi: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
ProviderGoogle: "https://generativelanguage.googleapis.com/v1beta/chat/completions",
ProviderZhipuai: "https://open.bigmodel.cn/api/paas/v4/chat/completions",
ProviderMinimax: "https://api.minimax.chat/v1/text/chatcompletion_v2",
ProviderOpenai: "https://api.openai.com/v1/chat/completions",
ProviderTongyi: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
ProviderGoogle: "https://generativelanguage.googleapis.com/v1beta/chat/completions",
ProviderZhipuai: "https://open.bigmodel.cn/api/paas/v4/chat/completions",
ProviderMinimax: "https://api.minimax.chat/v1/text/chatcompletion_v2",
ProviderDeepSeek: "https://api.deepseek.com/v1/chat/completions",
// Azure 需要动态构建 URL,不使用默认值
}
@@ -53,6 +55,7 @@ var DefaultAPIBase = map[string]string{
ProviderAWS: "https://bedrock-runtime.us-east-1.amazonaws.com",
ProviderZhipuai: "https://open.bigmodel.cn",
ProviderMinimax: "https://api.minimax.chat",
ProviderDeepSeek: "https://api.deepseek.com",
// Azure 的 base URL 来自 openai_api_base 配置,不设置默认值
}
@@ -129,4 +132,10 @@ var BuiltinModelPricing = map[string]ModelPricing{
// 命中分支见 service/gaia/model_provider.go 中的 isImageOrPerRequestPath 与 ProxyRequest 计费逻辑
"gpt-image-1": {Input: 0.04, Currency: "USD"},
"gpt-image-2": {Input: 0.05, Currency: "USD"},
// ──── DeepSeek 系列(USD / 百万 token ────
// deepseek-v4-pro:旗舰推理模型,定价参考官方 https://platform.deepseek.com/api-docs/pricing
"deepseek-v4-pro": {Input: 2.19 / 1000, Output: 8.19 / 1000, Unit: 0.001, Currency: "USD"},
// deepseek-v4-flash:高速轻量模型
"deepseek-v4-flash": {Input: 0.27 / 1000, Output: 1.10 / 1000, Unit: 0.001, Currency: "USD"},
}
+21 -41
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
@@ -73,40 +74,18 @@ func (s *ModelProviderService) proxyBedrockRequest(
return fmt.Errorf("重写 Bedrock 请求 body 失败:%w", err)
}
// 3) 确定代理地址(优先凭证内 bedrock_proxy_url,其次全局 BEDROCK_PROXY env
proxyAddr := creds.BedrockProxyURL
if proxyAddr == "" {
proxyAddr = global.GVA_CONFIG.Gaia.BedrockProxy
}
// 规范化:去除 scheme,仅保留 host:port
proxyHost := strings.TrimPrefix(strings.TrimPrefix(proxyAddr, "https://"), "http://")
proxyHost = strings.TrimRight(proxyHost, "/")
// 4) 构建请求 URL
// 真实 Bedrock hostSigV4 & Host 头使用)
bedrockHost := fmt.Sprintf("bedrock-runtime.%s.amazonaws.com", region)
// 3) 构建 Bedrock URL
host := fmt.Sprintf("bedrock-runtime.%s.amazonaws.com", region)
op := "invoke"
if streaming {
op = "invoke-with-response-stream"
}
var requestURL string
if proxyHost != "" {
// 反向代理模式:发普通 HTTP 到代理地址,不需要代理支持 CONNECT 隧道
requestURL = fmt.Sprintf("http://%s/model/%s/%s", proxyHost, modelID, op)
} else {
requestURL = fmt.Sprintf("https://%s/model/%s/%s", bedrockHost, modelID, op)
}
requestURL := fmt.Sprintf("https://%s/model/%s/%s", host, modelID, op)
httpReq, err := http.NewRequest(method, requestURL, bytes.NewReader(rewritten))
if err != nil {
return fmt.Errorf("构建 Bedrock 请求失败:%w", err)
}
// 代理模式下显式设置 Host 为真实 Bedrock 地址,使 SigV4 校验通过
if proxyHost != "" {
httpReq.Host = bedrockHost
global.GVA_LOG.Info("Bedrock 经反向代理转发",
zap.String("proxy", proxyHost), zap.String("bedrockHost", bedrockHost), zap.String("model", modelID))
}
httpReq.Header.Set("Content-Type", "application/json")
if streaming {
httpReq.Header.Set("Accept", "application/vnd.amazon.eventstream")
@@ -115,23 +94,26 @@ func (s *ModelProviderService) proxyBedrockRequest(
httpReq.Header.Set("Accept", "application/json")
}
// 5) SigV4 签名(始终针对真实 Bedrock HTTPS URL 签名,再把签名头复制到实际请求
// 4) SigV4 签名(service=bedrock
awsCreds := credentials.NewStaticCredentials(creds.AWSAccessKeyID, creds.AWSSecretAccessKey, creds.AWSSessionToken)
signer := v4.NewSigner(awsCreds)
signURL := fmt.Sprintf("https://%s/model/%s/%s", bedrockHost, modelID, op)
signReq, _ := http.NewRequest(method, signURL, bytes.NewReader(rewritten))
signReq.Header.Set("Content-Type", "application/json")
if _, err = signer.Sign(signReq, bytes.NewReader(rewritten), "bedrock", region, time.Now()); err != nil {
if _, err = signer.Sign(httpReq, bytes.NewReader(rewritten), "bedrock", region, time.Now()); err != nil {
return fmt.Errorf("Bedrock SigV4 签名失败:%w", err)
}
// 把签名产生的 Authorization / X-Amz-* 头复制到实际发送的请求
for k, vals := range signReq.Header {
httpReq.Header[k] = vals
}
// 6) 发起请求
// 5) 发起请求(若配置了 bedrock_proxy_url 则经 HTTP 代理转发)
startTime := time.Now()
client := &http.Client{Timeout: 5 * time.Minute}
transport := http.DefaultTransport
if creds.BedrockProxyURL != "" {
proxyAddr := creds.BedrockProxyURL
if !strings.HasPrefix(proxyAddr, "http://") && !strings.HasPrefix(proxyAddr, "https://") {
proxyAddr = "http://" + proxyAddr
}
if proxyURL, parseErr := url.Parse(proxyAddr); parseErr == nil {
transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
}
}
client := &http.Client{Timeout: 5 * time.Minute, Transport: transport}
resp, err := client.Do(httpReq)
if err != nil {
s.logBedrock(userID, modelID, "error", err.Error(), startTime, 0, 0)
@@ -139,7 +121,7 @@ func (s *ModelProviderService) proxyBedrockRequest(
}
defer func() { _ = resp.Body.Close() }()
// 7) 写回响应头/状态码(流式改写 Content-Type 为 SSE
// 6) 写回响应头/状态码(流式改写 Content-Type 为 SSE
if w, ok := writer.(http.ResponseWriter); ok {
for k, v := range resp.Header {
lower := strings.ToLower(k)
@@ -166,7 +148,7 @@ func (s *ModelProviderService) proxyBedrockRequest(
return nil
}
// 8) 处理响应体
// 7) 处理响应体
var inputTokens, outputTokens int
if streaming {
inputTokens, outputTokens, err = s.streamBedrockEventStream(resp.Body, writer)
@@ -184,7 +166,7 @@ func (s *ModelProviderService) proxyBedrockRequest(
inputTokens, outputTokens = parseAnthropicUsage(buf.Bytes())
}
// 9) 记录日志 + 计费扣款
// 8) 记录日志 + 计费扣款
s.logBedrock(userID, modelID, "success", "", startTime, inputTokens, outputTokens)
if inputTokens > 0 || outputTokens > 0 {
pricing, _ := s.fetchModelPricingFromDify(modelID)
@@ -300,5 +282,3 @@ func (s *ModelProviderService) logBedrock(userID, modelID, status, errMsg string
global.GVA_LOG.Warn("logBedrock 写日志失败", zap.Error(err))
}
}
+12 -1
View File
@@ -422,6 +422,10 @@ func (s *ModelProviderService) GetAvailableModelsFromDify(providerName string) (
if providerName == gaia.ProviderAWS || providerName == gaia.ProviderAnthropic {
return nil, nil
}
// DeepSeek 模型由前端手输(避免拉取全量列表),直接返回空列表
if providerName == gaia.ProviderDeepSeek {
return nil, nil
}
creds, err := s.GetDifyProviderCredentials(providerName)
if err != nil || creds.APIKey == "" {
@@ -1026,6 +1030,10 @@ func (s *ModelProviderService) getProviderCandidatesByModel(modelName string) []
if strings.HasPrefix(modelLower, "minimax") || strings.Contains(modelLower, "abab") {
return []string{gaia.ProviderTongyi, gaia.ProviderMinimax}
}
// DeepSeek 系列模型走 deepseek 渠道
if strings.HasPrefix(modelLower, "deepseek") {
return []string{gaia.ProviderDeepSeek}
}
return nil
}
@@ -1076,6 +1084,10 @@ func (s *ModelProviderService) getProviderByModel(modelName string) (string, err
if strings.HasPrefix(modelLower, "minimax") || strings.Contains(modelLower, "abab") {
return gaia.ProviderTongyi, nil
}
// DeepSeek 系列走 deepseek 渠道
if strings.HasPrefix(modelLower, "deepseek") {
return gaia.ProviderDeepSeek, nil
}
return "", fmt.Errorf("无法识别模型 %s 的提供商", modelName)
}
@@ -1456,4 +1468,3 @@ func (s *ModelProviderService) difyProviderLikePattern(providerName string) stri
return fmt.Sprintf("%%%s%%", providerName)
}
}
@@ -151,7 +151,8 @@ const providerDisplayNames = {
aws: 'AWS Bedrock',
azure: 'Azure OpenAI',
zhipuai: '智谱 AI',
minimax: 'MiniMax'
minimax: 'MiniMax',
deepseek: 'DeepSeek'
}
const getProviderDisplayName = (providerName) => {