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.
234 lines
6.8 KiB
234 lines
6.8 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
|
|
}
|
|
// 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
|
|
}
|
|
}
|
|
|