4.4 管理上下文長度限制
模型一次能讀的長度有限,資料放不下的時候該怎麼取捨。
每個 LLM 一次能處理的長度有一個上限,叫做上下文視窗(context window),通常用 token 計算。RAG 要把資料放進提示詞,就一定會碰到這個限制。
預算怎麼分配
上下文視窗是輸入和輸出共用的預算。要放進去的東西包括:
- 指示:長度固定。
- 問題:通常很短。
- 預留給回答的空間:要先扣掉,不然資料塞滿了,模型沒空間寫答案,會被截斷。
- 檢索到的資料:這是剩下可以調整的部分。
換句話說:資料可用的空間 = 視窗大小 − 指示 − 問題 − 回答預留。
就算視窗很大,也不該塞滿
現在有些模型的視窗已經很長,是不是就可以把檢索到的資料全都放進去?不建議,原因是:
- 成本:輸入越長,費用越高。
- 速度:輸入越長,回應越慢。
- 品質:塞進越多不相關的內容,雜訊越多,模型越容易被干擾,也更容易出現 4.3 提到的「迷失在中間」。
所以「放得下」和「應該放」是兩回事。少而精,通常比多而雜好。
計算長度:token
模型的長度單位是 token,不是字數。
- 英文大約一個單字是一到兩個 token。
- 中文一個字大約是一個或更多 token,實際比例依模型的分詞方式而定。
要精確計算,應該用該模型對應的 tokenizer,或是 API 提供的 token 計數功能。粗略估計的時候,可以先用字數,並預留安全空間。
放不下的時候
1. 減少檢索的數量(調整 k)
最直接的做法:只留最相關的前幾段。
2. 設相似度門檻
分數太低的,代表可能根本不相關,即使名額還沒滿也不放。這樣有時候放進去的資料比 k 還少,這是好事。
3. 依預算逐段加入
從最相關的開始,一段一段放進去,快超過預算就停下來。一個簡單的做法:
def pick_within_budget(chunks, max_chars):
"""依序放入資料,超過字數預算就停止(最相關的先放)"""
picked, used = [], 0
for chunk in chunks:
if used + len(chunk["text"]) > max_chars:
break
picked.append(chunk)
used += len(chunk["text"])
return picked
我拿 3 段資料、預算 70 字去跑,結果只放進了最相關的 2 段,第 3 段因為放不下被擋在外面:
預算 70 字,放進了 2 段(共檢索到 3 段)
(這裡為了示範用字數當預算;實際要用 token。)
4. 壓縮內容
把長段落先摘要,或只抽出和問題相關的句子,再放進去。要小心摘要可能遺漏關鍵細節。
5. 調整分塊大小
如果塊本來就切得太大,每塊都吃掉很多空間,可以回頭調整分塊的大小(第 3 章)。
6. 換上下文更長的模型
當資料真的需要很多,而且不能被省略的時候,可以換視窗更長的模型。但仍然要考慮成本和品質。
多輪對話的特別問題
如果是連續的對話,歷史訊息也要佔空間。每多一輪,提示詞就更長。常見的處理方式:
- 只保留最近幾輪。
- 把較早的對話摘要成一小段。
- 每一輪都重新檢索,不要把上一輪檢索的資料一直累積著。
另外,使用者的追問常常省略主語,例如「那加班呢?」,直接拿去檢索會找不準。通常要先請模型把追問改寫成完整的獨立問題(「加班費怎麼計算?」),再去檢索。
一個取捨的心法
- 先從少的開始(例如 k = 3 到 5)。
- 當發現「答案需要的資料沒被找到」,再增加。
- 當發現「答案被雜訊干擾」,再減少。
沒有放諸四海皆準的數字,要用第 6 章的評估方法,在自己的資料上找到合適的平衡。