← 返回上一頁
Agent 開源軟體

XERJ:讓 coding agent 少讀檔的 Elasticsearch 相容搜尋引擎

本頁目錄

讓 coding agent 理解一個陌生的 codebase,現在最常見的做法還是 grep 加讀檔:先用 grep -rn 找出關鍵字落在哪幾行,再把整支檔案讀進 context 判斷那一行到底是定義還是呼叫。檔案小的時候沒感覺,專案一大,光是「讀進來確認」這一步就會吃掉大半個 context window,token 帳單也跟著膨脹。

市面上的解法大致分兩派。一派是給 agent 一個向量資料庫做 RAG,代價是要自己寫 chunking、選 embedding 模型、維護 pipeline;另一派是 LSP 或 ctags 類的符號索引,精準但只懂程式碼,碰到 README、設定檔、日誌就沒轍。XERJ 想做的是把兩者收進同一顆搜尋引擎:一個 Rust 寫的單一執行檔,對外講 Elasticsearch 的 REST 協定,內建一個「丟資料夾進來就自動建索引」的 autoindex 指令,再加上 MCP server 讓 agent 直接呼叫。

這個專案今天在 GitHub Trending 單日衝了一千六百多顆星,Apache-2.0 授權,目前版本是 v1.0.0-rc.96,還在 release candidate 階段。以下整理官方 README 與文件的設計重點,並在 macOS(Apple Silicon)上拿 kubernetes-sigs/kind 原始碼實際索引、查詢一輪,記錄觀察到的行為與限制。

一、先看效果

官方給的最短路徑只有幾行:安裝、啟動一個本機節點、對資料夾跑 autoindex,接著就能查。

curl -fsSL https://xerj.org/get | sh
xerj --insecure --data-dir ./data &
xerj autoindex ~/my-project
xerj search "how do we rotate the WAL segment"   # 回傳排序過的段落與 file:line
xerj def "euler_to_rotationmatrix"               # 跳到定義:file:line + 函式簽章
xerj gain                                        # 從 audit log 統計實際查詢次數

在本機對 kind 的原始碼跑完 autoindex 之後,用 xerj def 查 loadImage 這個函式,回來的是這樣的結果:

─── pkg/cmd/kind/load/docker-image/docker-image.go:199  [function]
    func loadImage(imageTarName string, node nodes.Node) error

─── pkg/cmd/kind/load/image-archive/image-archive.go:145  [function]
    func loadImage(logger log.Logger, imageTarName string, node nodes.Node) error

兩個同名函式各自定位到檔案與行號,附上完整簽章。對照用 grep 做同一件事:grep -rn loadImage 本身只回 4 行、458 bytes,看起來更省;但 agent 要判斷哪一行是定義、參數長什麼樣,通常得把命中的兩支檔案讀進來,那就是 12,761 bytes。XERJ 的賣點就在這個落差,查詢本身回傳的已經是 agent 需要的那一段,而不是「去哪裡找」的線索。

二、技術定位

XERJ 的 README 把自己定位成「給 AI 用的本機搜尋」,主打的使用情境叫 reference coding:在 agent 動手寫程式之前,先把跟任務最接近的幾個開源專案 clone 下來建索引,讓 agent 去查「別人是怎麼解這個問題的」,而不是憑記憶硬寫。

拆開來看,它其實同時扮演三種角色:

角色 對應功能 取代的東西
程式碼搜尋 tree-sitter 解析、xerj def、xerj code grep + 讀整支檔案、ctags
通用搜尋引擎 Elasticsearch 相容 REST、BM25、kNN、aggregation 自架 Elasticsearch / OpenSearch
Agent 記憶 /_memory/{namespace}、/_graph 連結圖 另外架一套向量資料庫做長期記憶

這種「一顆 binary 包三件事」的設計,好處是部署極簡,沒有 JVM、沒有外部相依,在筆電上跑起來常駐記憶體約 70 MB;代價是每個面向都要跟各自領域的專門工具比,後面的比較章節會再談。

