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.
 
 
 
 
 
 

248 lines
10 KiB

package echomeet
import (
"context"
"yunyan/lego/core"
"yunyan/lego/core/cbase"
"yunyan/pb"
"fmt"
"strings"
)
// providersComp 服务聚合组件:
// - Init 阶段按 echomeet 模块配置初始化字节/阿里/Google/微软四套单元服务(转写+翻译+总结),
// 并把它们组合成「按服务商成套」的 provider 集合;
// - 运行期把转写/翻译/总结三类能力按 ServiceType 路由到同一服务商的 provider。
type providersComp struct {
cbase.ModuleCompBase
module *Echomeet
transcribers map[pb.TranscribeService]Transcriber
translators map[pb.TranscribeService]Translator
summarizers map[pb.TranscribeService]Summarizer
}
// 任务状态常量,对齐底层 sys 包
const (
StatusRunning = "RUNNING"
StatusSuccess = "SUCCESS"
StatusFailed = "FAILED"
)
// SubmitRequest 转写提交请求
type SubmitRequest struct {
Uid string
AudioURL string
Language string // BCP-47
EnableSpeaker bool
Seconds int32
SizeBytes int64
CallbackURL string
CallbackData string
}
// SubmitResult 转写提交结果。Done=true 时同步完成(仅 ByteDance flash),直接取 Contexts;
// 否则需要后续调用 Query 拉取异步结果。
type SubmitResult struct {
Done bool
TaskID string
LogID string
Contexts []*pb.ContextStruct
}
// QueryResult 转写查询结果
type QueryResult struct {
Status string
Contexts []*pb.ContextStruct
}
// ChatMessage 对齐 sys/doubao / sys/google/gemini 的统一消息结构。
// Images 为可选的图片 url 列表(多模态总结用):非空时该条消息以「文本 + 图片」的多模态形式
// 发给模型;为空时退化为纯文本,行为与以前一致。要求所配模型支持视觉输入。
type ChatMessage struct {
Role string
Content string
Images []string
}
// Transcriber 转写 provider 接口
type Transcriber interface {
Name() pb.TranscribeService
SupportsLanguage(bcp47 string) bool
Submit(ctx context.Context, req SubmitRequest) (*SubmitResult, error)
Query(ctx context.Context, taskID, logID string) (*QueryResult, error)
}
// Translator 翻译 provider 接口
type Translator interface {
Name() pb.TranscribeService
Translate(ctx context.Context, from, to string, texts []string) ([]string, error)
}
// Summarizer AI 总结 provider 接口
type Summarizer interface {
Name() pb.TranscribeService
Chat(ctx context.Context, messages []ChatMessage) (string, error)
}
// ==================== 组件生命周期 ====================
func (this *providersComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, options core.IModuleOptions) (err error) {
this.ModuleCompBase.Init(service, module, comp, options)
this.module = module.(*Echomeet)
opt := options.(*Options)
// 按服务商成套初始化:每套各自完成转写/翻译/总结三个单元服务的装配。
bdTranscriber, bdTranslator, bdSummarizer := newByteDanceProviders(opt.ByteDance)
aliTranscriber, aliTranslator, aliSummarizer := newAliProviders(opt.Ali)
ggTranscriber, ggTranslator, ggSummarizer := newGoogleProviders(opt.Google)
msTranscriber, msTranslator, msSummarizer := newMicrosoftProviders(opt.Microsoft)
this.transcribers = map[pb.TranscribeService]Transcriber{
pb.TranscribeService_TranscribeService_ByteDance: bdTranscriber,
pb.TranscribeService_TranscribeService_Ali: aliTranscriber,
pb.TranscribeService_TranscribeService_Google: ggTranscriber,
pb.TranscribeService_TranscribeService_Microsoft: msTranscriber,
}
this.translators = map[pb.TranscribeService]Translator{
pb.TranscribeService_TranscribeService_ByteDance: bdTranslator,
pb.TranscribeService_TranscribeService_Ali: aliTranslator,
pb.TranscribeService_TranscribeService_Google: ggTranslator,
pb.TranscribeService_TranscribeService_Microsoft: msTranslator,
}
this.summarizers = map[pb.TranscribeService]Summarizer{
pb.TranscribeService_TranscribeService_ByteDance: bdSummarizer,
pb.TranscribeService_TranscribeService_Ali: aliSummarizer,
pb.TranscribeService_TranscribeService_Google: ggSummarizer,
pb.TranscribeService_TranscribeService_Microsoft: msSummarizer,
}
this.module.Infof("echomeet providers 初始化完成 DefaultService=%s", opt.DefaultService)
return
}
// ==================== 服务商解析 ====================
// parseServiceType 把配置里的服务商名称解析为枚举;空 / auto / 未识别返回 Auto。
func parseServiceType(s string) pb.TranscribeService {
switch strings.ToLower(strings.TrimSpace(s)) {
case "bytedance", "byte", "字节":
return pb.TranscribeService_TranscribeService_ByteDance
case "ali", "aliyun", "阿里", "阿里云":
return pb.TranscribeService_TranscribeService_Ali
case "google", "谷歌":
return pb.TranscribeService_TranscribeService_Google
case "microsoft", "azure", "微软":
return pb.TranscribeService_TranscribeService_Microsoft
default:
return pb.TranscribeService_TranscribeService_Auto
}
}
// ResolveServiceType 解析本次会议最终使用的服务商。优先级:
// 1. 请求显式指定 ServiceType → 直接采用(覆盖配置);
// 2. 模块配置 DefaultService 指定了服务商 → 采用配置;
// 3. 默认模式(以前的方案):按源语言自动路由——字节支持该语言走字节,否则走阿里。
//
// 解析结果一旦确定,会议撰写、翻译、总结三步都走同一服务商。
func (this *providersComp) ResolveServiceType(svcType pb.TranscribeService, language string) pb.TranscribeService {
if svcType != pb.TranscribeService_TranscribeService_Auto && svcType != pb.TranscribeService_TranscribeService_Unknown {
return svcType
}
if d := parseServiceType(this.module.options.DefaultService); d != pb.TranscribeService_TranscribeService_Auto {
return d
}
if _, ok := toByteDanceLang(language); ok {
return pb.TranscribeService_TranscribeService_ByteDance
}
return pb.TranscribeService_TranscribeService_Ali
}
// transcribeFallbackOrder 转写兜底顺序:字节的 SupportsLanguage 有真实语言表,最可信,排首位。
var transcribeFallbackOrder = []pb.TranscribeService{
pb.TranscribeService_TranscribeService_ByteDance,
pb.TranscribeService_TranscribeService_Ali,
pb.TranscribeService_TranscribeService_Microsoft,
pb.TranscribeService_TranscribeService_Google,
}
// pickTranscribeService 纯函数:若 resolved 的转写引擎不支持 language,则按 transcribeFallbackOrder
// 选第一个支持的服务商。返回 (选中的服务商, 是否改道, 选中的服务商是否真的支持该语言)。
func pickTranscribeService(transcribers map[pb.TranscribeService]Transcriber, resolved pb.TranscribeService, language string) (svc pb.TranscribeService, changed bool, supported bool) {
if t, ok := transcribers[resolved]; ok && t != nil && t.SupportsLanguage(language) {
return resolved, false, true
}
for _, s := range transcribeFallbackOrder {
if s == resolved {
continue
}
if t, ok := transcribers[s]; ok && t != nil && t.SupportsLanguage(language) {
return s, true, true
}
}
return resolved, false, false
}
// ResolveTranscribeService 在 ResolveServiceType 之上叠加「所选服务商是否支持该源语言」的校验,
// 不支持时换到第一个支持的服务商。
//
// 为什么需要:ResolveServiceType 只看请求/配置/字节语言表,一旦 DefaultService 固定成某家,
// 就会把引擎不认识的语种也硬塞进去。典型事故是西语被送进阿里 paraformer-v2(该模型不支持西语),
// 引擎静默返回 SUCCESS_WITH_NO_VALID_FRAGMENT,表现得像「音频没声音」,极难排查。
// 只作用于**转写**选路;翻译/总结仍沿用记录已定的 ServiceType(三段同源不变)。
func (this *providersComp) ResolveTranscribeService(svcType pb.TranscribeService, language string) pb.TranscribeService {
resolved := this.ResolveServiceType(svcType, language)
picked, changed, supported := pickTranscribeService(this.transcribers, resolved, language)
if changed {
this.module.Warnf("转写服务 %s 不支持语言 %s,改用 %s", ServiceTypeName(resolved), language, ServiceTypeName(picked))
} else if !supported {
this.module.Warnf("无转写服务支持语言 %s,维持 %s(转写可能失败)", language, ServiceTypeName(picked))
}
return picked
}
// ==================== 工厂方法 ====================
// GetTranscriber 取转写 provider;svcType=Auto/Unknown 时先 ResolveServiceType。
func (this *providersComp) GetTranscriber(svcType pb.TranscribeService, language string) (Transcriber, error) {
resolved := this.ResolveServiceType(svcType, language)
if p, ok := this.transcribers[resolved]; ok && p != nil {
return p, nil
}
return nil, fmt.Errorf("transcriber not registered: service=%s", ServiceTypeName(resolved))
}
// GetTranslator 取翻译 provider;与转写同服务商,svcType=Auto/Unknown 时先 ResolveServiceType。
func (this *providersComp) GetTranslator(svcType pb.TranscribeService, language string) (Translator, error) {
resolved := this.ResolveServiceType(svcType, language)
if p, ok := this.translators[resolved]; ok && p != nil {
return p, nil
}
return nil, fmt.Errorf("translator not registered: service=%s", ServiceTypeName(resolved))
}
// GetSummarizer 取总结 provider;与转写同服务商,svcType=Auto/Unknown 时先 ResolveServiceType。
func (this *providersComp) GetSummarizer(svcType pb.TranscribeService, language string) (Summarizer, error) {
resolved := this.ResolveServiceType(svcType, language)
if p, ok := this.summarizers[resolved]; ok && p != nil {
return p, nil
}
return nil, fmt.Errorf("summarizer not registered: service=%s", ServiceTypeName(resolved))
}
// ServiceTypeName 返回服务类型的友好名称,便于日志输出
func ServiceTypeName(svcType pb.TranscribeService) string {
switch svcType {
case pb.TranscribeService_TranscribeService_ByteDance:
return "ByteDance"
case pb.TranscribeService_TranscribeService_Ali:
return "Ali"
case pb.TranscribeService_TranscribeService_Auto:
return "Auto"
case pb.TranscribeService_TranscribeService_Google:
return "Google"
case pb.TranscribeService_TranscribeService_Microsoft:
return "Microsoft"
default:
return fmt.Sprintf("Unknown(%d)", int32(svcType))
}
}