# 我們是如何打造知識庫的

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

> 原作者：Cerebras (@cerebras) · 策展與摘要：EasyVibeCoding · 平台：X (Twitter) · 熱度：🔥🔥 · 日期：2026-07-17

> 原始來源：https://x.com/cerebras/status/2077822555159945507

## 中文摘要

# 我們是如何打造知識庫的

作者：@hi_im_isaac_, @learnwdaniel, @gaozenghao
註：完整技術部落格的互動版本請見：https://www.cerebras.ai/blog/how-we-built-our-knowledge-base

我們的員工每天會向內部知識庫提出超過 15,000 個問題。自 3 個月前推出以來，它已成為公司內部採用率最高的工具之一，服務對象包括人類、自動化程式與 Agent。

在 Cerebras，我們的團隊橫跨資料中心維運、晶片設計、硬體、訓練、推論、雲端平台等多個領域。隨著每年有數百名新進員工加入，我們的通訊管道充斥著同樣的問題：

- 「在哪裡可以找到 X？」
- 「誰是 Y 領域的專家？」
- 「Z 是什麼？」

![這是一張展示檢索增強生成（RAG）系統架構的流程圖，從資料來源到最終合成答案的六個層次。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/562bc1d31825b5b0.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">該圖表以層疊方式呈現 RAG 系統的處理流程，由上至下包含以下階段：
1. SOURCES：包含 SLACK、WIKI、CODE、INCIDENTS。
2. DISTILLATION：使用 LLM EXTRACTORS。
3. EMBEDDINGS：使用 PGVECTOR、3072-DIM、HNSW。
4. RETRIEVAL：SIX LISTS IN PARALLEL。
5. FUSION + RERANK：使用 RRF (K=60) -&gt; LLM RERANK。
6. SYNTHESIS：輸出 ANSWER + CITATIONS。</div></details>

我們打造了 Cerebras Knowledge，旨在協助人們將人員與系統連結至有用的資訊。

# 讓資料留在它原本的地方

在組織內部尋找資訊非常困難。資料散落在各種工具中，每隔一季左右，就會有人提出同樣絕妙的解決方案：將所有內容記錄在同一個平台，讓所有資訊集中一處。當然，這種「單一事實來源」（single source of truth）的夢想在實務上鮮少成功。

資訊總是在最方便、最符合人體工學的地方產生：文件中的建議編輯、Slack 中的討論串、GitHub 中的程式碼參考，以及 Jira 中的狀態中繼資料。這些平台是為特定領域量身打造的，並經過多年的產品工程與分析優化。在 Google Docs 中討論 Pull Request 會是一場災難。

因此，我們著手設計一套對現有行為變動最小的系統。在資料收集方面，這意味著直接從各個平台提取資料。

## 知識庫的結構

我們的知識庫提供三項功能：

- 用於收集與儲存內部資料的平台。
- 用於查詢該資料的平台。
- 執行身分驗證與授權，並具備稽核與分析功能的層級。

核心是一個單一的 Postgres 資料表，存放來自多個來源的 embedding、原始摘要與中繼資料。系統持續從全公司攝取資料，並維護一個可隨時查詢的資料儲存庫。

我們需要一個簡單但能處理大多數資料形式的資料介面。同時，我們也希望 Cerebras 的其他開發人員能夠自行建構自訂連接器。最終的結果刻意保持簡單：每個來源（從 Slack 討論串到 netlist）都會進入同一個 embedding 資料表，且該表中的任何內容都能透過同一個介面立即查詢：

![這是一張展示資料整合架構的流程圖，說明如何將多種來源的資料轉換為統一的嵌入（Embeddings）表格，並供查詢介面使用。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/e9076b59f80e8076.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">此圖表展示了一個資料處理流程，將多個資料來源整合至單一嵌入表格中。
左側列出了六個資料來源，並標註「ONE CONNECTOR PER SOURCE」（每個來源對應一個連接器）：
1. SLACK
2. WIKI / CONFLUENCE
3. CODE REPOS
4. NETLISTS
5. PRM DOCS
6. CUSTOM DATABASES

這些來源匯入至中間的「EMBEDDINGS」區塊，該區塊被標註為「ONE EMBEDDINGS TABLE」，內部包含以下欄位：
- DOCUMENT
- EMBEDDING
- METADATA
- SOURCE + TIMESTAMPS

最後，資料流向右側的「QUERY」區塊，上方標註「MCP - WEB UI - AGENTS」。</div></details>

每個資料來源都會定義資料內容、連線方式以及擷取頻率。無論資料來自 Slack、程式碼庫、文件系統還是自訂資料庫，產生的每個 embedding 資料列都遵循相同的介面。

# 我們如何處理非結構化的 Slack 對話

Slack 是我們設計時最關鍵的資料來源，因為公司內最新的工程討論都在這裡發生。

![這是一張展示訊息處理流程的架構圖，描述了從接收 Socket 事件到執行向量同步與更新的邏輯路徑。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/38130aea8ab1ab45.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">該圖表展示了一個系統的處理流程，包含以下節點與路徑：
1. 起始點為「SOCKET EVENT」，進入「ROUTE」判斷節點。
2. 若為「@MENTION / DM」，則導向「BOT REPLY」。
3. 若為「TRACKED CHANNEL」，則進入「REINGEST_THREAD」流程，接著執行「UPSERT THREAD」。
4. 隨後進入「SYNC_WORKER」節點，該節點與「DISTILL」、「THREAD VECTOR」以及「BURST VECTORS」進行互動。
5. 圖表底部標註了「RESET WATERMARKS」。</div></details>

我們最初測試了對原始文字進行簡單的 embedding 是否足夠有效。我們很快發現，單靠向量搜尋不足以匹配所有相關資料。

Slack 訊息帶來了幾個挑戰：

- 資訊密度差異極大：「嘿，好的，沒問題 Mike」與詳細的 Kernel 解釋都是訊息。
- 訊息長度不一，且較短的訊息在餘弦相似度（cosine similarity）上經常勝過較長、較詳細的訊息。
- 訊息的含義通常取決於前後文的對話。

我們需要一種混合式方法。我們建構了 Slack 攝取機制，讓每個討論串都能透過多種搜尋技術同時檢索，每種技術都能彌補彼此的弱點：

- **全文搜尋（Full-text search）**：能捕捉 embedding 會模糊處理的精確 token，例如錯誤字串、旗標名稱、主機名稱。當工程師貼上字面上的錯誤訊息時，精確的詞彙匹配幾乎總是最好的證據，任何程度的語意相似度都不應排在它前面。

- **Embedding 搜尋**：能捕捉同義改寫。詢問「restore 在 manifest 載入後當機」的人，與回答「checkpoint 在 NFS 掛載時卡住」的人，可能永遠不會使用相同的詞彙。向量相似度正是連結問題與以不同措辭撰寫的答案的關鍵。(1)

- **逆向文件頻率（IDF）**：能將訊號從填充內容中分離出來。圍繞稀有 token（如冷門的設定旗標）建構的短訊息，值得被排序。在 embedding 空間中，「聽起來不錯，謝謝！」與許多查詢接近，但一旦考慮到詞彙稀有度，其分數就會趨近於零。

- **時間衰減（Age decay）**：編碼 Slack 答案的時效性。兩個討論串可能回答同一個問題，但六個月前的討論串可能描述的是已不存在的基礎設施。當相關性相當時，較新的討論串勝出。

![該圖表展示了搜尋系統如何透過四種訊號（Full-Text、Embedding、IDF、Age Decay）對搜尋結果進行排序與權重調整。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/3659e72984357c48.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">畫面左側為「SEARCH / SLACK CANDIDATES」搜尋結果列表，查詢語句（QUERY）為："restore hangs after manifest load"。
搜尋結果包含以下項目：
1. 2W AGO (兩週前) - THREAD: "Checkpoint stalls on the NFS mount — set CKPT_PREFETCH=4." (標記為 NO SHARED TOKENS)
2. 1D AGO (一天前) - MESSAGE: "ERR_MANIFEST_TIMEOUT: restore hangs after manifest load." (標記為 EXACT TOKENS)
3. 3H AGO (三小時前) - MESSAGE: "sounds good, thanks! will try that"
4. 8M AGO (八個月前) - THREAD: "restore hangs after manifest load -&gt; use LEGACY_FETCHER=1." (標記為 ALSO MATCHES)

畫面右側列出四種搜尋排序訊號：
01 FULL-TEXT: EXACT TOKENS, PASTED ERRORS
02 EMBEDDING: PARAPHRASE ACROSS VOCABULARY
03 IDF: RARE TOKENS BEAT FILLER
04 AGE DECAY: NEWER ANSWERS WIN TIES

圖例說明：
- 實線框 (BOOSTED BY THIS SIGNAL)：表示該項目被此訊號加權提升。
- 虛線框 (PENALIZED)：表示該項目被此訊號懲罰降低。

畫面重點：此介面展示了搜尋引擎如何利用全文檢索、語意向量（Embedding）、詞頻逆轉（IDF）及時間衰減（Age Decay）等演算法，針對 Slack 中的技術討論進行排序，並透過視覺化標記顯示哪些結果因特定訊號而獲得加權。</div></details>

我們不信任單一評分器。每一種技術都會對同一個語料庫產生各自的排序視圖，並在查詢時進行融合（請參閱「重排序」）。

## Socket Mode

為了即時收集資料，我們在 workspace 中安裝了一個 Slack bot 並以 Socket Mode 執行。Slack 透過持久的 WebSocket 將每個訊息事件推送到我們這裡，因此我們無需輪詢 Web API 並消耗其速率限制，即可獲得即時更新。

當事件到達時，我們會立即確認，使用穩定的事件 ID 進行去重（deduplicate），並將訊息標記給攝取消費者（ingest consumer）。

攝取消費者不會單獨儲存新訊息。它會解析訊息所屬的討論串，並從 Slack API 重新擷取整個對話，包括父訊息與所有回覆。然後，它會將整個討論串寫回為一行。因此，對現有討論串的回覆會重新拉取父訊息與所有同級訊息，確保儲存的內容、參與者清單與最後活動時間戳記始終反映完整的對話。

我們系統中的每個 Slack 頻道都有自己的資料來源。這提供了對資料新鮮度的細粒度調整。例如，團隊可以選擇更頻繁地攝取繁忙的事故處理頻道。

## 討論串與訊息

原始 Slack 文字在落地後即可進行關鍵字搜尋，因為我們在原始內容上維護了 Postgres 全文（GIN）索引。然而，為了啟用有效的向量搜尋，我們進行了一些額外的處理。(8)

在蒸餾過程中，LLM 會從完整討論串中提取結構化資料：

- 工程師實際會搜尋的一行問題。
- 簡短摘要。
- 解決方案。
- 提到的系統與程式碼參考。

![這張圖表展示了將原始對話串轉換為結構化資料並存入向量資料庫的處理流程。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/acaee0cdbc12e712.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">該圖表描述了三個處理階段：
1. RAW THREAD (原始對話串)：包含四則訊息，分別標記為 QUESTION、SUMMARY、CONTEXT、RESOLUTION，內容涉及「Restore stalls after manifest load」問題的排查過程。
2. THREAD ARTIFACT (對話串產物)：將上述對話整理為一個 JSON 物件，包含欄位：question、summary、resolution、systems (["checkpoint restore", "NFS"])、code_refs (["CKPT_PREFETCH"])。
3. EMBEDDED ROW (嵌入列)：將結構化文件存入 PGVECTOR，包含正規化文件（Question + summary + resolution + systems + code references）、向量圖示、source (slack_thread)、source_id (thread_8f42)、embedding (3,072 dims)、metadata (channel + authors + time)。</div></details>

我們將這些資料點進行 embedding 並寫入共用的 embedding 資料表。原始文字記錄不會直接進行 embedding。在我們的實驗中，當討論串被正規化為一致的格式時，準確度顯著提高。(7,9) 額外的中繼資料也為語意匹配提供了更有用的訊號。

## 爆發（Bursting）

此時 Slack 搜尋效果已不錯，但我們仍不斷遇到同樣的問題：長討論串中的重要訊息並不總是在討論串層級的摘要中體現。

為了增強個別訊息的訊號，我們使用了「爆發」（bursting）。爆發是指同一作者連續發送的一連串訊息。我們將個別爆發進行 embedding，並將討論串主題作為上下文預先附加(2)，因為有時答案存在於某個離題的訊息中，而該訊息的詞彙從未進入討論串摘要。爆發 embedding 讓該訊息能被獨立搜尋到。

為了防止低訊號資料進入資料庫，每個爆發都會根據加權訊號組合進行評分，並必須在 embedding 前通過門檻：

- 包含語料庫中相對稀有的 token，IDF 至少為 4.0。
- 組合後的爆發長度至少 200 個字元。
- 爆發中的一條或多條訊息包含反應（reactions），提供社交加權。

![此圖表展示了將不同作者的訊息內容，根據其特徵分類為「BURST 01 - FILTERED」與「BURST 02 - EMBEDDED」的處理流程。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/a3c4533e7830f42e.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">圖表顯示了五個輸入項目被歸類至兩個處理類別：

1. 左側輸入項目：
   - AUTHOR A - SHORT
   - AUTHOR A - FOLLOW-UP
   - AUTHOR B - TECHNICAL DETAIL
   - AUTHOR B - RESOLUTION
   - AUTHOR C - ACK

2. 分類處理結果：
   - BURST 01 - FILTERED（包含 AUTHOR A 的兩項內容）：
     - 特徵：SHORT - COMMON TOKENS - NO REACTIONS
   - BURST 02 - EMBEDDED（包含 AUTHOR B 的兩項內容）：
     - 特徵：LONG - RARE TERMS - REACTED TO

畫面重點在於將訊息流根據長度、詞彙稀有度及互動反應進行分組處理，其中 AUTHOR C 的項目未被歸入上述兩個 BURST 類別中。</div></details>

蒸餾後，符合條件的爆發會被 embedding 並與討論串層級的記錄一起儲存在 embedding 資料表中。

# 程式碼儲存庫

最初我們爭論是否有必要對程式碼儲存庫進行 embedding。隨著 Claude Code 與其他命令列工具的興起，當「grep 就足夠了」時，建立程式碼 embedding 感覺有些反直覺。但在與業界人士交流並閱讀 Cursor 關於大型程式碼儲存庫語意搜尋的發現後，我們決定嘗試。

我們有許多內部程式碼儲存庫，有些超過 40 GB。我們主要的擔憂是如何有效率地保持它們的最新狀態。

## 使用 @cocoindex_io 維護程式碼 embedding

經過幾次實驗，我們選擇了 CocoIndex，這是一個專門用於程式碼儲存庫向量化的開源文件 embedding 框架。

對於每個程式碼儲存庫，我們使用語言特定的 regex 邊界將程式碼拆分，順序由粗到細。拆分器會先嘗試較高層級的邊界，例如類別（classes）。如果產生的區塊仍然太大，它會退回到方法（method）邊界，然後是更小的區塊。我們將產生的區塊進行 embedding 並將向量寫入 Postgres。單一檔案可能會在不同特異性層級產生多個 embedding，例如檔案層級與函式層級的記錄。

![這張圖展示了名為「CHECKPOINT_LOADER.CC」的程式碼片段，並對比了「NAIVE WINDOWS」與「LANGUAGE-AWARE」兩種分割策略。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/dd4ad633fd9e041c.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">圖中顯示一個名為 `CHECKPOINT_LOADER.CC` 的 C++ 類別 `CheckpointLoader`，包含三個主要方法：`LoadManifest`、`WarmShardCache` 與 `Restore`，以及私有欄位 `fs_`、`manifest_` 與 `shard_cache_`。

程式碼中透過註解標示了「SEMANTIC NODE」（語意節點，以淺粉色底色區塊表示）與「FIXED SPLIT」（固定分割，以虛線表示）。

右側說明了兩種處理策略：
01 NAIVE WINDOWS：CUT EVERY N TOKENS（每 N 個 Token 切割一次）。
02 LANGUAGE-AWARE：HIGH -&gt; LOW REGEX BOUNDARIES（基於正規表達式邊界進行語意感知切割）。

程式碼邏輯重點：
- `LoadManifest`：讀取路徑並解析清單。
- `WarmShardCache`：遍歷清單中的分片（shards）並將其載入快取。
- `Restore`：透過協調器（coordinator）獲取叢集資源並執行資料恢復。</div></details>

CocoIndex 在 Postgres 中追蹤同步中繼資料。在每次 commit 時，它只會重新 embedding 並重新匯出變更過的程式碼區塊，而不是重新計算整個程式碼儲存庫。這對我們來說特別有效，因為同步狀態與 embedding 儲存庫位於同一個資料庫中。

隨著程式碼儲存庫數量增加，我們將程式碼儲存庫的導入移至設定檔中，團隊可以自行提交，包括檔案路徑層級的允許清單與拒絕清單。

## 自訂資料來源

有些團隊已經擁有自己的資料庫，不想為了參與知識庫而將資料移至 Slack 或文件系統。他們希望在現有的資料表上擁有相同的查詢介面。

為了支援這一點，我們將自訂來源視為 plugin 指令碼。團隊只需提交一個包含小型 Python 模組的 Pull Request，該模組知道如何從其系統讀取資料並發出符合我們 embedding 資料表格式的資料列，以及對應的資料來源條目。

只要指令碼使用與其他所有 embedding 資料列相同的 Schema 寫入共用資料庫，其餘的堆疊就能維持不變。資料將與 Slack、程式碼與文件一起變得可查詢，系統其他部分無需任何特殊處理。

## 規劃與工具分發（Fan-out）

對於每個查詢，我們首先執行一個簡短的規劃階段，由 LLM 決定哪些工具與資料來源可能相關。主要工具包括：

- `subsystem_index`：每個檔案的 LLM 摘要。
- `search`：跨 Slack、Wiki、程式碼與其他已索引來源的統一向量管線，在內部進行合併與重排序。
- `search_slack`：直接 Slack 檢索。
- `search_code`：對原始程式碼庫進行 `ripgrep`。
- `recent_prs`：與問題相關的近期 Pull Request。
- `who_knows`：在該主題上展現過專業知識的人員。

規劃器會根據我們已索引內容的精簡描述來運作：存在哪些專案、每個專案有哪些可用來源，以及每個來源擅長回答什麼。根據使用者的查詢與作用中範圍，它會選出工具，由執行器（executor）並行分發、正規化為通用的證據格式，並傳遞給最終的合成 LLM。(4)

![這是一張展示 AI 系統處理問題流程的架構圖，包含規劃、多來源搜尋、證據整合與最終答案生成步驟。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/2270d325652f345f.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">該架構圖描述了一個自動化問答系統的處理流程，文字內容如下：
1. 起始節點：QUESTION（問題）
2. 處理節點：PLANNER（規劃器）
3. 並行搜尋節點：
   - SEARCH
   - SEARCH_SLACK
   - SEARCH_CODE
   - RECENT_PRS
   - WHO_KNOWS
4. 證據處理節點：EVIDENCE（證據），下方註記為 NORMALIZED ROWS（標準化列）
5. 最終輸出節點：SYNTHESIS（綜合），下方註記為 ANSWER + CITATIONS（答案與引用）

流程邏輯為：系統接收問題後，由規劃器分配任務至多個搜尋來源，搜尋結果匯總至證據處理階段，最後進行綜合分析並產出包含引用的答案。</div></details>

## 重排序（Reranking）

文件可能僅僅因為與查詢共享詞彙而排在前面，但實際上回答的是不同的問題。在重排序之前，我們使用倒數排名融合（Reciprocal Rank Fusion, RRF）來結合檢索器不相容的結果清單。對於每份文件，我們為其出現的每個清單增加 `weight / (60 + rank)`，預設權重為 1.0，平滑常數為 60。

![這張圖表展示了不同檢索管道（包括向量、全文檢索、執行緒摘要、圖譜等）的排名結果，並透過倒數排名融合（RRF）公式將其合併，最終「CLASS: CHECKPOINTLOADER」以 0.0792 的得分位居合併後的第一名。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/f953e4af74cf6695.jpg)

<details class="chart-data"><summary>展開數據表（1）VECTOR</summary><table><thead><tr><th></th><th>2</th><th>3</th><th>4</th><th>5</th></tr></thead><tbody><tr><td>1 = CKPT_LOADER.CC</td><td>CLASS: CHECKPOINTLOADER</td><td>WIKI: CHECKPOINT FORMAT</td><td>THREAD: NFS STALL</td><td>INC-82: SLOW RESTORE</td></tr></tbody></table></details><details class="chart-data"><summary>展開數據表（2）FTS</summary><table><thead><tr><th></th><th>2</th><th>3</th><th>4</th><th>5</th></tr></thead><tbody><tr><td>1 = WIKI: CHECKPOINT FORMAT</td><td>CKPT_LOADER.CC</td><td>RUNBOOK: NFS MOUNTS</td><td>INC-82: SLOW RESTORE</td><td>THREAD: CACHE WARMUP</td></tr></tbody></table></details><details class="chart-data"><summary>展開數據表（3）THREAD SUMMARIES</summary><table><thead><tr><th></th><th>2</th><th>3</th><th>4</th><th>5</th></tr></thead><tbody><tr><td>1 = THREAD: NFS STALL</td><td>THREAD: CACHE WARMUP</td><td>CLASS: CHECKPOINTLOADER</td><td>WIKI: LOAD STALLS</td><td>INC-82: SLOW RESTORE</td></tr></tbody></table></details><details class="chart-data"><summary>展開數據表（4）GRAPH</summary><table><thead><tr><th></th><th>2</th><th>3</th><th>4</th><th>5</th></tr></thead><tbody><tr><td>1 = CLASS: CHECKPOINTLOADER</td><td>MANIFEST_PARSER.H</td><td>RUNBOOK: NFS MOUNTS</td><td>THREAD: NFS STALL</td><td>WIKI: CHECKPOINT FORMAT</td></tr></tbody></table></details><details class="chart-data"><summary>展開數據表（5）WIKI VECTOR</summary><table><thead><tr><th></th><th>2</th><th>3</th><th>4</th><th>5</th></tr></thead><tbody><tr><td>1 = WIKI: CHECKPOINT FORMAT</td><td>WIKI: LOAD STALLS</td><td>RUNBOOK: NFS MOUNTS</td><td>INC-82: SLOW RESTORE</td><td>CLASS: CHECKPOINTLOADER</td></tr></tbody></table></details><details class="chart-data"><summary>展開數據表（6）SLACK FTS</summary><table><thead><tr><th></th><th>2</th><th>3</th><th>4</th><th>5</th></tr></thead><tbody><tr><td>1 = THREAD: NFS STALL</td><td>THREAD: CACHE WARMUP</td><td>BURST: TIMEOUT FLAGS</td><td>INC-82: SLOW RESTORE</td><td>CLASS: CHECKPOINTLOADER</td></tr></tbody></table></details><details class="chart-data"><summary>展開數據表（7）MERGED</summary><table><thead><tr><th></th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th></tr></thead><tbody><tr><td>1 = CLASS: CHECKPOINTLOADER (0.0792)</td><td>INC-82: SLOW RESTORE (0.0776)</td><td>WIKI: CHECKPOINT FORMAT (0.0640)</td><td>THREAD: NFS STALL (0.0640)</td><td>THREAD: CACHE WARMUP (0.0476)</td><td>RUNBOOK: NFS MOUNTS (0.0476)</td></tr></tbody></table></details>

平滑常數讓共識比單一強力的投票更重要：一份在多個檢索器中都排在前面的文件，可以勝過僅在其中一個檢索器中排名第一的文件。然後，我們將重複的區塊合併回單一來源，限制每個檔案能貢獻的結果數量，最終得到更多樣化的前二十名結果。

我們將原始查詢與這些候選結果發送給小型重排序模型。它會給每份文件 0 到 10 的分數，我們保留前十名。(6)

一旦排名確定，我們就會將上下文加回獲勝的結果中。例如，如果我們匹配到一個 Wiki 章節，我們會拉取前後兩個相鄰章節，這樣就不會遺失因區塊化（chunking）而分開的標題、前提條件與注意事項。這為讀者提供了完整的片段，而不是缺少重要上下文的孤立段落。

因此，搜尋的輸出是一個豐富的證據封包：來自不同檢索器的融合結果、在來源層級去重、針對實際問題重排序，最後才擴充周圍的上下文。

## MCP

在 MCP 整合中，我們將檢索建構區塊直接公開為工具，而不是將其隱藏在「回答此問題」的端點後。這些工具刻意保持簡單，並盡可能不依賴 LLM，以便客戶端能快速且低成本地查詢它們。(5)

每個 MCP 工具對應一個底層檢索原語（primitive），例如 `search_slack`、`search_code`、`search` 或 `who_knows`。工具的輸入與輸出狹窄、結構化且穩定，使其易於從任何客戶端或 Agent 呼叫，而無需在工具內部嵌入額外的編排邏輯。

大多數工具執行一個查詢管線（如向量搜尋、詞彙搜尋或 ripgrep），應用輕量級的啟發式評分，並返回原始證據列。

Claude Code 或任何相容 MCP 的 Agent 成為編排引擎。它決定呼叫哪些工具、呼叫順序，以及如何將結果組合成最終答案或程式碼編輯。檢索層本身不需要依賴這些 LLM 決策來服務請求。

## Web UI

在 Web UI 中，存在相同的工具，但它們連接到一個為每個使用者問題執行端到端的完整查詢管線。UI Agent 負責規劃與執行步驟。

- **規劃器（Planner）**：輕量級的 LLM 檢查查詢與作用中專案，然後選擇要呼叫的檢索工具，例如 `search`、`search_slack` 與 `subsystem_index`。

- **執行器（Executor）**：系統並行分發這些工具呼叫，收集結果，並將其正規化為包含分數、時效性與來源提示的共用證據 Schema。

- **合成（Synthesis）**：最終的 LLM 階段接收型別化的證據組合與原始問題，然後產生 UI 中顯示的答案，包括引用、注意事項與跨來源合成。

從使用者的角度來看，Web UI 只是「問一個問題並得到答案」。在底層，它執行與 MCP 客戶端可以明確重現的相同的「規劃器 → 執行器 → 合成器」模式。

![此圖表展示了 MCP CLIENT 與 WEB UI 兩種架構模式的組成元件與運作流程對比。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/d040632dacc8a897.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">圖片包含兩個並列的方框架構：
左側標題為「MCP CLIENT」，內部包含三個並列的區塊：
1. DIRECT TOOL CALLS
2. RAW EVIDENCE ROWS
3. CLIENT ORCHESTRATES

右側標題為「WEB UI」，內部包含三個垂直排列並由箭頭連接的區塊：
1. PLANNER
2. EXECUTOR
3. SYNTHESIS

畫面重點在於對比兩種不同的系統運作模式，左側強調客戶端（Client）的直接工具呼叫與協調，右側則展示了網頁介面（Web UI）中從規劃、執行到合成的線性處理流程。</div></details>

# 組織

隨著語料庫的增長，「搜尋所有地方的一切」很快就不再實用。編譯器團隊的工程師不想在結果中看到基礎設施的操作手冊，反之亦然。專案（Projects）是我們預設讓搜尋具備相關性的方式。

## 專案與範圍化搜尋

我們引入了專案作為組織查詢運作空間的主要方式。專案是資料來源的具名組合：特定 Slack 頻道、程式碼儲存庫、內部資料庫，以及與團隊或計畫相關的文件空間。

專案刻意保持輕量。相同的資料來源（例如共用的事故頻道或中央平台儲存庫）可以被多個專案引用，而無需複製。

![此圖展示了兩個專案（Compiler Project 與 Platform Project）與五個不同資料來源（Repo、Slack、Runbooks 等）之間的預設查詢範圍（Default Query Scope）關聯架構。](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/148389a836c33a21.jpg)

<details class="chart-data"><summary>展開畫面重點</summary><div class="me-note">該圖表呈現了一個系統架構的邏輯關聯，左側為兩個主要的專案節點，右側為五個目標資料來源。圖中文字轉錄如下：

左側區塊：
1. COMPILER PROJECT (DEFAULT QUERY SCOPE)
2. PLATFORM PROJECT (DEFAULT QUERY SCOPE)

右側區塊：
1. COMPILER SLACK
2. MONOLITH REPO
3. SHARED INCIDENTS
4. PLATFORM REPO
5. CLOUD RUNBOOKS

關聯說明：
- COMPILER PROJECT 連接至：COMPILER SLACK、MONOLITH REPO、SHARED INCIDENTS、PLATFORM REPO。
- PLATFORM PROJECT 連接至：MONOLITH REPO、SHARED INCIDENTS、PLATFORM REPO、CLOUD RUNBOOKS。</div></details>

## 導入與預設

在導入過程中，系統會提示使用者選擇或建立一個符合其工作方式的預設專案，例如「ML 訓練基礎設施」、「編譯器」或「資料中心維運」。

該預設專案會儲存在使用者設定檔中，並自動限制查詢範圍。新進工程師無需先學習哪些 Slack 頻道、儲存庫或文件空間重要，就能獲得高訊號的答案。

# 結語

歸根究柢，知識庫之所以有效，是因為它在資訊原本存在的地方與人們相遇，而不是強迫所有內容進入一個僵化的系統。透過結合各種搜尋技術，我們可以快速浮現證據。其結果是一種既能保持靈活以應對真實公司資料，又能保持結構化以在 Cerebras 持續成長時依然有用的搜尋體驗。

如果你讀到這裡並且對此感興趣，我們的 ai/growth 團隊正在招募中，如果你有興趣，請聯繫 @learnwdaniel。

# 參考文獻

1. Malkov and Yashunin, [Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs](https://arxiv.org/abs/1603.09320), arXiv:1603.09320 / IEEE TPAMI 2018.
2. Anthropic, [Introducing Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval), 2024.
3. Cormack, Clarke, and Büttcher, [Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf), SIGIR 2009.
4. Li et al., [Search-o1: Agentic Search-Enhanced Large Reasoning Models](https://arxiv.org/abs/2501.05366), arXiv:2501.05366, 2025.
5. Anthropic, [Code Execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp), 2025.
6. Liu et al., [Lost in the Middle: How Language Models Use Long Contexts](https://arxiv.org/abs/2307.03172), arXiv:2307.03172, 2023.
7. Anthropic, [Use XML Tags](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags), 2025.
8. Salesforce/Slack Engineering, [How Slack AI Processes Billions of Messages](https://engineering.salesforce.com/how-slack-ai-processes-billions-of-messages-to-reduce-information-overload-with-ai-powered-search-and-summarization/), 2025.
9. Improving Agents, [Best Nested Data Format](https://www.improvingagents.com/blog/best-nested-data-format/), 2025.
10. Cursor, [Improving Agent with Semantic Search](https://cursor.com/blog/semsearch), 2025.

## 標籤

Agent, 教學資源, 其他, Cerebras
