Search Engine

當搜尋引擎要服務上千個 AI agent:Exa 用 DAG 管線解決可觀察性難題

當客戶從一般使用者變成上千個 AI agent,Exa 用 DAG 架構的搜尋管線編排器 Canon 應對複雜度:20 多種節點類型、按查詢組合流程,把可觀察性與彈性變成搜尋引擎的一級設計問題。

當搜尋引擎要服務上千個 AI agent:Exa 用 DAG 管線解決可觀察性難題 — 文章封面
本頁內容7 個段落
  1. 當搜尋不再是「查詢→排序→回傳」
  2. 把搜尋管線變成一張圖
  3. 可觀察性的關鍵:追蹤每一條路徑
  4. 執行器的設計哲學:讓節點保持單純
  5. 給 Agent 寫的架構
  6. 總結:複雜度不會消失,但可以讓它變得可管理
  7. 參考來源

幾年前,搜尋引擎的架構還算單純:建一個倒排索引、跑查詢、排序、回傳結果。但當你的客戶從一般使用者變成上千個 AI agent,每個 agent 都有自己的語言偏好、時效需求、資料來源要求,事情就完全不一樣了。

Exa 是一家專注於為 AI agent 打造搜尋引擎的公司,他們最近公開了自己如何應對這個複雜度的解法——一個叫做 Canon 的搜尋管線編排器。這篇文章不是要推銷 Canon,而是想聊聊他們面對的問題,以及為什麼用有向無環圖(DAG)來描述搜尋流程,可能是產品 builder 值得參考的設計取捨。

當搜尋不再是「查詢→排序→回傳」

Exa 在 官方部落格 中描述,一個看似簡單的搜尋請求,背後其實是一張包含 20 多種節點類型的圖。有些使用者要日文結果,有些要最新新聞,有些要百科全書式的參考資料。同一個查詢可能同時打到知識圖譜、產品索引、網頁索引,然後再根據結果做分類、本地化、重新排序。

更麻煩的是,這些管線的程式碼現在大部分是由 AI agent 寫的。Agent 寫的程式碼在局部通常正確,但要它理解全域的限制與相依關係,依然很困難。當你每天要處理數十億次搜尋請求,而且每個請求的路徑都可能不同,傳統的 if/else 加上手動並行處理,很快就會變成除錯惡夢。

把搜尋管線變成一張圖

Exa 的解法是將整個搜尋流程定義為一個 DAG。每個節點代表一個獨立的工作(例如:密集檢索、稀疏檢索、融合、抓取內容),節點之間的邊代表資料相依性。執行器(runtime)會自動找出沒有相依的節點,平行執行它們。

這個設計帶來了幾個實質好處:

  • 自動平行化:你不用手動寫 Promise.allThreadPool,圖的結構本身就告訴執行器哪些可以同時跑。
  • 耐久執行:如果某個節點失敗,執行器可以只重試那個節點,不需要重跑前面的所有步驟。
  • 可觀察性:DAG 本質上是資料,你可以視覺化、驗證、分析它,不需要讀原始碼就能理解控制流程。
  • 定義與執行分離:同一張圖可以在測試時同步執行、在正式環境分散執行、或乾脆做乾執行預覽。快取、重試、追蹤這些機制都放在執行器裡,節點本身不需要知道。

當然,DAG 不是萬靈丹。Exa 也坦承,如果你的系統是反應式事件迴圈(沒有排程工作,只有狀態與回呼)、共識協議(需要嚴格順序)、或反饋迴圈(例如編譯器的 pass,會迭代直到收斂),DAG 就不適合。

可觀察性的關鍵:追蹤每一條路徑

在導入 Canon 之前,Exa 的搜尋管線是一串連續的函式呼叫、if/else 分支、以及手動的錯誤處理。要換一個新的重新排序模型,可能需要審查每一個可能呼叫它的條件判斷。要除錯「為什麼某個 URL 沒有出現在結果中」,就得手動翻日誌,從新的 reranker 一路往回追,檢查是否有任何 fallback 被觸發。

