全部產品
Search
文件中心

Vector Retrieval Service for Milvus:通過阿里雲Milvus知識庫搭建法律案例檢索應用

更新時間:Aug 28, 2026

阿里雲 Milvus 知識庫可以把公開法規、裁判文書與企業合規制度建成可檢索的法律資料助手:按案情檢索相似案例、歸納裁判觀點,並展示原文來源。

方案說明

整體鏈路與通過阿里雲Milvus知識庫搭建智能客服問答應用完全一致:控制台定義標籤 → 按標籤大量匯入資料 → 發布版本 → SDK 檢索(可按標籤過濾)→ 大模型產生帶來源標註的回答 → Flask 提供問答頁面。工程代碼(kb_client.py、upload.py、app.py、templates/index.html、start.sh)請直接複用該文,本文只說明法律情境需要改動的部分:案例標籤(含數實值型別標籤)、檢索參數、提示詞約束,以及必須補充的空結果處理。

建議第一版只選擇一個罪名或一個合規主題的 10~50 篇材料,驗證後再擴大範圍。整體耗時約 25~40 分鐘,其中文檔解析耗時取決於資料數量與大小。

重要

本方案是資料檢索協助工具輔助,不產出法律意見。檢索結果與模型歸納都必須保留人工複核環節。

前提條件

  • 已建立 Milvus 知識庫並記錄知識庫 ID(形如 kd-803ae9b10cc31)。

  • 已使用主帳號建立 RAM 使用者、勾選使用永久 AccessKey 訪問,並授予系統策略 AliyunMilvusFullAccess。

  • 已準備支援 OpenAI chat/completions 協議的大模型地址與 API Key。

  • 本地已安裝 Python 3.8 或以上版本,並已按上文教程建好工程目錄。

  • 已完成資料的授權與脫敏檢查,詳見法律情境注意事項。

步驟一:定義案例標籤

在知識庫詳情頁的基本資料下方單擊標籤 > 管理,添加以下四個標籤。欄位類型在標籤名輸入框右側的下拉框中選擇,可選 string、int64、list、float32、bool。

把裁判年份定義為 int64 並不能用於範圍查詢。檢索介面目前不支援整型範圍運算式,定義為 int64 的作用是服務端會把字串數字轉換成整數,從而讓 =、in、not in 正常工作。因此「近三年的案例」這類需求只能由用戶端先算出年份列表,再用 in 傳入,例如 [2024, 2025, 2026];不要在樣本中使用 ≥ 或 ≤。年份跨度很大時沒有等價的高效寫法,需要拆成多次查詢。

標籤名

欄位類型

說明

樣本值

docType

string

資料類型

裁判文書、法規、合規制度

court

string

審理法院

某某市第一人民法院

caseType

string

案由

合約詐騙、交通肇事

judgmentYear

int64

裁判年份

2025

重要

標籤管理對話方塊是整體儲存的。對話方塊開啟後標籤列表為非同步載入,請等到已有標籤全部顯示出來,再添加新標籤並單擊完成;否則可能以"空列表 + 新標籤"整體覆蓋,導致已有標籤定義被清除。

關於標籤類型與取值,有兩點需要注意:

  • int64 類型的標籤,在 documents.jsonl 中可直接寫 JSON 數字(如 2025),過濾時 value 傳數字或字串均可命中。

  • 標籤值允許為空白字串。法規、合規制度沒有審理法院,可寫 "court": "",控制台會顯示為 court=,且後續可用 court = "" 作為過濾條件精確命中這類資料。

步驟二:準備資料與匯入清單

  1. 把資料放進 documents/ 目錄,支援 PDF、DOCX、Markdown、TXT 等格式。案號、法院、裁判日期應保留在本文或檔案名稱中——實測把案號寫在本文首行後,可以用案號直接檢索到對應文書。

  2. 建立 documents.jsonl,為每篇資料標註資料類型、法院、案由與裁判年份。

    {"path": "documents/criminal-case-001.md", "metadata": {"docType": "裁判文書", "court": "某某市第一人民法院", "caseType": "合約詐騙", "judgmentYear": 2025}}
    {"path": "documents/criminal-case-002.md", "metadata": {"docType": "裁判文書", "court": "某某市第二人民法院", "caseType": "合約詐騙", "judgmentYear": 2023}}
    {"path": "documents/law-excerpt.md", "metadata": {"docType": "法規", "court": "", "caseType": "刑事", "judgmentYear": 2024}}
    {"path": "documents/compliance-policy.md", "metadata": {"docType": "合規制度", "court": "", "caseType": "合規", "judgmentYear": 2024}}
  3. 裁判文書需要保留案情、裁判理由與結論的上下文,可在知識庫詳情頁的處理策略中單擊建立策略調整切片粒度。最大分段長度的單位是字元(預設 512),建議設定為 770~1150 字元(約相當於 512~768 tokens),盡量讓"爭議焦點 + 裁判理由"落在同一個切片內。

  4. 建議同一案由下同時匯入結論不同的案例。法律檢索的價值恰在於呈現分歧:實測匯入兩份結論相反的合約詐騙判決後,模型會同時引用並明確指出"一案認定構成共同犯罪,另一案因缺乏通謀證據不認定",比只匯入單一結論的資料更有參考價值。

