摘要: Exa 公開 Canon 搜尋管線編排器,把檢索、融合與抓取流程宣告成有向無環圖。DAG 讓彼此沒有相依的節點自動平行執行,支援失敗重試、完整追蹤、快取與取消。文章也提醒反應式事件迴圈、共識協議和反饋迴圈並不適合套用 DAG。
過去的搜尋引擎其實長得一模一樣:先建好倒排索引,接到查詢就算排序、送回結果,幾年來都是這套流程。然而客戶一旦從螢幕前的人類換成上千個 AI agent,而每個 agent 又各自帶著語言偏好、時效要求與資料來源條件,整個遊戲規則就不同了。
Exa 是專門替 AI agent 打造搜尋引擎的公司,最近他們公開了自己消化這股複雜度的辦法——一套名為 Canon 的搜尋管線編排器。這裡要談的不是 Canon 的宣傳,而是他們踩到的問題,以及「用有向無環圖(DAG)描寫搜尋流程」這個選擇,對產品 builder 有哪些值得借鏡的設計取捨。
搜尋早已不是「查詢、排序、回傳」三步驟
Exa 在 官方部落格 指出,表面上一筆再普通不過的搜尋,底層其實攤開成一張含 20 多種節點類型的圖。有的使用者要日文結果,有的指定只要最新新聞,也有人要百科全書式的參考資料。同一個查詢可能同時落在知識圖譜、產品索引與網頁索引上,接著再依結果做分類、本地化與重新排序。
更棘手的是,這些管線的程式碼如今多數出自 AI agent 之手。Agent 寫出來的程式在局部往往沒問題,但要它們顧及整體的限制與相依關係依然困難。當系統每天扛下數十億次搜尋請求、每筆請求走的路徑又各自不同,靠 if/else 加手動並行處理,很快便會淪為除錯惡夢。
用一張圖來定義搜尋管線
Exa 選擇把整段搜尋流程宣告成一個 DAG:節點各自代表一項獨立的工作(像是密集檢索、稀疏檢索、融合、抓取內容),連接節點的邊則記載資料相依性。執行器(runtime)負責找出彼此沒有相依的節點,讓它們平行執行。
這套設計帶來幾項實際的好處:
- 自動平行化:
Promise.all或ThreadPool都不必親手寫——圖的結構本身就告訴執行器,哪些節點可以同時開跑。 - 耐久執行:某個節點出錯時,只需重試那一個節點,不必把前面所有步驟整段重跑。
- 可觀察性: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?
