# 策展 · X (Twitter) 🔥

> 📖 本站完整內容索引（documentation index）：[llms.txt](/llms.txt)

> 作者：Dens Sumesh (@densumesh) · 平台：X (Twitter) · 日期：2026-04-04

> 原始來源：https://x.com/densumesh/status/2039765361533637016

## 中文摘要

# 為 Mintlify 的 AI Assistant 構建虛擬檔案系統

RAG（檢索增強生成）很棒，直到它不再適用為止。

我們的 Assistant 只能檢索與查詢相符的文字區塊。如果答案分散在多個頁面中，或者使用者需要精確的語法卻沒有出現在前 K 個結果中，它就會卡住。我們希望它能像探索程式庫一樣探索文件。

Agent 正逐漸將檔案系統作為其主要介面，因為 grep、cat、ls 和 find 就是 Agent 所需的一切。如果每個文件頁面都是一個檔案，而每個章節都是一個目錄，那麼 Agent 就可以自行搜尋精確的字串、閱讀完整頁面並遍歷整個結構。我們只需要一個能反映即時文件網站的檔案系統。

## 容器瓶頸

最顯而易見的做法是直接給 Agent 一個真實的檔案系統。大多數解決方案是透過啟動一個隔離的「沙盒」並複製儲存庫來實現。我們已經在非同步背景 Agent 中使用了沙盒，那裡的延遲問題可以忽略不計，但對於使用者正盯著載入轉圈圈的前端 Assistant 來說，這種方法完全行不通。我們 p90 的工作階段建立時間（包括 GitHub clone 和其他設定）大約是 46 秒。

除了延遲之外，為閱讀靜態文件而配置專用的微型虛擬機（micro-VM）會帶來沉重的基礎設施費用。

以每月 850,000 次對話計算，即使是最小的配置（1 vCPU、2 GiB 記憶、5 分鐘工作階段壽命），根據 Daytona 的每秒沙盒定價（每 vCPU 每小時 $0.0504，每 GiB 記憶每小時 $0.0162），我們每年的成本將超過 70,000 美元。更長的工作階段時間會使成本翻倍。（這是基於純粹天真的做法，真正的生產環境工作流程可能會有預熱池和容器共享，但重點依然存在。）

我們需要檔案系統的工作流程既即時又便宜，這意味著必須重新思考檔案系統本身。

## 偽造一個 Shell

Agent 不需要真實的檔案系統；它只需要一個檔案系統的幻覺。我們的文件已經被索引、分塊並儲存在 Chroma 資料庫中以支援搜尋功能，因此我們構建了 ChromaFs：一個虛擬檔案系統，它會攔截 UNIX 指令並將其轉換為針對同一個資料庫的查詢。工作階段建立時間從約 46 秒縮短至約 100 毫秒，且由於 ChromaFs 重用了我們已經付費的基礎設施，因此每次對話的邊際運算成本為零。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1775303873861-diaHE3LCGwacAA9B0jpg.jpg)

ChromaFs 是基於 Vercel Labs 的 just-bash 構建的（向 Malte 致敬！），這是一個支援 grep、cat、ls、find、cd 等功能的 TypeScript 版 bash 重新實作。just-bash 公開了一個可插拔的 IFileSystem 介面，因此它處理了所有的解析、管道傳輸和旗標邏輯，而 ChromaFs 則將每個底層檔案系統呼叫轉換為 Chroma 查詢。

```typescript
export class ChromaFs implements IFileSystem {
  private files = new Set<string>();
  private dirs = new Map<string, string[]>();

  async readFile(path: string): Promise<string> {
     this.assertInit();
     const normalized = normalizePath(path);

    // Serve from cache or fetch from Chroma
    const slug = normalized.replace(/\\.mdx$/, '').slice(1);

    // Pages are chunked in Chroma. Reassemble them on the fly:
    const results = await this.collection.get<ChunkMetadata>({
      where: { page: slug },
      include: [IncludeEnum.documents, IncludeEnum.metadatas],
    });

    const chunks = results.ids
      .map((id, i) => ({
        document: results.documents[i] ?? '',
        chunkIndex: parseInt(String(results.metadatas[i]?.chunk_index ?? 0), 10),
      }))
      .sort((a, b) => a.chunkIndex - b.chunkIndex);

    return chunks.map((c) => c.document).join('');

  }

  // Enforce completely stateless, read-only interaction
  async writeFile(): Promise<void> { throw erofs(); }
  async appendFile(): Promise<void> { throw erofs(); }
  async mkdir(): Promise<void> { throw erofs(); }
  async rm(): Promise<void> { throw erofs(); }
}
```