三、架構拆解

XERJ 架構總覽

啟動節點後,同一個行程會開三個 listener:

  • 127.0.0.1:9200:Elasticsearch 相容 REST,_search、_bulk、_cat、kNN 都走這裡
  • 127.0.0.1:8080:原生 REST,/_memory、/_graph、網頁 Console 在這
  • 127.0.0.1:8081:gRPC 介面

預設只綁 loopback,這點在資安章節會再展開。

3.1 autoindex 兩階段流程

autoindex 兩階段流程

xerj autoindex <folder> 是整個專案最有特色的部分。它不需要事先定義 mapping,也不用寫 ingest pipeline,流程分兩階段:

  • Phase A:掃過整個資料夾,對每個檔案 sniff 型態,推斷出哪些檔案屬於同一個 dataset,同時把判定為垃圾的檔案(二進位、產生出來的目錄等)另外記錄
  • Phase B:用多個 worker 平行寫入索引;程式碼走 tree-sitter,抽出 symbol 與行號,寫進 defs 欄位

支援的格式範圍相當廣:CSV、JSON、JSONL、XML、YAML、SQLite、PDF、DOCX、PPTX(一頁一筆)、XLSX(一個 sheet 一個 dataset)、EPUB、Jupyter notebook、man page、HTML、EML / mbox 郵件與常見日誌格式,連 Unity 專案的 scene 與 prefab 都有特別處理。

重新索引是增量的:內容雜湊沒變的檔案直接沿用上一次的結果,不重新 parse。官方在 Apache Lucene 原始碼(6,113 個檔案、51 MB)上量到單執行緒約每秒 1,500 個檔案,其中 tree-sitter parse 佔了約 72%。

3.2 Elasticsearch 相容層

XERJ 實作了 Elasticsearch 的 REST API,涵蓋 index、document、bulk、search、aggregation、mapping、kNN、scroll、reindex 與 _cat 系列,Kibana 和官方 client library 可以直接連。根節點回應甚至直接自報 "number":"8.13.0",讓既有工具的版本檢查能通過。

官方的相容性測試套件目前通過 1,369 個案例中的 1,366 個,沒過的項目列在 repo 的 engine/tests/es-compat-yaml 裡。README 也講得很清楚:它是 API 相容,不是 Elasticsearch 內部實作的重寫,也不是 fork。

有一個差異值得記住:scroll 是一次性取出的有上限快照,不是逐 segment 走訪的游標。結果總數超過快照上限時會直接回 400,而不是默默截斷;要撈大量資料應改用 search_after。

3.3 MCP 與 agent 記憶