步驟三:配置檢索參數與提示詞

在 config.json 中按法律情境調整 aliyun.knowledge_base_version、retrieval 與 scenario。

{
  "aliyun": {
    "knowledge_base_version": "LATEST_PUBLISHED"
  },
  "retrieval": {
    "page_size": 8,
    "candidate_count": 80,
    "min_score": 0.25,
    "semantic_weight": 0.4,
    "enable_query_expansion": true,
    "rerank_model_name": "qwen3-rerank",
    "tag_filter": {
      "relation": "and",
      "conditions": [
        {"field": "docType", "op": "=", "value": "裁判文書"}
      ]
    }
  },
  "scenario": {
    "title": "法律與合規案例檢索",
    "system_prompt": "你是法律資料檢索助手,不提供最終法律意見。只依據檢索資料歸納事實、爭議焦點、裁判觀點和依據,逐項標註[來源N];不同案例結論不一致時分別陳述;檢索資料為空白或與問題無關時,只回複“資料不足,建議人工複核”,禁止引用任何未出現在檢索資料中的法律法規、司法解釋或案例。",
    "image_enabled": false,
    "sample_questions": [
      "虛構履約能力騙取貨款時,如何判斷合約詐騙共同犯罪?",
      "交通肇事後自首對量刑有哪些影響?",
      "哪些案例討論了主犯和從犯的區分?"
    ]
  }
}

參數說明:

  • semantic_weight=0.4:法律檢索大量使用案號、罪名與法律術語,語義權重調低、關鍵詞權重相應提高。實測用完整案號提問可精確命中對應文書。可通過檢索結果中的 scoreDetails(含 keywordScore 與 semanticScore 兩項)校準該值。

  • candidate_count=80 並啟用 qwen3-rerank:擴大候選集後由重排模型做類案相關性排序。需注意重排分數與向量分數不是同一量綱,與 min_score 疊加時可能過濾掉一部分結果,調參時建議先固定其中一項。

  • knowledge_base_version 建議保持 LATEST_PUBLISHED,詳見下方說明。

版本號碼不要寫死

LATEST_PUBLISHED 表示自動使用最新的發行版本。也可以填寫明確的版本號碼(如 v2)來鎖定版本。合規情境通常需要追溯「哪一版資料支撐了哪次判斷」,建議在應用的調用日誌中記錄本次實際使用的版本號碼。刪除某個版本後,該版本會立即不可檢索(後台再非同步清理資料),因此刪除前必須先把仍鎖定它的應用程式切換到新版本。此外:

重要

同時最多隻能存在 3 個發行版本。達到上限後發布版本按鈕會置灰,而頁面仍會顯示"當前有 N 條待發布變更",需要先在版本記錄中刪除不再需要的舊版本。一旦刪除了應用正在使用的版本,檢索會立即返回 404 Knowledge base version ... does not exist,頁面上表現為每次提問都失敗。因此若要鎖定版本,必須同時建立"刪除舊版本前先更新配置"的流程。

標籤過濾只能使用三個運算子

實測在目前的版本中,tag_filter.conditions 裡的 op 只有 =、in、not in 會真正生效:

運算子

行為

樣本

=

精確匹配,int64 標籤傳數字或字串均可

{"field": "judgmentYear", "op": "=", "value": 2025}

in

枚舉匹配

{"field": "judgmentYear", "op": "in", "value": [2024, 2025]}

not in

排除枚舉

{"field": "docType", "op": "not in", "value": ["合規制度"]}

>、≥、<、≤

條件被忽略,返回全部資料(不報錯)

—

≠、empty、not empty、start with、end with

條件被忽略,返回全部資料

—

contains、not contains

是 in/not in 的別名,不是字串包含;按字串片段傳值只會返回 0 條

—

重要
  • 傳入上表中不生效的運算子時,介面不會報錯,而是返回未經過濾的全部資料。在法律情境下這意味著本應被排除的案例也會進入大模型上下文,且從頁面上看不出異常。因此:

  • 不要用 >、≥ 做年份區間篩選,請改用 in 枚舉年份,例如 {"field": "judgmentYear", "op": "in", "value": [2023, 2024, 2025]}。

  • 不要用 empty 判斷標籤為空白,請改用 {"field": "court", "op": "=", "value": ""}。

  • 配置好過濾條件後,務必與不加過濾的結果對比總條數,確認過濾確實生效;若兩者相同,說明該條件未被應用。

  • 若 op 寫成 eq、==、like 等別名,會返回 400 Unsupported tag filter operator,導致每次提問都失敗。注意該報錯資訊中列出的 "Supported operators" 包含上表中不生效的運算子,不能作為可用清單。

必須處理"檢索結果為空白"的情況

