关联主题:: cflow(2026.02.04), cflow-语义搜索实现文档(2026.02.27)
同级:: Claude Skills说明(2026.03.23)
下一级::
CFlow-Brain——用Codex开发cflow智能助手Skill
为什么需要这个 Skill?
CFlow 是我用了很久的碎片化笔记工具(魔改版 Memos),两年来积累了上千条微想法、资源链接和技术备忘。但问题来了:
- 搜索太机械:CFlow 自带的关键词搜索只能做字面匹配,搜”观影”找不到写”电影清单”的笔记
- 找不到关联:一条笔记可能和另外三条潜在相关,但没有办法自动发现
- 每次要单独打开:想查 CFlow 里的东西,必须切换到浏览器或 App,打断了在 Obsidian/Claude Code 里的工作流
我真正想要的是:像和自己的”第二大脑”对话一样,用自然语言问问题,让 AI 帮我在 CFlow 里找到答案,还要给出带引用的可靠回复。
这就是 cflow-brain 要做的事。
架构总览
cflow-brain 第一版可以理解为三层协作系统:Skill 负责检索策略,MCP 负责连接工具,ChromaDB 负责向量搜索。
但在 2026-06-06 的二次迭代后,它更准确地说是一个 Skill 入口 + Brain CLI 引擎 + MCP 工具层 + 分块向量索引 的组合。
这点很重要:最终迭代的是 Skill,但不是只改了一份 SKILL.md。 Skill 负责告诉 Codex 什么时候、如何调用 cflow-brain;真正稳定执行同步、检索、重排、回源引用的是新增的 cflow_brain.py CLI。MCP 则把这个 CLI 能力暴露给 Claude Code / Codex 这类对话式工具。
┌──────────────────────────────────────────────────────────────────────────┐
│ CFlow-Brain 第一版三层架构示意 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ① Skill 层 (SKILL.md + RAG Playbook) │ │
│ │ · 查询扩展:1 个词 → 8-20 个多语言变体 │ │
│ │ · 检索编排:精确 + 语义双通道并行 │ │
│ │ · 答案合成:结论 → 主题聚类 → 引用来源 → 检索覆盖 │ │
│ └───────────────────────────┬─────────────────────────────────────┘ │
│ │ 调用 MCP tools │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ② MCP 层 (cflow_mcp_server.py) │ │
│ │ · search_cflow_notes → ChromaDB 关键词扫描 │ │
│ │ · semantic_search_cflow → SentenceTransformer 语义搜索 │ │
│ │ · get_cflow_note → HTTP API 按 ID 精准拉全文 │ │
│ │ · auto_sync_if_needed → 自动增量同步索引 │ │
│ └───────────────────────────┬─────────────────────────────────────┘ │
│ │ 本地读写 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ③ 向量引擎 (chroma_db/ + SentenceTransformer) │ │
│ │ · 模型:paraphrase-multilingual-MiniLM-L12-v2 (384维) │ │
│ │ · 存储:每条 memo 存 id + 原文 + 向量 + 元数据 │ │
│ │ · 搜索:余弦距离排序,本地磁盘 I/O,零 Token 消耗 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ④ 数据源:CFlow API (http://192.168.10.2:5236) │ │
│ │ · /api/v1/memo?limit=9999 → 全量拉取 (索引构建) │ │
│ │ · /api/v1/memo/{id} → 按需精准拉全文 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
当前版本的真实调用链更像这样:
用户自然语言提问
例如:"[$cflow-brain] 帮我找观影相关资源"
│
▼
Skill 层
SKILL.md 判断这是 cflow 知识库检索任务
优先选择 brain_search_cflow
│
▼
MCP 层
cflow_mcp_server.py
├─ brain_search_cflow
├─ cflow_brain_status
├─ semantic_search_cflow
└─ get_cflow_note
│
▼
Brain CLI
cflow_brain.py
├─ sync :quick sync / full rebuild
├─ status :索引健康检查
└─ search :混合检索、轻量 rerank、回源引用
│
▼
分块向量索引
cflow_embed_indexer.py + chroma_db/
├─ paragraph-v1 按段落/滑窗切 chunk
├─ manifest v2 记录 content_hash、chunk_ids、模型和切块版本
└─ ChromaDB 存每个 chunk 的向量和元数据
│
▼
CFlow API
/api/v1/memo
/api/v1/memo/{id}
工作流程
二次迭代后的日常使用方式很简单:我不用手动运行 CLI,只需要在对话里触发 Skill。
[$cflow-brain] 请在 cflow 里找和观影有关的资源,并整理成一篇笔记
或者不显式写 Skill 名也可以:
请根据 cflow 里已有资源,帮我探索一下 NotebookLM 的使用场景,并给出引用
背后的实际流程是:
用户提问
-> Skill 判断任务类型
-> brain_search_cflow / cflow_brain.py search
-> quick sync 检查索引差异
-> 关键词召回 + 向量召回
-> 合并去重
-> 轻量 rerank
-> 按 memo 聚合
-> 回源 CFlow API 获取最新原文
-> 输出 memo ID、链接、摘录、匹配 chunk、检索覆盖
如果需要在终端里手动检查,则可以直接运行:
python3 cflow_brain.py search "观影相关资源"
python3 cflow_brain.py status
python3 cflow_brain.py sync第一步:查询扩展(Query Expansion)
用户说一句模糊的自然语言,Skill 层将其扩展为 8-20 个精确查询词。以”观影”为例:
原始输入:"cflow 里有没有关于观影的内容?"
↓
┌────────────────────────────┐
│ 字面:观影, 看电影, 电影 │
│ 同义:影视, 影院, 片单 │
│ 英文:movie, film │
│ 标签:#观影 #电影 #影视 │
│ 资源:观影网站, 电影资源 │
│ 动作:收藏, 观看, 学习 │
└────────────────────────────┘
这个策略来自 cflow-rag-playbook.md,核心思想是:与其让用户精准描述需求,不如让系统多角度覆盖。
第二步:双通道混合检索
扩展后的查询词分两路并行搜索:
┌──────────────────────┐ ┌──────────────────────────┐
查询词 ───▶│ keyword 通道 │ │ semantic 通道 │
│ ChromaDB $contains │ │ SentenceTransformer │
│ 精确匹配 + 大小写 │ │ 384维向量 → 余弦距离 │
│ 不敏感 + 多变体 │ │ 语义相近但用词不同的笔记 │
└──────────┬───────────┘ └───────────┬──────────────┘
│ │
└──────────┬──────────────────┘
▼
┌─────────────────┐
│ 按 memo ID 去重 │
│ 合并两路结果 │
└─────────┬───────┘
▼
返回:300-400 字预览 + memo ID + 相似度
关键:这一步搜索在本地 ChromaDB 完成,不消耗任何 LLM Token。
第三步:按需精准拉取全文
拿到预览结果后,不是全量拉取全文,而是按规则筛选:
- ✅ 每条精确命中的 memo →
get_cflow_note(id) - ✅ 语义搜索 top-K 中有意义的
- ✅ 出现在多个查询组中的 memo
- ✅ 引用了其他 memo 的(递归拉取父级)
- ❌ 只匹配了一个边缘关键词的 → 跳过
每次 get_cflow_note 是单条 HTTP 请求,精准按 ID 拉取。
第四步:合成答案
按四段式结构组织回复:
1. 结论 → "cflow 中有 12 条相关内容,其中 5 条直接相关"
2. 按主题整理 → 聚类展示 + [#123] 引用标注
3. 引用来源 → ID + 摘录 + 相关度(强/中/弱)
4. 检索覆盖 → 用了哪些查询词,还有哪些盲区
向量搜索是怎么工作的?
这是整个系统最核心的部分,值得展开讲。
离线索引(一次性操作)
# cflow_embed_indexer.py 的核心逻辑(简化)
# 1. 从 CFlow API 拉取全量笔记
memos = fetch_all_memos() # GET /api/v1/memo?limit=9999
# 2. 加载向量模型(384维,支持中英文多语言)
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
# 3. 逐条编码存入 ChromaDB
for memo in memos:
vector = model.encode(memo.content) # 文本 → 384维向量
collection.add(
id=memo.id,
document=memo.content, # 存原文
embedding=vector, # 存向量
metadata={"created_at": ..., "tags": ...}
)索引存储在本地 chroma_db/ 目录,不需要联网,不依赖外部向量数据库服务。
上面是第一版的简化逻辑。二次迭代后,索引粒度从“一张 memo 一个向量”升级成了 一张 memo 多个 chunk 向量:
一张 memo
-> 按空行/段落切块
-> 超长段落按字符滑窗切分
-> 每个 chunk 独立生成向量
-> chunk_id 形如:memo_id:chunk:index
-> metadata 记录 memo_id、chunk_index、chunk_count、content_hash
这样搜索“观影资源”时,系统可以只命中某个影视网站段落,而不是把整张可能很长、很杂的 memo 都当作同等相关。
在线搜索(每次查询)
用户查询:"我想找和观影网站、电影资源相关的笔记"
│
▼
SentenceTransformer.encode("我想找和观影网站...")
│
▼
┌─────────────────────────────────┐
│ 384维向量 [0.023, -0.451, ..] │
└─────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ ChromaDB.query() 余弦距离排序 │
│ │
│ memo_456: cos_sim=0.89 "观影网站合集" │
│ memo_123: cos_sim=0.82 "最近看的电影" │
│ memo_789: cos_sim=0.71 "豆瓣片单备份" │
│ memo_234: cos_sim=0.45 "买电影票" │
│ ... │
│ │
│ 取 top_k 返回 │
└─────────────────────────────────────────┘
自动增量同步
第一版的同步逻辑是检查 CFlow 总数和 ChromaDB 索引数:
if CFlow总数 - 已索引数 > 10:
→ 自动运行 cflow_embed_indexer.py 增量补全
→ 只索引新增的笔记(跳过已有 ID)
→ 无需手动维护
二次迭代后,这个逻辑更严格了。索引器会维护一个本地清单:
chroma_db/cflow_index_manifest.json
里面记录每张 memo 的:
content_hash:内容、标签、可见性的哈希chunk_ids:这张 memo 对应的所有向量 IDmodel:当前 embedding 模型chunker_version:当前切块策略版本chunk_size/chunk_overlap:切块参数indexed_at:索引时间
每次 quick sync 会生成差异计划:
CFlow 当前 memo
+ 本地 manifest
+ ChromaDB 已有向量 ID
│
▼
差异计划
├─ 新增 memo:生成新 chunk 向量
├─ 修改 memo:删除旧 chunk,重新生成
├─ 删除 memo:删除对应向量和 manifest 记录
├─ 模型/切块策略变化:重新生成
└─ 多余旧向量:清理
如果没有变化,quick sync 不会加载 embedding 模型,只做 API 拉取、hash 对比和 Chroma 状态检查。在当前 2800 多条 CFlow 卡片规模下,无变化检查通常是 1-4 秒。
这也回答了“向量库如何保证正确”的问题:每次都会检查全库是否有差异,但只对有差异的卡片重新生成向量。
Token 消耗分析
这是设计时重点考虑的问题。每次对话都全量拉取 CFlow(上千条笔记)是不现实的。
实际 Token 消耗(以搜索”观影”为例):
┌───────────────────────────────────────────────┐
│ 阶段 数据量 Token 消耗 │
│ ───────────────────────────────────────── │
│ 查询扩展(Skill 层) 15 个查询词 ~500 │
│ ChromaDB 扫描 本地磁盘 0 │
│ MCP 返回预览 12条×300字 ~1,500 │
│ get_cflow_note 拉全文 5条×200字 ~1,000 │
│ AI 合成答案 - ~2,500 │
│ ───────────────────────────────────────── │
│ 总计 ~5,500 │
└───────────────────────────────────────────────┘
对比:如果每次都把 CFlow 全库交给 LLM 阅读(假设 2000 条 × 100 字 = 200K 字),需要约 50K+ tokens。cflow-brain 的设计节省了约 10 倍。
需要注意:quick sync 仍然会通过本地 API 拉取 CFlow 当前 memo 来做 hash 对比,但这是本地程序处理,不进入 LLM 上下文,所以 消耗的是本地计算和局域网请求,不是 Token。
三层节省机制:
- ChromaDB 本地搜索:零 Token 消耗
- 预览截断:搜索只返回 300-400 字,不是全文
- 按需拉取:只有判定为强相关的才拉全文
与现有 Claude Code CFlow Skills 的互补关系
在开发 cflow-brain 之前,我已经有三个 CC CFlow Skills:
| Skill | 定位 | 交互方式 | 搜索类型 |
|---|---|---|---|
| cflow-knowledge-connector | 日报→知识关联 | 提取日报 CFlow→推荐关联文档→生成报告 | 基于 learning-data.json |
| cflow-ai-extractor | AI 标签过滤 | 提取日报中 AI 标签内容→整理到博客 | 标签匹配 |
| cflow-resource-extractor | 资源 标签过滤 | 提取日报中 资源 标签内容→整理到博客 | 标签匹配 |
| cflow-brain 🆕 | 对话式知识库检索 | 自然语言问答→引用回复 | 精确+语义混合 |
互补关系:
cflow-knowledge-connector(分析 + 归档)
↓ 用户决定哪些要归档
INKP Archive(正式知识归档)
↓ 写入 Obsidian
cflow-brain(实时问答 + 探索)
↓ 直接在对话中检索 CFlow
即时回复,不需要走归档流程
简单说:前三者偏”处理”和”归档”,cflow-brain 偏”对话”和”探索”。
开发过程:用 Codex CLI 从零搭建
整个 Skill 是用 Codex CLI(OpenAI 的编码助手)开发的,放在 ~/.codex/skills/cflow-brain/ 目录下。
文件结构:
cflow-brain/
├── SKILL.md # 主 Skill 定义:工具优先级、检索策略、回答规则
├── references/
│ └── cflow-rag-playbook.md # RAG 检索策略详解(查询扩展、匹配规则、失败处理)
└── agents/
└── openai.yaml # Codex agent 配置(display_name:"cflow智能助手")
二次迭代后,Skill 目录仍然是入口,但真正的工程能力主要放在 cflow 项目里:
cflow_brain.py # 统一 CLI:sync / status / search
cflow_embed_indexer.py # quick sync、分块、embedding、manifest
cflow_mcp_server.py # MCP 工具:brain_search_cflow、cflow_brain_status 等
chroma_db/ # ChromaDB 向量库 + cflow_index_manifest.json
CFLOW_MCP_README.md # MCP、CLI、索引机制说明
SKILL.md 定义了 Skill 的核心行为:
- 工具优先级:MCP 工具优先,不可用时的降级路径
- 检索策略:至少两轮(精确轮 + 语义轮),迭代到边际收益低
- 回答规则:基于 memo 的真实内容,不编造来源;每条引用标注 memo ID
- 输出格式:结论 → 按主题整理 → 引用来源 → 检索覆盖
cflow-rag-playbook.md 是检索策略的”操作手册”,包含:
- 查询扩展规则:一个字面词 → 8-20 个变体(同义词、英文、标签、资源意图、动作意图)
- 相关性标签体系:强相关 / 相关 / 弱相关 / 排除
- 三种合成模式:资源盘点、内容匹配、课题探索
- 失败处理:索引过期、语义不可用、无匹配结果
关键设计决策
1. 为什么用 ChromaDB 而不是直接调 CFlow API 搜索?
CFlow API 的搜索只能在内容里做简单的子字符串匹配,搜”看电影”找不到”观影清单”,搜”AI”找不到”大模型”。ChromaDB + 语义搜索解决了”不同用词但相同含义”的同义词问题。
2. 为什么模型选择 MiniLM 而不是更大的模型?
paraphrase-multilingual-MiniLM-L12-v2 只有 118M 参数,384 维向量,在 Mac 上加载只需 500MB 内存。对比更大的模型(如 text2vec-large-chinese 的 1024维),MiniLM 速度快 3-5 倍,对碎片化笔记(平均 100 字)的语义区分度完全够用。
3. 为什么用懒加载而不是常驻内存?
模型 ~500MB,如果每次 Claude Code 启动都加载,会占用大量内存。MCP 服务器只在首次调用语义搜索时才加载模型(_embed_model 全局变量懒初始化),之后整个 session 复用,session 结束后释放。
4. 为什么要”按需拉全文”而不是”搜索结果直接返回全文”?
搜索结果如果每条都返回全文,10 条结果就可能塞满 Claude Code 的上下文窗口。300 字的预览足以让 AI 判断相关性,然后只拉真正需要的几条全文——这是典型的 两阶段检索 设计。
5. 为什么新增 CLI,而不是只迭代 Skill?
Skill 更像“使用说明”和“行为策略”,适合告诉 Codex:什么时候触发、优先用哪个工具、答案怎么组织。但同步、分块、向量检索、重排、状态检查这些动作需要稳定、可测试、可复用的执行入口。
所以二次迭代新增了 cflow_brain.py:
python3 cflow_brain.py sync
python3 cflow_brain.py status
python3 cflow_brain.py search "观影相关资源"
这样做的好处是:
- 对话里可以由 Skill 自动调用
- 终端里也可以手动排查
- MCP 工具可以直接复用
- 后续如果换模型、换 rerank、换向量库,不必重写 Skill
6. 向量库是否必要?
对于“精确找某个 URL / 某个标题”,关键词搜索就够了。
但对于下面这些问题,向量库很有价值:
- “观影”可能对应“影视网站”“电影片单”“豆瓣榜单”
- “AI 写作”可能对应“大模型”“提示词”“Claude”“NotebookLM”
- 用户给一段材料,想找 CFlow 里概念相近的旧资源
所以现在采用的是 混合检索:关键词保证精确命中,向量负责模糊召回,rerank 再把更符合意图的内容排到前面。
7. 当前模型够不够?
当前模型是:
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
它不是最强的中文向量模型,但它有几个适合当前场景的优点:
- 支持中英文混合
- 模型小,加载和搜索速度相对快
- 本地运行,不需要外部 API
- 对 CFlow 这种短文本、资源链接、碎片备忘已经够用
如果后续要继续优化,可以考虑两条路:
- 换更强中文 embedding 模型,提高语义召回质量
- 增加 cross-encoder reranker,把 top 50 候选再精排
但这会带来更多模型体积、加载时间和维护成本。当前阶段先用 MiniLM + 关键词 + 轻量 rerank,是一个比较稳的平衡点。
总结
cflow-brain 解决的问题很简单:让碎片化笔记在对话中重新变得可用。 它不替代 CFlow 的界面,也不替代 Obsidian 的归档系统,而是在两者之间补上了”实时探索”这一环。
当前架构(Skill → Brain CLI → MCP → ChromaDB)让每个环节职责清晰,Token 消耗可控,语义搜索让”说不同的话但找同一件事”成为可能。
如果你也有上千条碎片化笔记散落在各个工具里,这套方案或许可以参考:本地向量索引 + MCP 桥接 + Skill 编排,不需要外部 SaaS 或模型 API Key;如果 CFlow 自身需要认证,只要在本地配置 CFLOW_API_TOKEN 即可。
本文对应的 Skill 文件:~/.codex/skills/cflow-brain/SKILL.md
MCP / CLI 服务端:cflow_mcp_server.py + cflow_brain.py + cflow_embed_indexer.py
开发日期:2026-06-05;二次迭代:2026-06-06