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.
 
 
 
 
 
 

8.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

根 CLAUDE.md 描述整体架构(lego 框架、反射注册路由、pb 生成);本文件只补充 echomeet 模块自身。

模块职责

echomeet 是「会议记录」模块:上传音频 → 转写(Transcribe) → 翻译(Translate) → AI 总结(Summary),产出 overview(短总结)+ summary(按模板的完整纪要)。由 home 服务装载运行(services/home/main.go),不是独立服务。HTTP 接口经反射注册为 echomeet_<方法名小写>,前缀路由示例 /api/home/echomeet_addrecord。

能力下发(客户端界面数据来源)

客户端不写死语言表和模型表,一律拉 echomeet_getcapabilities(api_getcapabilities.go):

  • transcribe_languages / translate_languages:后台编排里在用的识别/翻译服务 languages 字段并集,按 Priority 顺序去重;对应 *_any_language=true 时该段不限语言(有服务没配 languages),列表只是"明确声明过的",不能当白名单用。
  • summary_services / client_choice:同 echomeet_getsummaryservices(旧接口保留兼容,是本接口的子集)。

数据口径与选路同源(都读 resolvedSvc.Languages,见 providers.go 的 langsOf 与 svcSupportsLanguage),所以下发的语言必定选得出服务。后台改配置经 NATS ConfigChanged 即时重载,10 分钟 cron 兜底。

同一份数据还经 comm.IEchomeet(module.go 的 MeetCapabilities)给同进程的 user 模块, 拼进 user_getappconfig_v3 的 meet 字段——客户端启动只拉一次 v3 即可,无需再单独调本模块接口。 改这里的口径要记得两处出口是同一份实现,不会漂移。

核心设计:一会一服务商,三段同源

每条会议记录在 providers.go 的 ResolveServiceType 解析出唯一服务商,转写/翻译/总结三步都走它,结果记在 record.ServiceType。解析优先级:

  1. 请求显式 service_type →
  2. 模块配置 DefaultService →
  3. 兜底按源语言:字节支持该语言走字节,否则阿里。

四套服务商,每套三件套(见对应 provider_*.go):

ServiceType 转写 翻译 总结(Summarizer)
ByteDance sys/bytedance/audomodel 火山翻译 豆包 sys/doubao
Ali DashScope sys/aliyun/filetrans 阿里 MT 通义千问(sys/openai 兼容端点)
Google Cloud Speech Cloud Translation Gemini sys/google/gemini
Microsoft Azure Speech Azure Translator Azure OpenAI(sys/openai 兼容端点)

三类能力抽象为 Transcriber / Translator / Summarizer 接口(providers.go),运行期 GetTranscriber/GetTranslator/GetSummarizer(svcType, language) 按 ServiceType 路由。新增服务商 = 写一个 provider_xxx.go 实现三接口 + 在 providersComp.Init 注册进三张 map。

组件与异步流水线

module.go 装配组件:api(HTTP) / model(gorm) / cache(模板缓存) / providers / tasks。

