ChromaFs:把文档检索伪装成文件系统

摘要

ChromaFs 的核心不是“又一种 RAG”,而是把已有的文档索引能力重新包装成 Agent 最擅长使用的接口:文件系统 + Shell 工具。相比传统“query top-k chunk prompt”的标准 RAG,它更强调:

  1. 保留知识结构:把页面视作文件、把站点层级视作目录。
  2. 把检索变成可探索过程:Agent 通过 ls / find / grep / cat / cd 多步探索,而不是一次性赌 top-k。
  3. 复用现有索引基础设施:底层依然可用 Chroma 存 chunk 和元数据,但上层暴露为虚拟文件系统。
  4. 把权限控制前移到路径层:先裁剪目录树,再允许 Agent 探索,天然适合文档分层授权。

对 Agent 时代的 retrieval 来说,这篇文章最重要的启发是:很多场景真正需要的不是更“聪明”的向量召回,而是更“可操作”的信息接口。

来源

背景:为什么标准 RAG 不够

Mintlify 的文档助手最初采用常见 RAG 流程:

  • 把文档切成 chunks
  • 为 chunk 建 embedding
  • 用户 query 向量化
  • top-k 召回相关 chunk
  • 拼上下文给 LLM 生成答案

这个流程在“问一个模糊问题,返回几段相关上下文”时工作良好,但在以下场景容易失效:

1. 答案跨多个页面分布

如果真正答案分散在多个页面、多个章节、甚至需要顺着目录逐层定位,那么一次 top-k 召回很容易丢掉关键上下文。

2. 需要精确匹配而不是语义相似

比如:

  • 某个精确配置项名
  • 某个 API 字段
  • 某段错误码
  • 某个函数名 / 参数名 / 命令行 flag

这类问题更接近 grep,而不是 embedding similarity。

3. 文档原有结构被打散

目录、章节、路径、父子关系本身就是强语义信号。把它们先切碎,再只靠向量找回,本质上是在丢弃一部分人工组织好的知识结构。

ChromaFs 的核心想法

Agent 不一定需要真实文件系统,只需要“文件系统的错觉”。

Mintlify 已经有一套基于 Chroma 的文档存储与检索系统,因此他们没有再为每次会话拉起真实 sandbox,而是在已有数据库之上实现了一个虚拟文件系统:

  • Agent 看到的是目录、文件、路径
  • Agent 调用的是 ls / cd / find / grep / cat
  • 实际执行时,这些操作被翻译为对 Chroma 的元数据查询、chunk 拉取与缓存操作

这相当于把:

“面向 chunk 的检索系统”

改造成:

“面向 agentic exploration 的文件系统接口”

为什么不用真实 sandbox

Mintlify 原文给出的数据很有代表性:

  • 真实 sandbox / clone repo 的 p90 会话创建时间约 46 秒
  • 改为 ChromaFs 后,启动约 100 毫秒
  • 由于复用现有 Chroma 基础设施,边际计算成本接近 0

他们估算:若每月约 85 万次对话,若每次都起轻量沙箱,仅基础设施成本每年就可能超过 7 万美元

因此,ChromaFs 解决的是一个非常实际的工程约束:

  • 前台用户不能等几十秒
  • 纯读场景不值得起完整 VM / sandbox
  • 已有索引基础设施应该被复用

架构拆解

1. 用 just-bash 承接 Shell 语义

Mintlify 基于 Vercel Labs 的 just-bash

  • 它是一个 TypeScript 实现的 bash 解释器
  • 支持 grepcatlsfindcd
  • 暴露可插拔的 IFileSystem 接口

因此 Shell 解析、管道、flag 处理不需要自己从头实现;只要实现 filesystem backend 即可。

2. 预存目录树:__path_tree__

系统在 Chroma collection 中保存一个 gzip 压缩的 JSON 路径树,例如:

{
  "auth/oauth": { "isPublic": true, "groups": [] },
  "auth/api-keys": { "isPublic": true, "groups": [] },
  "internal/billing": { "isPublic": false, "groups": ["admin", "billing"] },
  "api-reference/endpoints/users": { "isPublic": true, "groups": [] }
}

