iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI Engineering

30 天打造 Codebase Intelligence Agent:從程式碼檢索、結構化索引到變更影響分析實戰系列 第 14 篇

Day 14:第二週收尾:Deterministic Reranking 與兩週檢索地基總驗收

  • 分享至 

  • xImage
  •  

昨天我們利用 RRF 演算法成功將關鍵字(Lexical)與向量(Semantic)融合為 Hybrid Search。

但如果你直接把 Hybrid Search 的 Top 5 丟給 LLM 或呈現在 CLI,會發現一個尷尬的問題:
當使用者搜尋 build_index 時,清單裡可能同時出現:

  1. src/build_index.py 的整檔 file chunk(包含一堆頂層說明)。
  2. src/rag_chat.py:4 的單行 import build_index。
  3. src/build_index.py:42 真正的 def build_index(): 函式本體。

如果隨機把這三個丟給模型,LLM 很容易抓著頂層說明或 import 行講半天,反而漏掉了真正實作邏輯的核心函式。

在第三週引入昂貴的 LLM Reranker 前,今天我們要先加上一層零成本、零延遲的 Deterministic Reranking(確定性規則重排),讓「真正定義」與「精確名稱」無條件排在前面,並為前兩週的成果進行一次完整盤點!

為什麼需要 Deterministic Rerank?

模型不是萬能的,簡單的規則往往比神經網路更可靠:

  • 定義優先(Definition Over Reference):在程式碼理解情境中,使用者問一個名稱,90% 是想看它的「實作定義(class / function)」,而不是看別的地方怎麼 import 它。

  • 同分穩定性(Tie-Breaking):當兩筆結果分數相同時,若沒有穩定的第二排序依據,每次重跑順序都會亂跳,導致測試無法驗證、使用者無法重現結果。

重排權重規則

我們為候選集設計簡單透明的階梯式加權:

  1. 完全符合名稱(Exact Match):Symbol 名稱完全等於 Query 者,權重加分($+0.5$)。
  2. 定義節點優先(Definition Priority):類型為 function、class 或 method 者,優先於泛泛的 file 或單行 import($+0.3$)。
  3. 同分決勝(Tie-breaking):若最終分數相同,依「檔案相對路徑字母順序」與「起始行號由小到大」排序,保證輸出絕對確定。

核心實作:app/reranker.py

在 app/reranker.py 中實作輕量重排邏輯:

# app/reranker.py
from typing import List, Dict, Any

class DeterministicReranker:
    DEFINITION_KINDS = {"function", "class", "method"}

    @classmethod
    def rerank(cls, query: str, candidates: List[Dict[str, Any]], limit: int = 5) -> List[Dict[str, Any]]:
        """
        對候選 Evidence 進行規則加權與穩定排序
        """
        query_clean = query.strip().lower()
        scored_candidates = []

        for item in candidates:
            # 複製資料避免汙染原始候選
            entry = dict(item)
            base_score = entry.get("rrf_score", 0.0)
            bonus = 0.0

            name = str(entry.get("name", "")).lower()
            kind = entry.get("kind", "")

            # 1. 精確名稱命中加分
            if name == query_clean:
                bonus += 0.5

            # 2. 定義區塊優先於單純引用或整檔
            if kind in cls.DEFINITION_KINDS:
                bonus += 0.3

            final_score = base_score + bonus
            entry["final_score"] = final_score
            scored_candidates.append(entry)

        # 3. 穩定排序:先比 final_score(降冪),同分比路徑(升冪),再比行號(升冪)
        scored_candidates.sort(
            key=lambda x: (-x["final_score"], x["path"], x["start_line"])
        )

        return scored_candidates[:limit]

實作單元測試:tests/unit/test_reranker.py

驗證定義節點是否能成功逆轉 RRF 分數稍微落後的情況,並確保同分時順序固定不跳動:

# tests/unit/test_reranker.py
from app.reranker import DeterministicReranker

def test_deterministic_rerank_priority():
    query = "build_index"

    # 模擬 Hybrid Search 出來的候選:import 行的 RRF 分數略高於函式定義
    candidates = [
        {
            "path": "src/rag_chat.py",
            "start_line": 4,
            "end_line": 4,
            "kind": "import",
            "name": "build_index",
            "rrf_score": 0.032
        },
        {
            "path": "src/build_index.py",
            "start_line": 42,
            "end_line": 80,
            "kind": "function",
            "name": "build_index",
            "rrf_score": 0.030
        },
        {
            "path": "src/build_index.py",
            "start_line": 1,
            "end_line": 100,
            "kind": "file",
            "name": "build_index",
            "rrf_score": 0.028
        }
    ]

    reranked = DeterministicReranker.rerank(query, candidates, limit=3)

    # 斷言:函式定義必須被推到第 1 名
    assert reranked[0]["kind"] == "function"
    assert reranked[0]["path"] == "src/build_index.py"
    assert reranked[0]["start_line"] == 42

全量單元測試驗收

目前專案累積的所有確定性單元測試(包含邊界安全、AST 解析、SQLite 索引、直接 import 追蹤、向量打包與 Rerank):

uv run python -m pytest tests/unit -q

終端機驗收輸出:

........                                                                 [100%]
8 passed in 0.12s

8 個單元測試全數通過,且完全不需要開網路或依賴本機模型運作。

第二週成果回顧:我們完成了什麼?

花兩週打下的地基,讓系統具備了完整的 Codebase 靜態分析與檢索管線:

模組 產出成果與物理意義 狀態
AST 結構化切分 取得 23 個不被截斷的語法 Chunks(7 檔案 + 16 類別/函式)

| PASS |
| 持久化 Symbol 庫 | 23 個 Symbol 寫入 SQLite,支援 Qualified Name 與同名衝突保留

| PASS |
| 直接依賴關係圖 | 28 條 Import 記錄,可逆向追蹤模組被誰引用

| PASS |
| 本機 Code Vector | 整合 Ollama bge-m3:latest,產出 1024 維向量並支援餘弦檢索

| PASS |
| 混合檢索 (Hybrid) | RRF 無痛融合關鍵字精確度與向量語意

| PASS |
| 確定性重排 (Rerank) | 定義與精確名稱優先,排序 100% 可重現

| PASS |

面對目前的局限(Limitations)

  1. 尚未具備跨檔案呼叫鏈:目前只知道「A import B」,還不知道「A 裡面的哪個 function 實際呼叫了 B 裡面的哪個 method」。

  2. 檢索品質尚未量化:目前的排序是直覺規則,還沒有科學化的評估指標來證明 Hybrid 到底比純文字好多少。

下一週,我們暫時放下新功能開發,專注於「評估(Evaluation)」與「本地 LLM Reranker」:

  • 建立第一批可重複執行的評估資料集(Eval Cases)。
  • 引進搜尋評價指標:Hit@k、MRR 與 Precision@k。
  • 讓本地的小模型負責複雜語意的重排,並實作安全的 JSON Fallback 防線。

明天,我們將動手寫下第一份 data/eval_cases.json,讓檢索系統不再憑感覺打分數!


上一篇
Day 13:不要在 Lexical 和 Semantic 中二選一:Hybrid Search 與 RRF 融合
下一篇
Day 15:先做一份真的能重跑的評估資料集
系列文
30 天打造 Codebase Intelligence Agent:從程式碼檢索、結構化索引到變更影響分析實戰 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言