iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
AI & Data

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

Day 13|實戰 Generation Pipeline:Chroma 檢索 × Ollama 生成的完整流程

  • 分享至 

  • xImage
  •  

今天要實作的內容是 Generation pipeline 的部分,就是怎麼將提問跟 RAG 檢索到的資料全部丟到 LLM 給他做回應。

因為這邊後面的實作內容我檔案和昨天的檔案其實我是分開來的,所以可能還是會提及一點點昨天的內容,所以這邊再補充昨天的內容,維度只跟「檢索」有關:建索引跟查詢都要用同一個嵌入模型(同一個名稱),這樣向量的維度與語意空間才會一致,所以建議大家不要亂改嵌入模型。
至於 LLM 要用哪個不重要,因為他只負責生成答案,跟維度無關。


一、前置設定

在開始之前我們需要先下載好要使用的模型,這邊是在 使用 Ollama 的 mistral,所以需要再命令提示字元輸入:

ollama run mistral

如果你對他聊天有回應就是完成了。https://ithelp.ithome.com.tw/upload/images/20250927/20178897axGK49CszD.png

二、流程

1. 連結 Chroma & 設定嵌入模型(之前實作過的)

# 連結 Chroma,要跟之前設定的一樣
client = chromadb.PersistentClient(path="./law_db")
coll = client.get_or_create_collection("laws")

# 設定嵌入模型
embed_model  = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

2. 將查詢轉成向量(之前實作過的)

def embed(texts):
    vec = embed_model.encode(texts, convert_to_numpy=True, normalize_embeddings=True)
    return vec.tolist()

怕上次沒有說明很清楚這邊再繼續解釋明白點,這邊是把每句文字編成一個 384 維的向量(這邊的維度是由嵌入模型決定的),並做 L2 正規化;最後回傳 list[list[float]] 讓 Chroma 使用。

3. 相似檢索(Top-k) (之前實作過的)

def search_chunks(query, k=4):
    # 獲取先前轉成向量的查詢
    q_vec = embed([query])
    # 執行查詢,找最相似的 k 筆
    res = coll.query(
        query_embeddings=q_vec,
        n_results=k,
        include=["documents", "metadatas", "distances"],
    )
   
    # 因為 Chroma 支援一次丟多個查詢,回傳會是「每個查詢一個清單」的巢狀結構。
    # 這裡只丟 1 個查詢,所以拿第 0 個即可
    docs = res["documents"][0]
    metas = res["metadatas"][0]
    dists = res["distances"][0]
    
    # 回傳 items:每一筆都有 章名、條號、距離、文字,方便下一步組 Prompt
    items = []
    for doc, meta, dist in zip(docs, metas, dists):
        items.append({
            "chapter": meta.get("chapter", ""),
            "section_id": meta.get("section_id", ""),
            "distance": float(dist),
            "text": doc
        })
    return items

因為不想要弄得太複雜、太難解釋,這邊只考慮你只提問一個問題的情況。

  1. 組 Prompt(把段落塞進去)
def build_prompt(query, hits):
    blocks = []

    # 取得欄位數值,做基本處理
    for i, h in enumerate(hits, 1):
        chapter  = h.get("chapter", "")
        section  = h.get("section_id", "")
        distance = float(h.get("distance", 0.0))
        text     = (h.get("text", "") or "").replace("\n", " ")

        header = f"[{i}] {chapter} | {section} | 距離={distance:.4f}"   # (來源+分數)
        body   = f"```text\n{text}\n```"    # 內文
        blocks.append(header + "\n" + body)

    context = "\n\n".join(blocks) if blocks else "(本次查無相關段落)"

    prompt = textwrap.dedent(f"""
    你是一位專業的台灣資安法規顧問。可以參考相關文件內容回答;
    若文件中沒有明確資訊,請回答「文件中沒有相關內容」,不要亂猜。
    回答請用中文條列式,並在每條末尾標註對應來源索引。

    【相關文件內容】
    {context}

    【問題】
    {query}
    """).strip()

    return prompt

這邊稍微解釋一下裡面的功能:

  • get(..., ""):當「這個 key 不存在」時,回傳後面的預設值""。如果 key 存在但值是 None,get 會回 None(不會套用預設)。所以程式裡用的 text = (h.get("text", "") or "")
    就能把 None 也安全地轉成空字串。
  • replace("\n"," "):把段落的換行變成空格,攤平文字,避免 LLM 把奇怪斷行誤當語意切分。

後面的 prompt 我們就有定義他的角色身分,他需要的參照內容有甚麼,我們丟問題給他後又有甚麼回應限制,這部分的話其實不一定要照著我的格式來寫,但大致可以參考一下,多嘗試提問才是重點。

  1. 丟給 LLM 產生答案
def ask_ollama(prompt, llm_model="mistral"):
    resp = requests.post(
        "http://localhost:11434/api/generate",
        json={"model": llm_model, "prompt": prompt, "stream": False},
        timeout=120
    )
    return resp.json().get("response", "").strip()

在這個階段會指定要用的 LLM。用 Ollama 的話,請先把模型拉好並啟動服務(ollama run mistral 會自動下載並啟動)。模型大小要自行評估:檔案太大會佔用磁碟與記憶體;如果是一般筆電,建議先用 7B~8B 級的量化模型。
若想讓中文表達更自然,也可改成 qwen2.5:7b 或 llama3.1:8b 等;不過是否產生幻覺主要還是取決於引用內容 + 提示詞限制 + temperature,不是只靠換模型。
Ollama 通常都是使用 http://localhost:11434/api/generate 這是 Ollama 本機伺服器的預設 API 端點(port 11434),他會用 POST 送 JSON。
timeout 是設定這是 HTTP 客戶端的等待上限(用 requests 設的),超過就丟出超時錯誤。stream=False 會一次性拿到完整回覆。
這些設定完提問後會解析回來的 JSON,抓出模型產生的主體文字在 response 欄位,並用 strip() 去頭尾的空白。

  1. 實際操作(結合上述功能)
query = "什麼是關鍵基礎設施?"
hits = search_chunks(query, k=4)
prompt = build_prompt(query, hits)
answer = ask_ollama(prompt, llm_model="mistral")
print(answer)

這邊圖片太小我很抱歉 TT
https://ithelp.ithome.com.tw/upload/images/20250927/201788976TFpoYoy6o.png


這樣就完成了我們的 RAG 系統,其實我自己也有用同樣的模型然後直接進行提問,這邊可以給大家看一下他的回應。
https://ithelp.ithome.com.tw/upload/images/20250927/20178897tdaqq8KcOm.png
可以很明顯地看到他對於這方面的知識確實是不足的,如果沒有給他參考資料,他就會開始胡言亂語。
今天的部分就到這邊,明天可能會想談談關於 RAG 相關的議題,或是繼續其他方面的研究,先到這啦~


上一篇
Day 12|實戰向量資料庫:用 ChromaDB 查詢法規內容
系列文
RAG × Agent:從知識檢索到智慧應用的30天挑戰13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言