关联主题:: 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 对应的所有向量 ID
  • model:当前 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

三层节省机制:

  1. ChromaDB 本地搜索:零 Token 消耗
  2. 预览截断:搜索只返回 300-400 字,不是全文
  3. 按需拉取:只有判定为强相关的才拉全文

与现有 Claude Code CFlow Skills 的互补关系

在开发 cflow-brain 之前,我已经有三个 CC CFlow Skills:

Skill定位交互方式搜索类型
cflow-knowledge-connector日报→知识关联提取日报 CFlow→推荐关联文档→生成报告基于 learning-data.json
cflow-ai-extractorAI 标签过滤提取日报中 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