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.
 
 
 
 
 
 

59 KiB

AI 助手中枢:翻译入纪要 · 纪要入拾忆 · EMAI 综合入口 —— 设计与落地文档

版本:v0.2  日期:2026-09-15(v0.1 同日草案;v0.2 按现有服务器架构复核:五进程同容器、mcp 无 Redis、RPC 转发写法、模板按应用隔离、部署只发模板,见 §2.1,并据此改了 §3.4/§3.5/§4.2a/§5.4/§5.5/§6.4) 范围:apps/services(后端 home/mcp)、apps/client(Flutter)、百炼控制台配置 状态:设计稿,未动代码。三条需求的现状全部按当前 along 分支逐文件核过,引用带路径与行号。 前置文档:记忆中心-设计与开发文档(v0.3,代码已落地);本文在它之上演进,与它冲突的地方在 §5.2 明说。


零、一句话

三条需求不是三个功能,是一条数据链的三段:

 翻译会话(同传/面对面/通话/音视频)  ──①──▶  语音纪要(echomeet_record)  ──②──▶  拾忆时间线(memory_item)
 现场录音 / 通话录音 / 支架 TF 卡   ──已通──▶        │                              │
                                                  │ 待办抽取(已通)                  │
                                                  ▼                              ▼
                                        ③ EMAI 综合入口:MCP 查(纪要/事项/统计/报告) + 工具写(待办/闹钟/花销) + 端侧指令(启动功能/设备控制)
                                                  ▲
                                   耳机唤醒 / 支架 / 车载香薰 / App 对话页  —— 同一个会话持有者、同一套 MCP 身份
  • ① 翻译转写已经在客户端手里(TranslationItem 列表),差的只是把它送进纪要并「只总结、不转写」。服务端这条通道已经存在但从没被调用过(§3.1)。
  • ② 纪要目前只把「待办」抽进拾忆,会议本身不在时间线上;翻译会话更是连服务端都没到过。做法是给拾忆加一个 meeting 分类的索引项,正文不复制(§4)。
  • ③ EMAI 已有 MCP 读工具与 JWT 身份链路,缺的是写工具、指令清单、与多设备/多 agent 的统一口径(§5)。最大的一处改口径:记忆类写操作从「端侧指令」改走「MCP 服务端工具」,理由见 §5.2。

一、现状盘点(三段的地基)

1.1 段 ①:翻译会话在客户端的形态

项 现状 位置
模式 四个裸字符串 simultaneous / faceToFace / audioVideo / call,无枚举 translation_controller.dart:160
转写条目模型 TranslationItem{sourceText, translatedText, sourceLanguageCode, targetLanguageCode, timestamp, sessionId, isFirstInSession, isIntermediate, utteranceId, serviceId('A'/'B'), …} translation_models.dart:6
持久化 GetStorage,键 translation_history_<mode>,每模式一份,上限 1000 条,无 TTL,无服务端副本 translation_history_controller.dart:14-24
会话概念 只有 sessionId(毫秒时间戳串)+ isFirstInSession;没有会话对象、没有起止时间 translation_controller.dart:1419
说话人 通话模式 serviceId A=己方 / B=对方(不落盘);面对面 activeSpeaker 1/2 只存在于控制器内存;同传单说话人 :1754、:2215
音频 只有 call / simultaneous 在用户点了录音键时录 WAV 到 TranslatAudio/(16k 双声道,左=本地麦,右=对端 TTS);停止时 RecordingArchive.archive() 只把音频送进纪要 translation_controller.dart:1150-1192,_shouldArchiveRecording 明确排除 faceToFace/audioVideo
转写 ↔ 音频 零关联:sessionId 与 WAV 文件名是两个独立时间戳,条目上没有音频偏移 —
已有出口 历史列表只有「复制 / 删除」;详情页有 Share.share 纯文本;没有「生成纪要」 translation_long_press_menu.dart:68、translation_detail_view.dart:745
服务端 user_translate 无状态不落库;user_usages 只报秒数;没有任何翻译文本表 api_translate.go:25、api_usages.go:52

1.2 段 ②:语音纪要流水线(服务端 echomeet)

项 现状 位置
记录表 DBEchoMeetRecord:original / translate 两列都是 []ContextStruct{Meetingid, Content, Starttime, Endtime, Speaker} 的 JSON 串,overview 短总结,summary 模板纪要,asr/mt/llm_svc_id 三路选路结果 echomeet_db.proto:38-78
状态机 Unknow(0)→AwaitTranscribing(1)→Transcribing(2)→AwaitSummarizing(3)→Summarizing(4)→Completed(5)→Readed(6);失败 10001/10002 数值大于 Completed 同上 :7-18
建记录 echomeet_addrecord:original 字段已存在(repeated ContextStruct),传了就写 Original 并镜像到 Translate,状态留 Unknow api_addrecord.go:49-59
起任务 echomeet_starttask:VIP 闸门 → 按 Seconds 计算力 → 解析模板 → 选路;rtype=="VOICETRANSLAT" 分支跳过 ASR,直接 TranslateProcess + SubmitAITask,且不要求 audiourl api_starttask.go:135-152
总结输入 AIProcess 取 translate(空则 original)拼成 [speaker]:content 行,加 <remark>,两路并发出 summary/overview tasks.go:481-622
总结后 起 goroutine 调 IMemory.ExtractMeetingTodos(待办抽取,第三路) tasks.go:621-644
默认模板 服务端没有「默认模板」概念;客户端取 GetStorage meeting_last_template_Data,没有就列表第 0 项 generate_bottom_sheet.dart:70-77
会议时间 服务端 Creationtime = time.Now(),忽略客户端上报;客户端 recording_time.dart 已按「服务端将来接受」的形状送了 api_addrecord.go:31
客户端归档入口 RecordingArchive.archive({path, seconds, tag, notify, createdAt}):只收音频,标题=文件名,没有转写、没有场景参数 recording_archive.dart:38

结论:「已转写 → 只总结」的服务端通道已经存在,客户端一次都没用过(全仓 VOICETRANSLAT 只出现在 api_starttask.go:135 一处,没有任何写入方)。

1.3 段 ②→③:纪要有没有「给到拾忆」

只给了一半:

内容 是否进拾忆 方式
纪要里的待办 ✅ meeting_extract.go 抽成 category=todo, source=meeting, source_id=记录id
会议本身(时间、标题、时长、概览) ❌ 不在 memory_item,拾忆时间线上看不到「今天开了什么会」
翻译会话 ❌ 连服务端都没到
现场录音 / 通话录音 / 支架 TF 卡 进纪要 ✅,进拾忆 ❌ 同会议本身

memory_item 的分类白名单是 alarm/todo/idea/expense(comm/memory.go:23-28),source 已有 meeting。

1.4 段 ③:EMAI 现状

