iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
AI & Data

RAG × Agent:從知識檢索到智慧應用的30天挑戰系列 第 12

Day 12|實戰向量資料庫:用 ChromaDB 查詢法規內容

  • 分享至 

  • xImage
  •  

昨天已經教學大家要怎麼做 Chunking 了,今天就是要教學如何把它放進一個可以用來查詢的資料庫,這邊我們也會試著提問,看產出。
雖然我昨天的教學只有教你怎麼切章的部分,但我實際上還有將章節、條款一筆筆的切細,當然你也可以試著把之前 chunk 的資料直接存進來練習這邊的操作也可以,只不過你可能要做一些刪減。


1. 建立一個 ChromaDB 資料庫
ChromaDB 是一個「向量資料庫」。我們先建立一個會存在地端的資料庫:

import chromadb

# 建一個存到 ./law_db 資料夾的 ChromaDB
client = chromadb.PersistentClient(path="./law_db")
coll = client.get_or_create_collection("laws")

「大家平常只要用 PersistentClient(path="...") 就好,因為它會真的存檔案起來,這樣也方便你去看裡面的內容;如果只是想快速試,Client() 就行;要連到 server 才用 HttpClient()。」

2. 準備 Embedding 模型
因為我們要做「語意檢索」,需要先把文字轉成向量。這邊用的是多語言的模型(可以自己換別的):

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

3. 幫每一個 chunk 產生唯一 ID
其實我覺得 Chroma 的資料庫就很像是一個圖書館,你要找到那本書肯定是需要靠索書號甚麼的來輔助你尋找,所以我們每一筆的資料都應該幫她貼上一個標籤,為了不讓系統幫你亂亂貼,我們就應該自己設計一個固定的標籤。
所以為了避免重複寫入,我們用「章名 + 條號 + 文字」去算一個 SHA1:

import hashlib

def make_id(c):
    # 第 一 章 總則|第 3 條|政府應推動資通安全(範例)
    base = c["chapter"] + "|" + c["section_id"] + "|" + " ".join(c["text"].split())
    return hashlib.sha1(base.encode("utf-8")).hexdigest()

4. 把所有 chunks 存進去
為了避免他一次把一堆資料全部丟進去給電腦吃,電腦直接炸開,所們要批次丟資料,這邊先設定每次處理 3 筆,畢竟我們資料量不大,但你就視情況調整,如果資料量很多可以考慮設定 128、256,他的結果其實都會一樣,就是速度快慢而已,batch 設小會安全但較慢
這裡我們會存三種東西:ID(避免重複)、文件內容(chunk 文字)、章/條等欄位(這些是 metadata,用來過濾或顯示)
另外,在切 chunk 之前記得先做基本清理。法條通常夠乾淨,但還是建議做幾件小事:

  • 把不是句尾的換行黏起來(避免句子被硬切斷)
  • 移除頁碼或版面序號(例如行首的 1 2 這種)
  • 縮減多餘空白(多空格→單一空格)
  • 保護章/條標題(避免被合併掉)做完再切,後面存庫跟查詢都會比較穩。
    當然這也是我自己遇到的情境,不一定適用任何情境。
batch = 3
buf_ids, buf_docs, buf_meta = [], [], []

把每一條 chunk 暫存起來。

for c in chunks:
    buf_ids.append(make_id(c))
    buf_docs.append(c["text"])
    buf_meta.append({"chapter": c["chapter"], "section_id": c["section_id"]})

    # 批次存入資料庫,存好三筆就清空放新的上去
    if len(buf_ids) == batch:
        emb = model.encode(buf_docs, convert_to_numpy=True, normalize_embeddings=True)
        coll.upsert(ids=buf_ids, documents=buf_docs, metadatas=buf_meta, embeddings=emb)
        buf_ids, buf_docs, buf_meta = [], [], []

如果還有不到三筆的資料,這邊還是會把他補上

if buf_ids:
    emb = model.encode(buf_docs, convert_to_numpy=True, normalize_embeddings=True)
    coll.upsert(ids=buf_ids, documents=buf_docs, metadatas=buf_meta, embeddings=emb)

5. 試著提問
我們的法規資料都切好跟存好了,接下來就是要做提問測試看看他的回答。

q = "什麼是關鍵基礎設施?"
q_emb = model.encode([q], normalize_embeddings=True) #先把問題轉成向量

# 用向量資料庫查詢
res = coll.query(
    query_embeddings=q_emb,
    n_results=5, # 看你要抓相關的幾筆出來
    include=["documents", "metadatas", "distances"]  # 除了內容我們還會抓的東西
)

下面這邊是我自己在看結果用的,你們也可以測試看看,順帶一提,距離越小代表越相近(越相關)

for i, (doc, meta, dist) in enumerate(zip(res["documents"][0], res["metadatas"][0], res["distances"][0]), 1):
    print(f"[{i}] {meta['chapter']} | {meta['section_id']} | 距離={dist:.4f}")
    print(doc[:160].replace("\n", " "), "\n---")

放上我這邊的結果照片:
https://ithelp.ithome.com.tw/upload/images/20250926/20178897sKLnGLYOUa.png
大家跑出來的結果可能不完全一樣,因為 embedding 和檢索會有些差異。

明天我們會開始試試看怎麼跟 LLM 結合,希望今天的內容大家吸收的了,明天繼續努力!


上一篇
Day 11|實戰 Chunking:從《資通安全管理法》學習切分技巧
下一篇
Day 13|實戰 Generation Pipeline:Chroma 檢索 × Ollama 生成的完整流程
系列文
RAG × Agent:從知識檢索到智慧應用的30天挑戰13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言