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.
185 lines
9.7 KiB
185 lines
9.7 KiB
package comm
|
|
|
|
import (
|
|
"context"
|
|
|
|
"yunyan/pb"
|
|
)
|
|
|
|
// 本文件集中定义「模块对外接口」。
|
|
//
|
|
// 一个模块若要把能力开放给其它模块调用,在此定义一个接口;目标模块的实现类型
|
|
// 实现该接口即可。调用方在启动阶段通过 service.GetModule(模块名) 取到模块实例,
|
|
// 断言为对应接口后调用——业务模块之间因此无需直接 import 对方的包。
|
|
|
|
// ===================== 统计模块 analyze =====================
|
|
|
|
// StatEventType 埋点事件类型。
|
|
type StatEventType int32
|
|
|
|
const (
|
|
StatEventUnknown StatEventType = 0
|
|
StatEventLogin StatEventType = 1 // 用户登录
|
|
StatEventRegister StatEventType = 2 // 新用户注册
|
|
StatEventDeviceActive StatEventType = 3 // 设备激活
|
|
StatEventDeviceBind StatEventType = 4 // 设备绑定账号
|
|
StatEventOrderCreate StatEventType = 5 // 创建订单
|
|
StatEventOrderPaid StatEventType = 6 // 订单支付完成
|
|
StatEventOrderFailed StatEventType = 7 // 订单失败
|
|
StatEventTranslate StatEventType = 8 // 翻译使用
|
|
StatEventMeeting StatEventType = 9 // 会议使用
|
|
StatEventAiChat StatEventType = 10 // AI 聊天
|
|
StatEventResourceGrant StatEventType = 11 // 资源发放
|
|
StatEventQRCodeActive StatEventType = 12 // 二维码(公码)激活:用户扫公码领奖成功一次计一次
|
|
)
|
|
|
|
// StatEvent 统一埋点对象:业务模块通过 IAnalyze.Report 投递,一个对象走天下,
|
|
// 靠 Type 区分语义,公共数值字段按需填写。
|
|
type StatEvent struct {
|
|
Type StatEventType
|
|
ProductId uint32 // 产品ID:设备类事件填设备产品;订单/发放/消耗类事件填用户最后绑定产品(DBUser.Lastbindproductid);无绑定填 0
|
|
ChannelId string // 渠道商ID(短码):设备类事件填该设备授权码上的渠道商;其余事件填用户最后绑定设备的渠道商(DBUser.Lastbindchannelid);无归属填 ""
|
|
Uid string // 触发用户;用于登录/活跃/付费用户的去重统计
|
|
|
|
Count int64 // 通用次数:0 视为未指定按 1 计;负数保留,用于冲正(如解绑 -1)
|
|
Amount int64 // 金额(分):订单类事件
|
|
Second int64 // 时长(秒):翻译 / 会议
|
|
Words int64 // 字数:翻译
|
|
UpToken int64 // 上行 token:AI
|
|
DnToken int64 // 下行 token:AI
|
|
|
|
// 资源发放(StatEventResourceGrant)
|
|
GrantVipDay int64
|
|
GrantAiIntegral int64
|
|
GrantTradeSecond int64
|
|
GrantMeetSecond int64
|
|
}
|
|
|
|
// StatSnapshotKind 是 StatSnapshot.Kind 的固定取值,供消费端识别快照消息、
|
|
// 区分(并丢弃)管道里可能残留的历史逐事件消息。
|
|
const StatSnapshotKind = "snapshot"
|
|
|
|
// StatSnapshot 是业务侧 analyze 推送到 NATS 统计管道的「当日某分区绝对快照」
|
|
// (发送端 analyze 与 console 统计消费端共享)。
|
|
//
|
|
// 设计:业务侧 analyze 始终把埋点实时累加到本地 Redis(崩溃不丢);同步器在【启动时 / 每个
|
|
// 同步周期 / 0 点】把 Redis 里 app×region×product×day 各分区的【当日绝对聚合】打包成本结构推送。
|
|
// console 收到后直接以绝对值覆盖 upsert 到 stats_global_day——
|
|
// - 绝对值幂等:重复/乱序投递、重启重放都收敛到最新值,不会重复计数;
|
|
// - 自愈:偶发丢消息由下一次快照补回,业务重启时的启动快照即完成对账;
|
|
// - 去重人数(HLL)在业务侧本地算好,随 Row.*UserCount 以整数带过来,console 不再维护 HLL。
|
|
//
|
|
// Row 即一行 stats_global_day(含复合主键维度 app_id/region/product_id/stat_day 与全部指标),
|
|
// 直接复用以避免维度/字段两处定义漂移。Row.AppId 须与 console 注册表中的应用名称完全一致,
|
|
// 否则 console 视为未注册、不落库。
|
|
type StatSnapshot struct {
|
|
Kind string `json:"kind"` // 固定 StatSnapshotKind
|
|
Ts int64 `json:"ts"` // 快照生成时间(unix 秒);与接收时间差大=迟到
|
|
Row *pb.StatsGlobalDay `json:"row"` // 该分区当日绝对聚合,直接落 stats_global_day
|
|
}
|
|
|
|
// IAnalyze 是统计模块 analyze 对外暴露的埋点接口。
|
|
//
|
|
// 用法:业务模块在 Start 阶段获取并缓存——
|
|
//
|
|
// if m, err := service.GetModule(comm.ModuleAnalyze); err == nil {
|
|
// this.analyze, _ = m.(comm.IAnalyze)
|
|
// }
|
|
//
|
|
// 之后在业务流程中调用 this.analyze.Report(...) 投递埋点(注意判空)。
|
|
type IAnalyze interface {
|
|
// Report 投递一个埋点事件。非阻塞,失败即丢弃,不阻断业务主流程。
|
|
Report(e *StatEvent)
|
|
}
|
|
|
|
// MeetSummaryService 会议记录可选的总结大模型项(下发给客户端选择)。
|
|
type MeetSummaryService struct {
|
|
SvcId string `json:"svc_id"` // 服务 id(starttask/summary 的 summary_svc_id 传它)
|
|
Name string `json:"name"` // 显示名
|
|
IsDefault bool `json:"is_default"` // 是否后台默认模型
|
|
}
|
|
|
|
// MeetCapabilities 会议记录当前可用的能力:识别/翻译支持的语言 + 可选总结大模型。
|
|
// 全部由后台「第三方服务配置 + 会议记录服务编排」决定,客户端不写死。
|
|
// AnyLanguage=true 表示该段存在未限定 languages 的服务(= 不限语言),此时语言列表只是
|
|
// 「明确声明过的」,客户端不能拿它当白名单去禁用其他语言。
|
|
type MeetCapabilities struct {
|
|
TranscribeLanguages []string `json:"transcribe_languages"`
|
|
TranscribeAnyLanguage bool `json:"transcribe_any_language"`
|
|
TranslateLanguages []string `json:"translate_languages"`
|
|
TranslateAnyLanguage bool `json:"translate_any_language"`
|
|
SummaryClientChoice bool `json:"summary_client_choice"` // 后台是否允许客户端指定总结模型
|
|
SummaryServices []MeetSummaryService `json:"summary_services"`
|
|
}
|
|
|
|
// IEchomeet 是会议记录模块 echomeet 对外暴露的能力查询接口,供同进程的其他模块
|
|
// (user 的 v3 配置下发)一次性取会议记录能力,避免客户端多跑一趟接口。
|
|
//
|
|
// 用法与 IAnalyze 相同,在 Start 阶段获取并缓存:
|
|
//
|
|
// if m, err := service.GetModule(comm.ModuleEchomeet); err == nil {
|
|
// this.echomeet, _ = m.(comm.IEchomeet)
|
|
// }
|
|
//
|
|
// 注意判空:echomeet 未随本服务装载时取不到,调用方应把会议记录能力留空而不是报错。
|
|
type IEchomeet interface {
|
|
// MeetCapabilities 返回当前编排快照下的会议记录能力。读内存快照,不查库。
|
|
MeetCapabilities() MeetCapabilities
|
|
|
|
// ChatLLM 用后台编排选出的总结模型跑一次对话,返回文本与实际用到的服务 id。
|
|
//
|
|
// 开这个口子是为了让别的模块(memory 的周报生成、会议待办抽取)也能走同一套
|
|
// 编排选路,而不是各自 import "yunyan/sys/doubao" 写死服务商——allhelp/summary.go
|
|
// 就是写死的,后果是海外应用照样去调国内豆包、后台换模型也换不掉它。
|
|
// svcId 传空则按编排取默认。
|
|
ChatLLM(ctx context.Context, svcId, systemPrompt, userContent string) (text string, usedSvcId string, err error)
|
|
}
|
|
|
|
// ===================== 记忆中心 memory =====================
|
|
|
|
// IMemory 记忆中心(拾忆)对外开放的能力。
|
|
//
|
|
// 目前只有一个:让 echomeet 在纪要生成完之后把待办抽出来落进 memory_item。
|
|
// 反向调用(memory → echomeet 的 ChatLLM)走 IEchomeet,两边都不直接 import 对方。
|
|
type IMemory interface {
|
|
// ExtractMeetingTodos 从会议纪要抽取待办并落库。
|
|
//
|
|
// **调用方必须另起 goroutine**:它内部要跑一次 LLM,同步调会把会议流水线卡住。
|
|
// 实现里已做 panic 兜底与失败隔离——抽取失败只记日志,绝不回头改会议记录的状态。
|
|
//
|
|
// 「第几轮」由 memory 自己按 source_id 现有的最大 gen_round 推出来,不从这里传:
|
|
// 调用方(echomeet)没有地方存轮次计数器,硬编码成 1 会让第二次「重新生成」
|
|
// 的清理条件 gen_round < 1 一条都匹配不上,旧待办从此永远留在库里。
|
|
ExtractMeetingTodos(ctx context.Context, in MeetingExtractInput)
|
|
|
|
// DeleteMeetingItems 删掉这些会议记录**自动抽取**出来的待办,返回删掉几条。
|
|
//
|
|
// 纪要被删之后待办还挂在日历上,点进去找不到来源。
|
|
//
|
|
// ⚠️ 只删 user_edited=0 的:用户改过的待办已经是他自己的东西,
|
|
// 来源没了也该留着(与「重新生成时保留用户改动」同一条口径)。
|
|
// recordIDs 是 echomeet 记录 id 的字符串形式(= memory_item.source_id)。
|
|
DeleteMeetingItems(uid string, recordIDs []string) (int64, error)
|
|
}
|
|
|
|
// MeetingExtractInput 一次会议待办抽取的输入。
|
|
//
|
|
// 用结构体而不是一串位置参数:这里已经有 5 个字符串字段,
|
|
// 调用方把 summary 和 meetingDate 写反了编译器一句话都不会说。
|
|
type MeetingExtractInput struct {
|
|
Uid string // 会议归属用户
|
|
RecordID string // 会议记录 id(落库成 source_id)
|
|
Summary string // 已生成的纪要正文,抽取的输入
|
|
MeetingDate string // 会议归属日期 YYYY-MM-DD,模型据它把「下周三」换算成具体日期
|
|
LLMSvcID string // 与本条会议的总结同一个模型,便于出问题时归因
|
|
|
|
// ── 下面两个是内容门槛用的,见 memory 的 meeting_extract.go ──
|
|
|
|
// Seconds 录音时长(秒)。
|
|
Seconds int32
|
|
// ContentRunes 转写正文的有效字数:已去掉 [Speaker_x] 标签,只数说的话。
|
|
//
|
|
// ⚠️ 门槛必须卡正文不能卡纪要:内容太薄时模型按模板的兜底规则照样能写出
|
|
// 六百多字的废话纪要(测试库 id=23/24/26 就是),拿纪要字数当门槛一条也拦不住。
|
|
ContentRunes int
|
|
}
|
|
|