複用的 app.py 中,llm_answer() 在檢索結果為空白時仍會調用大模型,此時上下文為空白字串,模型會完全依據自身知識作答。實測提問知識庫未覆蓋的問題(如"智慧財產權侵權的賠償數額如何計算"),模型輸出了大段賠償計算規則,並偽造了 [來源1:《…懲罰性賠償的解釋》第2條] 這樣的來源標註,而此時 sources 為空白。法律情境下這類輸出極具誤導性,必須在代碼層攔截:

def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
    if not LLM.get("enabled", True):
        return "大模型未啟用;請查看下方檢索結果。"
    if not results:
        return "知識庫中沒有檢索到與該問題相關的資料,無法作答。建議補充相關材料後重試,或轉人工複核。"
    ...

僅靠提示詞約束並不可靠——即使 system_prompt 已寫明"資料不足時明確說明",模型仍會作答。上述兜底加入後,未覆蓋的問題會穩定返回提示,且對有檢索結果的正常提問沒有影響。

步驟四:上傳、發布與驗證

  1. 上傳資料(MetaFields 對整批生效,指令碼會先按標籤分組再分批提交)。

    python upload.py --manifest documents.jsonl
  2. 到控制台資料管理頁確認資料狀態為處理完成。上傳介面返回成功僅表示已提交非同步解析。

  3. 在版本管理頁單擊發布版本,完成嚮導後確認新版本狀態為發行。若按鈕置灰,請先刪除不再需要的舊版本(注意上文關於版本上限的說明)。

  4. 啟動服務並驗證。

    python app.py
    curl -sS http://127.0.0.1:7860/api/ask \
      -H 'Content-Type: application/json' \
      -d '{"question":"虛構履約能力騙取貨款時,如何判斷合約詐騙共同犯罪?"}'

按以下五條驗收:

  • 頁面能正常開啟,提交問題後同時返回答案與檢索來源。

  • 回答中的 [來源N] 能在下方來源區找到對應的資料切片。

  • 用完整案號提問,能精確命中對應的裁判文書。

  • 提問知識庫未覆蓋的內容時,返回"資料不足"提示,且不出現任何法條引用。這一條務必實測,是本情境最關鍵的驗收項。

  • 補充資料並重新發布版本後,頁面能檢索到新內容。

法律情境注意事項

  • 只使用有權公開和處理的資料,上傳前完成授權檢查。裁判文書如包含個人資訊,應先脫敏——資料切片會作為上下文發送給大模型,屬於資料出域。

  • 頁面應有常駐免責聲明。樣本頁面唯寫了"回答由發行知識庫內容產生",建議改為明確提示"本結果僅為資料檢索輔助,不構成法律意見,請由專業人員複核"。

  • 同案由下保留結論不同的案例,並在提示詞中要求分別陳述,避免模型把個案結論表述為通則。

  • 按資料類型建立獨立入口。例如對外諮詢入口用 docType = 法規 限定只檢索公開法規,內部研判入口再放開裁判文書。

  • 檢索結果中的 tags 欄位不會回顯寫入的法院與裁判年份,它與 AddDocuments 的 MetaFields 是兩個不同的欄位,也沒有參數可以開啟返回,為空白不代表上傳失敗。需要在回答中標註這些資訊時,按檢索結果的 documentId 關聯 documents.jsonl 後再拼進上下文。

  • 線上部署不要繼續使用 Flask 程式開發伺服器,應改用生產 WSGI,並補充密鑰管理、鑒權、審計與限流。

常見問題

現象

原因與處理

每次提問都返回 404 Knowledge base version ... does not exist

配置中寫死的版本號碼已被刪除或從未發布。改用 LATEST_PUBLISHED,或填寫版本管理頁中實際存在的版本名。

提問返回 500,日誌顯示 400 Unsupported tag filter operator

op 使用了 eq、==、like 等別名,改用 =、in、not in。

加了標籤過濾但結果條數與不加過濾時完全一樣

使用了不生效的運算子(如 >、≥、≠、empty)。改用 =、in、not in,詳見步驟三。

按年份區間篩選沒有效果

數值比較子當前不生效,請用 in 枚舉年份。

按標籤過濾後總是 0 條

標籤名拼字與寫入時不一致(介面不會報錯,只返回 0 條),在知識庫詳情頁 → 標籤 → 管理中核對;另外 contains 運算子實測也會返回 0 條。

問了知識庫裡沒有的問題,卻得到了看似規範的法條引用

llm_answer() 在檢索結果為空白時仍調用了大模型。按步驟三加入空結果兜底。

檢索結果的 tags 為空白

目前的版本不回填標籤,需自行用 manifest 反查。

上傳返回 400 No OSS document can be registered.

整批檔案都因同名被去重。修改檔案名稱或先在控制台刪除舊資料。

檢索時報"失敗:None"

複用代碼中 _check() 的判斷有誤(getattr(body, "code", 0) != 0),成功響應的 code 為 None。改為 getattr(body, "code", None) not in (None, 0, "0")。

啟用重排後結果反而變少

重排分數與向量分數量綱不同,與 min_score 疊加會過濾更多結果。可先降低 min_score 或關閉重排對比效果。