继续复盘 Synapse 的真实实现:笔记后台处理、关键词搜索、Qdrant note 级向量、对话上下文拼装、citations,以及下一步要补的 chunk 与重排。
2026年5月13日 · 13 min read
Reading route
围绕隐私优先的 AI 记忆、RAG、模型路由和长期上下文治理。
Focus
Next
接下来我会写:离线优先同步在个人记忆系统里的取舍
做 Synapse 之后,我对“AI 记忆系统”的理解变得更具体了。
它不是一个会聊天的笔记软件,也不是把所有内容塞进 prompt 里让模型自由发挥。真正有价值的地方,是从一条笔记出发,经过处理、检索、摘录、引用,最后让回答能回到证据上。
如果回答不能追溯来源,它就只是一次生成;如果能看到它依据了哪几条笔记、每条笔记贡献了哪段上下文,它才更像一个可长期信任的记忆系统。
这篇继续写 Synapse 当前已经落地的检索与对话链路。重点不是把它讲成完美架构,而是把代码里真实存在的东西讲清楚:做到了什么,边界在哪里,下一步应该补什么。
Synapse 里的笔记不是写入数据库就结束。创建或更新后,API 层会通过 BackgroundTasks 触发 process_note_background,做几件后台工作:
NoteCreate / NoteUpdate
-> process_note_background
-> summarize
-> extract_entities
-> embed_text(title + content)
-> upsert_note_vector
-> find_related_pairs这里有两个细节很重要。
第一个细节是隐私边界。process_note_background 会先检查笔记是否存在、是否属于当前用户、是否是私有笔记。私有笔记不会进入这条 AI 处理链路。
第二个细节是时间语义。后台处理会改 processing_state,但代码里刻意保留原来的 updated_at,避免“AI 后处理”污染用户理解里的编辑时间。这个点很小,但它决定了时间线和排序是否可信。
笔记后台处理里已经落地的内容
调用 AIService.summarize,结果写回 note.summary。
调用 AIService.extract_entities,最多处理 30 个实体,并写入 Entity 与 NoteEntity。
对 title + content 做 embedding,再写入 Qdrant。
处理完成后用相似搜索建立 NoteRelationship,让笔记之间能长出关系。
这条链路让我意识到,AI 记忆系统里的“智能”不是某一次回答,而是每条数据进入系统时就开始被组织。
当前 Synapse 的向量存储在 qdrant_store.py。它做得很克制:如果没有配置 qdrant_url,整个向量能力会 no-op;如果 embedding 维度和配置不一致,也会跳过写入。
真正写入 Qdrant 的 payload 主要是:
note_id
user_id
title向量本身来自整条笔记的 title + content。搜索时会用 user_id filter 做隔离,只查当前用户的向量点。
这意味着现在的 Synapse 还不是成熟的 chunk 级 RAG。它更准确的状态是:已经有 note 级语义召回,并且能基于召回结果组织对话上下文。
这个边界必须说清楚。因为如果笔记很短,note 级向量已经够用;但如果笔记很长,相关信息可能只是其中一段,整篇笔记的向量会变得“平均”。这时召回还能命中大方向,但不一定能精确命中答案所在的位置。
search_service.py 里现在有三个核心函数:关键词搜索、语义搜索、混合搜索。
| 能力 | 当前实现 | 适合什么场景 |
|---|---|---|
search_keyword | 标题和正文 ilike,支持时间与标签过滤,上限 50 条 | 确定词、项目名、明确短语 |
search_semantic | 查询句生成 embedding,Qdrant topK,相同 note 去重,再过滤已删除笔记 | 同义表达、概念问题、模糊记忆 |
search_hybrid | 关键词结果与向量结果按 note_id 合并,再按 score 排序 | 兼顾确定性和语义召回 |
这里最值得继续打磨的是 search_hybrid。
现在关键词命中的 score 是固定的 1.0,向量命中使用 Qdrant 返回的相似度分数。它能解决“两个入口都能返回结果”的问题,但还不是严格意义上的重排系统。更理想的做法,是把关键词、向量相似度、时间、标签、实体、笔记长度和用户行为一起变成一套可解释的 ranking。
不过我不急着一步做到复杂。对个人记忆系统来说,先把入口拆清楚,比一开始就上重排模型更重要。
conversation_service.py 是这条链路里最像“产品能力”的地方。
用户发来一个问题后,它不是直接调用模型,而是先做语义检索:
query
-> search_semantic(top_k=8)
-> load matched notes
-> excerpt each note around query
-> build citations
-> build prompt
-> AIService.chat当前实现里有几个很具体的参数:
citations,包含 note id、标题、摘录和相关分数。这个设计有一个很实际的好处:回答不是凭空出现的。即使模型说错了,也能顺着 citations 回去看,是检索错了、摘录错了,还是生成阶段理解错了。
对话回答的可追溯性
先用用户问题做语义检索,避免模型直接面对全部笔记。
每条笔记只取一个短窗口,控制 prompt 污染和 token 成本。
把 Citation 一起返回给前端,让回答能够回到原始笔记。
多轮追问会带最近历史,但有长度上限,避免旧上下文无限膨胀。
我越来越觉得,citations 不是“展示用的小功能”,而是 RAG 系统的调试接口。没有引用,你只能猜模型为什么这么答;有引用,问题会暴露得很快。
如果语义搜索没有命中,当前代码会退化到取 3 条未删除笔记,再用同样的摘录方式拼上下文。
这个兜底让系统不会立刻断掉,但它也暴露了一个后续要修的点:这里的查询目前只是 .limit(3),没有明确排序。也就是说,它能兜底,但还不够稳定。
更合理的版本应该是:
semantic no hit
-> fallback to recently updated notes
-> or fallback to pinned / active project notes
-> or ask the user to narrow the question我更倾向把兜底做成产品逻辑,而不是技术上的“随便拿几条”。因为在记忆系统里,没有证据时直接说“不确定”,往往比硬凑一个上下文更可信。
如果只看现在的效果,Synapse 已经能做到:笔记进入系统后被摘要、抽实体、写向量;用户提问时先检索,再拼上下文,最后返回带引用的回答。
但如果从工程质量看,边界也很清楚:
我喜欢把这些边界写出来。因为真实系统不是靠“看起来完整”变好的,而是靠知道下一步该补哪里。
如果继续推进 Synapse 的 RAG,我会先补四件事。
第一,建立 chunk 层。每条笔记仍然保留 note 级向量,但同时把正文按语义段落切成 chunk,payload 里保存 note_id、chunk_id、段落序号、标题路径和字符范围。
第二,引入重排。第一阶段用向量召回更多候选,第二阶段再按问题和 chunk 内容做 rerank,减少“看起来相似但不能回答”的片段。
第三,做证据压缩。不是把所有摘录直接塞给模型,而是先把摘录整理成更短的 evidence block,再让模型基于 evidence 回答。
第四,让 citations 更像产品。引用不只是列在回答下面,而是可以点击、定位、展开上下文,必要时显示“这句话来自哪条笔记的哪一段”。
下一版检索链路
负责粗粒度召回和关联发现。
负责精确定位答案所在片段。
负责从候选片段里选出真正能回答问题的证据。
负责把上下文压缩成模型更容易遵守的材料。
负责把模型回答重新接回原始记忆。
这篇写下来,我反而更确定一件事:Synapse 的核心不是“让模型知道很多”,而是让每一次回答都有来路。
从笔记到向量,从检索到摘录,从上下文到 citations,这条链路看起来不炫,但它决定了一个 AI 记忆系统能不能被长期信任。
模型负责生成,系统负责约束。真正的记忆感,往往就藏在这些约束里。
Keep reading
把 RAG 从 demo 推到可用系统时,最容易踩的 6 个坑:chunk 切分、检索不准、上下文污染、token 预算、本地模型、latency。结合 Synapse 的真实取舍给出可操作解法。
2026年5月9日 · 12 min
结合 Synapse 当前代码,复盘模型路由的真实实现:隐私分级、本地优先阈值、对话云端优先、失败降级、运行时配置、心跳探测和成本记录。
2026年5月12日 · 13 min
我复盘 Synapse 的初衷与架构:从零摩擦输入、自动整理、语义检索、对话式查询,到本地优先的模型路由、隐私边界和数字分身愿景。
2026年4月17日 · 22 min
After reading
这个博客暂时不开放站内评论。文章如果有用,可以先收藏、转发,或者从我的 GitHub 主页继续交流。