Exa 用了一個很具體的例子來說明這種痛苦:為什麼某個查詢在某個時間點,沒有回傳某個明顯應該出現的 URL? 在傳統的管線中,URL 可能在分類階段被濾掉、在本地化階段被忽略、在排序階段被降權、或是在某個並行分支中被覆蓋。要找出真正的原因,就像在數十億筆資料的大海撈針。

Canon 的做法是將搜尋管線編譯成可序列化的圖。當你需要除錯時,這張圖可以讓你追蹤每個節點的決策:哪個子系統丟掉了那個 URL?為什麼?而且因為圖的結構是明確的,當客戶有特殊設定時,你也可以保證圖與實際的搜尋路徑一致。

執行器的設計哲學:讓節點保持單純

Canon 的執行器採用 pull-based 模型:一個節點只有在下游節點要求它的值時才會執行。這聽起來有點抽象,但帶來的效果很實際:

  • 延遲與取消傳播:如果使用者斷線或請求超時,執行器會自動取消所有還在跑的節點,不會浪費運算資源。
  • 記憶化:如果兩個下游節點共用同一個上游節點(形成鑽石相依),那個上游節點只會執行一次,結果被快取起來。
  • 完整追蹤:執行器位於每個節點的呼叫邊界,自動記錄時間、輸入、輸出、決策。任何節點拋出的錯誤都會被附加上下文:哪個節點失敗了?上游正在做什麼?

關鍵在於,節點本身完全不需要知道這些機制。一個檢索節點只需要知道怎麼查它的索引,一個 reranker 只需要知道怎麼排序文件。它們不需要處理並行、取消、快取、或追蹤。

Exa 特別強調:如果一個節點自己發起網路呼叫(例如直接呼叫 ranking service),執行器就無法取消它、快取它、或追蹤它。那個節點會變成系統中的黑盒子。 所以 Canon 要求所有節點透過統一的介面與執行器溝通。

給 Agent 寫的架構

Exa 點出了一個 2026 年軟體工程師都必須面對的現實:大部分程式碼現在是由 coding agent 寫的。這些 agent 需要一個結構,讓它們在第一次嘗試時就能寫出正確的程式碼,而不是靠反覆除錯。

在傳統的指令式程式碼中,正確性隱藏在分支結構、執行順序、和程式碼慣例裡。Agent 必須從附近的程式碼中學習這些隱含的不變量:內容已經抓過了嗎?有內容可以重新排序嗎?審核處理了嗎?這些都是 agent 容易忽略的細節。

Canon 的做法是把這些隱含假設變成明確的型別與圖結構。Agent 不需要猜測「這個函式應該在什麼時候被呼叫」,它只需要檢查它產生的圖是否正確。型別系統、圖的 schema、以及執行器共同承擔正確性的責任,而不是靠 agent 的上下文視窗。

此外,Canon 要求每個節點必須處理所有可能的結果( totality )。型別檢查器會拒絕任何有未處理分支的圖。這聽起來很嚴格,但實際上消除了大量執行期的意外。

總結:複雜度不會消失,但可以讓它變得可管理

Exa 的搜尋只會越來越複雜:更多搜尋類型、更多客戶、更多 agent。Canon 不是一個通用的框架,它是為了解決特定問題而生的工具:當你的系統有明確的相依關係、需要平行執行、而且可觀察性至關重要時,DAG 是一個值得考慮的抽象。

對產品 builder 來說,這篇文章最有價值的部分或許不是 Canon 本身,而是 Exa 對「複雜度管理」的態度:不要試圖讓系統變簡單(因為它本來就不簡單),而是建立一個讓複雜度可以被看見、被追蹤、被控制的結構。當你的 agent 或同事不需要猜測系統的行為時,除錯時間就會從數小時縮短到幾分鐘。

如果你正在設計一個需要處理多種資料來源、多種客戶需求的系統,或許可以問自己:你的管線是一張可以被視覺化的圖,還是一串只有你記得住順序的 if/else?

參考來源

本文由 AI 協助自上述來源整理,經人工審核後發布。

分享X電郵