iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
AI Engineering

從零搞懂 RAG 評測之旅系列 第 26 篇

DAY26. 檢索器與生成鏈,完成第一版 RAG

  • 分享至 

  • xImage
  •  

昨天我們已經能從向量庫撈出最相關的 chunks。今天要把檢索接上語言模型:用 qwen3:8b 根據撈回來的內容生成答案,並且同時顯示答案引用了哪些來源。做完之後,你就擁有一個可以運作的 RAG 系統,也是 DAY27 開始要評估的對象。

今日目標

  1. 把向量庫轉成檢索器(retriever)
  2. 設計「只根據參考資料回答」的 prompt
  3. 用 LCEL 把檢索、prompt、模型串成一條鏈
  4. 提問並檢查答案與來源
  5. 測試知識庫以外的問題,觀察系統如何反應

RAG 的完整流程

整條鏈的資料流向如下:

問題 ─┬─▶ 檢索器 ─▶ 相關 chunks ─┐
      │                          ├─▶ Prompt ─▶ qwen3:8b ─▶ 答案
      └──────────────────────────┘

問題會同時走兩條路:一條進檢索器撈出參考資料,另一條原封不動帶到 prompt。兩者在 prompt 裡會合,一起交給模型。

Step 1:建立檢索器

檢索器是對向量庫的一層包裝,輸入問題,輸出相關的 Document 列表。先在 build_rag.py 更新匯入並加入設定:

from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough

LLM_MODEL = "qwen3:8b"

注意第一行要把原本的 OllamaEmbeddings 匯入一起改掉。接著測試檢索器:

def get_retriever(vectorstore, k: int = 4):
    return vectorstore.as_retriever(search_kwargs={"k": k})

k 是每次檢索回傳幾個 chunks,也就是昨天 similarity_search 的 k。我們先設 4,這也是之後 DAY29 調參的對象之一。

Step 2:設計 Prompt

Prompt 是 RAG 品質的關鍵之一。它必須明確告訴模型:只能根據參考資料回答,找不到就說不知道。

PROMPT = ChatPromptTemplate.from_template(
    """你是一位嚴謹的技術問答助理,請使用繁體中文回答。
請「只根據」下方提供的參考資料回答問題。
如果參考資料中找不到答案,請直接回答「根據提供的資料,我無法回答這個問題」,不要自行補充或編造。

參考資料:
{context}

問題:{question}

回答:"""
)


def format_docs(docs):
    return "\n\n".join(f"[{i}] {doc.page_content}" for i, doc in enumerate(docs, 1))

format_docs 把檢索到的 chunks 編號後串成一段文字,填進 {context}。昨天做的實驗已經證明,向量搜尋不管相不相關都會回傳 k 筆結果,所以這句「找不到就說不知道」不是多餘的,它是擋住幻覺的第一道防線。

Step 3:用 LCEL 組成生成鏈

LCEL(LangChain Expression Language)用 | 符號把元件串成管線,前一個的輸出會成為後一個的輸入。

def get_llm():
    return ChatOllama(
        model=LLM_MODEL,
        base_url=OLLAMA_URL,
        temperature=0,
        reasoning=False,
    )


def build_rag_chain(vectorstore, k: int = 4):
    retriever = get_retriever(vectorstore, k)

    generate = (
        RunnableLambda(
            lambda x: {"context": format_docs(x["context"]), "question": x["question"]}
        )
        | PROMPT
        | get_llm()
        | StrOutputParser()
    )

    return {
        "context": retriever,
        "question": RunnablePassthrough(),
    } | RunnablePassthrough.assign(answer=generate)

這段是今天最重要的部分,逐段拆解:

片段 作用
{"context": retriever, "question": RunnablePassthrough()} 同一個問題同時送進兩處:retriever 撈出 chunks,RunnablePassthrough() 把問題原樣保留
RunnablePassthrough.assign(answer=generate) 保留前面的 context 與 question,並額外加上 answer 欄位
generate 把 chunks 格式化 → 套入 prompt → 交給模型 → 取出純文字答案

關於兩個模型設定:

  • temperature=0:讓輸出盡量穩定,問答與評估場景通常不需要創意。
  • reasoning=False:qwen3 預設會先進入「思考模式」,產生一大段推理過程才回答。RAG 問答不需要,關掉可以讓回應更快、輸出更乾淨。

Step 4:提問並檢查來源

加入一個提問函式:

def ask(chain, question: str):
    result = chain.invoke(question)
    print(f"\n問題:{question}")
    print(f"回答:{result['answer']}")
    print("來源:")
    for doc in result["context"]:
        source = os.path.basename(doc.metadata["source"])
        print(f"  - {source}(起始位置 {doc.metadata.get('start_index')})")

再把 __main__ 改成最終版本(沿用昨天的 REBUILD 機制):

REBUILD = False  # 修改文件內容或切割參數後,才需要改成 True

if __name__ == "__main__":
    if REBUILD or not os.path.exists(PERSIST_DIR):
        docs = load_documents()
        chunks = split_documents(docs)
        vectorstore = build_vectorstore(chunks)
    else:
        vectorstore = get_vectorstore()

    chain = build_rag_chain(vectorstore)

    ask(chain, "200 和 201 狀態碼有什麼差別?")
    ask(chain, "git revert 和 git reset 有什麼不同?")
    ask(chain, "docker run -p 8080:80 是什麼意思?")
    ask(chain, "ACID 的四個特性是什麼?")

執行:

uv run python build_rag.py

第一次呼叫 qwen3:8b 需要先把模型載入記憶體,會等比較久,之後就快了。

請對每一題檢查三件事:

  1. 答案是否正確:對照 data/ 裡的原文確認
  2. 來源是否合理:列出的 chunks 是否真的來自相關文件
  3. 答案有沒有超出來源:模型有沒有講了參考資料裡根本沒有的內容

Step 5:問一個知識庫以外的問題

接著重現昨天的實驗,但這次是讓模型來回答:

ask(chain, "今天台中的天氣如何?")

因為檢索器仍然會回傳 4 筆「最接近」的 chunks,但內容和天氣毫無關係,模型應該會依照 prompt 的指示,回答它無法回答。

小結

今天完成了第一版完整的 RAG,明天開始進入評估:我們會準備 20 到 30 筆有標準答案的測試問題,讓這個 RAG 跑一遍,並用 RAGAS 的四個核心指標量化它的表現,建立第一份 baseline。


上一篇
DAY25. Embedding 與向量資料庫
下一篇
DAY27. 用 RAGAS 建立評估 baseline
系列文
從零搞懂 RAG 評測之旅 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言