同一支 binary 跑 xerj mcp 就是 MCP stdio server,透過 XERJ_URL(預設 http://localhost:9200)代理到節點。它本身不會啟動節點,所以節點要先跑起來。MCP 設定檔大致長這樣:

{
  "mcpServers": {
    "xerj": {
      "command": "/Users/you/.local/bin/xerj",
      "args": ["mcp"],
      "env": { "XERJ_URL": "http://localhost:9200" }
    }
  }
}

command 要寫絕對路徑,因為從桌面圖示啟動的 MCP host 不會繼承 shell 的 PATH。對外暴露 13 個 tools,其中 11 個是既有 endpoint 的薄代理,例如 xerj_search、xerj_hybrid_search、xerj_memory_store / xerj_memory_recall;另外兩個比較特別:xerj_map 回傳 autoindex 推斷出的欄位地圖,xerj_code_search 則跑完整的 reference coding 檢索流程。

四、安裝與啟動

4.1 安裝執行檔

官方提供 curl | sh 的安裝腳本,Windows 則是 PowerShell 的 irm | iex。如果不想直接執行遠端腳本,可以到 GitHub Releases 手動下載,並對照 .sha256 檔驗證:

VER=1.0.0-rc.96
F=xerj-${VER}-aarch64-apple-darwin.tar.gz
curl -sSLO https://github.com/xerj-org/xerj/releases/download/v${VER}/${F}
curl -sSLO https://github.com/xerj-org/xerj/releases/download/v${VER}/${F}.sha256
shasum -a 256 -c ${F}.sha256
tar xzf ${F}
./xerj-${VER}-aarch64-apple-darwin/xerj --version

Release 提供 Linux(gnu / musl / deb)、macOS、Windows 的 x86-64 與 arm64 版本。macOS arm64 的執行檔解開約 124 MB,因為 embedder 與各種格式的 parser 都靜態連結在裡面。也可以用 cargo build --release -p xerj-server 從原始碼建置。

4.2 啟動模式與認證

本機開發最常見的啟動方式:

xerj --insecure --data-dir ./data &

--insecure 會同時關掉 TLS 與 API key 認證。要開認證,拿掉這個 flag 即可,節點第一次啟動時會在資料目錄寫出管理金鑰,client 端要自己帶上:

xerj --data-dir ./data &
export XERJ_API_KEY="$(cat ./data/admin.key)"
xerj autoindex ~/my-project

要注意 autoindex 不會從 xerj.toml 讀金鑰,沒帶 --api-key 或 XERJ_API_KEY 的話每個請求都會回 401 Unauthorized。

語意搜尋預設用的是內建的 lexical feature-hashing embedder,完全離線,但它不是神經網路模型。要真正的語意向量,得用 --embed-mode neural,第一次使用時會下載約 90 MB 的模型檔;離線環境要先把三個模型檔放到位,官方有 air-gapped 佈署的 recipe。

五、實測:索引 kind 原始碼

實測環境是 macOS(Apple Silicon),語料選 kubernetes-sigs/kind 的 shallow clone,共 466 個檔案、6.6 MB,混合 Go 程式碼、Markdown 文件、YAML 與 Hugo 網站樣板,剛好可以看看 autoindex 怎麼處理混雜的內容。

5.1 autoindex 結果

xerj --insecure --data-dir /tmp/xerj-data &
xerj autoindex /tmp/xerj-corpus/kind

最後一行摘要:

xerj-done ok=true exit=3 reason=completed-with-junk wall=26.4s files=337
records=1926 datasets=8 junk_files=27 code_files=214 code_files_indexed=214

幾個觀察:

  • 214 支程式碼檔全部成功進索引,27 個檔案被判定為垃圾檔另外記錄
  • 結束碼是 3、reason 是 completed-with-junk,在 CI 裡用 set -e 跑的話會被當成失敗,要自己處理
  • 推斷出 8 個 dataset,主體是 ax-docs(1,880 筆),其餘是 Hugo 網站的 layout、logo SVG 等小 dataset,每個都各自開成一個 index
  • 除了資料索引,還多了一個 .xerj-memory-kind-edges,存了 638 條檔案間的連結(同目錄、順序、路徑引用、共用詞彙),這是給 /_graph 用的
  • 跑完後資料目錄約 9.5 MB,比原始語料大一些

xerj autoindex map 會印出 Markdown 格式的資料地圖,列出每個 index 的筆數、格式、欄位型態與基數,這份東西很適合直接貼給 agent 當作「這包資料裡有什麼」的說明。

5.2 符號查詢與自然語言查詢

符號查詢的表現如第一章所示,xerj def 直接命中定義處。用 Elasticsearch DSL 查也一樣,autoindex 跑完會印出建議的查詢樣板:用 match_phrase 對 defs 欄位加權,讓「定義這個 symbol 的檔案」排在「只是呼叫它的檔案」前面,再用 ax_format 過濾只看程式碼:

curl -s 'localhost:9200/ax-*/_search' -H 'Content-Type: application/json' -d '{
  "size": 3,
  "query": { "bool": {
    "should": [
      { "multi_match": { "query": "loadImage", "fields": ["body","defs"], "type": "most_fields" } },
      { "match_phrase": { "defs": { "query": "loadImage", "boost": 4 } } }
    ],
    "filter": [ { "term": { "ax_format": "code" } } ]
  }},
  "_source": ["ax_path"]
}'