tasks.go 是异步引擎:Redis 队列 + ants 协程池 + cron 每秒调度。

  • 两条流水线,各有「等待队列 / 在执行集合」两个 list:短音频转写(keyShortAwait/Proc)、AI 总结(keyAIAwait/Proc)。队列 key 用 service.GetTag() + redissys.RKey(应用分组前缀)命名空间隔离,多应用共用一个 Redis 不串扰。
  • TaskScheduling(cron */1 * * * * ?)每秒把等待队列搬进执行集合(受 MaxShortAudioProcess/MaxAIAwaitProcess 限流),再 pool.Invoke 真正处理。
  • 转写完成三条路径:异步回调(字节 api_backcall.go / 阿里 api_alibackcall.go)+ 客户端拉取时主动轮询(PollTranscribe,由 api_getrecord.go/api_getrecords.go 在状态为 Transcribing 时驱动,TranscribeQueryMinInterval 限频)+ cron 兜底扫描(SweepStuckTasks,每分钟)。 ⚠️ 兜底是 2026-09-06 补的,之前只有前两条:回调没到 + 客户端不轮询时记录会永远卡在 Transcribing,没有任何自愈(真机上卡了 8 分钟,直到用户杀 App 重开)。业务在服务端跑,不该由客户端在不在线决定它推不推进。 兜底做两件事:① 转写中且 60s 无人查询 → 主动 PollTranscribe;超过 2h 未完成判 TranscribeFail(否则第三方丢单的记录会被无限查下去)。② 停在待总结/总结中超 5 分钟且不在 AI 队列里 → 重新入队;「不在队列里」这个前提不能省,正在跑的重复入队 = 两次大模型计费且后者覆盖前者。
  • 两处返回中间态的坑:PollTranscribe 成功后先把 State 推到 AwaitSummarizing 落库,再 go finishTranscribe(...) 在后台写 Translate(刻意不共用指针,避免与 HTTP 响应序列化产生数据竞争)。于是 getrecord/getrecords 手上那个待序列化的 rec 必然是「状态说转写完了、Translate 还是空」的中间态——不是偶发竞态,是每次首轮必中。客户端据此 jsonDecode(translate) 会抛异常。三个拉取入口统一用 hideHalfDoneTranscribe 在响应侧降级回 Transcribing(只改响应不落库,且只对本次真正轮询过的记录降级,否则 finishTranscribe 出错时记录会被永久钉住)。
  • 短音频走字节 flash 同步通道(ShortAudioProcess);长音频/其他服务商走异步 submit+query。

状态机见 DBEchoMeetRecordState(proto):Unknow→AwaitTranscribing→Transcribing→AwaitSummarizing→Summarizing→Completed→Readed,失败 TranscribeFail/SummarizFail。转写失败会退还 Meetintegral 时长并冲正统计埋点。

语言码映射(core.go,易错)

输入统一是客户端 BCP-47(zh-CN/en-US…)。每个平台格式不同,core.go 逐家维护 map。铁律:未识别语言一律返回原值,绝不 fallback 成 zh——否则「未知语言→中文」会藏在调用栈深处。翻译时在 TranslateProcess 直接传 BCP-47,由各 provider 内部转换,不要提前转(会被二次转换误判)。

AI 总结输入约定(含图片)

AIProcess(tasks.go)拼 originalText:把 translate(已翻译的分句)按 [说话人]:内容 拼接;若 record.Remark(用户创建时填的备注)非空,用语言中立的结构化标签包裹:<remark>…</remark>\n\n<transcript>\n…\n</transcript>——不要硬编码某语言的提示词,避免污染多语言总结。模板 template.Template/template.Outline 作为 system prompt,分别产出 summary/overview(两路并发)。

图片注释(支持多张):请求 EchomeetAddRecordReq.imageurls 是 repeated string,DB DBEchoMeetRecord.imageurls 是 string 列存 JSON 数组(编码方式同 original:utils.ToString 写、json.Unmarshal 读)。AIProcess 解析出 url 列表后,作为多模态内容随 user 消息一并发给总结模型(要求所配模型支持视觉)。链路:ChatMessage.Images []string → 各 Summarizer.Chat → sys 层(豆包/通义/Azure 用 OpenAI 风格 image_url 直传;Gemini 在 sys/google/gemini 内逐张下载转 base64 内联 inlineData,单张失败仅跳过)。列表为空时完全走原文本路径,不影响既有行为。

重新生成 pb

改了 apps/proto/echomeet/*.proto 后需重生成(protoc-gen-go v1.36.6,已安装),并复刻源项目 pb.py 的 @go_tags 注入(覆盖 json 去掉 omitempty、追加 gorm/自定义 tag):

cd apps/services
protoc --go_out=pb --go_opt=paths=import -I ../proto ../proto/echomeet/echomeet_db.proto
protoc --go_out=pb --go_opt=paths=import -I ../proto ../proto/echomeet/echomeet_msg.proto
# 再对生成的 pb/echomeet_*.pb.go 执行 @go_tags 注入(见根 CLAUDE.md / 源项目 pb.py)

注意:HTTP 层用 jsoniter 按 json tag 收发、DB 用 gorm tag,protobuf 二进制 wire 不参与这些 API,所以字段的 json/gorm tag 必须正确。