戰(zhàn):從Token預(yù)算到RAG策略)
1. 上下文管理到底是什么為什么它是大模型應(yīng)用的命門做AI模型落地尤其是做大模型應(yīng)用繞不開(kāi)一個(gè)詞上下文Context。朋友圈里狂熱討論的“長(zhǎng)文本大模型”跑分榜上的百萬(wàn)token窗口本質(zhì)上都是在搶這一個(gè)東西——讓模型“記住”更多信息。但真正到了實(shí)際業(yè)務(wù)里上下文管理幾乎可以說(shuō)是決定項(xiàng)目成敗的隱形關(guān)卡。我先解釋清楚這個(gè)名詞。上下文管理指的不是讓模型去“理解”每一句話的瑣碎細(xì)節(jié)而是解決一個(gè)工程問(wèn)題如何在你與模型交互的有限窗口里既塞進(jìn)足夠的關(guān)鍵信息又保證模型不會(huì)被噪聲帶偏更不會(huì)因?yàn)槌岸鴪?bào)錯(cuò)、遺忘甚至產(chǎn)生幻覺(jué)。一個(gè)最直觀的例子。你在做一個(gè)AI客服Bot用戶連續(xù)問(wèn)了三個(gè)問(wèn)題第一個(gè)問(wèn)“發(fā)票怎么開(kāi)”你回答完規(guī)則流程第二個(gè)問(wèn)“那電子發(fā)票開(kāi)不來(lái)了怎么辦”你還能接住第三個(gè)問(wèn)“那剛才說(shuō)的紅沖具體怎么操作”如果你的上下文中沒(méi)有保留第一輪和第二輪的原話模型大概率會(huì)給你胡編一個(gè)紅沖流程。這時(shí)候你罵模型智商低其實(shí)冤枉它了——是上下文管理沒(méi)做好該保留的信息被沖掉了。我用通俗的話來(lái)定位它的價(jià)值上下文管理是大模型從“玩具”變成“生產(chǎn)工具”的核心分界線。沒(méi)有它的應(yīng)用只會(huì)是一問(wèn)一答的機(jī)械工具有了它的應(yīng)用才算是一個(gè)有記憶、會(huì)歸納、能自查的智能體。這篇文章適合誰(shuí)適合正在做AI應(yīng)用開(kāi)發(fā)但被對(duì)話記憶、Token超長(zhǎng)、多輪邏輯搞到頭禿的開(kāi)發(fā)者也適合產(chǎn)品經(jīng)理、技術(shù)負(fù)責(zé)人想建立對(duì)AI系統(tǒng)底層機(jī)制的判斷力。我會(huì)從一個(gè)實(shí)際做項(xiàng)目的經(jīng)驗(yàn)視角出發(fā)把上下文管理的原理、方案選型、實(shí)現(xiàn)細(xì)節(jié)和排查方法全部拆開(kāi)講盡量做到不給PPT式的空話。2. 底層機(jī)制上下文窗口、Token與注意力之間的關(guān)系很多初學(xué)者會(huì)把“上下文窗口”誤解為模型的記憶容量認(rèn)為窗口越大模型記住的東西就越多。這個(gè)理解只對(duì)了一半。上下文窗口嚴(yán)格來(lái)說(shuō)是模型能處理的序列長(zhǎng)度上限但這個(gè)上限雙制約著輸入和輸出——也就是你一次性發(fā)給它的所有內(nèi)容包括問(wèn)題、歷史記錄、系統(tǒng)指令加上它即將生成的回答總token數(shù)不能超過(guò)窗口值。一旦超了最直接的表現(xiàn)是報(bào)錯(cuò)或粗暴截?cái)唷?.1 Token是上下文的基本計(jì)量單位不是字?jǐn)?shù)Token是模型處理文本的最小單位可以粗略理解成一個(gè)雙字節(jié)的集裝箱。中英文的Token計(jì)算差異極大。一般來(lái)說(shuō)1個(gè)英文單詞約等于1.3個(gè)Token而1個(gè)漢字在多數(shù)大模型上約等于1到2個(gè)Token。這個(gè)細(xì)節(jié)在實(shí)戰(zhàn)中極其重要你覺(jué)得你只發(fā)了2000字的中文但模型眼里這可能是3000個(gè)Token再疊加上系統(tǒng)的角色設(shè)定、歷史記錄可能輕松突破窗口。我見(jiàn)過(guò)不少開(kāi)發(fā)者在Token估算上栽跟頭。有人用“字符數(shù)除以2”來(lái)估算中文token發(fā)現(xiàn)精度很差因?yàn)樵诓煌P偷姆衷~器之下同樣一句話的Token數(shù)可能差出20%。實(shí)操中我的習(xí)慣是硬編碼一段基準(zhǔn)文本直接在模型服務(wù)里跑一次tokenizer統(tǒng)計(jì)然后算出一個(gè)經(jīng)驗(yàn)系數(shù)。比如某模型對(duì)中文的經(jīng)驗(yàn)系數(shù)約為0.6 token/字那1000字中文大約600 Token這個(gè)系數(shù)比任何在線估算器都要準(zhǔn)。2.2 注意力機(jī)制決定了模型的“關(guān)注范圍”不等于“全窗口范圍”模型處理上下文時(shí)并不是把窗口里所有歷史內(nèi)容一視同仁地讀一遍。從原理上來(lái)講模型里每一層都有一套注意力權(quán)重它會(huì)根據(jù)當(dāng)前的生成任務(wù)動(dòng)態(tài)分配對(duì)歷史各個(gè)位置的關(guān)注程度。這就帶來(lái)一個(gè)非常重要、但被很多人忽略的結(jié)論模型能看到的Token越多并不代表它“在乎”的Token越多。當(dāng)窗口塞入8000個(gè)Token時(shí)其中真正對(duì)當(dāng)前回答起決定作用的可能只有十幾個(gè)關(guān)鍵Token。注意力分?jǐn)?shù)就像聚光燈不可能均攤照亮每一個(gè)角落。這也是為什么有些人把長(zhǎng)篇文檔一股腦塞給模型結(jié)果回答質(zhì)量反而不如只傳關(guān)鍵段落——有效信息被淹沒(méi)在海量歷史里聚光燈照不到它該照的地方。2.3 位置編碼與“迷失在中間”效應(yīng)還有一層更隱蔽的機(jī)制位置編碼。模型會(huì)在處理序列時(shí)給每一個(gè)Token所在的位置計(jì)算一個(gè)位置向量幫助它區(qū)分先后順序。早期的位置編碼對(duì)長(zhǎng)序列不友好Token離當(dāng)前位置越遠(yuǎn)位置向量就越容易被“稀釋”。后來(lái)有了相對(duì)位置編碼如RoPE這類改進(jìn)技術(shù)情況好轉(zhuǎn)但仍然存在一個(gè)工程師圈內(nèi)很經(jīng)典的現(xiàn)象模型對(duì)上下文的頭部和尾部?jī)?nèi)容更敏感對(duì)中間段的記憶相對(duì)模糊。我還特意做過(guò)對(duì)照實(shí)驗(yàn)。把一段需要精確引用的合同條款放在上下文的前段、中段、后段分別讓模型回答條款細(xì)節(jié)。結(jié)果中間段的回答準(zhǔn)確率比前段低約17%。這不是巧合而是注意力分布天然導(dǎo)致的“中間遺忘”現(xiàn)象。所以工作中凡是關(guān)鍵指令、核心約束條件我寧愿把它放在上下文開(kāi)頭比如系統(tǒng)提示詞里強(qiáng)調(diào)也不放在中部隨波逐流。3. 四大上下文管理策略對(duì)應(yīng)不同應(yīng)用場(chǎng)景上下文管理沒(méi)有銀彈方案。不同業(yè)務(wù)對(duì)歷史信息的精度要求、時(shí)效要求、成本預(yù)算完全不同。我從實(shí)踐中梳理出四種主流策略你可以把它們當(dāng)成上下文管理的基礎(chǔ)工具箱。3.1 全量傳入最簡(jiǎn)單的方案但有限制全量傳入就是把所有對(duì)話歷史、文檔內(nèi)容、用戶問(wèn)題一次性組裝進(jìn)Prompt丟給模型。它的優(yōu)勢(shì)是信息無(wú)損模型能看到完整上下文適用于交互輪次少、單次信息密度高的場(chǎng)景比如一次性給模型做長(zhǎng)文檔摘要。缺點(diǎn)也很明顯Token消耗高、響應(yīng)延遲長(zhǎng)、超窗風(fēng)險(xiǎn)大。某些長(zhǎng)文檔任務(wù)動(dòng)輒幾萬(wàn)Token一次調(diào)用可能就讓用戶的耐心耗盡。全量傳入在目前大模型參數(shù)量膨脹、上下文窗口擴(kuò)大比如從4K漲到128K的趨勢(shì)下曾經(jīng)看起來(lái)特別香但工程上很快遇到了瓶頸大窗口意味著高昂的注意力計(jì)算開(kāi)銷在線服務(wù)的首字響應(yīng)時(shí)間會(huì)被拉長(zhǎng)到不可接受。所以即便窗口變大工業(yè)界也不會(huì)真的把每輪對(duì)話都無(wú)腦全量塞進(jìn)去。3.2 滑動(dòng)窗口最近N輪的記憶模型滑動(dòng)窗口是最常用的輕量方案核心思路是只保留最近N輪對(duì)話比如最近10輪更早的歷史一律裁剪。它模擬的是人類聊天的短期記憶機(jī)制——時(shí)間久遠(yuǎn)的話說(shuō)忘了就忘了只關(guān)注當(dāng)下話題。好處在于實(shí)現(xiàn)簡(jiǎn)單、控制Token穩(wěn)定、延遲可控。壞處是它會(huì)喪失長(zhǎng)期依賴。典型場(chǎng)景是客服機(jī)器人用戶前20分鐘問(wèn)過(guò)“退貨政策是什么”20分鐘后問(wèn)“那我超期了怎么辦”如果中間夾雜了大量其他輪次超過(guò)了滑動(dòng)窗口范圍模型就完全不記得退貨政策相關(guān)的信息只能重問(wèn)用戶體驗(yàn)極差。要緩解這個(gè)缺陷我的經(jīng)驗(yàn)是在做滑動(dòng)窗口時(shí)不能只是生硬地按輪次切而應(yīng)該加入一個(gè)語(yǔ)義優(yōu)先級(jí)。比如把用戶首次問(wèn)題、系統(tǒng)回復(fù)的關(guān)鍵結(jié)論抽出來(lái)固定保留然后對(duì)較舊的內(nèi)容降權(quán)裁剪。也就是“滑動(dòng)窗口 關(guān)鍵節(jié)點(diǎn)釘住”的組合策略比純滑動(dòng)窗口的魯棒性高得多。3.3 摘要記憶把歷史壓縮成精華摘要記憶是我個(gè)人最看重的策略之一。核心做法是每當(dāng)對(duì)話進(jìn)行到一定輪數(shù)比如每5輪就觸發(fā)一次摘要將之前的所有內(nèi)容壓縮成一個(gè)結(jié)構(gòu)化或非結(jié)構(gòu)化的摘要段落放進(jìn)上下文里作為長(zhǎng)期記憶的替代。舉個(gè)例子。你和一個(gè)AI助手討論今天的日程安排聊到了會(huì)議變更、出差航班、餐飲預(yù)訂。如果不做摘要每輪全量保留歷史上下文體積線性膨脹做了摘要之后變成一段話“用戶上午會(huì)議從10點(diǎn)改到11點(diǎn)航班改為下午3點(diǎn)中午在機(jī)場(chǎng)解決午餐?!边@段精煉摘要既保留了關(guān)鍵事實(shí)又極大壓縮了Token占用。要注意的是摘要過(guò)程本身也會(huì)消耗時(shí)間和Token。每做一次摘要就是一次模型調(diào)用如果摘要頻率太高成本就上去了。所以我的落地經(jīng)驗(yàn)是設(shè)定一個(gè)閾值比如總Token達(dá)到窗口的30%就觸發(fā)摘要而不是機(jī)械地按5輪或10輪。摘要的本質(zhì)是損失重構(gòu)壓縮得越厲害丟失的細(xì)節(jié)越多因此在做摘要時(shí)我還會(huì)強(qiáng)制要求模型保留關(guān)鍵專有名詞、數(shù)字、時(shí)間、金額防止“壓縮出幻覺(jué)”導(dǎo)致關(guān)鍵信息被改寫(xiě)。3.4 向量檢索與RAG讓模型擁有“外部記憶庫(kù)”當(dāng)上下文的量級(jí)從幾輪對(duì)話變成幾百頁(yè)文檔前三種策略就都不夠用了。向量檢索方案通常以RAG的形式出現(xiàn)是目前最主流的選擇。原理也不復(fù)雜先把文檔切塊每塊通過(guò)嵌入模型轉(zhuǎn)成向量存入向量數(shù)據(jù)庫(kù)查詢時(shí)把用戶問(wèn)題也轉(zhuǎn)成向量通過(guò)相似度檢索找到最相關(guān)的幾個(gè)片段拼進(jìn)Prompt。RAG的最大好處是上下文管理的顆粒度從“全量”變成了“按需提取”理論上你可以裝下一個(gè)企業(yè)知識(shí)庫(kù)的全部文檔但每一輪實(shí)際傳輸給模型的只是其中一小撮最相關(guān)的塊。缺點(diǎn)也同樣明顯。首先檢索質(zhì)量直接決定回答質(zhì)量——如果檢索出來(lái)的塊不相關(guān)后面模型再怎么努力也答不對(duì)其次切塊策略是玄學(xué)切得太碎丟語(yǔ)義切得太粗撈噪聲再次向量相似度并不總能正確捕捉語(yǔ)義相關(guān)性對(duì)于隱式表達(dá)的語(yǔ)義比如同義改寫(xiě)、上下文指向檢索經(jīng)常會(huì)漏掉真關(guān)聯(lián)。另外RAG的上下文管理不只是檢索放入這一步還包括檢索后如何排序、如何與歷史對(duì)話拼接、如何控制多個(gè)檢索塊之間的邏輯一致性。3.5 四種策略怎么選一張決策表策略適用場(chǎng)景優(yōu)點(diǎn)缺點(diǎn)成本全量傳入單輪長(zhǎng)文檔處理、一次性分析信息無(wú)損、實(shí)現(xiàn)零成本Token消耗大、超窗風(fēng)險(xiǎn)高高滑動(dòng)窗口輪次少、話題連貫的對(duì)話實(shí)現(xiàn)簡(jiǎn)單、Token穩(wěn)定長(zhǎng)期記憶丟失低摘要記憶多輪復(fù)雜對(duì)話、需長(zhǎng)期跨話題壓縮比高、保留關(guān)鍵信息摘要過(guò)程額外開(kāi)銷、有損中向量檢索RAG知識(shí)庫(kù)問(wèn)答、超大文檔對(duì)話按需提取、可擴(kuò)展海量數(shù)據(jù)檢索不確定性、依賴切塊高實(shí)際業(yè)務(wù)中這幾種策略并不互斥。我做的某跨平臺(tái)系統(tǒng)中就是滑動(dòng)窗口負(fù)責(zé)短期對(duì)話上下文摘要記憶負(fù)責(zé)每日匯總RAG負(fù)責(zé)長(zhǎng)期知識(shí)引用三者疊在一個(gè)Pipeline里才跑出比較穩(wěn)定可靠的效果。4. 實(shí)操一個(gè)多輪客服場(chǎng)景的上下文管理方案落地說(shuō)得再漂亮不如跑一個(gè)能直接抄作業(yè)的方案。下面我用一個(gè)虛構(gòu)的智能客服項(xiàng)目X來(lái)演示上下文管理完整鏈路。假設(shè)面臨的需求是用戶會(huì)與Bot進(jìn)行多輪交互主題包括政策咨詢、訂單查詢偶爾還會(huì)跨主題比如從售后政策聊到具體訂單狀態(tài)同時(shí)線上需要一個(gè)知識(shí)庫(kù)承擔(dān)FAQ問(wèn)答。4.1 需求分析與方案設(shè)計(jì)第一步先拆需求明確幾個(gè)約束條件模型上下文窗口為32K Token這是上限實(shí)際使用預(yù)算設(shè)定為24K預(yù)留出模型輸出的空間。對(duì)話平均輪次在10輪以上用戶經(jīng)?;仡^續(xù)問(wèn)。需要接入知識(shí)庫(kù)但知識(shí)庫(kù)文檔總量超過(guò)1萬(wàn)條。系統(tǒng)存在延遲要求首字響應(yīng)時(shí)間目標(biāo)在1.5秒以內(nèi)?;谶@四條全量傳入方案直接放棄。判斷理由10輪對(duì)話如果每輪約600Token累計(jì)6000Token能扛住但知識(shí)庫(kù)插入后總量完全失控如果每輪把1萬(wàn)條文檔都塞進(jìn)去窗口立刻爆掉。所以主要策略鎖定為“滑動(dòng)窗口 長(zhǎng)期摘要 按需RAG”三段式組合。具體設(shè)計(jì)為短期上下文對(duì)話輪次層面用滑動(dòng)窗口保留最近8輪原始記錄長(zhǎng)期記憶通過(guò)每6輪的摘要生成摘要將固化進(jìn)一個(gè)“長(zhǎng)期記憶片段”并跟每輪對(duì)話一起拼進(jìn)Prompt知識(shí)信息通過(guò)RAG檢索Top4塊加入上下文。這樣一個(gè)Prompt的結(jié)構(gòu)是核心指令 長(zhǎng)期摘要 最近8輪對(duì)話 RAG檢索片段 用戶當(dāng)前輸入。4.2 Token預(yù)算的計(jì)算過(guò)程拿到方案后我們細(xì)算預(yù)算。以32K窗口為基準(zhǔn)預(yù)留輸出約4K Token正常對(duì)話模型輸出不會(huì)太長(zhǎng)但保險(xiǎn)起見(jiàn)留足余量。剩下28K給輸入。各組成部分的預(yù)算分配核心指令系統(tǒng)提示詞控制在800Token以內(nèi)。這里就要精簡(jiǎn)語(yǔ)言把指令寫(xiě)清楚、少?gòu)U話。長(zhǎng)期摘要壓縮到1000Token以內(nèi)。最近8輪對(duì)話每輪平均約600Token總約4800Token。RAG檢索片段每個(gè)塊約300Token取4塊共1200Token。當(dāng)前用戶輸入通常幾百Token??傤A(yù)算約800100048001200幾百算下來(lái)大約8K左右距離28K上限還有富余。這個(gè)富余很關(guān)鍵一是給極端情況留緩沖比如用戶某條消息特別長(zhǎng)二是給Prompt里可能插入的中間結(jié)果例如工具調(diào)用返回預(yù)留空間。于是我在代碼中實(shí)現(xiàn)了一個(gè)預(yù)算保護(hù)器當(dāng)單次組裝上下文時(shí)按配額依次填充哪一部分超了優(yōu)先壓縮摘要再往前的策略是截?cái)喔f的非關(guān)鍵對(duì)話而絕不動(dòng)核心指令和用戶當(dāng)前輸入。理由很簡(jiǎn)單核心指令是模型行為的定盤星用戶當(dāng)前輸入是必須要回答的目標(biāo)為了防止預(yù)算溢出而犧牲這兩項(xiàng)就是本末倒置。4.3 核心代碼實(shí)現(xiàn)示意下面給出簡(jiǎn)化但完整的核心流程代碼展示怎么把幾個(gè)模塊串成一個(gè)上下文管理器。from dataclasses import dataclass from typing import List, Dict dataclass class Message: role: str # user or assistant content: str timestamp: float class ContextManager: def __init__(self, max_prompt_tokens28000, max_turns8): self.system_prompt self.history: List[Message] [] self.long_summary self.max_prompt_tokens max_prompt_tokens self.max_turns max_turns def add_message(self, role: str, content: str): self.history.append(Message(rolerole, contentcontent)) if len(self.history) self.max_turns * 2: self._maybe_summarize() def _maybe_summarize(self): # 摘要觸發(fā)策略歷史消息過(guò)多時(shí)壓縮前1/3 old_messages self.history[:len(self.history) // 3] text \n.join([f{m.role}: {m.content} for m in old_messages]) # 實(shí)際項(xiàng)目中這層調(diào)用模型接口生成摘要此處省略 self.long_summary self._call_summary_model(text) self.history self.history[len(self.history) // 3:] def search_knowledge(self, query: str, top_k4): # 實(shí)際接入向量庫(kù)此處示意 return [] def build_prompt(self, user_input: str) - List[Dict]: rag_blocks self.search_knowledge(user_input) prompt [{role: system, content: self.system_prompt}] if self.long_summary: prompt.append({role: system, content: f[長(zhǎng)期記憶摘要]\n{self.long_summary}}) for msg in self.history: prompt.append({role: msg.role, content: msg.content}) for block in rag_blocks: prompt.append({role: system, content: f[知識(shí)庫(kù)參考]\n{block}}) prompt.append({role: user, content: user_input}) # 這里應(yīng)加入Token裁剪檢查超出預(yù)算時(shí)按優(yōu)先級(jí)降低歷史內(nèi)容 return prompt這段代碼的核心意圖是先保證結(jié)構(gòu)清晰再通過(guò)預(yù)算保護(hù)器控制Token。還有一個(gè)細(xì)節(jié)大家容易忽略摘要納入Prompt時(shí)我用的角色是system而不是user或assistant。因?yàn)檎举|(zhì)上是系統(tǒng)附加給模型的記憶參考不屬于對(duì)話的一方發(fā)言。如果你把它當(dāng)成assistant歷史會(huì)讓模型在風(fēng)格遷移時(shí)誤以為這是自己的既有回答繼而產(chǎn)生語(yǔ)氣混亂。4.4 切換話題時(shí)如何識(shí)別上下文邊界多輪客服最常見(jiàn)的問(wèn)題之一就是用戶突然變換話題。前兩輪還在聊退款第三輪直接問(wèn)“那物流到哪了”。如果你的上下文管理把兩段話題混在一起模型很容易把退款相關(guān)的規(guī)則錯(cuò)誤遷移到物流查詢中。我的處理方式是加一層話題邊界檢測(cè)。當(dāng)一個(gè)新消息到來(lái)先用一個(gè)輕量分類器來(lái)判斷它與最近話題是否一致。如果話題發(fā)生了明顯偏移歷史對(duì)話記錄會(huì)重新分組舊的隔段就會(huì)被收進(jìn)摘要新段獨(dú)立開(kāi)啟。這個(gè)思路等價(jià)于給對(duì)話打上了場(chǎng)景標(biāo)簽上下文不再是一串無(wú)序的歷史而是按主題組織起來(lái)的幾個(gè)記憶片段。實(shí)現(xiàn)起來(lái)輕量分類可以用文本嵌入向量的余弦相似度來(lái)判定當(dāng)相似度低于閾值時(shí)觸發(fā)話題切換。還有一點(diǎn)很實(shí)際有的用戶問(wèn)題是復(fù)合型的一句話同時(shí)涉及兩個(gè)主題。比如“退款政策里的紅沖流程和物流顯示已簽收但沒(méi)收到貨怎么處理”。這時(shí)候話題邊界檢測(cè)保持連續(xù)但在RAG檢索時(shí)就要檢索跨兩個(gè)板塊的內(nèi)容。所以我設(shè)計(jì)檢索時(shí)允許拆分子查詢分別去不同知識(shí)板塊撈片段再合并進(jìn)上下文。這也是上下文管理中容易被低估的難點(diǎn)單一Prompt中的多話題并存時(shí)你需要結(jié)構(gòu)化的拼接保障而不是簡(jiǎn)單的一句話前綴。5. 上下文壓縮如何在不丟關(guān)鍵信息的前提下瘦身某些場(chǎng)景下既不能粗暴裁剪歷史也不能接受摘要帶來(lái)的細(xì)節(jié)損失。這時(shí)候就得靠更有針對(duì)性的壓縮手段。我把實(shí)踐中常用的幾種壓縮策略整理出來(lái)供大家在具體項(xiàng)目里按需組合。5.1 結(jié)構(gòu)化提取把非結(jié)構(gòu)化文本變成結(jié)構(gòu)化字段很多對(duì)話歷史中有大量的問(wèn)候語(yǔ)、語(yǔ)氣詞、重復(fù)確認(rèn)車轱轆話來(lái)回說(shuō)。上下文壓縮的首選思路不是摘要而是結(jié)構(gòu)化提取。比如下面一組原始對(duì)話用戶“你好我想問(wèn)一下我之前買的那款耳機(jī)還在保修期內(nèi)嗎大概是一月份買的。不好意思我記不太清了你幫我看看嘛……”客服“您好請(qǐng)?zhí)峁┮幌履挠唵翁?hào)我來(lái)幫你查詢哦。”用戶“訂單號(hào)是A123456麻煩你了?!比绻孔鳛樯舷挛拇嫦聛?lái)會(huì)消耗不少Token。但真正關(guān)鍵的信息只需要提取為用戶購(gòu)買時(shí)間1月產(chǎn)品耳機(jī)訂單號(hào)A123456待辦保修期查詢。這樣一段結(jié)構(gòu)化信息不僅壓縮了體積而且比原始對(duì)話更容易被模型檢索和引用。實(shí)戰(zhàn)中我特別推薦用少量樣本微調(diào)一個(gè)信息提取器定期從對(duì)話中抽取出實(shí)體、意圖、所需參數(shù)、情緒狀態(tài)。上游的上下文管理器只保存結(jié)構(gòu)化結(jié)果原始對(duì)話則歸檔到外部存儲(chǔ)。這樣一來(lái)上下文瘦身的同時(shí)還順便給下游的報(bào)表分析、用戶行為洞察打了基礎(chǔ)。5.2 漸進(jìn)式摘要不是一次壓縮到位而是多輪迭代漸進(jìn)式摘要是一種讓摘要按梯度更新的策略。它不是每6輪一次性把所有歷史拿去摘要而是每次只對(duì)新增的幾輪對(duì)話做摘要再與之前的摘要做合并。這個(gè)思路有個(gè)很實(shí)在的好處你不需要每次重新完整讀一遍全部歷史摘要調(diào)用的Token開(kāi)銷大幅下降。我踩過(guò)一次坑初期設(shè)計(jì)是每6輪重新生成一次全部歷史的摘要。結(jié)果對(duì)話到50輪的時(shí)候哪怕歷史片段已經(jīng)有大量總結(jié)模型總結(jié)時(shí)仍然要把所有文本全部傳一遍單次摘要調(diào)用就吃掉了近20K Token時(shí)間開(kāi)銷也好幾秒。改為漸進(jìn)式摘要之后每次摘要只需要讀取“舊摘要 新幾輪對(duì)話”總量一般不超過(guò)3K Token速度快了十倍成本也降了一個(gè)量級(jí)。漸進(jìn)式摘要還有個(gè)副作用需要注意多次迭代壓縮后早期細(xì)節(jié)可能會(huì)逐漸被“洗掉”。比如用戶在第3輪提到“不要香菜”經(jīng)過(guò)第9輪、第15輪兩次摘要迭代之后這條信息可能就慢慢消失。對(duì)于必須長(zhǎng)期保留的約束類信息我會(huì)在摘要Prompt里強(qiáng)制加入一條規(guī)則“當(dāng)對(duì)話中出現(xiàn)否定性約束如不要、必須是、禁止必須原樣保留在摘要中?!边@個(gè)細(xì)小的規(guī)則幫我解決了不少因摘要丟失約束導(dǎo)致的Bug。5.3 硬截?cái)嗯c軟截?cái)嗍裁磿r(shí)候用哪一種截?cái)嗍亲詈笫侄蔚彩遣坏貌徽莆盏氖侄?。硬截?cái)嗑褪菑哪硞€(gè)位置直接切斷上下文比如只看最近N輪之前的一律不要。軟截?cái)鄤t是保留所有歷史但給每一條歷史分配一個(gè)權(quán)重超窗時(shí)優(yōu)先丟棄低權(quán)重的部分。硬截?cái)鄬?shí)現(xiàn)簡(jiǎn)單但丟失信息的風(fēng)險(xiǎn)是“黑天鵝式”的——你永遠(yuǎn)不知道被截掉的哪句話里藏著關(guān)鍵事實(shí)。軟截?cái)嘞鄬?duì)聰明但計(jì)算權(quán)重需要額外開(kāi)銷。我的建議是把軟截?cái)嗟臋?quán)重分?jǐn)?shù)建立在幾個(gè)維度上消息的角色權(quán)重系統(tǒng)指令最高用戶輸入必須保留assistant回復(fù)中帶有工具調(diào)用結(jié)果的次之純閑聊墊底。語(yǔ)義關(guān)聯(lián)度與當(dāng)前Query的向量相似度越高越保留。時(shí)效性時(shí)間越近越保留。信息類型含數(shù)字、訂單號(hào)、時(shí)間、金額、協(xié)議條款的高保留優(yōu)先級(jí)。在實(shí)際產(chǎn)品里我一般用加權(quán)綜合分對(duì)歷史消息排序把分?jǐn)?shù)低的先淘汰。這個(gè)方案比“一刀切最近N輪”溫和得多實(shí)測(cè)下來(lái)多輪問(wèn)答的準(zhǔn)確率能提升8到10個(gè)百分點(diǎn)。6. 實(shí)戰(zhàn)排查上下文相關(guān)問(wèn)題的高頻故障與解決實(shí)錄上下文管理上線后真正的挑戰(zhàn)才開(kāi)始。我把實(shí)際項(xiàng)目里遇到過(guò)的典型故障和排查思路整理成一個(gè)速查表這些都來(lái)自一線的血淚經(jīng)驗(yàn)不是文檔里能查到的。6.1 模型“突然失憶”不記得之前說(shuō)過(guò)的關(guān)鍵信息這是最常見(jiàn)的故障。用戶對(duì)Bot說(shuō)“我剛才不是說(shuō)了我的預(yù)算不能超過(guò)3000嗎你怎么還推薦5000的”模型不是故意犯錯(cuò)而是它的上下文里壓根沒(méi)有“預(yù)算3000”這條消息了。排查時(shí)不要只盯著Prompt看先檢查上下文組裝鏈路。我遇到過(guò)一次問(wèn)題出在滑動(dòng)窗口的窗口長(zhǎng)度是根據(jù)“輪數(shù)”而不是“Token數(shù)”設(shè)定的。用戶每輪中有些話特別長(zhǎng)比如發(fā)了大段需求文檔導(dǎo)致8輪的Token總量遠(yuǎn)超預(yù)期后半段被預(yù)算保護(hù)器裁掉了關(guān)鍵約束信息恰好被裁掉。修復(fù)方式改為按Token數(shù)滑動(dòng)窗口并且在裁剪時(shí)保留“約束類信息抽取結(jié)果”把這個(gè)結(jié)果常駐在長(zhǎng)期摘要里。我后來(lái)還養(yǎng)成了一個(gè)經(jīng)驗(yàn)每輪對(duì)話在進(jìn)入上下文管理器之前先走一個(gè)“關(guān)鍵信息檢測(cè)器”把含金額、否定詞、時(shí)間節(jié)點(diǎn)的句子抽出單獨(dú)存放。這樣就算窗口再擠關(guān)鍵約束也不會(huì)丟。6.2 回答含混不清上下文里有多個(gè)互相矛盾的信息有時(shí)候模型回答得含混不是因?yàn)樾畔⒉蛔愣且驗(yàn)樯舷挛闹写嬖诨ハ鄾_突的信息。比如用戶在一開(kāi)始的對(duì)話里說(shuō)“我要黑色版本的”后來(lái)又說(shuō)“算了換白色吧”。如果兩輪原話都保存在上下文中模型就會(huì)看到兩處矛盾的顏色指令最終可能給出“黑色和白色都可以”的模糊回答。這個(gè)問(wèn)題本質(zhì)上是上下文管理沒(méi)有做“信息版本控制”。我在實(shí)際項(xiàng)目中加了一套狀態(tài)追蹤模塊專門維護(hù)用戶的“當(dāng)前偏好快照”。每當(dāng)新消息出現(xiàn)某個(gè)關(guān)鍵屬性的更新比如顏色從黑色到白色就去覆蓋快照中的舊值。上下文里始終只保留最新判定舊的矛盾信息則歸入?yún)R總歷史但不會(huì)在Prompt里高亮出現(xiàn)。這個(gè)改動(dòng)對(duì)客服場(chǎng)景的效果立竿見(jiàn)影用戶明顯感覺(jué)到Bot“更懂我了”。6.3 Token預(yù)算超限明明沒(méi)聊幾句就報(bào)超長(zhǎng)錯(cuò)誤有一種情況讓人極其困惑總共沒(méi)聊幾句也沒(méi)傳文檔怎么Token就超了排查后你會(huì)發(fā)現(xiàn)問(wèn)題可能出在角落里。比如系統(tǒng)中某個(gè)工具調(diào)用的返回結(jié)果被完整存到了歷史里某次調(diào)API返回了8000Token的原始JSON這些信息被當(dāng)成assistant回復(fù)記錄塞入上下文幾輪下來(lái)窗口就被幾段冗長(zhǎng)JSON撐爆了。解決思路有幾種。首先是對(duì)工具調(diào)用返回結(jié)果做截?cái)嗵幚碇槐A艉诵淖侄伪热纭坝唵螤顟B(tài)、預(yù)計(jì)到達(dá)時(shí)間”剔除完整的嵌套對(duì)象。其次可以考慮把工具返回結(jié)果不放入上下文歷史而是獨(dú)立放進(jìn)一個(gè)“工具觀察區(qū)”只保留最近一條調(diào)用的結(jié)果。這個(gè)設(shè)計(jì)更貼近智能體的設(shè)計(jì)模式模型看得到當(dāng)前觀察但不需要把上一輪調(diào)用的垃圾信息永遠(yuǎn)背著。6.4 角色錯(cuò)亂模型開(kāi)始把摘要當(dāng)作自己的發(fā)言如果你在Prompt里細(xì)心地把摘要放在system角色模型通常不會(huì)錯(cuò)亂。但如果你圖省事把摘要直接拼接成歷史消息放在assistant角色模型就會(huì)把它當(dāng)成自己曾經(jīng)的發(fā)言進(jìn)而影響后續(xù)回答的立場(chǎng)和語(yǔ)氣。這類問(wèn)題不太會(huì)報(bào)錯(cuò)但回答質(zhì)量會(huì)詭異下降。排查這類問(wèn)題的方法是打開(kāi)模型調(diào)試面板檢查每一條消息的role與content是否匹配。我見(jiàn)過(guò)有人用流式拼接的方式動(dòng)態(tài)組裝歷史結(jié)果把system消息的字符插到了user內(nèi)容里導(dǎo)致模型把指令當(dāng)用戶問(wèn)題處理。這個(gè)問(wèn)題在代碼里看起來(lái)只是一個(gè)小Bug但對(duì)實(shí)際回答影響極大排查難度堪比大海撈針。6.5 超長(zhǎng)文檔檢索質(zhì)量不佳檢索到了但答非所問(wèn)另一種高頻故障是文檔確實(shí)被檢索進(jìn)來(lái)了但模型給出的答案依然與文檔無(wú)關(guān)。這個(gè)情況多半是RAG的檢索階段出了問(wèn)題而不是上下文管理的問(wèn)題但它與上下文管理密切相關(guān)因?yàn)樘幚聿划?dāng)會(huì)把噪聲塊帶進(jìn)來(lái)。我總結(jié)過(guò)三個(gè)排查維度切塊大小是否合適。切得太小比如100字一塊語(yǔ)義不完整切得太大比如2000字一塊可能混入多個(gè)主題。一般經(jīng)驗(yàn)是256到512字符一塊具體根據(jù)內(nèi)容類型調(diào)節(jié)。檢索TopK的數(shù)量是否合適。太少會(huì)漏信息太多會(huì)引入噪聲。我一般從Top3開(kāi)始調(diào)用評(píng)測(cè)集判斷最優(yōu)值。塊與塊之間是否重復(fù)。如果知識(shí)庫(kù)本身有大量重復(fù)內(nèi)容檢索會(huì)返回多個(gè)幾乎相同的片段白白占用上下文窗口。這時(shí)候要做一遍去重。還有一個(gè)容易被忽視的點(diǎn)RAG檢索返回的文本格式。如果不做格式化處理原始PDF中的表格、列表被切塊后可能變成亂序的字符串模型讀起來(lái)一頭霧水。我的建議是檢索后做一個(gè)輕量級(jí)的“上下文重構(gòu)”把離散的塊按邏輯順序重新排列比如規(guī)則類塊在前、示例類塊在后并且給每個(gè)塊加上清晰的小標(biāo)題模型理解起來(lái)會(huì)輕松很多。7. 上下文管理的擴(kuò)展方向短期緩存、長(zhǎng)期記憶與Agent記憶分層前六節(jié)聊的還都是“在一輪請(qǐng)求之內(nèi)把上下文組裝好”的問(wèn)題。但真正復(fù)雜的AI應(yīng)用尤其是Agent類的應(yīng)用上下文管理遠(yuǎn)遠(yuǎn)超出了單次請(qǐng)求的范疇。它更像人的記憶系統(tǒng)需要有工作記憶、短期記憶、長(zhǎng)期記憶的分層設(shè)計(jì)。7.1 會(huì)話級(jí)緩存減少重復(fù)計(jì)算的工程優(yōu)化同一位用戶連續(xù)多輪對(duì)話時(shí)如果每次請(qǐng)求都把同一個(gè)系統(tǒng)指令、同一份長(zhǎng)期摘要重新編碼一遍不僅浪費(fèi)Token還浪費(fèi)模型服務(wù)的計(jì)算資源。工業(yè)級(jí)的做法是引入Prompt緩存。某些模型服務(wù)支持在API層面對(duì)相同前綴做KV-Cache緩存也就是說(shuō)如果Prompt前一段完全一致第二次調(diào)用就可以復(fù)用計(jì)算過(guò)的KV狀態(tài)直接跳過(guò)這幾步計(jì)算。這帶來(lái)的好處非常顯著首字延遲降低Token成本下降。但代價(jià)是需要你在設(shè)計(jì)Prompt時(shí)把不變的靜態(tài)部分系統(tǒng)指令、固定摘要、指令模板統(tǒng)一排在最前面把頻繁變化的內(nèi)容本輪輸入、檢索結(jié)果排到后面。這個(gè)順序設(shè)計(jì)直接決定了你能不能吃到緩存的紅利。我到后面已經(jīng)把這當(dāng)成一條硬性工程規(guī)范Prompt的靜態(tài)前綴和動(dòng)態(tài)后綴開(kāi)發(fā)時(shí)就必須強(qiáng)制分離不能隨手拼接。這個(gè)節(jié)省在調(diào)用量大的生產(chǎn)環(huán)境里一個(gè)月能省出一筆不小的開(kāi)銷。7.2 Agent記憶分層從“對(duì)話記憶”升級(jí)為“實(shí)體記憶”到了Agent場(chǎng)景上下文管理不能只停留在消息層面需要把信息抽象成實(shí)體關(guān)系。比如一個(gè)生活助手類的Agent它需要記住的不只是你昨天說(shuō)了什么還要建立“家庭成員父母、孩子”、“常用偏好口味、時(shí)間”和“當(dāng)前待辦”的實(shí)體模型。這就催生了結(jié)構(gòu)化長(zhǎng)期記憶的概念。實(shí)現(xiàn)上的做法是從對(duì)話中持續(xù)抽取實(shí)體和關(guān)系更新到一個(gè)持久化的知識(shí)圖譜或JSON結(jié)構(gòu)里。對(duì)話時(shí)先從這些結(jié)構(gòu)中查詢與當(dāng)前意圖相關(guān)的部分塞進(jìn)上下文跟RAG合在一起。這套方案比單純的“對(duì)話摘要”更接近人的記憶模式也讓Agent具備更強(qiáng)的規(guī)劃與執(zhí)行能力。但實(shí)體記憶也有復(fù)雜度。比如用戶說(shuō)“幫我訂周五晚上七點(diǎn)的餐廳”這里的“周五”“七點(diǎn)”是一次性的命令參數(shù)不應(yīng)該寫(xiě)入長(zhǎng)期記憶但如果用戶說(shuō)“我每周五晚上都要加班”這就值得進(jìn)長(zhǎng)期記憶了。區(qū)分瞬時(shí)指令與持久偏好需要依賴語(yǔ)義判斷規(guī)則也需要依賴模型的推理能力。這個(gè)邊界目前業(yè)界仍在持續(xù)打磨不是能一錘定音的方案。7.3 多智能體協(xié)作下的上下文傳遞如果你在做一個(gè)多智能體系統(tǒng)比如一個(gè)Agent負(fù)責(zé)檢索信息另一個(gè)Agent負(fù)責(zé)生成回復(fù)上下文管理又增加了一層挑戰(zhàn)Agent之間的消息上下文如何高效傳遞全量傳遞效率極低而且會(huì)混入不同Agent的關(guān)注噪聲。我目前采用的模式是“消息摘要 指令式傳遞”主控Agent負(fù)責(zé)全局上下文管理它會(huì)把壓縮后的任務(wù)描述、中間結(jié)果、約束條件分別包裝成結(jié)構(gòu)化消息傳給各個(gè)子Agent。每個(gè)子Agent不必知道全量歷史只需拿到完成自己子任務(wù)所需的那一段上下文。這個(gè)模式跟人類組織的協(xié)作模式很像項(xiàng)目負(fù)責(zé)人不會(huì)把整份合同細(xì)節(jié)丟給每個(gè)人而是把跟每個(gè)人相關(guān)的部分拆好發(fā)下去。實(shí)現(xiàn)中需要嚴(yán)格保證主控Agent的上下文不能過(guò)載。所有子Agent返回的結(jié)果必須通過(guò)一個(gè)結(jié)果清洗模塊做裁剪和標(biāo)準(zhǔn)化再進(jìn)入主控上下文。一開(kāi)始做這個(gè)設(shè)計(jì)時(shí)我也圖省事讓所有子Agent完整返回日志結(jié)果主控上下文很快就塞滿了冗余調(diào)試信息。后來(lái)強(qiáng)制規(guī)定每個(gè)Agent的返回字段只有兩條——“完成狀態(tài)”和“結(jié)果摘要”上下文體積一下子小了一半以上。7.4 上下文管理與評(píng)測(cè)用一套測(cè)試集持續(xù)度量最后分享一個(gè)容易被忽略但極其重要的經(jīng)驗(yàn)上下文管理要可評(píng)測(cè)。很多團(tuán)隊(duì)改完上下文管理方案的邏輯只靠幾個(gè)手工樣例驗(yàn)證一下就覺(jué)得OK了這是大忌。我做項(xiàng)目時(shí)一定會(huì)在早期構(gòu)建一個(gè)上下文評(píng)測(cè)集包含至少上百個(gè)模擬對(duì)話場(chǎng)景覆蓋多輪連貫、話題切換、長(zhǎng)距離依賴、矛盾信息更新、長(zhǎng)文檔檢索等典型情況。每次改動(dòng)上下文管理邏輯后都跑一遍評(píng)測(cè)集看回答準(zhǔn)確率、上下文命中率、Token平均消耗等指標(biāo)變化。這個(gè)習(xí)慣幫我避開(kāi)了不少“看似優(yōu)化實(shí)則退化”的改動(dòng)。很多時(shí)候你以為自己加了更復(fù)雜的上下文邏輯系統(tǒng)會(huì)更好評(píng)測(cè)數(shù)據(jù)卻告訴你它負(fù)優(yōu)化了。沒(méi)有數(shù)據(jù)幫你兜底就只能靠玄學(xué)調(diào)參。8. 關(guān)于上下文管理我個(gè)人還在踐行的幾條心得上下文管理做得越久越覺(jué)得它不只是技術(shù)層面的問(wèn)題而是對(duì)整個(gè)AI應(yīng)用架構(gòu)觀的重塑。每次看到有人興致勃勃地說(shuō)“我們模型窗口很大不用做上下文管理”我都覺(jué)得他們大概率還沒(méi)經(jīng)歷過(guò)生產(chǎn)環(huán)境的毒打。窗口大只能降低管理頻率不能抵消管理的必要性——因?yàn)門oken成本、響應(yīng)延遲、注意力稀釋、信息版本沖突這些問(wèn)題不會(huì)因?yàn)榇翱谧兇蠖?。我個(gè)人把上下文管理的能力歸納為四個(gè)層面能控制控制Token在預(yù)算內(nèi)、能篩選篩選值得進(jìn)入窗口的信息、能演化能隨著對(duì)話動(dòng)態(tài)更新記憶、能度量能持續(xù)評(píng)估方案效果。這四個(gè)層面越往后越困難也越是拉開(kāi)普通應(yīng)用與頂尖應(yīng)用差距的地方。如果你正在從零搭建一個(gè)基于大模型的應(yīng)用我建議你從最樸素的滑動(dòng)窗口開(kāi)始不要一上來(lái)就上高深的RAG和多級(jí)記憶。把基礎(chǔ)的對(duì)話流跑通用真實(shí)數(shù)據(jù)看看出現(xiàn)什么樣的上下文問(wèn)題再逐步引入摘要、檢索和結(jié)構(gòu)化管理。上下文管理是演進(jìn)式的工程不是一步到位的方案。最后再分享一個(gè)小技巧也是我每次做架構(gòu)評(píng)審時(shí)都會(huì)提的在Prompt里用角色名稱暗示上下文中的信息性質(zhì)。比如用“知識(shí)庫(kù)參考”而不是直接丟進(jìn)System或User里用“歷史摘要”而不是跟在User問(wèn)題后面。這不僅僅是為了模型理解更是為了可觀測(cè)和可調(diào)試。當(dāng)你需要排查問(wèn)題的時(shí)候清晰的角色結(jié)構(gòu)能讓你一眼定位是哪一段上下文出了狀況。這個(gè)習(xí)慣替我省下過(guò)數(shù)不清的排查時(shí)間。