项 现状 位置
链路 客户端 直连百炼 multimodal-dialog WebSocket,服务端不在对话链路上 bailian_multimodal_service.dart:113
身份 client_info.user_id = uid(游客回退 eaimar-guest);MCP 身份靠 user_getmcptoken 签的 2h 令牌,按工具名嵌套塞进 biz_params.user_defined_params :277-301、api_getmcptoken.go
需要令牌的工具清单 客户端写死 _mcpAuthTools(7 个),服务端加工具必须同步改 :583-591
跨会话记忆 dialog_id 由 Started 事件给、只在本连接内回传,不持久化;每次重连是新对话。聊天记录本地 200 条 :399,474、emai_chat_store.dart
端侧指令 只有 SET_clock 落地(双写本地 + memory_add);SEND_message/MAKE_A_PHONE_CALL 在 _declined;其余 unsupported。模块注册表的 directives 机制接好了但没有任何模块声明 assistant_directive_service.dart:62-134、module_registry.dart:88
MCP 读工具(已启用) GLOBAL:get_memory_items / get_memory_stats / get_memory_report / search_meeting_notes / hefeng_weather / get_user_tasks / cancel_user_task;CHINA/OVERSEAS:地图 + 联网搜索 modules/mcp/
MCP 鉴权 已落地:header JWT 或 auth_token(aud=mcp)解 uid,参数 uid 不一致直接拒,无密钥则全拒 auth.go:92-162
MCP 写工具 一个都没有(add_user_task 代码里根本没 AddTool,是死的) tool_allhelp_task.go:31-35
知识库 没有。纪要检索是自家 MySQL ngram 全文(词面匹配),换向量方案的触发条件写在工具头注释里 tool_meeting.go:21-53
多 agent EMAI 与 Smartcar 车载助手是两个百炼应用(bailianAppId 在 data 层 AgentModule),设备唤醒按厂商品类选;共用 EmaiController/BailianMultimodalService/AssistantDirectiveService agent_module.dart:52、device_ai_session_service.dart:52-72
后台 agent_config 表两台机都是 0 行,页面 9-12 已删;百炼 app_id/工具绑定当前不受后台管,在百炼控制台 + 客户端常量里 menus.ts:68-71

⚠️ MCP 工具注册的四处名字对不上(顺手要修,否则新工具也会同样静默失效)

AddTool 只登记 Groups 里点了名的工具(module.go:163-171),名字对不上就不报错、直接不暴露:

YAML 里写的 代码里的真名 后果
add_user_task 代码 Start() 没调 AddTool 永远不暴露;客户端 _mcpAuthTools 里还列着它
gaode_map_route_navigation / google_map_route_navigation maps_route_navigation / maps_google_route_navigation 导航工具从未启用
music_search / music_play music_search_play(CHINA)/ migu_music_search(CHINA)/ spotify_music_play(未装配) 音乐工具从未启用
(缺)gaode_map_weather / finance_query 代码归 CHINA 组 从未启用

(deploy/app/confs/mcp.yaml.example:50-73)


二、架构约束(决定实现方式的硬事实)与设计原则

2.1 现有架构里与本文直接相关的 9 条

