package echomeet import ( "fmt" "strings" alitranslate "yunyan/sys/aliyun/translate" "yunyan/sys/aliyun/filetrans" "yunyan/sys/bytedance/audomodel" bdtranslate "yunyan/sys/bytedance/translate" "yunyan/sys/doubao" "yunyan/sys/google/gemini" gspeech "yunyan/sys/google/speech" gtranslate "yunyan/sys/google/translate" msspeech "yunyan/sys/microsoft/speech" mstranslate "yunyan/sys/microsoft/translate" "yunyan/sys/openai" ) // 服务池 provider 标识 → 三类能力实例的构造注册表。 // 字段 key 与 console 服务商模板 seed(modules/console/api_svctemplate.go)严格对齐; // 关键凭据缺失直接报错(fail-closed),不让空凭据实例静默失败在业务深处。 // need 取必填字段,缺失报错。 func need(f map[string]string, provider, key string) (string, error) { v := strings.TrimSpace(f[key]) if v == "" { return "", fmt.Errorf("%s 缺少必填字段 %s", provider, key) } return v, nil } // buildTranscriber 构建识别(录音文件转写)实例。 func buildTranscriber(svc *resolvedSvc) (Transcriber, error) { f := svc.Fields switch strings.ToLower(svc.Provider) { case "bytedance": appid, err := need(f, "bytedance", "appid") if err != nil { return nil, err } token, err := need(f, "bytedance", "token") if err != nil { return nil, err } sys, err := audomodel.NewSys(audomodel.SetAppID(appid), audomodel.SetToken(token)) if err != nil { return nil, err } return &byteDanceTranscriber{sys: sys}, nil case "alibaba": key, err := need(f, "alibaba", "api_key") if err != nil { return nil, err } // workspace_id 可选:填了走 qwen-audio-3.0-asr-flash-filetrans(27 语种), // 留空则退回老的 paraformer-v2(8 语种),保证存量配置升级后不中断。 sys, err := filetrans.NewSys( filetrans.SetApiKey(key), filetrans.SetWorkspaceId(f["workspace_id"]), ) if err != nil { return nil, err } return &aliTranscriber{sys: sys}, nil case "google": sys, err := newGoogleSpeech(f) if err != nil { return nil, err } return &googleTranscriber{sys: sys}, nil case "azure": key, err := need(f, "azure", "subscription_key") if err != nil { return nil, err } sys, err := msspeech.NewSys(msspeech.SetKey(key), msspeech.SetRegion(f["region"])) if err != nil { return nil, err } return µsoftTranscriber{sys: sys}, nil default: return nil, fmt.Errorf("未支持的识别服务商: %s", svc.Provider) } } // newGoogleSpeech Google 语音客户端:json 字段既可存服务账号 JSON 内容({ 开头)也可存文件路径。 func newGoogleSpeech(f map[string]string) (gspeech.ISys, error) { j, err := need(f, "google", "json") if err != nil { return nil, err } if strings.HasPrefix(strings.TrimSpace(j), "{") { return gspeech.NewSys(gspeech.SetJsonContent([]byte(j))) } return gspeech.NewSys(gspeech.SetJsonPath(j)) } // buildTranslator 构建翻译实例。 func buildTranslator(svc *resolvedSvc) (Translator, error) { f := svc.Fields switch strings.ToLower(svc.Provider) { case "doubao", "volcengine", "bytedance": // 火山翻译 ak, err := need(f, "volc", "access_key") if err != nil { return nil, err } sk, err := need(f, "volc", "secret_key") if err != nil { return nil, err } opts := []bdtranslate.Option{bdtranslate.SetAccessKey(ak), bdtranslate.SetSecretKey(sk)} if f["region"] != "" { opts = append(opts, bdtranslate.SetRegion(f["region"])) } sys, err := bdtranslate.NewSys(opts...) if err != nil { return nil, err } return &byteDanceTranslator{sys: sys}, nil case "alibaba": ak, err := need(f, "alibaba", "access_key_id") if err != nil { return nil, err } sk, err := need(f, "alibaba", "access_key_secret") if err != nil { return nil, err } sys, err := alitranslate.NewSys(alitranslate.SetAccessKeyId(ak), alitranslate.SetAccessKeySecret(sk)) if err != nil { return nil, err } return &aliTranslator{sys: sys}, nil case "google": j, err := need(f, "google", "json") if err != nil { return nil, err } var sys gtranslate.ISys if strings.HasPrefix(strings.TrimSpace(j), "{") { sys, err = gtranslate.NewSys(gtranslate.SetJsonContent([]byte(j))) } else { sys, err = gtranslate.NewSys(gtranslate.SetJsonPath(j)) } if err != nil { return nil, err } return &googleTranslator{sys: sys}, nil case "azure": key, err := need(f, "azure", "subscription_key") if err != nil { return nil, err } sys, err := mstranslate.NewSys(mstranslate.SetKey(key), mstranslate.SetRegion(f["region"])) if err != nil { return nil, err } return µsoftTranslator{sys: sys}, nil default: return nil, fmt.Errorf("未支持的翻译服务商: %s", svc.Provider) } } // openAICompatBase 各 OpenAI 兼容服务商的默认接口地址(服务字段 base_url 可覆盖)。 var openAICompatBase = map[string]string{ "qwen": "https://dashscope.aliyuncs.com/compatible-mode/v1", "deepseek": "https://api.deepseek.com/v1", } // buildSummarizer 构建总结(大模型)实例。 // doubao 走豆包 SDK、google 走 Gemini,其余(openai/qwen/deepseek/azure 兼容端点…)统一走 OpenAI 兼容协议。 func buildSummarizer(svc *resolvedSvc) (Summarizer, error) { f := svc.Fields provider := strings.ToLower(svc.Provider) switch provider { case "doubao", "volcengine": key, err := need(f, "doubao", "api_key") if err != nil { return nil, err } model := strings.TrimSpace(f["endpoint_id"]) if model == "" { model = strings.TrimSpace(f["model"]) } if model == "" { return nil, fmt.Errorf("doubao 缺少 endpoint_id/model") } sys, err := doubao.NewSys(doubao.SetApikey(key), doubao.SetModel(model)) if err != nil { return nil, err } return &doubaoSummarizer{sys: sys}, nil case "google", "gemini": key, err := need(f, "gemini", "api_key") if err != nil { return nil, err } model, err := need(f, "gemini", "model") if err != nil { return nil, err } sys, err := gemini.NewSys(gemini.SetApikey(key), gemini.SetModel(model), gemini.SetEndpoint(f["endpoint"])) if err != nil { return nil, err } return &geminiSummarizer{sys: sys}, nil case "anthropic": return nil, fmt.Errorf("anthropic 暂无对应 sys 客户端,请改用 OpenAI 兼容代理接入") default: // openai / qwen / deepseek / azure 等 OpenAI 兼容端点 key, err := need(f, provider, "api_key") if err != nil { return nil, err } model, err := need(f, provider, "model") if err != nil { return nil, err } base := strings.TrimSpace(f["base_url"]) if base == "" { base = openAICompatBase[provider] } opts := []openai.Option{openai.SetToken(key), openai.SetModel(model), openai.SetMaxfunccall(1)} if base != "" { opts = append(opts, openai.SetBaseURL(base)) } sys, err := openai.NewSys(opts...) if err != nil { return nil, err } return &openaiSummarizer{sys: sys}, nil } }