如何在2秒内浮现正确的笔记:OpenOats实时建议三层并发架构设计全解析
【免费下载链接】OpenOatsA meeting note-taker that talks back.项目地址: https://gitcode.com/gh_mirrors/op/OpenOats
OpenOats 是一款开源的 macOS 会议笔记助手("A meeting note-taker that talks back"),它能实时转写会议双方对话,并在关键时刻从你自己的笔记库中浮现相关建议。本文深入解析 OpenOats 实时建议引擎的三层并发架构设计:它如何用不到 2 秒的延迟,把最该说的数据点、客户反馈和定价依据推到屏幕边上的浮动面板中——让你在对方面前显得有备而来。
为什么旧的 5 级串行管道不够快
在重构之前,OpenOats 的建议管道是5 级串行执行:每一句话定稿后,要串行完成 3 次 LLM 往返,还带着一个 90 秒的冷却期,端到端延迟高达5~10 秒以上。在真实的销售通话里,对方问完问题 5 秒之后才弹出建议,早就没人看了。
新架构的核心目标只有一个:从对话触发到建议可见,控制在 2 秒以内。完整的设计动机与数据流图可以见官方设计文档 2026-03-26-realtime-suggestions-design.md。
三层并发架构总览
新引擎用一个统一的调度类 SuggestionEngine.swift 驱动三条并行工作、互不阻塞的层:
| 层 | 职责 | 触发时机 | 延迟预算 |
|---|---|---|---|
| 第一层:持续上下文累积 | 后台预取知识库 + 追踪对话状态 | 会议全程持续运行 | 不占用建议时间线 |
| 第二层:即时检索与门控 | 本地启发式闸门 + 缓存命中检索 | 任一方话语定稿时 | 目标 <100ms |
| 第三层:流式 LLM 合成 | 快速模型逐词流式生成洞察 | 建议浮现后立即并行 | 流式覆盖原始片段 |
关键思想:用户看到的第一眼永远不需要等待 LLM。原始知识库片段先上屏,LLM 合成结果随后以流式方式"原地升级"它。
第一层:持续上下文累积,让检索"抢跑"
第一层是三个"投机性工作"的集合,核心原则是:任何工作都不阻塞建议交付。
周期性知识库预取
会议进行中,引擎每隔约 4 秒(preFetchIntervalSeconds可配置)抓取当前滚动的部分转写文本(取最近约 40 个词),提前执行一次知识库检索,并把结果存入指纹缓存。实现见 SuggestionEngine.swift 中的startPreFetching()与runPreFetch()。
缓存本身是 PreFetchCache.swift 里的一个 Swiftactor(线程安全),规则很克制:
- 文本指纹:小写化、取最后约 50 个词作为缓存键,重复的文本窗口不会重复检索
- TTL 30 秒:过期条目自动驱逐,最多保留 20 条
这意味着当第二层真正被触发时,检索结果大概率已经在缓存里了,命中耗时 0ms。
后台对话状态跟踪器
每隔 2~3 句定稿话语,引擎会用你的主模型异步调用一次 LLM,把ConversationState(当前话题、简短摘要、未决问题、活跃分歧、近期决策、对方的目标)更新到共享状态中(SuggestionEngine.swift)。其他层只读、不等待这份状态——它让第三层的合成提示词自带"这场会聊到哪了"的上下文。
一个容易被忽略的前置工程:为了让第一层"抢跑",转写层本身被改造为在说话进行中每 250~500ms 发出一次尽力而为的部分假设(见 StreamingTranscriber.swift),而不是只等整句定稿。
第二层:即时检索与本地启发式闸门
当任何一方(你的麦克风或对方的系统音频)说一句完整的话,第二层接管。它只做三件事,全部是本地计算:
1️⃣ 本地启发式闸门(RealtimeGate.swift)——这是取代"LLM 出场判断"的关键设计,把决策压到毫秒级:
- 触发类型识别:通过关键词和标点启发式,把话语归类为
question(提问/决策点)、claim(观点/异议)、topic(客户、定价、留存等主题词)或general - 相似度门槛:知识库最高分必须 ≥ 可配置阈值(默认 0.35),否则直接丢弃
- 去重抑制:用 Jaccard 相似度对比最近 3 条已展示建议,相似度 > 0.7 判定为重复,不再刷屏
2️⃣ 缓存优先检索:先用文本指纹查PreFetchCache,命中则 0ms 拿到上下文包;未命中才同步执行一次真实检索(SuggestionEngine.swift)。检索返回的不是干巴巴的文本块,而是知识库上下文包KBContextPack(见 KnowledgeBase.swift):
- 相对路径 + 文件夹面包屑 + 标题面包屑(如
sales/pricing.md > Pricing > Unit Economics) - 命中片段的前后相邻章节(来自同一文件),保证片段可被理解
- 嵌入时携带文档结构元数据,让排序不依赖正文本身
3️⃣ 爆发-衰减节流(BurstDecayThrottle.swift)——替代旧的 90 秒固定冷却:
burstScore = 提问密度(近60秒) × 0.4 + 知识库相关度 × 0.6| 信号强度 | burstScore | 最小间隔 | 替换所需分差 |
|---|---|---|---|
| 高强度(密集提问 + 强匹配) | > 0.7 | 0 秒 | 0.05 |
| 中等强度 | > 0.5 | 4 秒 | 0.10 |
| 低强度 | 其余 | 12 秒 | 0.20 |
候选只有三种命运:立即浮现、替换当前、丢弃——不存在"排队稍后展示"。过期的候选宁可扔掉也不迟到,这正是"2 秒内浮现"体验的最后一道保险。
第三层:流式 LLM 合成,原地升级原始片段
闸门放行的候选立即进入第三层(SuggestionEngine.swift),这里有一个精心设计的生命周期:
raw(原始片段上屏)→streaming(LLM 逐词流入)→completed/failed/superseded
- 候选一过闸门,原始知识库片段立刻可见,此时延迟已经远低于 2 秒
- 同时向"快速模型"(
realtimeModel,默认是 gemini-2.0-flash 级别的小快模型)发起流式请求,按触发类型切换输出指令:提问类建议"给出可引用的具体答案",观点类"呈现支持或反驳的证据" - 合成文本逐词覆盖原始片段;如果 LLM 失败,原始片段保留(状态置为
failed),用户依然有东西可看 - 如果流式期间来了更强的新候选,旧建议被标记
superseded并淡出,新的立即顶上 - 每个建议有稳定 ID,贯穿整个生命周期并落盘(SessionRepository.swift),保证延迟写入日志时不会张冠李戴
浮动的建议面板本身基于一个不抢焦点、屏幕共享中不可见的NSPanel实现(OverlayPanel.swift + SuggestionPanelContent.swift),宽度约 250px,可用Cmd+Shift+O全局热键随时收起——对方在会议里完全看不到它。
2 秒延迟预算是怎么算出来的
把三层串成一条时间线,你就能看清"2 秒"从何而来:
- 0ms 起:某句话定稿(部分假设甚至让第一层提前 2~4 秒就缓存好了检索结果)
- <100ms:本地闸门 + 缓存命中检索 + 节流判定(纯本地计算)
- <200ms:原始 KB 片段已在浮动面板上屏 ✅——用户已看到建议
- 后续 1~3 秒:快速模型流式合成逐词替换原文,建议从"可用"升级为"好用"
对比旧架构"串行 5 级、3 次 LLM 往返、90 秒冷却",新架构把感知延迟和生成成本彻底解耦了:感知延迟由本地层决定,LLM 只是锦上添花。
源码导航:顺着这篇文章读代码
想动手看实现,按依赖顺序读这几个文件效率最高:
- 架构总设计与数据流图:2026-03-26-realtime-suggestions-design.md
- 分任务实施计划:2026-03-26-realtime-suggestions.md
- 三层调度中枢:SuggestionEngine.swift
- 本地闸门与触发识别:RealtimeGate.swift
- 爆发-衰减节流:BurstDecayThrottle.swift
- 指纹缓存(actor 隔离):PreFetchCache.swift
- 上下文包检索:KnowledgeBase.swift
- 提问密度与滚动文本窗口:TranscriptStore.swift
- 部分假设增量输出:StreamingTranscriber.swift
- 面板 UI:SuggestionPanelContent.swift
这套设计给初学者的三点启示
- 先展示、后增强:能用本地数据立刻给的答案,绝不让用户等 AI。原始片段上屏 + 流式升级,是把"AI 慢"变成"AI 锦上添花"的经典手法
- 用启发式守门,用 LLM 增值:毫秒级的关键词/相似度判断负责"要不要说",LLM 负责"说得更好",成本与延迟都可控
- 宁可丢弃、不可迟到:实时场景里,一条 5 秒后才到达的建议价值为零。"浮现、替换、丢弃"三选一无队列,比智能排队更简单也更好用
下次开会时,不妨把自己过往的会议纪要、竞品分析、客户简报丢进一个文件夹,指着它运行 OpenOats——2 秒内,它会开始替你开口。
【免费下载链接】OpenOatsA meeting note-taker that talks back.项目地址: https://gitcode.com/gh_mirrors/op/OpenOats
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考