# 事实 对本设计的约束
A1 gateway / home / api / mcp / timer 五个进程打在一个镜像、一个容器,entrypoint 见任一 pid 退出就杀全部(entrypoint.voitrans.sh:46-48) 新增模块方法的 Init 不能返回 error;存量回填放 Start 里起 goroutine;任何建表/回填失败只记日志
A2 echomeet 与 memory 同在 home 进程,互相通过 comm.IEchomeet / comm.IMemory 接口调用,不 import 对方(comm/module.go:143) 需求 ② 的四个钩子是进程内接口调用,不走 RPC、不走队列
A3 mcp 是独立进程(rpcx 集群服务,端口 7300 HTTP / 7004 RPCX),只配了 MySQL,没有 Redis、没有 Postgres(mcp.yaml.example) MCP 侧不能做基于 Redis 的幂等/限流;写操作不在 mcp 进程里实现
A4 gateway 转发业务请求的方式是 RpcCall(ctx, "<服务名>", Rpc_GatewayHttpRoute, {MsgName, Meta{userid,ip}, Message}),home 侧 SCompHttpRoute.Rpc_GatewayHttpRoute 按 MsgName 反射分发、从 Meta.userid 建会话、过一遍拦截器(wservice_comp.go:231、comp_httproute.go:103-135) mcp 同为 rpcx 服务,**可以用完全相同的调用当「内部网关」**调 home 的 memory_add/update/complete/del——写逻辑零复制(§5.4)
A5 服务间靠 ETCD 发现,CLUSTER_TAG 按应用隔离;每个应用一套容器、一套业务 MySQL、一个 Redis 前缀、一个对外 MCP 域名(测试 mcp-dev.ymaikj.com、正式 mcp.ymaikj.com,正式反代 9-12 已建) 百炼应用 ↔ 部署一一对应;MCP_ADDR 已是外网域名,三期不需要新的网络工作
A6 dev-deploy.sh / prod-deploy.sh 只下发 *.example 模板,服务器上的真实 confs/*.yaml 与 .env 要手工改(deploy/app/README.md) mcp.yaml 的 Groups 加工具名、home.yaml 的 memory 段,两台机各改一次并重启容器;本文不新增 .env 变量(复用 MCP_TOKEN_KEY/GATEWAY_TOKEN_KEY)
A7 算力系数存业务库 config 表,comm.LoadComputeRates 30s 缓存;console 「会员与算力」页用应用连接读写(api_compute.go:29-73,注释明写 console 不能用包级 mysql) 新系数 COMPUTE_RATE_SUMMARY 三处同改:comm/compute.go 常量与 ComputeRates、api_compute.go 的 key 列表、compute.vue 一行
A8 会议公共模板在 Postgres 公共库、按 app_name 隔离(echomeet/model.go:101-121),新应用要先在后台「从其它应用复制」 默认模板解析必须带 comm.AppName();查不到时自动链路只建记录不起任务并记一条日志,不能让翻译结束流程报错
A9 客户端登录时 Synchrodata 用 echomeet_getallrecords 拉全量整行(含 original/translate/summary),服务端有 getrecordbriefforuid 但没接线(model.go:232) 翻译会话自动进纪要会让记录数上一个量级;二期要把 getallrecords 换成 brief + 按需拉详情(§4.5)

2.2 设计原则(三段共用)

  1. 不新造第二套「记录」。翻译会话进纪要就是一条 echomeet_record,进拾忆就是一条 memory_item;不建 translation_session 表、不建 timeline 表。
  2. 服务端为真相源,客户端本地是缓存。翻译历史现在只活在 GetStorage,重装即丢;进纪要之后自然有了服务端副本,这是副产品不是目标。
  3. 拾忆存索引不存正文。memory_item.category=meeting 只放标题/时间/时长/概览摘要 + 指回记录的 source_id,正文永远在 echomeet_record。两处正文必然漂移。
  4. 能让模型看到结果的写操作走 MCP,看不到结果的设备动作走端侧指令(§5.2)。
  5. 失败隔离不变:任何附赠链路(进拾忆、抽待办、入索引)失败只记日志,不改主记录状态。
  6. 凡是「名字对不上就静默失效」的地方补一道启动自检(MCP 工具组、令牌工具清单)。

三、需求 ①:翻译会话 → 语音纪要(只总结)

3.1 复用隐藏通道,把它做成正式契约

现有两步已经能跑:

echomeet_addrecord {rtype:"VOICETRANSLAT", original:[ContextStruct…], seconds, title}
   → 记录落库,Original 写入、Translate 镜像,state=Unknow
echomeet_starttask {id, formlanguage, tolanguage, tid|templateid}
   → VOICETRANSLAT 分支:跳过 ASR → TranslateProcess(同语种直接跳)→ SubmitAITask → summary/overview

不新增接口族,只把这条路补齐四处缺口(每处都是现在会踩的坑):

# 缺口 改法
a addrecord 只收 original;翻译会话两种语言的文本都现成,再让服务端 MT 一遍是白花钱 EchomeetAddRecordReq 加 translate repeated ContextStruct。传了就写 Translate,不再镜像 Original
b TranslateProcess 的同语种短路 rec.Translate = rec.Original(tasks.go:420-424)会把 a 传上来的 Translate 抹掉 VOICETRANSLAT 分支改成:Translate 非空且 ≠ Original → 跳过 TranslateProcess;否则按原逻辑
c 没有幂等键。客户端会话结束时网络抖动重试就插两条 EchomeetAddRecordReq 加 client_key(= 翻译 sessionId),DBEchoMeetRecord 加列 + (uid, client_key) 唯一索引;命中返回已存在记录而非报错。空串要现生成(同 memory_item.client_key 那条教训)
d Creationtime 服务端写死 time.Now();rtype 只有 4 个值,列表里分不清「同传」还是「通话」 加 meet_time int64 + tz string(客户端上报,服务端只校验 2020 < t ≤ now+1d,否则退 now)+ scene string(simultaneous/faceToFace/call/audioVideo/fieldRecording/callRecording/holder/import)。rtype 语义收窄为「服务端怎么处理」,scene 负责「从哪来」

EchomeetStartTaskReq 不改。默认模板见 §3.6。

3.2 客户端:会话结束 → 组转写 → 建记录 → 触发总结

挂点:stopRecognition()(translation_controller.dart:2004)与 _stopFaceToFaceRecognition()(:587)——这两处是 currentSessionId 置空的地方,四种模式都经过,不再区分 _shouldArchiveRecording。

会话结束
  ├─ 取本会话条目:_historyManager 里 sessionId == currentSessionId 且 !isIntermediate
  ├─ 门槛:条目 ≥ 3 且 有效时长 ≥ 30s,否则不建记录(避免 10 秒试麦也进列表)
  ├─ 组 original / translate(规则见 3.3)
  ├─ 新的 TranscriptArchive.archive({scene, sessionId, items, audioPath?, seconds, meetTime, tz})
  │     ├─ echomeet_addrecord {rtype:VOICETRANSLAT, scene, client_key:sessionId, original, translate, seconds, title, meet_time, tz}
  │     ├─ SqfliteApi.insertMeeting + meetingspeaker(本地立刻可见,不等 Synchrodata)
  │     ├─ 有 WAV → MeetingUploadService.addUpload(id, path, 'TranslatAudio')(scene 白名单里已有)
  │     └─ 「翻译结束后自动生成纪要」开关开 → MeetingTaskService.submitTask(id, formlanguage, tolanguage, isSpeaker, templateid:0, tid:'')
  └─ 提示条「已存入语音纪要」,点击进详情页(复用 RecordingArchive 的 notify 样式)
  • TranscriptArchive 与现有 RecordingArchive 并列放在 lib/data/services/,不改 RecordingArchive 的签名——它有 4 个音频调用方,加转写参数会把它变成两个函数揉一个。
  • 有 WAV 时 audiourl 由上传服务事后 uprecord 补上,与现有链路一致;总结不等音频(VOICETRANSLAT 分支不查 audiourl)。
  • MeetingTaskService.addTask 现有逻辑「audiourl 为空就先挂起、等上传完再提交」(meeting_task_service.dart:57-100)对这条链路是反的:转写记录不需要音频。要么绕过 addTask 直接 submitTask,要么给 addTask 加 requiresAudio:false。选后者,一个布尔参数,默认 true 不影响老调用方。

3.3 说话人 / 语言 / 时间戳的归一规则

字段 规则
Speaker 通话:A→Speaker_1(本人)、B→Speaker_2(对方);面对面:activeSpeaker 1/2 → Speaker_1/2;同传/音视频:全部 Speaker_1。统一下划线(服务端异步转写路径就是 Speaker_+id,tasks.go:675;同步路径的 Speaker %s 带空格是既有不一致,顺手统一成下划线)
original[i].Content sourceText(允许逐条语种不同——面对面就是交替的)
translate[i].Content 归一到一种语言 tolanguage(= 用户在翻译页设定的目标语,通话/面对面取「本人语种」):sourceLanguageCode == tolanguage 用 sourceText,targetLanguageCode == tolanguage 用 translatedText,两者都不是(理论上不会)退 translatedText
Starttime/Endtime 毫秒偏移。有 WAV:相对录音开始时刻(需在 startRecording 时记下 _recordingStartedAt);无 WAV:相对首条 timestamp。Endtime = 下一条的 Starttime,末条 +3000
Meetingid 服务端回填,客户端填 0
formlanguage/tolanguage(starttask) 都传 tolanguage。这样服务端同语种判断为真,且 b 处改法保证不清空 Translate
title <模式名> <yyyy-MM-dd HH:mm>,41 语言用现有模式标题 key;不再用文件名当标题
seconds 有 WAV 用录音秒数;无 WAV 用 末条 timestamp − 首条 timestamp,再取与 VAD 有效时长(_recordAsrUsageStats 那套)的较小值——计费依据,不能虚高

⚠️ serviceId、activeSpeaker 现在不落盘(toJson 丢弃),所以这套归一只能在会话结束当场做,不能事后从历史里补做。要支持「历史会话补生成纪要」得先把 speaker 字段加进 TranslationItem.toJson——这是一个小改动,建议一并做,否则「历史→纪要」入口只能全标 Speaker_1。

3.4 计费与闸门

starttask 按 Seconds × ForMeeting 计算力(api_starttask.go:60-99)。翻译会话在 user_usages 里已经按秒计过一次翻译算力,再按会议全价算一次是重复计费,且这次没有 ASR 成本。

决策:新增用量类型 ComputeUsageSummary,系数键 COMPUTE_RATE_SUMMARY(算力/分钟),后台「会员与算力」页多一行。starttask 在 rtype==VOICETRANSLAT 时用它,其余不变。默认值建议 = 会议系数的 1/2,产品可调。

落点按 A7:comm/compute.go 加常量 + ComputeRates.Summary + ForSummary()(与 ForMeeting 同形:向上取整到分钟、最低 1);console/api_compute.go 的 key 列表与 computeRatesView 各加一项;admin/app/pages/compute.vue 加一行。config 表没这一行时回默认值(与其它系数同口径,计费不能因配置表缺行而中断)。

  • 流水与埋点不加新维度:DBUserUseLog.Addmeetsecond 照记(它就是「纪要秒数」台账),analyze 仍报 StatEventMeeting——后台看板的「会议」列语义变成「纪要生成」,比改 writeEvent 的固定 switch + console 看板列划算。写进看板说明即可。
  • VIP 闸门(VipRequired 5302)照旧:非 VIP 只得到「记录 + 转写」,不生成总结;详情页的「生成纪要」按钮照常引导续费。
  • ⚠️ 顺带发现:echomeet_summary(重新总结)完全不过 VIP 和算力闸门(api_summary.go 里没有 ResolveUserVip/ApplyComputeUsage)。这是既有漏洞,本次不修但要记:自动化之后「重新总结」的调用量会上来。

3.5 默认模板

服务端没有默认模板概念(§1.2)。自动链路没有 UI 让用户挑,需要一个确定的兜底:

starttask / summary 在 templateid==0 && tid=="" 时,服务端按 tolanguage 解析默认模板:优先本应用(app_name = comm.AppName(),A8)echomeet_template 里 source='public' && language 匹配 && sort 最大 的那条(= 客户端列表的第 0 项,两边口径一致),语言退化沿用 model_cache.go 的 base-lang 规则。客户端自动链路 templateid:0, tid:'';手动链路照旧传用户挑的。

  • 解析走 cache 组件的内存快照(模板缓存本就存在,NATS Rpc_ModifyEchomeetTemplate 热更),不加查库。
  • 本应用一条公共模板都没有时(新部署没复制模板):starttask 返回 TemplateNotFound,客户端自动链路把它当成「记录已建、总结未起」静默处理并记日志,详情页「生成纪要」照常可手动重试。翻译结束流程不能因此弹错。
  • 以后要在后台加「默认模板」勾选,只改这个解析函数,客户端不用动。

3.6 客户端展示与老包兼容

  • 详情页「转写」Tab 现在 tasktype >= 3 才解锁(meeting_details_view.dart:354-393),转写记录建好时 state 还是 Unknow(0),转写看不到。改成 tasktype >= 3 || textList.isNotEmpty。
  • 列表按 scene 显示来源图标(同传/面对面/通话/音视频/录音/支架/导入),rtype 不再参与展示。
  • 无音频的记录:播放器区域隐藏,不要放一个点了报错的播放条。Synchrodata 已能处理 audiourl 空串。
  • 老包:多出来的 scene/meet_time/tz/client_key 字段被手写 fromJson 忽略,无害;老包看到新记录 type=VOICETRANSLAT,上传桶选择 type == 'LOCAL' ? … : 'ExternalAudio' 落到 ExternalAudio,但老包不会给这类记录上传音频,无影响。
  • 翻译历史页保留,但每个会话卡片加「查看纪要」(本地 sqflite 里按 client_key 反查记录 id)。历史列表长按菜单加「生成纪要」(对 3.3 的 ⚠️ 有依赖)。

3.7 设置项

「翻译结束后自动生成纪要」:默认开;关闭时只建记录不起任务。存 GetStorage,不需要服务端。


四、需求 ②:纪要 → 拾忆时间线

4.1 设计:category=meeting 的索引项

在 memory_item 加分类 meeting(comm/memory.go 白名单加一行,不改表):

字段 取值
category meeting
source / source_id meeting / 记录 id(与待办抽取同一对键,拾忆里「来自同一场会」能关联)
client_key meeting:<记录id>(服务端生成,天然幂等)
title 记录标题
detail overview(短总结,≤ 500 字截断;summary 全文不放)
happen_date / happen_time / tz 由 meet_time + tz 折算(§3.1 d;没有 meet_time 的存量记录用 creationtime + Asia/Shanghai,与待办抽取现状同口径)
state 0;不参与「完成」语义,客户端不显示勾选框
remind_ahead 0(不提醒)
extra {"scene":"call","seconds":1830,"rec_state":5,"has_audio":true}
user_edited 恒 false;这类项只读(编辑入口跳会议详情页)

为什么不让拾忆页直接查 echomeet_record:memory_list / memory_today / memory_stats / 周报月报 / get_memory_items 全部只读一张表;再加一个数据源意味着日期过滤、时区、分页、MCP 工具各写两份。索引项一行 200 字节,代价可忽略。

4.2 同步钩子(都在 home 进程内,走 comm.IMemory,不跨服务)

IMemory 加两个方法(comm/module.go:143):

// UpsertMeetingIndex 会议索引项写入/更新;幂等键 meeting:<recordID>。失败只记日志。
UpsertMeetingIndex(ctx, uid, recordID, title, overview, meetTime int64, tz, scene string, seconds int64, recState int32)
// RemoveBySource 删除来源为 source_id 的索引项(不删待办:那些已经是用户自己的任务)。
RemoveBySource(ctx, uid, source, sourceID string)
时机 位置 动作
建记录 api_addrecord.go 末尾 Upsert(此时 overview 空、rec_state=0)
总结完成 tasks.go AIProcess 置 Completed 之后,与 extractMemoryTodos 并排 Upsert(带 overview、rec_state=5)
改标题 api_modifyrecord.go Upsert 标题
删记录 api_delrecords.go RemoveBySource;抽出的待办保留,只把它们的 source_id 留着(拾忆里显示「来源会议已删除」)
转写/总结失败 tasks.go 置 TranscribeFail/SummarizFail Upsert rec_state,拾忆卡片显示「生成失败」而不是永远「生成中」

存量记录:memory 模块 Start 里起 goroutine 跑一次幂等回填(照 migrate_task.go 的写法,按 uid 分批、每批 200 条、批间 sleep 100ms;client_key 唯一索引保证重跑不重插;失败只记日志——A1,绝不能让 home 起不来)。回填只读 echomeet_record 的 id/title/overview/creationtime/state/seconds 六列,不要 SELECT *(original/translate/summary 三列每行可达几十 KB)。

4.2a 性能与列表接口(A9)

翻译会话自动进纪要后记录数会翻几倍。Synchrodata 现在每次登录 echomeet_getallrecords 拉全量整行(含三个大文本列)。二期一并做:

  • echomeet_getallrecords 加 brief:true 参数 → 走已有但未接线的 getrecordbriefforuid(不返回 original/translate/summary);
  • 客户端 Synchrodata 改成 brief 同步 + 打开详情时 echomeet_getrecord 补拉;老包不传 brief 行为不变。
  • 拾忆页读的是 memory_item 索引项,本来就不碰大文本列,不受影响。

4.3 客户端拾忆页

  • CalendarEventCategory 加 meeting;卡片:来源图标 + 标题 + 时长 + 状态 chip(转写中 / 已生成 / 失败)+ overview 前两行;点击 → Routes.meetingDetails(记录 id 从 source_id)。
  • 筛选条加「会议」。
  • 未知分类兜底已在(记忆中心 §3.3),所以老包收到 meeting 分类不会崩,只是按 todo 卡片显示——可接受。
  • 每日弹窗/播报:memory_today 把 meeting 项排除在播报文案之外(播「今天有一场 30 分钟的通话翻译」没有意义),列表里照常显示。

4.4 报告与 MCP

  • stat_json 加 meeting_count / meeting_seconds;报告 prompt 的「依次覆盖」加一句「会议与录音场次」。
  • get_memory_items 的 category 枚举加 meeting;EMAI 问「我这周开了几次会」直接从 get_memory_stats 答;问内容仍走 search_meeting_notes。
  • 新增 get_meeting_detail(record_id):拿到 overview 之后追问「第二点具体说了什么」用,返回 summary 全文(截断 4000 字)。

五、需求 ③:EMAI 综合入口

5.1 中枢模型

                 ┌───────────────── 客户端(会话持有者)──────────────────┐
 App 对话页 ─────┤  EmaiController                                       │
 耳机按键唤醒 ───┤  DeviceAiSessionService  ──▶ BailianMultimodalService ─┼──WS──▶ 百炼应用(EMAI / Smartcar)
 支架 / 香薰 ────┤        │                        │ RespondingContent      │            │ 工具调用(带 auth_token)
                 │        │                        ▼                        │            ▼
                 │        │              AssistantDirectiveService          │     我们的 MCP(7300)
                 │        │                 ├─ 设备/功能类指令 → 端侧执行    │       ├─ 读:纪要/事项/统计/报告/天气/地图/联网
                 │        │                 └─ tool_infos → 触发本地刷新     │       └─ 写:待办/闹钟/灵感/花销/完成/取消
                 └────────┴────────────────────────────────────────────────┘            │ 直查业务库(uid 来自令牌)
                                                                                        ▼
                                                                          memory_item / echomeet_record

四条链路(App、耳机、支架、香薰)已经共用同一个会话持有者与指令分发器(§1.4),本节不动这个结构,只补三件事:写路径、指令清单、身份清单下发。

5.2 决策:记忆类写操作从端侧指令改走 MCP 工具

记忆中心文档 §8.3 定的是「记录走端侧、查询走 MCP」,理由是延迟低、离线能先落本地。本文推翻这条,理由:

  1. 模型看不到端侧指令的执行结果。tool_calls 下发即结束,模型在同一帧里已经说了「已经帮你定好了」——CLAUDE.md 记录的「取消闹钟 / 记待办不下发指令却回已取消」就是这种结构的必然产物。MCP 工具的返回会进入模型的下一句话,「记好了,明天下午三点」是真的。
  2. 「离线先落本地」不成立。指令只在百炼会话建立时才收得到,会话本身就要联网;不存在「离线拿到指令」这回事。
  3. 耳机在后台唤醒这个场景两种方式一样:会话都跑在手机进程里。
  4. 写路径在服务端,幂等、限流、注销清表全在一处;端侧指令那套 client_key 双写、syncPending 补传、本地 reminders 迁移期双写全部可以退役。

保留端侧的:所有「结果在设备上」的动作——启动/停止某个功能、音量、地图跳转、播放控制。这些 MCP 做不了。

过渡:SET_clock 指令处理器保留一个版本(百炼控制台摘掉工具前老应用仍会下发),新老同时存在时按 client_key 幂等不会重。

5.3 端侧指令清单(保留 / 新增 / 摘除)

指令 处置 落点
SET_clock 保留一版作过渡,之后由 MCP add_memory_item(category=alarm) 取代 现有
SEND_message / MAKE_A_PHONE_CALL_phone_call 百炼控制台摘除(_declined 保留兜底) 外部操作
ROUTE_map(endLoc_city/endLoc_poi) 实现:拼高德/Google 地图 URL scheme 打开外部应用;国内外按 region 新增
INCREASE_DEFAULT_volume / DECREASE_… 实现:系统媒体音量 ±(现有 volume_controller 类插件或原生 MethodChannel) 新增
START_feature(module_id, params) 新增,走模块注册表 directives:simultaneous/faceToFace/call/audioVideo/fieldRecording/callRecording/meeting 各自在 module.dart 声明,参数含 from/to 语种;这是 AgentModuleDescriptor.directives 第一次真正被用 各模块
STOP_feature 同上 各模块
MUSIC_control(play/pause/next) 车载香薰已有 commands 形状(intent_info),归到同一分发器 smartcar

AssistantDirectiveService._dispatch 的顺序不变:declined → 模块 handler → 内置 → unsupported。unsupported 继续记流水,这是发现百炼新下发了什么的唯一渠道。

⚠️ START_feature 的能力校验:模块声明了 requiredCapabilities(通话翻译要 callAudioTap),指令到达时 DeviceHub.to.has(cap) 不满足要把「缺设备」作为结果告诉用户(本地 TTS 一句「需要连接耳机」),而不是静默 unsupported——这条指令的用户正对着设备说话。

5.4 MCP 工具清单

新增写工具(GLOBAL,全部要 auth_token):

工具 参数 落点 返回
add_memory_item category(todo/alarm/idea/expense)、title、date(YYYY-MM-DD,缺省今天)、time(HH:mm)、repeat、amount(元,expense)、detail memory_item,source=assistant {id, title, happen_date, happen_time}
complete_memory_item id 或 title_keyword state=1 完成的那条
cancel_memory_item 同上 state=2 取消的那条;这就是「取消闹钟」的落点
update_memory_item id + 改动字段 user_edited=true 改后的那条
get_meeting_detail record_id 只读 overview + summary(截断)

实现方式:mcp 进程当「内部网关」,写逻辑留在 home(A3 + A4)

百炼 → mcp:7300  add_memory_item(args, auth_token)
         │ ResolveUIDWithToken → uid(现有 auth.go)
         │ 组 pb.DBMemoryItem(分类/日期/时间/金额归一,见下)
         ▼
   this.module.Service().RpcCall(ctx, "home", comm.Rpc_GatewayHttpRoute,
        &pb.Rpc_GatewayHttpRouteReq{MsgName:"memory_add",
              Meta:{userid: uid, ip: "mcp", servicetag: <本集群>},
              Message: json(MemoryAddReq)}, &reply)
         ▼
   home SCompHttpRoute.Rpc_GatewayHttpRoute → memory.apiComp.Add(校验、幂等、recalcRemind、落库)
         ▼
   reply.Body(业务 JSON)→ 转成可朗读短句回给模型
  • 这是 gateway 转发业务请求的原样写法(wservice_comp.go:231),只是调用方从 gateway 换成 mcp;mcp 本就是 rpcx 集群服务,同 CLUSTER_TAG 下 ETCD 能发现 home。mcp 模块里从没 RpcCall 过,这是第一次,先用 get_memory_items 之外的一个只读工具(get_meeting_detail)走通再上写工具。

  • 分类白名单、日期校验、recalcRemind、client_key 幂等、user_edited 语义全部复用 home 的 memory_add/update/complete/del,mcp 侧零业务逻辑——记忆中心 §8.2 担心的「第二份实现漂移」不存在。

  • home 侧拦截器(实名/VIP 类)对 RPC 进来的请求同样生效(comp_httproute.go:135),EMAI 已过实名闸门所以不会被拦;万一被拦,返回给模型的是同一句业务提示。

  • mcp 没有 Redis(A3):写工具的限流用 mcp 进程内的按 uid 令牌桶(每应用单进程,够用,重启即清零可接受);幂等不需要 Redis——client_key 由 mcp 生成:mcp:<sha1(uid|category|title|happen_date|happen_time)>,home 的 (uid, client_key) 唯一索引天然去重,命中返回 Duplicated:true(api_add.go 已有这个返回)。

  • RPC 超时 5s;超时/失败回给模型「没记上,请再说一次」,不要回「已记好」。

  • 今天是几号由服务端给:工具入参日期允许省略,mcp 按 user_getmcptoken 时上报并签进令牌 claims 的 tz 折算「今天」(令牌 2h 内时区不会变;不查库)。不能靠模型自己算日期——它不知道今天几号,SET_clock 那次 03:30 的坑就是这么来的。

  • 幂等:见上,靠 client_key 唯一索引;同一句话被模型重试两次只会落一条。

  • 写工具的 title_keyword 匹配命中多条时不动手,返回候选让模型追问——宁可多说一句,不能删错。

  • 写工具的错误码走 pb.ErrorCode 5201 段(记忆中心已开),返回给模型的是可朗读的中文短句,不是 code。

修正现有:§1.4 表里四处名字对齐;add_user_task / get_user_tasks / cancel_user_task 三个 allhelp 旧工具下线(DBTask 已迁到 memory_item,记忆中心 §9.1),从 YAML 与 _mcpAuthTools 一并删。

启动自检:mcp 模块 Start 结束时,对每个 AddTool 过的名字若不在任何组 → safeLogErrorf 一行;YAML 里点名但没有实现的 → 同样一行。名字对不上不再是「安静地没有」。

5.5 身份链路:令牌工具清单改由服务端下发

现在 _mcpAuthTools 写死在客户端(§1.4),服务端每加一个要身份的工具就要发版。改:

  • UserGetMcpTokenResp 加 auth_tools repeated string。清单的唯一来源是 comm.McpAuthTools(一个字符串切片常量):mcp 模块用它判定哪些工具必须解身份,user 模块用它下发。user 在 home 进程、读不到 mcp.yaml,而五个服务出自同一个镜像(A1),放 comm 就是同一份代码、同一次构建,不会漂移;comm/mcpauth_test.go 断言每个 tool_*.go 里调了 ResolveUID* 的工具名都在这个切片里。
  • 令牌 claims 加 tz(客户端 user_getmcptoken 请求带 IANA 时区),供 §5.4 的「今天」折算。
  • 客户端 BailianMultimodalService 按下发清单嵌套 user_defined_params,本地清单退为兜底。
  • 百炼只透传给被调用的那个工具,多嵌几个没有成本。

⚠️ 记忆文件里的实测:只有按工具名嵌套这一种形状能收到,平铺 / 按 MCP 服务名嵌套 / mcp 子节点三种都静默丢弃。别改形状。

5.6 会话记忆与上下文注入(两条都要先验证)

目标 方案 验证方法
跨会话记忆(重开 App 还记得上一轮) Start 的 input 里回传上次的 dialog_id(现在只在 continue-task 里回传,Start 不传,bailian_multimodal_service.dart:399)。持久化到 GetStorage,按 uid 分,超过 24h 作废 必须做对照组:一组传真 dialog_id,一组传乱写的 id,一组传虚构字段名。三组表现一致 = 字段被静默丢弃,方案不成立(这是 rag_options 那次实验的教训)
模型知道「今天几号 / 用户在哪个时区 / 接着哪个设备 / 今天有什么事」 biz_params.user_prompt_params(协议里确实存在的字段)注入 {"user_context": "..."},百炼应用提示词里放 ${user_context} 占位。内容 ≤ 300 字:日期、时区、设备、未来 3 条事项标题 控制台提示词里加占位 → 真机问「今天几号」;同样加对照组

两条都不成立时的退路:跨会话记忆靠服务端把最近 N 轮对话摘要(allhelp 的 DBChatSummary 机制已有)作为 user_context 的一部分注入——这条只依赖 user_prompt_params,不依赖百炼的会话保持。

5.7 联网与知识库

  • 联网:百炼应用自带 enable_web_search(客户端已按开关发 extra_config),与我们 MCP 的 bocha/tavily 是两条路。定一条:国内用百炼内置(省一次工具往返),海外用 tavily(内置搜索对海外站点覆盖未验证)。
  • 知识库:不建。纪要检索继续用 search_meeting_notes(MySQL ngram 全文),换向量/百炼知识库的三个触发条件已写在 tool_meeting.go:42-53,到了再评估。原因复述一遍免得再议:对话协议里没有入口告诉知识库「只检索这个用户的文件」,隔离只能落在我们自己这一侧;MCP 工具刚好就是这一侧。
  • 翻译会话进纪要之后自动进入同一个检索范围(echomeet_record.summary 有全文索引),不用另做。灵感/待办的关键词检索由 get_memory_items 加 keyword 参数覆盖(title/detail LIKE,量小够用)。

5.8 多设备 / 多 agent 统一

  • EMAI 与 Smartcar 是两个百炼应用。约束:两个应用绑定同一个 MCP 服务、同一份工具勾选、同一份提示词变量,否则「耳机能记待办、香薰不能」。做成一份控制台配置清单(§5.9),每次加工具按清单过两遍。
  • 设备唤醒选哪个应用:现有 AgentDefaults.categoryForVendor → defaultsForCategory → bailianAppId 不动。
  • 游客:client_info.user_id 回退 eaimar-guest 会让多设备串记忆;MCP 侧无令牌一律拒,所以游客只能闲聊——这是对的。EMAI 已过实名闸门,游客本来进不来。
  • 车载香薰的 commands 形状(intent_info)与 tool_calls 形状都进 AssistantDirective.parseFromOutput,分发器不分设备。

5.9 百炼控制台配置清单(外部操作,每个应用各做一遍)

# 操作 应用
C1 MCP 服务「emai-mcp」:技能 → MCP 服务 → 逐个勾选新工具(add/complete/cancel/update_memory_item、get_meeting_detail),点发布 EMAI、Smartcar
C2 摘掉 SEND_message、MAKE_A_PHONE_CALL_phone_call、SET_clock(写工具上线一版之后) 两者
C3 端侧函数加 START_feature / STOP_feature / 音量 / ROUTE_map(参数按 §5.3) 两者
C4 提示词加 ${user_context} 占位(§5.6 验证通过后) 两者
C5 开 enable_web_search(国内应用) EMAI
C6 后台 svc_config 的 sts_bailian.app_id / workspace_id 填实值,别再靠客户端硬编码兜底 后台

⚠️ 控制台「语音交互体验」里测不出身份链路(没有客户端就没有 user_defined_params),验证只能真机或脚本直连 WS。


六、跨需求的公共改动汇总

6.1 proto

文件 改动
echomeet_msg.proto EchomeetAddRecordReq + translate(9, repeated ContextStruct) client_key(10) meet_time(11) tz(12) scene(13)
echomeet_db.proto DBEchoMeetRecord + scene(33) meet_time(34) tz(35) client_key(36, uniqueIndex:idx_echo_uid_ck 与 uid 联合)
memory_msg.proto MemoryStatsResp + meeting_count meeting_seconds
memory_msg.proto MemoryListReq + keyword
user_msg.proto UserGetMcpTokenReq/Resp Req + tz;Resp + auth_tools
errorcode.proto 5201 段续编:MemoryItemAmbiguous(写工具命中多条)、EchomeetTranscriptEmpty

生成后 git diff --stat -- apps/services/pb/ 必须只动这几个文件(CLAUDE.md 那条 protoc-gen-go 版本坑)。

6.2 服务端

模块 改动
comm MemoryCatMeeting;ComputeUsageSummary + COMPUTE_RATE_SUMMARY;IMemory 加 UpsertMeetingIndex/RemoveBySource;meet_time 校验函数
echomeet addrecord 收新字段 + 幂等;starttask VOICETRANSLAT 分支不清空 Translate、走 Summary 计费、默认模板解析;四个钩子调 IMemory;Speaker 前缀统一
memory 分类白名单;两个新方法;stats 加会议;list 加 keyword;启动回填存量记录索引
mcp 5 个新工具(写工具经 RPCX 调 home,client_key 由 mcp 生成);名字对齐;下线 allhelp 三工具;启动自检;进程内按 uid 限流
user user_getmcptoken 下发 comm.McpAuthTools、收 tz 并签进 claims
echomeet getallrecords 加 brief 参数接上 getrecordbriefforuid
console 「会员与算力」页加 COMPUTE_RATE_SUMMARY 一行(api_compute.go + compute.vue)
注销 cancel() 表清单不用改(echomeet_record、memory_item 都在)

6.3 客户端

位置 改动
data/services/transcript_archive.dart 新:组转写 → addrecord → 本地库 → 上传 → 起任务
translation_controller.dart 两处结束挂点;_recordingStartedAt;TranslationItem 加 speaker 并落盘
meeting_task_service.dart addTask(requiresAudio:)
meeting_details_view.dart 转写 Tab 解锁条件;无音频隐藏播放器
拾忆页 meeting 分类卡片 + 筛选 + 跳转
assistant_directive_service.dart tool_infos 解析 → 命中写工具名则 MemoryService.refresh() + MemoryReminderScheduler.reschedule();ROUTE_map/音量处理器
各 module.dart 声明 directives: {START_feature…}
bailian_multimodal_service.dart 令牌清单改读服务端;Start 回传 dialog_id(验证通过后);user_prompt_params
mcp_token_service.dart 缓存 auth_tools,上报 tz
设置页 「翻译结束后自动生成纪要」

⚠️ tool_infos 的精确形状(工具名字段叫什么、结果在哪一层)要先抓一帧再写解析,记忆中心那次只确认了「它是服务端已执行的结果」。


6.4 落地清单(按环境;A5/A6 决定了这些都是手工步骤)

服务端改动全部在 starpivot-app 一个镜像里,走既有 dev-deploy.sh along / prod-build.sh + prod-deploy.sh ym along。代码之外要动的:

步骤 测试机 8.133.166.29 /home/work/starpivot/app 正式机 47.116.104.181 /home/work/ym-a11 哪一期
confs/home.yaml memory 段无新键(回填与钩子无需配置) 同 二期
confs/mcp.yaml Groups.GLOBAL 加 add/complete/cancel/update_memory_item、get_meeting_detail;删 add/get/cancel_user_task、music_search、music_play;导航项改成代码真名 同 三期
.env 不新增(复用 MCP_TOKEN_KEY / GATEWAY_TOKEN_KEY,正式机 9-11 已配) 同 —
业务库 config 表 COMPUTE_RATE_SUMMARY 由后台「会员与算力」页写入;不写走默认 同 一期
echomeet_template 确认本应用(app_name=EAIMAR)有公共模板,否则默认模板解析落空 同 一期
重启 改 confs/*.yaml 后 docker compose up -d --force-recreate(Groups 只在启动时读) 同 三期
NPM 反代 mcp-dev.ymaikj.com 已有 mcp.ymaikj.com 9-12 已建(SSE 关缓冲) 无需动
百炼控制台 §5.9 C1~C6,EMAI 与 Smartcar 各一遍 同一套应用 三期
客户端 一期/二期改动不依赖服务端先上(addrecord 多传的字段老服务端忽略,只是转写不落库);三期客户端依赖 auth_tools 下发,服务端先上 — —

⚠️ 顺序:一期服务端先于客户端上(否则翻译结束建出的记录 translate 会被同语种短路抹掉,用户看到的总结是原文语种)。二期无顺序要求。三期服务端 → 百炼控制台 → 客户端。

七、分期实施

一期:翻译 → 纪要(两周内可交付,服务端改动最小)

# 任务 端
1.1 proto:AddRecordReq 5 字段 + DBEchoMeetRecord 4 列 后端
1.2 addrecord 幂等 + translate 直写 + meet_time/tz/scene;starttask VOICETRANSLAT 分支修 Translate 清空、默认模板解析 后端
1.3 ComputeUsageSummary + 后台系数行 后端/后台
1.4 TranslationItem.speaker 落盘;TranscriptArchive;两处结束挂点;addTask(requiresAudio);自动生成开关 客户端
1.5 详情页转写 Tab 解锁、无音频播放器隐藏、列表 scene 图标 客户端
1.6 翻译历史会话卡「查看纪要」 客户端

验收:四种模式各跑一段 ≥ 30s 的会话 → 结束 3s 内语音纪要列表出现记录、转写 Tab 可看、说话人分行正确;VIP 用户自动出总结;非 VIP 只有转写;user_getcompute 里多出的算力 = 时长 × Summary 系数;同一会话重复触发不插第二条。

二期:纪要 → 拾忆

# 任务 端
2.1 MemoryCatMeeting、IMemory 两方法、echomeet 四钩子、存量回填 后端
2.2 memory_stats 会议字段、memory_today 播报排除、报告 prompt 后端
2.3 拾忆页 meeting 卡片/筛选/跳转 客户端
2.4 get_memory_items 分类枚举加 meeting;get_meeting_detail(经 RPCX 调 home,作为三期写工具的链路预演) 后端 MCP
2.5 echomeet_getallrecords 加 brief;客户端 Synchrodata 改 brief + 详情按需拉 两端

验收:一期产生的记录在拾忆当天格子里出现,状态 chip 随转写/总结推进;删记录后索引项消失、抽出的待办还在;周报里有「本周 N 场会议共 M 分钟」。

三期:EMAI 综合入口

# 任务 端
3.1 MCP 名字对齐 + 启动自检 + 下线 allhelp 三工具 后端
3.2 四个写工具:mcp 组 item → RPCX 调 home memory_*;client_key 由 mcp 生成;进程内限流;按令牌 tz 折算「今天」 后端
3.3 comm.McpAuthTools 单一来源 + 守卫测试;user_getmcptoken 下发 auth_tools、签 tz;客户端改读 两端
3.8 两台机 confs/mcp.yaml Groups 手改 + 重启(§6.4) 运维
3.4 tool_infos 抓帧 → 解析 → 本地刷新与重排通知 客户端
3.5 START/STOP_feature 走模块注册表;ROUTE_map;音量 客户端
3.6 百炼控制台 C1~C6 外部
3.7 dialog_id 回传与 user_prompt_params 两个对照实验;通过则接入 客户端

验收:对耳机说「明天下午三点提醒我开会」→ 模型回复里带服务端返回的日期时间 → 拾忆页 2s 内出现该项 → 本地通知已排;说「取消它」→ state=2;说「开始通话翻译,中译英」→ 翻译页打开且模式/语种正确,未连耳机时 TTS 提示;香薰上同一套说法结果一致。


八、风险与待确认

风险

级别 风险 应对
高 TranslateProcess 同语种短路会抹掉客户端传的 Translate,表现为总结用的是原文语种、且不报错 §3.1 b,一期必改;加单测:VOICETRANSLAT + 非空 Translate → AIProcess 输入等于 Translate
高 MCP 工具名对不上就静默不暴露,新写工具会重蹈 add_user_task 覆辙 §5.4 启动自检;新工具上线用真机验一次
高 _mcpAuthTools 与服务端脱节 → 新工具永远「身份校验失败」 §5.5 改服务端下发;过渡期两边都列
高 写工具的模型幻觉方向反转:以前是「没做说做了」,MCP 之后可能「做了两遍」(模型重试) 120s 幂等窗口 + 结果里 duplicated 标记
高 dialog_id / user_prompt_params 两条百炼能力未验证,且失败是静默的 对照组实验先于编码(§5.6)
高 mcp → home 的 RPCX 调用是这个模块第一次跨服务调用,ETCD 发现/CLUSTER_TAG/超时任何一环不通都表现为「工具超时」 先用只读的 get_meeting_detail 走通链路再上写工具;RPC 超时 5s 回「没记上」
高 新应用没复制公共模板 → 自动总结 TemplateNotFound 客户端静默降级为「只建记录」;上线清单里核对模板(§6.4)
中 getallrecords 全量整行同步随记录数膨胀 二期 brief 参数 + 详情按需拉(§4.2a)
中 五进程同容器:memory 回填或新钩子 panic 会拖垮全站 回填在 goroutine + recover;钩子沿用 ExtractMeetingTodos 的 recover 写法
中 翻译会话自动进纪要 → 列表被短会话刷屏 3 条 / 30s 门槛 + 设置开关
中 重复计费:翻译秒 + 纪要秒 独立 Summary 系数(§3.4)
中 echomeet_summary 不过闸门,自动化后调用量上升 记为既有漏洞,单独排期
中 面对面/通话的说话人信息现在不落盘,历史会话补生成纪要全是 Speaker_1 TranslationItem.speaker 一期就加
中 拾忆 meeting 项进每日播报会念出无意义内容 memory_today 播报排除该分类
中 存量记录回填索引时 meet_time 空,全部按上传日期 已知偏差,与待办抽取一致,写进说明
中 EMAI 与 Smartcar 两个百炼应用工具配置漂移 控制台清单 §5.9,加工具按清单过两遍
低 老包收到 category=meeting 兜底已发(记忆中心一期)

待确认(产品/运营)

  1. 翻译会话默认自动生成纪要还是默认只存转写?默认自动会持续消耗算力与 VIP 用户的耐心(每次通话结束都多一条纪要);默认关则功能等于没做。建议默认开 + 30s 门槛,两周后看 user_getcompute 的 Summary 用量再定。
  2. COMPUTE_RATE_SUMMARY 的初值。建议会议系数的 1/2。
  3. 记忆写操作改走 MCP(§5.2)是否接受——它推翻记忆中心文档 §8.3,且意味着 SET_clock 那套本地双写在一个版本后退役。
  4. START_feature 要不要让香薰能拉起手机上的翻译页:技术上是同一条链,但用户在车里对着香薰说「开始通话翻译」、手机自己跳页,体验是否合理。
  5. 拾忆里「来源会议已删除」的待办要不要跟着删。建议不删(已经是用户的任务)。
  6. 联网搜索国内走百炼内置、海外走 tavily——需要确认百炼内置搜索在海外应用上的表现,没验证过。

附录 A:关键文件索引

用途 路径
隐藏的只总结通道 apps/services/modules/echomeet/api_starttask.go:135、api_addrecord.go:49
同语种短路(要改) apps/services/modules/echomeet/tasks.go:420
总结 + 待办抽取挂点 apps/services/modules/echomeet/tasks.go:481-644
翻译条目模型 / 历史存储 apps/client/lib/modules/translation/models/translation_models.dart、controllers/translation_history_controller.dart
会话结束挂点 apps/client/lib/modules/translation/controllers/translation_controller.dart:587,2004
音频归档(不改签名) apps/client/lib/data/services/recording_archive.dart
任务提交 / 轮询 apps/client/lib/data/services/meeting/meeting_task_service.dart
记忆中心跨模块接口 apps/services/comm/module.go:143、comm/memory.go
MCP 鉴权 / 工具组 / 会议工具 / 记忆工具 apps/services/modules/mcp/auth.go、module.go:163、tool_meeting.go、tool_memory.go
MCP 工具组配置 deploy/app/confs/mcp.yaml.example:50-73
MCP 令牌签发 / 客户端缓存 apps/services/modules/user/api_getmcptoken.go、apps/client/lib/data/services/mcp_token_service.dart
gateway → home 的 RPC 转发写法(mcp 照抄) apps/services/modules/gateway/wservice_comp.go:231、apps/services/services/comp_httproute.go:103
五进程守护 apps/services/entrypoint.voitrans.sh
算力系数三处落点 apps/services/comm/compute.go:37、modules/console/api_compute.go:29、apps/admin/app/pages/compute.vue
部署方式(只发模板) deploy/app/README.md、deploy/app/confs/mcp.yaml.example
百炼会话 / 令牌嵌套 / dialog_id apps/client/lib/data/services/bailian_multimodal_service.dart:277-301,399,474,583
指令解析 / 分发 apps/client/lib/data/models/assistant_directive.dart、data/services/assistant_directive_service.dart
模块注册表(directives 未用) apps/client/lib/modules/module_registry.dart:88、module_descriptor.dart:55
设备唤醒选 agent apps/client/lib/data/services/device_ai_session_service.dart:52-72
算力用量类型 apps/services/comm/compute.go:473,543