初始化时把它解压到内存,构建两类结构:

  • Set<string>:所有文件路径
  • Map<string, string[]>:目录到 children 的映射

这样:

  • ls
  • cd
  • find

都可以直接在内存中完成,避免每次查询都打到数据库。

3. 文件内容通过 chunk 重组

当 Agent 执行:

cat /auth/oauth.mdx

ChromaFs 会:

  1. 查询 page slug 对应的全部 chunks
  2. chunk_index 排序
  3. 拼接成完整页面
  4. 放入缓存,避免重复读取时再次访问数据库

这一步把“向量检索时期为了 embedding 切碎的内容”,在读取时重新还原成“完整页面”。

4. grep 做两阶段过滤

如果 grep -r 对所有文件都走网络扫描,性能会很差。ChromaFs 的优化是:

阶段 A:数据库粗筛

把 grep 查询翻译为 Chroma 查询:

  • 固定字符串 $contains
  • 正则模式 $regex

目标是找到“可能命中”的文件集合。

阶段 B:内存精筛

对粗筛得到的文件:

  • bulk prefetch 相关 chunks 到 Redis / cache
  • 再交给 just-bash 在内存中做真正的 grep 匹配

也就是说,数据库负责 缩小候选集,bash 负责 忠实执行 grep 语义

这个思路非常关键:

不要试图让数据库完整模拟 grep;让数据库做 coarse filter,让 shell/runtime 做 fine filter。

5. 权限控制在“构建树”时完成

路径树中附带:

  • isPublic
  • groups

系统先根据用户 token 与组权限裁剪目录树,再允许 Agent 访问。

这样带来的好处是:

  • Agent 根本“看不见”无权限路径
  • 不只是不能读,而且不能引用、不能猜路径
  • 比在真实 sandbox 里维护 Linux 用户、组、chmod 或不同镜像简单很多

这个设计为什么适合 Agent

1. 它符合 LLM 的工具使用偏好

现代 agent 对以下接口天然友好:

  • 文件
  • 路径
  • shell 命令
  • 分步观察-行动循环

因为训练数据里本来就大量包含代码库、终端操作、目录浏览等模式。相比“请从 top-k chunk 里猜答案”,ls -> find -> grep -> cat 更接近 LLM 已熟悉的工作流。

2. 它把检索从“一次性召回”改成“可回溯探索”

传统 RAG 往往是:

query -> retrieve -> generate

ChromaFs 对应的是:

goal -> ls/find -> grep -> cat -> follow links/paths -> refine search -> answer

这是一个更接近 agentic workflow 的闭环,允许:

  • 多步定位
  • 中途修正
  • 先宽后窄
  • 结合结构和内容信号

3. 它天然更可解释、更易调试

如果回答错了,可以直接追踪:

  • 它看了哪些路径
  • grep 命中了哪些文件
  • cat 读了哪些页面
  • 哪一步过滤掉了关键内容

比起“embedding 为什么没召回来这个 chunk”,可解释性更强。

4. 它复用了已有存储,而不是推倒重来

ChromaFs 并不是“抛弃向量数据库”,而是:

  • 继续使用 Chroma 存 chunk / 元数据
  • 继续利用现有 indexing pipeline
  • 只是在 retrieval interface 层换了抽象

这说明一个重要工程原则:

真正要替换的往往不是底层存储,而是面向 Agent 的交互接口。

对 RAG 的真正修正

这篇文章最值得记住的一点是:

RAG != 向量数据库 top-k chunk retrieval

RAG 里的 “R” 是 Retrieval,检索手段完全可以多样化:

  • 向量搜索:处理语义模糊性
  • 全文搜索 / grep:处理精确匹配
  • SQL:处理结构化过滤
  • 图查询:处理关系遍历
  • 文件系统遍历:处理层级结构与探索式导航

因此更合理的看法不是“RAG 已死”,而是:

单一的、扁平化的向量召回,不再足以支撑 Agent 时代的复杂 retrieval。

我学到的设计原则

1. 先设计信息接口,再设计召回算法

如果使用者是 Agent,优先问:

  • 它最容易用什么接口探索?
  • 它需要一步拿答案,还是多步查证?
  • 它是否需要精确字符串匹配?

