package echomeet import ( "context" "errors" "strings" "sync" "yunyan/comm" "yunyan/modules" "yunyan/lego/core" ) func NewModule() core.IModule { m := new(Echomeet) return m } type Echomeet struct { modules.ModuleBase options *Options api *apiComp model *modelComp cache *modelCacheComp tasks *tasksComp providers *providersComp analyze comm.IAnalyze service core.IService mem comm.IMemory // 同进程的 memory 模块(会议纪要 → 待办抽取),惰性取 memOnce sync.Once } func (this *Echomeet) GetType() core.M_Modules { return comm.ModuleEchomeet } func (this *Echomeet) NewOptions() (options core.IModuleOptions) { return new(Options) } func (this *Echomeet) Init(service core.IService, module core.IModule, options core.IModuleOptions) (err error) { this.options = options.(*Options) this.service = service if err = this.ModuleBase.Init(service, module, options); err != nil { return } return } // memoryModule 惰性取同进程的 memory 模块(会议纪要 → 待办抽取用)。 // // 惰性而不是 Start 里取:模块 Start 顺序由 home/main.go 的 lego.Run 参数序决定, // echomeet 排在 memory 前面,Start 阶段去拿会拿到一个还没 Start 完的实例。 // 这里只在真正要用时取一次,缓存住。 func (this *Echomeet) memoryModule() comm.IMemory { this.memOnce.Do(func() { if m, e := this.service.GetModule(comm.ModuleMemory); e == nil { if mm, ok := m.(comm.IMemory); ok { this.mem = mm } } if this.mem == nil { this.Warnf("未取到 memory 模块,会议待办抽取不可用(本服务可能没装载它)") } }) return this.mem } func (this *Echomeet) Start() (err error) { if err = this.ModuleBase.Start(); err != nil { return } return } // MeetCapabilities 实现 comm.IEchomeet:把编排快照里的语言/总结模型汇总给同进程的其他模块 // (user 的 v3 配置下发)。读内存快照,不查库;echomeet_getcapabilities 接口用的是同一份数据。 func (this *Echomeet) MeetCapabilities() (c comm.MeetCapabilities) { if this.providers == nil { return } c.TranscribeLanguages, c.TranscribeAnyLanguage = this.providers.TranscribeLanguages() c.TranslateLanguages, c.TranslateAnyLanguage = this.providers.TranslateLanguages() clientChoice, items := this.providers.SummaryServices() c.SummaryClientChoice = clientChoice c.SummaryServices = make([]comm.MeetSummaryService, 0, len(items)) for _, it := range items { c.SummaryServices = append(c.SummaryServices, comm.MeetSummaryService{ SvcId: it.SvcId, Name: it.Name, IsDefault: it.IsDefault, }) } return } // ChatLLM 用后台编排选出的总结模型跑一次对话(实现 comm.IEchomeet)。 // // 开给其它模块用(memory 的周报生成、会议待办抽取),目的是让它们也走同一套编排选路, // 不必各自 import sys/doubao 把服务商写死。svcId 传空按编排取默认; // 返回实际用到的 svcId 便于排障时对齐「这条是谁生成的」。 func (this *Echomeet) ChatLLM(ctx context.Context, svcId, systemPrompt, userContent string) (string, string, error) { if this.providers == nil { return "", "", errors.New("echomeet providers 未就绪") } if strings.TrimSpace(userContent) == "" { // 空内容送给大模型只会换回一个语焉不详的 400(通义/豆包都直接报 // InternalError.Algo.InvalidParameter),在这里挡掉更好排查。 return "", "", errors.New("userContent 为空") } usedSvcId := svcId var ( s Summarizer e error ) if usedSvcId == "" { usedSvcId, s, e = this.providers.PickLLM("") } else { s, e = this.providers.GetSummarizer(usedSvcId) } if e != nil { return "", usedSvcId, e } msgs := make([]ChatMessage, 0, 2) if systemPrompt != "" { msgs = append(msgs, ChatMessage{Role: "system", Content: systemPrompt}) } msgs = append(msgs, ChatMessage{Role: "user", Content: userContent}) text, err := s.Chat(ctx, msgs) return text, usedSvcId, err } func (this *Echomeet) OnInstallComp() { this.ModuleBase.OnInstallComp() this.api = this.RegisterComp(new(apiComp)).(*apiComp) this.model = this.RegisterComp(new(modelComp)).(*modelComp) this.cache = this.RegisterComp(new(modelCacheComp)).(*modelCacheComp) this.providers = this.RegisterComp(new(providersComp)).(*providersComp) this.tasks = this.RegisterComp(new(tasksComp)).(*tasksComp) }