EN

4.3 上下文注入方法

把檢索到的資料放進提示詞,看起來只是貼上去,但放法和順序都有差。

RAG下一篇 →

上下文注入(context injection) 指的是把檢索到的資料,放進提示詞的過程。最簡單的做法就是直接串接,但有幾個細節會影響模型的表現。

最基本的做法:直接串接

把每一段資料依序放進提示詞,中間用分隔隔開:

def build_prompt(question, chunks):
    blocks = []
    for number, chunk in enumerate(chunks, start=1):
        blocks.append(f"[{number}](來源:{chunk['source']},第 {chunk['page']} 頁)\n{chunk['text']}")
    context = "\n\n".join(blocks)
    return f"{SYSTEM}\n\n# 參考資料\n{context}\n\n# 問題\n{question}"

這個做法簡單、可預測,是絕大多數 RAG 系統的起點。(完整可執行的版本在 4.4 和 4.5。)

每一段資料要怎麼包裝

單純貼上文字不夠,通常會加上:

  • 編號:[1]、[2],讓模型可以引用。
  • 來源資訊:檔名、頁數、標題,來自第 3 章準備好的 metadata。
  • 日期:資料可能有新舊版本,讓模型能判斷。
  • 清楚的邊界:每一段用空行、分隔線或標籤隔開,避免兩段混在一起。

用標籤包起來的寫法,常見而且清楚:

<資料 編號="1" 來源="員工手冊.pdf" 頁數="12">
員工在同一公司服務滿六個月未滿一年者,每年有三天特別休假。
</資料>

資料的順序

放進提示詞的順序會影響結果。研究發現,當提示詞很長時,模型比較能有效利用放在開頭和結尾的資訊,夾在中間的容易被忽略。這個現象常被叫做「迷失在中間(lost in the middle)」,出自 Liu 等人 2023 年的研究。

實務上的做法:

  • 最相關的放在最前面:也就是直接用檢索的相似度排序。
  • 如果放很多段,可以考慮把最重要的放在最前面和最後面。
  • 不要一次塞太多,資料越多,這個問題越明顯。

這個現象的程度會隨模型而不同,新的模型可能改善很多,所以最好用自己的資料測試。

其他注入的方式

去除重複

檢索回來的幾塊可能內容重疊(尤其是用了 overlap 的時候)。放進去之前先去重,節省空間。

合併相鄰的塊

如果檢索到的兩塊在原文裡本來就相連,可以合併成一段,保持上下文連貫,讀起來比兩段零碎的更自然。

補上前後文

只找到一小段時,可以把它前後相鄰的塊一起帶進來,提供完整的上下文。這就是 3.4 提到的「小塊找、大塊給」。

先壓縮再注入

把檢索到的內容先用 LLM 摘要,或只抽出和問題相關的句子,再放進提示詞。可以省空間、減少雜訊,但多一次模型呼叫,成本和延遲增加,而且摘要也可能漏掉細節。

分批處理

資料實在太多時,可以把它分成幾批,每批各自請模型回答,再把幾個答案合併。這類做法(有時叫 map-reduce)比較複雜,通常只在必要時使用。

位置:放在 system 還是 user

很多 LLM 的介面把訊息分成 system(系統指示)和 user(使用者訊息)。常見的放法:

  • 指示放在 system:角色、規則。
  • 參考資料和問題放在 user:因為每次提問的資料都不一樣。

不同的模型和服務對這兩個位置的處理方式不同,要看你使用的 API 說明。

← 全部文章