## 運作原理

引導目錄樹

ChromaFs 需要在 Agent 執行任何指令之前知道有哪些檔案存在。我們將整個檔案樹作為一個經過 gzip 壓縮的 JSON 文件（`__path_tree__`）儲存在 Chroma 集合中：

```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[]>`。

一旦建立完成，ls、cd 和 find 就能在本地記憶中解析，無需任何網路呼叫。樹狀結構會被快取，因此同一個網站的後續工作階段完全跳過了 Chroma 的獲取過程。

存取控制

請注意路徑樹中的 `isPublic` 和 `groups` 欄位。在建立檔案樹之前，ChromaFs 會根據目前使用者的權限修剪檔案樹，並將相符的篩選器套用到所有後續的 Chroma 查詢中。

在真實的沙盒中，這種層級的個別使用者存取控制需要管理 Linux 使用者群組、chmod 權限，或為每個客戶層級維護隔離的容器映像檔。而在 ChromaFs 中，這只是在執行 `buildFileTree` 之前幾行的篩選程式碼。

從區塊重組頁面

Chroma 中的頁面為了嵌入而被分割成區塊，因此當 Agent 執行 `cat /auth/oauth.mdx` 時，ChromaFs 會獲取所有具有相符頁面 slug 的區塊，按 `chunk_index` 排序，並將它們合併成完整的頁面。結果會被快取，因此在 grep 工作流程中重複讀取時，永遠不會兩次存取資料庫。

並非每個檔案都需要存在於 Chroma 中。我們註冊了延遲檔案指標（lazy file pointers），這些指標會在存取時解析，用於儲存在客戶 S3 儲存貯體中的大型 OpenAPI 規格。Agent 在 `/api-specs/` 中看到 `v2.json`，但內容只有在執行 `cat` 時才會獲取。

每個寫入操作都會拋出 EROFS（唯讀檔案系統）錯誤。Agent 可以自由探索，但永遠無法修改文件，這使得系統保持無狀態，無需清理工作階段，也不存在一個 Agent 破壞另一個 Agent 視圖的風險。

## 優化 Grep

cat 和 ls 的虛擬化很簡單，但如果 `grep -r` 天真地透過網路掃描每個檔案，速度會太慢。我們攔截了 just-bash 的 grep，使用 `yargs-parser` 解析旗標，並將其轉換為 Chroma 查詢（固定字串使用 `$contains`，模式使用 `$regex`）。

Chroma 作為一個粗略的篩選器，識別出哪些檔案可能包含搜尋結果，我們將這些相符的區塊批次預取（bulkPrefetch）到 Redis 快取中。隨後，我們重寫 grep 指令，使其僅針對相符的檔案，並將其交回給 just-bash 進行記憶內的精確篩選執行，這意味著大型遞迴查詢可以在毫秒內完成。

```typescript
const chromaFilter = toChromaFilter(
  scannedArgs.patterns,
  scannedArgs.fixedStrings,
  scannedArgs.ignoreCase
);

// 1. Coarse Filter: Ask Chroma for slugs matching the string/regex
const matchedSlugs = await chromaFs.findMatchingFiles(chromaFilter, slugsUnderDirs);
if (matchedSlugs.length === 0) return { stdout: ‘’, exitCode: 1 };

// 2. Prefetch: Pull the chunked files into local cache concurrently
await chromaFs.bulkPrefetch(matchedSlugs);

// 3. Fine Filter: Narrow the arguments to ONLY the resolved hits
const matchedPaths = matchedSlugs.map((s) => ‘/’ + s + ‘.mdx’);
const narrowedArgs = [...args, ...matchedPaths]; // e.g. ["-i", "OAuth", "/docs/auth.mdx"]

// 4. Exec: Let the in-memory RegExp engine format the final output
return execBuiltin(narrowedArgs, ctx);
```

## 結論

ChromaFs 為每天超過 30,000 次對話、數十萬名使用者的文件 Assistant 提供支援。透過以我們現有的 Chroma 資料庫之上的虛擬檔案系統取代沙盒，我們實現了即時的工作階段建立、零邊際運算成本，並在無需任何新基礎設施的情況下內建了 RBAC（角色存取控制）。

歡迎在任何 Mintlify 文件網站或 mintlify.com/docs 上試用。

[閱讀完整文章：https://www.mintlify.com/blog/how-we-built-a-virtual-filesystem-for-our-assistant]

## 標籤

RAG, Agent, 其他, Mintlify