很多知识库场景中,答案是:

  • 目录 + 文件 + grep 比单纯 top-k 更自然

2. 保留文档/代码原有结构是高价值信号

不要轻易把:

  • 目录结构
  • 页面边界
  • 文件边界
  • 章节顺序
  • 权限边界

全部打平为 chunk。即使要切 chunk,也应该保留可回溯到 page/path 的结构信息。

3. coarse-to-fine 检索是高性价比套路

ChromaFs 的 grep 优化是一个非常通用的模式:

  • 先用廉价索引缩小候选
  • 再用昂贵但精确的执行器完成最终判定

这个模式可以迁移到:

  • SQL + rerank
  • inverted index + model reader
  • metadata filter + full document reconstruction
  • vector recall + deterministic verifier

4. 读多写少场景非常适合“只读虚拟系统”

如果目标数据是:

  • 文档站
  • 知识库
  • 代码快照
  • API 参考

那只读虚拟文件系统几乎天然成立:

  • 无需 session cleanup
  • 无并发写冲突
  • 无状态更容易扩缩容
  • 安全模型更简单

5. 权限应该尽量前置到“可见性”层,而不是只做读时拒绝

“用户看不见路径”通常比“读文件时报权限错误”更稳健,因为它减少了:

  • prompt injection 猜路径
  • side channel 信息泄露
  • 工具调用级试探

局限与边界

ChromaFs 不是所有 retrieval 场景的通用答案。

更适合的场景

  • 文档站 / 知识库 / API 文档
  • 明确的页面边界和目录层级
  • 读多写少
  • 需要 grep / 精确匹配
  • Agent 允许多步探索

不那么适合的场景

  • 高度非结构化语料
  • 需要跨文档深层语义聚合,但文件边界弱
  • heavily personalized dynamic content
  • 强写入型交互式工作流

在这些情况下,向量召回、图检索、SQL、甚至 workflow memory 仍然重要。

对 Agent / Memory 系统的启发

对长期记忆或助手知识库来说,这篇文章带来的启发非常直接:

1. 知识库应首先是“可遍历”的,而不只是“可召回”的

好的知识库不只是方便 embedding,而是要方便:

  • 看目录
  • 找主题
  • 精确搜关键词
  • 读原文
  • 追溯来源

2. page/path 应该成为一级实体

相比只存 chunk,应该显式保存:

  • path
  • page
  • section
  • chunk_index
  • visibility / group

这样未来既能走向量检索,也能走虚拟文件系统或分层检索。

3. Agentic retrieval 往往比一次性 context stuffing 更稳

不要急着把“最像的 20 个 chunk”全塞给模型。 更稳的方式往往是:

  • 先探索
  • 再读取
  • 再压缩
  • 最后回答

4. 知识工程会重新重要起来

目录设计、命名规范、边界划分、权限模型、文档组织方式,在 Agent 时代不是次要问题,而是 retrieval 质量的重要组成部分。

可迁移到自己系统里的实现清单

如果以后在自己的 agent / memory 系统里借鉴 ChromaFs,我会优先考虑:

  1. 保留 page/path 级索引
    • chunk 不能脱离页面边界
  2. 单独构建 path tree
    • 目录树要能独立缓存
  3. 工具接口优先提供 ls/find/grep/cat 语义
    • 不只提供 search(query)
  4. 对 grep / 全文搜索采用 coarse-to-fine
    • metadata/inverted index 初筛
    • 原文精筛
  5. 把权限做成路径可见性过滤
    • 不是最后一步才 deny
  6. 只读优先
    • 先把读路径优化到极致,再考虑写入
  7. 多检索器并存
    • filesystem / full-text / vector / SQL 各司其职

一句话结论

ChromaFs 的本质不是“用文件系统替代数据库”,而是“用 Agent 更擅长的文件系统接口,重组底层数据库能力”。

它提醒我:在 Agent 时代,最有效的 retrieval 不一定是最花哨的,而往往是最符合工具使用习惯、最保留信息结构、最容易解释和调试的那一种。

相关主题

  • topics/ 下后续可继续补:Agentic Retrieval、RAG 分层架构、知识库信息架构、Memory filesystem abstraction