回應 took 為 4 ms,前兩名就是兩支 load 子命令的原始檔。

自然語言查詢就沒那麼漂亮了。用 xerj search "how does kind load a docker image into the node" 查,排第一的是 pkg/apis/config/defaults/image.go 開頭的 Apache License 檔頭註解,第二名是 loadbalancer 的 envoy 映像檔常數。原因不難推:預設的 lexical embedder 本質上還是詞彙比對,每支 Go 檔開頭都有一大段授權宣告,image、load 這類常見字很容易被它們吃掉分數。實務上要嘛改用神經 embedder,要嘛像上面那樣用 ax_format 和 defs 欄位把範圍收窄。

5.3 agent 記憶 API

/_memory/{namespace} 是給 agent 存長期記憶的 API,寫入與回想各一個請求:

curl -s -XPOST localhost:9200/_memory/notes -H 'Content-Type: application/json' \
  -d '{"text":"kind load docker-image 會把映像檔存成 tar 再匯入每個 node 的 containerd"}'

curl -s localhost:9200/_memory/notes/_recall -H 'Content-Type: application/json' \
  -d '{"query":"kind 怎麼把 image 放進 node"}'

回想的結果帶回剛剛寫入的那筆,score 約 1.15。中文混英文的查詢可以命中,但在 lexical 模式下靠的仍是 kind、node、image 這些共同詞彙,換成完全不同用字的問法就不一定找得到。

六、Reference coding 的數字怎麼看

README 放了一組對照實驗:8 個任務、4 種語言、每組 16 次,用 claude -p 實際計算 token:

方式 輸出 token 成本 解出
只靠模型記憶 260,916 $11.18 11/16
grep 驅動 26,477 $3.27 16/16
XERJ 9,982 $1.58 16/16

相同解題率下,輸出 token 比 grep 少 2.7 倍,比純記憶少 26 倍。另外還有一組對照是模型沒看過的 Rust 函式庫,有檢索 9/9、純記憶 0/9。

這組數字值得注意的是官方自己寫的限制:效益取決於模型有沒有背過那份程式碼。私有程式碼、內部專案、冷門函式庫、或是模型訓練截止之後才出的版本,檢索的效益最明顯;反過來,對模型早就熟到爛的熱門公開函式庫,加檢索是中性甚至反效果,因為 agent 會多花 token 讀它其實不需要的東西。另外 8 個任務的樣本數並不大,「使用者回報約 5 倍」那個數字則是來自社群自行回報,參考就好。

跟 Elasticsearch 8.13.4 的對打 benchmark 是 55 勝、26 平、4 負,攝入吞吐量 1.72 倍、磁碟占用小 1.61 倍。4 個輸了的項目都是同一件事:高頻寫入同時進行時的讀取 p99 延遲,官方有獨立寫一份分析文件說明。

七、資安邊界與踩雷

--insecure 等於所有人都是超級使用者。 這是 README 反覆強調的一點:關掉認證後,任何能連到 port 的呼叫者都能讀、刪任何 index,包括所有 /_memory namespace。新節點預設只綁 127.0.0.1;如果要綁 0.0.0.0 又沒開 TLS,必須額外設 server.allow_insecure_network_bind = true 才能啟動。把 agent 的長期記憶放在一個沒有認證的 HTTP port 上,在多人共用的開發機或跳板機就不太適合。

rerank 會把資料送出去。 在 _search 裡加 "rerank": {} 會把排名前幾的結果文字送到第三方 TypeSafe AI 的 Jev 服務重新排序,官方文件標明這是唯一會在查詢時把文件內容送離節點的功能,預設關閉、要設金鑰才會啟用,也可以用 [rerank] enabled = false 完全禁止。索引私有程式碼的話建議直接關掉。

