You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

229 lines
6.6 KiB

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
}
sys, err := filetrans.NewSys(filetrans.SetApiKey(key))
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 &microsoftTranscriber{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 &microsoftTranslator{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
}
}