diff --git a/admin/server/config/gaia.go b/admin/server/config/gaia.go index 065289f29..805657b52 100644 --- a/admin/server/config/gaia.go +++ b/admin/server/config/gaia.go @@ -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 目录路径,用于读取私钥 } diff --git a/admin/server/core/env_override.go b/admin/server/core/env_override.go index 8af53587d..2a24dfda2 100644 --- a/admin/server/core/env_override.go +++ b/admin/server/core/env_override.go @@ -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() } diff --git a/admin/server/model/gaia/model_provider_constants_extend.go b/admin/server/model/gaia/model_provider_constants_extend.go index fce2c4e56..3447ee07a 100644 --- a/admin/server/model/gaia/model_provider_constants_extend.go +++ b/admin/server/model/gaia/model_provider_constants_extend.go @@ -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"}, } diff --git a/admin/server/service/gaia/bedrock_extend.go b/admin/server/service/gaia/bedrock_extend.go index ae37ebce1..3b42c812e 100644 --- a/admin/server/service/gaia/bedrock_extend.go +++ b/admin/server/service/gaia/bedrock_extend.go @@ -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)) } } - - diff --git a/admin/server/service/gaia/model_provider.go b/admin/server/service/gaia/model_provider.go index 35f83d551..c8bbc2675 100644 --- a/admin/server/service/gaia/model_provider.go +++ b/admin/server/service/gaia/model_provider.go @@ -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) } } - diff --git a/admin/web/src/view/systemIntegrated/modelManagement/index.vue b/admin/web/src/view/systemIntegrated/modelManagement/index.vue index 2fd43a717..10e0cbf7c 100644 --- a/admin/web/src/view/systemIntegrated/modelManagement/index.vue +++ b/admin/web/src/view/systemIntegrated/modelManagement/index.vue @@ -151,7 +151,8 @@ const providerDisplayNames = { aws: 'AWS Bedrock', azure: 'Azure OpenAI', zhipuai: '智谱 AI', - minimax: 'MiniMax' + minimax: 'MiniMax', + deepseek: 'DeepSeek' } const getProviderDisplayName = (providerName) => {