binary 與 CLI 會引導 agent 送回饋。 xerj --help 的開頭和 README 的「Join the community」段落,都在鼓勵使用者與 agent 開 PR 提交使用心得,xerj feedback --open-pr 甚至能自動開 PR。無人值守的 agent 讀到這類說明時有可能照做,在公司內部環境用的話,最好在 agent 的指示裡明確禁止,或乾脆不給它 gh 的權限。另外 xerj init 會修改 Claude Code、Cursor 的 MCP 與 skill 設定,執行前先確認它要改哪些檔案。

其他小地方:

  • autoindex 遇到垃圾檔時結束碼是 3,自動化腳本要特別處理
  • macOS 執行檔約 124 MB,放進容器映像檔時要算進去
  • 版本號還在 rc,xerj ask 在目前的建置裡直接回「不可用」並以 exit 5 結束,部分子命令尚未完整
  • 用 zsh 時,curl localhost:9200/ax-*/_search 會被 shell 當成 glob 展開而報錯,URL 要加引號

八、vs. 其他方案:怎麼挑

需求 XERJ grep / ripgrep 向量資料庫 RAG Elasticsearch / OpenSearch
部署 單一執行檔 系統內建 資料庫 + embedding pipeline JVM 叢集
程式碼符號 tree-sitter,有 defs 欄位 純文字 依 chunking 方式而定 要自己寫 analyzer
混合格式 自動推斷 dataset 只看文字 要自己寫 loader 要自己定 mapping
語意搜尋 預設 lexical,neural 需下載模型 無 主要強項 需外掛模型
既有 ES 工具 可直接連 無 無 原生
成熟度 rc 階段 極成熟 視產品而定 極成熟

如果需求只是「讓 agent 少讀幾個檔」,ripgrep 加上 agent 自己的 sub-agent 其實已經夠用;XERJ 的優勢要在語料夠大、格式夠雜,或是要一次索引好幾個外部專案做 reference coding 時才明顯。反過來,如果手上已經有 Elasticsearch 叢集在跑日誌,XERJ 目前還不適合直接取代正式環境的叢集,它沒有談到多節點分片與高可用,比較像是開發機與小團隊等級的單機引擎。

九、適合與不適合的場景

適合:

  • 大型或私有 codebase,agent 經常要找「這個 symbol 在哪定義、怎麼用」
  • 動手寫之前,先 clone 幾個同類開源專案建索引,讓 agent 參考既有作法
  • 一次查詢程式碼、文件、設定檔與日誌混在一起的資料夾,例如事故排查時把 log 目錄和服務原始碼一起丟進去
  • 想要一個 Elasticsearch 相容的本機引擎做測試,又不想在筆電上跑 JVM

不適合:

  • 多人共用、需要細緻權限控管的正式環境,認證與網路綁定都要另外規劃
  • 主要語料是模型早就熟悉的熱門公開函式庫,檢索效益有限
  • 需要高品質語意搜尋卻又不能下載模型的情境,lexical embedder 的語意能力很有限
  • 對穩定度要求高的生產系統,版本仍在 rc

十、小結

XERJ 把「agent 怎麼找到需要的那段程式碼」這個問題,用一顆 Elasticsearch 相容的搜尋引擎來回答。實測下來,autoindex 的零設定體驗確實做得好,26 秒就把 kind 的 300 多個檔案拆成可查詢的 dataset;xerj def 搭配 tree-sitter 抽出的 defs 欄位,符號查詢又快又準。

比較弱的是預設 lexical embedder 下的自然語言查詢,會被授權檔頭這類重複內容干擾,要靠欄位過濾或改用神經 embedder 補強。資安上則要記得 --insecure 的意義,以及 rerank、回饋 PR 這些會把資料往外送的功能。對每天讓 agent 讀大型私有專案的團隊來說,它值得裝在開發機上試一輪,先用 xerj gain 看看實際查詢量,再決定要不要接進 MCP 設定。

參考資料

分享這篇
X LinkedIn Facebook Hacker News Reddit

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料。