mirror of
https://github.com/YFGaia/dify-plus.git
synced 2026-06-04 10:14:00 +08:00
fix: 添加DeepSeek支持
This commit is contained in:
@@ -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 目录路径,用于读取私钥
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
|
||||
@@ -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 host(SigV4 & 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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user