嗟綑z索的實(shí)戰(zhàn)指南)
上個(gè)月我在排查一個(gè)問(wèn)答機(jī)器人的線上事故用戶在第31輪對(duì)話時(shí)問(wèn)了一個(gè)極其基礎(chǔ)的問(wèn)題機(jī)器人卻給出了完全驢唇不對(duì)馬嘴的答案。翻了一大圈日志最終定位到根因模型壓根就沒(méi)看到用戶這句話——它還沒(méi)進(jìn)模型呢就被前面的上下文管理模塊給截?cái)嗔恕_@個(gè)故障讓我重新把大模型應(yīng)用里最容易被忽視、卻也最要命的環(huán)節(jié)翻出來(lái)研究了一遍也就是今天要聊的 context-mode上下文模式。這個(gè)場(chǎng)景挺典型的。很多人在做 RAG檢索增強(qiáng)生成、對(duì)話機(jī)器人、Agent 工作流時(shí)一開(kāi)始只關(guān)心模型選型、Prompt 設(shè)計(jì)、知識(shí)庫(kù)怎么切分結(jié)果系統(tǒng)一上線就被上下文相關(guān)的問(wèn)題反復(fù)折磨對(duì)話輪數(shù)一多就失憶長(zhǎng)文檔分析時(shí)關(guān)鍵信息被淹沒(méi)在海量文本里Token 成本居高不下甚至模型突然開(kāi)始胡言亂語(yǔ)。這些問(wèn)題表面上看五花八門(mén)深挖下去全都指向同一個(gè)根源——你沒(méi)有用正確的 context-mode 來(lái)管理上下文。這篇文章我會(huì)把 context-mode 掰開(kāi)揉碎講清楚它是什么、有哪些落地形態(tài)、上下文預(yù)算怎么算、代碼怎么寫(xiě)以及我過(guò)去半年踩過(guò)的那些坑和排查思路。適合正在做大模型應(yīng)用開(kāi)發(fā)、尤其是對(duì)話系統(tǒng)和 RAG 項(xiàng)目的工程師參考也適合剛?cè)腴T(mén)想搞懂上下文管理原理的同學(xué)。1. 從一次線上事故說(shuō)起為什么 context-mode 值得單獨(dú)拎出來(lái)講1.1 那個(gè)讓人失眠的失憶Bug先把這個(gè)事故展開(kāi)說(shuō)說(shuō)。那是一個(gè)基于知識(shí)庫(kù)的問(wèn)答機(jī)器人接入的是主流大模型 API對(duì)話歷史存在 Redis 里每次請(qǐng)求把最近 N 輪消息拼進(jìn) Prompt 發(fā)給模型。上線時(shí)只測(cè)了 5 輪以內(nèi)的對(duì)話一切正常。結(jié)果用戶實(shí)際使用起來(lái)聊到二三十輪機(jī)器人就開(kāi)始慢慢癡呆再往后連用戶剛說(shuō)完的話都會(huì)忽略。我最初懷疑是 Prompt 工程的問(wèn)題調(diào)了好幾版系統(tǒng)提示詞沒(méi)用。然后又懷疑是模型溫度參數(shù)太高降了 Temperature還是沒(méi)用。最后把發(fā)送給模型的實(shí)際請(qǐng)求體抓出來(lái)一看整個(gè)人都愣住了因?yàn)槠唇託v史消息的代碼用了簡(jiǎn)單的截?cái)噙壿嫛^(guò)最大 token 數(shù)就從頭丟棄舊消息按輪數(shù)來(lái)算第31輪提問(wèn)時(shí)前 30 輪對(duì)話加上系統(tǒng)提示詞已經(jīng)把 8K 的上下文窗口塞得滿滿當(dāng)當(dāng)。用戶最新的那句話是在截?cái)嘀蟛抛芳舆M(jìn)去的于是被模型視而不見(jiàn)。這個(gè)事故的教訓(xùn)非常深刻上下文窗口不是無(wú)限物理空間模型能看到什么完全取決于你在調(diào)用之前怎么組織進(jìn)入窗口的內(nèi)容。這個(gè)在調(diào)用模型之前對(duì)上下文的獲取、篩選、裁剪、排序和注入策略就是 context-mode 的實(shí)戰(zhàn)含義。1.2 一句話定義 context-mode如果非要用一句話概括我會(huì)說(shuō)context-mode 是控制大模型視野范圍的策略機(jī)制它決定哪些信息進(jìn)入上下文窗口、以什么順序進(jìn)入、以及放不下的時(shí)候舍棄什么。這里面有三個(gè)關(guān)鍵動(dòng)作第一是選什么不是所有對(duì)話歷史和資料都值得給模型看第二是怎么放系統(tǒng)提示詞、歷史消息、外部知識(shí)之間要有優(yōu)先級(jí)和結(jié)構(gòu)第三是放不下怎么辦窗口總會(huì)有上限必須有降級(jí)方案。這三件事合在一起構(gòu)成了一套完整的上下文管理方案而不是某個(gè)單一參數(shù)或者某個(gè)模型的特性。1.3 適合誰(shuí)看、解決了什么問(wèn)題這篇文章對(duì)三類(lèi)人最有價(jià)值一是對(duì)話式 AI 應(yīng)用的開(kāi)發(fā)者你需要一套能扛住長(zhǎng)對(duì)話的方案二是做 RAG 應(yīng)用的同學(xué)你會(huì)遇到檢索結(jié)果太多反而干擾回答的問(wèn)題本質(zhì)上也是 context-mode 的編排問(wèn)題三是想搞懂模型輸入側(cè)原理、想控制成本的產(chǎn)品或技術(shù)負(fù)責(zé)人。讀完你至少能回答三個(gè)問(wèn)題我的應(yīng)用當(dāng)前用的是哪種 context-mode它合不合理如果出現(xiàn)上下文相關(guān)的故障我該怎么排查2. context-mode 的四種主流落地形態(tài)選型前先認(rèn)清區(qū)別在實(shí)際工程里context-mode 不是一個(gè)開(kāi)關(guān)而是一組策略的統(tǒng)稱。我把它梳理成四種主流的落地形態(tài)它們各有適用場(chǎng)景也有各自的代價(jià)。很多系統(tǒng)從一開(kāi)始就有意無(wú)意地用了其中一種只是沒(méi)有系統(tǒng)性地思考過(guò)。2.1 截?cái)嗄J阶畲直┮沧畛S媒財(cái)嗄J絋runcate Mode是絕大多數(shù)團(tuán)隊(duì)第一版會(huì)采用的方式限制消息輪數(shù)或 token 數(shù)超了就從頭部丟棄舊消息。實(shí)現(xiàn)極簡(jiǎn)單幾乎不消耗額外資源接口延遲穩(wěn)定。但它是典型的頭痛醫(yī)頭方案你永遠(yuǎn)不知道被丟掉的那部分歷史里有沒(méi)有對(duì)當(dāng)前問(wèn)題至關(guān)重要的信息。我見(jiàn)過(guò)有人用先進(jìn)先出策略保留系統(tǒng)提示詞和最近 N 輪也有人用先進(jìn)后出保留開(kāi)頭幾輪和最新幾輪丟掉中間。后者在某些客服場(chǎng)景里更實(shí)用因?yàn)橛脩裘枋鰡?wèn)題通常在開(kāi)頭而當(dāng)前訴求在結(jié)尾。但無(wú)論哪種本質(zhì)上都是在做概率博弈——賭被丟掉的內(nèi)容不重要。當(dāng)對(duì)話輪數(shù)超過(guò)閾值模型就會(huì)出現(xiàn)記憶斷層而這種斷層用戶感知非常明顯信任感會(huì)急劇下降。截?cái)嗄J竭m合極短對(duì)話場(chǎng)景比如一次性問(wèn)答、表單式交互或者預(yù)算極其敏感的簡(jiǎn)單場(chǎng)景。只要預(yù)期對(duì)話輪數(shù)不超過(guò) 10 輪它可以是最優(yōu)解一旦超過(guò)你就得認(rèn)真考慮升級(jí)方案了。2.2 摘要模式用理解換空間摘要模式Summarize Mode的思路是既然歷史消息占地方那就把早期的對(duì)話壓縮成摘要釋放出空間給新內(nèi)容。實(shí)現(xiàn)方式通常有兩條路線一是滾動(dòng)摘要每 N 輪觸發(fā)一次把歷史消息丟給模型生成一段摘要之后用這段摘要替代原文二是按話題聚類(lèi)對(duì)話切換主題時(shí)對(duì)上一個(gè)主題做歸檔摘要當(dāng)前主題保留完整細(xì)節(jié)。摘要模式的最大好處是能記住更久之前的事情對(duì)話可以拉得很長(zhǎng)。代價(jià)也很明顯摘要過(guò)程本身要額外消耗 token 和時(shí)間而且摘要是不可逆的——模型在壓縮時(shí)可能丟掉你認(rèn)為重要但它覺(jué)得不重要的細(xì)節(jié)比如用戶提到的某個(gè)具體編號(hào)、某個(gè)時(shí)間節(jié)點(diǎn)甚至情緒傾向。一旦摘要生成錯(cuò)誤錯(cuò)誤會(huì)被凍結(jié)在上下文里后續(xù)所有回答都會(huì)被帶偏。所以做摘要模式時(shí)我強(qiáng)烈建議不只存一份摘要而是保留摘要 原始終端的最近 K 輪兩層結(jié)構(gòu)。摘要負(fù)責(zé)長(zhǎng)期記憶原始消息負(fù)責(zé)近期細(xì)節(jié)這樣即使摘要丟了對(duì)最近的準(zhǔn)確度影響也不大。預(yù)算充足時(shí)可以在摘要前加一輪關(guān)鍵信息抽取專(zhuān)門(mén)提取容易被漏掉的實(shí)體、數(shù)字和條件再與摘要合并存儲(chǔ)。2.3 檢索模式按需取用檢索模式Retrieval Mode是目前 RAG 場(chǎng)景最常用的一種 context-mode不把全部歷史或全部資料塞進(jìn)窗口中而是根據(jù)當(dāng)前問(wèn)題從外部存儲(chǔ)向量數(shù)據(jù)庫(kù)、ES、甚至關(guān)系型數(shù)據(jù)庫(kù)里檢索出最相關(guān)的片段只把這些片段注入上下文。這個(gè)模式的核心挑戰(zhàn)在于相關(guān)性的判斷質(zhì)量檢索不準(zhǔn)上下文再精煉也是白搭。我在實(shí)踐中的體會(huì)是檢索模式不能只做一層。純向量檢索在語(yǔ)義相近但關(guān)鍵詞不同的場(chǎng)景下表現(xiàn)不錯(cuò)但在面對(duì)精確數(shù)字查詢、指定條件過(guò)濾時(shí)就容易翻車(chē)。更穩(wěn)的做法是向量檢索 關(guān)鍵詞召回 重排模型的混合管線向量負(fù)責(zé)找語(yǔ)義相似的候選BM25 或倒排索引負(fù)責(zé)保證精確命中最后用一個(gè)重排模型Reranker把兩路結(jié)果合并打分截取 Top-K 注入上下文。檢索模式的另一個(gè)隱含問(wèn)題是檢索進(jìn)上下文的片段之間可能互相矛盾。比如用戶兩次問(wèn)類(lèi)似問(wèn)題檢索到的知識(shí)庫(kù)片段版本不同模型就會(huì)困惑并產(chǎn)生幻覺(jué)。我在后面第 5 部分會(huì)專(zhuān)門(mén)講這個(gè)上下文污染問(wèn)題它是檢索模式最隱蔽的坑。2.4 路由與混合模式生產(chǎn)環(huán)境里的真相成熟的系統(tǒng)幾乎不會(huì)只用某一種單一模式而是用路由Routing把不同場(chǎng)景分派給不同策略甚至在一次請(qǐng)求內(nèi)部組合多種模式。這就是我所說(shuō)的混合模式Hybrid Mode。比如第一輪直接走完整對(duì)話不加摘要第二輪起歷史超過(guò)閾值就把早期內(nèi)容摘要化如果當(dāng)前問(wèn)題命中用戶明確指定的某份文檔就優(yōu)先走檢索模式把文檔片段放在對(duì)話歷史之前?;旌夏J铰?tīng)起來(lái)復(fù)雜但本質(zhì)上你需要實(shí)現(xiàn)的就是一個(gè)策略路由給定輸入根據(jù)規(guī)則或分類(lèi)模型決定走截?cái)?、摘要還是檢索。規(guī)則的粒度可以是對(duì)話輪數(shù)閾值、token 占用比例、是否包含檢索意圖的關(guān)鍵詞等。工程上并不需要一次到位可以先寫(xiě)死規(guī)則跑出數(shù)據(jù)后再慢慢上模型判斷。這個(gè)模式最大的價(jià)值在于容錯(cuò)當(dāng)檢索結(jié)果質(zhì)量不佳時(shí)摘要?dú)v史還能兜底當(dāng)摘要丟失關(guān)鍵信息時(shí)原始消息的最近 K 輪還能補(bǔ)位當(dāng)對(duì)話很短時(shí)又不愿意多花摘要的錢(qián)。多種策略互相疊加比單一策略能扛更多極端場(chǎng)景。3. 核心參數(shù)與計(jì)算邏輯手把手教你算清上下文預(yù)算很多上下文問(wèn)題本質(zhì)上不是模型能力問(wèn)題而是預(yù)算估算失誤。你以為塞進(jìn)窗口的是 2000 token實(shí)際可能是 8000。這一部分我把 Token 計(jì)費(fèi)、窗口分配、動(dòng)態(tài)水位線這些最要命的計(jì)算邏輯講透全部可以直接套用。3.1 Token 不是字?jǐn)?shù)先搞懂單位換算Token 是模型處理文本的最小單位一個(gè) token 不一定是一個(gè)字它可能是半個(gè)詞、一個(gè)詞、一個(gè)標(biāo)點(diǎn)甚至幾字節(jié)。中文場(chǎng)景最實(shí)用的估算方法是1 個(gè)漢字大約對(duì)應(yīng) 1.5 到 2 個(gè) token1 個(gè)英文單詞大約對(duì)應(yīng) 1.3 到 1.5 個(gè) token。穩(wěn)妥起見(jiàn)做容量規(guī)劃時(shí)我會(huì)按中文 2 token/字、英文 1.5 token/詞的上限來(lái)估算留出緩沖。舉個(gè)例子一段 500 字的中文產(chǎn)品說(shuō)明按 2 換算就是約 1000 token。如果你用的是 8K 上下文的模型這筆賬就要這么算系統(tǒng)提示詞占 1500外部文檔占 2500一輪用戶消息加助手回復(fù)大概 400 token那么 8K 窗口最多只能裝 (8000 - 1500 - 2500) / 400 10 輪對(duì)話。超過(guò)這個(gè)輪數(shù)無(wú)論你代碼里怎么拼最終都會(huì)被 API 服務(wù)端截?cái)?。這個(gè)計(jì)算一定要前置不要等線上爆了再回頭算。3.2 上下文窗口的三層分配法我建議把所有進(jìn)入上下文窗口的內(nèi)容分成三層按優(yōu)先級(jí)順序分配預(yù)算第一層是系統(tǒng)提示詞與任務(wù)指令包括角色設(shè)定、輸出格式、回答邊界這部分絕對(duì)不能被截?cái)囝A(yù)算占比建議 10% 到 20%。第二層是外部知識(shí)和檢索結(jié)果這是回答問(wèn)題的資料占比建議 40% 到 50%但如果檢索質(zhì)量不高寧可少給不要硬塞。第三層是對(duì)話歷史占比建議 30% 到 50%且必須按從新到舊排列最近的對(duì)話擁有最高保留優(yōu)先級(jí)。這個(gè)分配比例不是拍腦袋。系統(tǒng)提示詞決定模型的行為框架沒(méi)了它模型就像沒(méi)領(lǐng)到任務(wù)的實(shí)習(xí)生外部知識(shí)決定回答的信息來(lái)源是用戶價(jià)值的核心對(duì)話歷史則負(fù)責(zé)提供對(duì)話連續(xù)性和用戶偏好。三者如果爭(zhēng)搶空間犧牲的順序應(yīng)該是對(duì)話歷史中最舊的部分而不是壓縮系統(tǒng)提示詞更不是砍掉檢索資料。3.3 一個(gè)可復(fù)用的預(yù)算公式我在項(xiàng)目里通常會(huì)維護(hù)一個(gè)工具函數(shù)它接收模型窗口上限、系統(tǒng)提示詞 token 數(shù)、外部知識(shí) token 數(shù)然后返回當(dāng)前可用的歷史消息輪數(shù)。可用歷史 Token 窗口上限 - (系統(tǒng)提示詞 Token 外部知識(shí) Token 安全緩沖) 可用對(duì)話輪數(shù) 可用歷史 Token / 單輪平均 Token安全緩沖一般是窗口上限的 10% 到 15%用于應(yīng)對(duì) Token 計(jì)數(shù)偏差和模型可能追加輸出占用的位置。注意許多 API 的輸入和輸出共享同一個(gè)上下文窗口模型答復(fù)也會(huì)占用空間。如果忽略了輸出預(yù)留你在輸入側(cè)壓滿窗口模型可能只能輸出很短就觸頂。再給個(gè)實(shí)際案例模型窗口 128K系統(tǒng)提示詞 2000知識(shí)庫(kù)檢索需注入 15000安全緩沖 10% 即 12800那么可用歷史 Token 就是 128000 - 2000 - 15000 - 12800 98200。假設(shè)單輪對(duì)話平均 800 Token你可以保留約 122 輪歷史。但如果系統(tǒng)提示詞被某次迭代膨脹到 8K知識(shí)檢索又調(diào)大到了 50K那可用歷史就只剩 57200可保留輪數(shù)直接掉到 71 輪。你的上下文策略不變但效果會(huì)肉眼可見(jiàn)地下降——這就是為什么每次改動(dòng) Prompt 或檢索策略后都要重新算一遍預(yù)算。3.4 用水位線動(dòng)態(tài)調(diào)整窗口策略固定閾值的問(wèn)題在于它是靜態(tài)的而對(duì)話是動(dòng)態(tài)的。我采用的方法是定義一個(gè)三段水位線當(dāng)已用 Token 低于窗口的 50% 時(shí)走完整歷史模式不做任何壓縮和摘要保證信息無(wú)損。當(dāng)達(dá)到 50% 到 80% 時(shí)開(kāi)啟溫和壓縮策略把最舊的對(duì)話按話題聚合成摘要保留當(dāng)前話題的完整原文。當(dāng)超過(guò) 80% 時(shí)進(jìn)入緊急模式全部歷史改為摘要 最近 5 輪原始消息檢索結(jié)果只保留重排后的 Top 3系統(tǒng)提示詞裁剪掉冗長(zhǎng)的示例。這個(gè)水位線的具體數(shù)值可以根據(jù)你的成本和體驗(yàn)?zāi)繕?biāo)調(diào)整但思路是關(guān)鍵不要等窗口快滿了才手忙腳亂地截?cái)喽崆胺謱咏导?jí)。用戶是無(wú)感的但模型看到的上下文結(jié)構(gòu)始終處于健康狀態(tài)。4. 落地實(shí)現(xiàn)一個(gè)支持多模式切換的 context 管理器講完選型和參數(shù)這一部分直接上工程實(shí)現(xiàn)。我會(huì)展示一個(gè)極簡(jiǎn)但足夠支撐生產(chǎn)環(huán)境的 ContextManager 核心結(jié)構(gòu)它在設(shè)計(jì)上預(yù)留了模式插槽方便你按需要替換具體策略。4.1 工程結(jié)構(gòu)設(shè)計(jì)核心類(lèi)只做三件事記錄消息、管理模式、構(gòu)建最終發(fā)給模型的消息列表。from enum import Enum from typing import List, Dict, Optional class ContextMode(Enum): TRUNCATE truncate SUMMARIZE summarize RETRIEVAL retrieval HYBRID hybrid class ContextManager: def __init__(self, max_tokens: int 8000, mode: ContextMode ContextMode.HYBRID): self.max_tokens max_tokens # 模型上下文窗口上限 self.system_prompt 你是智能助手... # 系統(tǒng)提示詞單獨(dú)存儲(chǔ) self.history: List[Dict] [] # 編碼后的歷史消息 {role: ..., content: ...} self.summary: Optional[str] None # 長(zhǎng)期摘要 self.mode mode def add_message(self, role: str, content: str) - None: # 先選擇是否觸發(fā)摘要壓縮再追加新消息 if self._should_summarize(): self._roll_summary() self.history.append({role: role, content: content}) def build_context(self, query: str, extra_docs: Optional[List[str]] None) - List[Dict]: # 根據(jù)模式和 token 預(yù)算組裝最終消息列表 ...實(shí)際工程中我會(huì)把 System Prompt 單獨(dú)存一份從來(lái)不塞進(jìn) history它是所有模式都要無(wú)條件保留的骨架。history 里的每一條都記錄消息內(nèi)容和 token 估算值避免每輪臨時(shí)重新計(jì)算整段 token。4.2 截?cái)嗯c摘要的代碼骨架截?cái)嗄J降暮诵氖遣眉艉蟮南⒘斜肀仨毐3衷陬A(yù)算內(nèi)。比較穩(wěn)的做法是先計(jì)算整體 token再按從新到舊的順序逐條往回加直到預(yù)算耗盡。def _build_truncate_context(self) - List[Dict]: result [{role: system, content: self.system_prompt}] remain self.max_tokens - estimate_tokens(self.system_prompt) - RESERVED_OUTPUT # 從最新消息往前加始終保持新消息優(yōu)先 for msg in reversed(self.history): t estimate_tokens(msg[content]) 4 # 4 是角色標(biāo)記等額外開(kāi)銷(xiāo) if remain - t 0: break result.append(msg) remain - t # 由于是倒序追加需要翻轉(zhuǎn)回時(shí)間正序 non_system [m for m in result[1:]][::-1] return [result[0]] non_system注意這個(gè)順序處理很多人直接正序遍歷舊消息結(jié)果最新消息反而被截?cái)嗑褪亲铋_(kāi)始說(shuō)的那個(gè)事故。摘要模式則是在截?cái)嘀獍言缙跉v史送給摘要模型再把 summary 以system身份注入def _roll_summary(self) - None: # 取最舊的若干消息生成摘要替代原始內(nèi)容 target self.history[:-KEEP_RECENT] # KEEP_RECENT 為保留的最近消息數(shù) if not target: return prompt f請(qǐng)把以下對(duì)話壓縮為不超過(guò)300字的摘要保留關(guān)鍵數(shù)字、實(shí)體和用戶偏好{target} self.summary call_llm(prompt) self.history self.history[-KEEP_RECENT:]實(shí)際操作里摘要生成不能把整個(gè)歷史全塞進(jìn)一個(gè) Prompt要做分塊聚合。先對(duì)每 10 輪生成一個(gè)分段摘要再把這些分段摘要合并成最終摘要避免長(zhǎng)文本超出摘要模型自身的窗口。4.3 檢索模式接入檢索模式與截?cái)?摘要最大的區(qū)別是它不依賴 history 作為主要上下文而是由外部檢索結(jié)果主導(dǎo)。接口上ContextManager 需要接受外部傳入的 docs并把它們按知識(shí)片段的形式放在 system prompt 之后、對(duì)話歷史之前def _build_retrieval_context(self, query: str, docs: List[str]) - List[Dict]: knowledge \n\n.join(f[資料{i1}] {doc} for i, doc in enumerate(docs)) k_system self.system_prompt \n\n請(qǐng)嚴(yán)格依據(jù)以下資料回答\n knowledge return [{role: system, content: k_system}] self._build_truncate_context()注意這里的優(yōu)先級(jí)順序知識(shí)片段跟著系統(tǒng)提示詞走而不是跟著用戶消息走。這樣模型會(huì)把知識(shí)當(dāng)作必須遵守的背景材料而不是把它當(dāng)作普通的歷史對(duì)話。檢索結(jié)果的排序也很有講究我在拼接前會(huì)用重排模型把最相關(guān)的三個(gè)片段放在最前面并在片段之間加序號(hào)。實(shí)踐表明模型對(duì)前兩個(gè)片段的關(guān)注度顯著高于后面的Top 3 之后的片段很多情況下只是湊數(shù)甚至帶來(lái)噪聲。4.4 模式切換的自動(dòng)決策規(guī)則混合模式的實(shí)現(xiàn)核心是決策函數(shù)。我建議先用可解釋的規(guī)則不要一上來(lái)就用分類(lèi)模型因?yàn)槟愫茈y調(diào)試。def decide_mode(self, query: str, docs: Optional[List[str]] None) - ContextMode: used_ratio self.estimate_used_tokens() / self.max_tokens if docs and self._has_retrieval_intent(query): return ContextMode.RETRIEVAL if used_ratio 0.5: return ContextMode.SUMMARIZE return ContextMode.TRUNCATE檢索意圖的判斷可以樸素一點(diǎn)用戶問(wèn)的是知識(shí)庫(kù)中存在的事實(shí)性問(wèn)題且問(wèn)題中包含主體名詞就優(yōu)先走檢索如果是閑聊或延續(xù)性追問(wèn)就走歷史模式。這個(gè)規(guī)則雖然簡(jiǎn)單但比所有請(qǐng)求一律檢索要省錢(qián)且更準(zhǔn)。等積累足夠多的日志后再把 decision 換成小粒度分類(lèi)模型但決策函數(shù)的外部接口保持不變。5. 我踩過(guò)的那些坑context 相關(guān)的典型故障與排查這部分是我最想分享的實(shí)戰(zhàn)內(nèi)容。上下文管理的問(wèn)題有一個(gè)特點(diǎn)現(xiàn)象在模型輸出側(cè)根因在輸入側(cè)排查鏈路過(guò)長(zhǎng)非常容易誤判。我把典型的故障現(xiàn)象、根因和排查路徑整理成了一張速查表。5.1 癥狀與根因速查表癥狀可能根因排查方向?qū)υ捿啍?shù)一多就失憶截?cái)嗖呗园言缙陉P(guān)鍵信息丟棄檢查實(shí)際發(fā)給模型的請(qǐng)求體確認(rèn)截?cái)囗樞蚰P椭貜?fù)說(shuō)同一句話上下文窗口內(nèi)信息冗余模型陷入自激檢查歷史中是否存在大量近似重復(fù)的助手回復(fù)回答與資料不符檢索結(jié)果內(nèi)混入低相關(guān)片段干擾判斷檢查注入的知識(shí)排序和閾值響應(yīng)延遲突然升高注入 token 太多或摘要生成鏈路過(guò)長(zhǎng)統(tǒng)計(jì)各模式下的平均請(qǐng)求 token 數(shù)用戶連續(xù)追問(wèn)時(shí)答非所問(wèn)模式切換邏輯錯(cuò)誤檢索模式丟失了對(duì)話意圖查看決策函數(shù)的輸入特征是否包含最近提問(wèn)這張表我在團(tuán)隊(duì)里貼了好幾個(gè)月每次線上出問(wèn)題第一件事不是去調(diào) Prompt而是先對(duì)著這個(gè)表做輸入側(cè)體檢。結(jié)果發(fā)現(xiàn)超過(guò)一半的上下文問(wèn)題都不是模型問(wèn)題而是消息構(gòu)建不對(duì)、預(yù)算估算錯(cuò)誤或模式切換策略不合理。這個(gè)認(rèn)知很重要?jiǎng)e一遇到輸出不對(duì)就調(diào) Prompt先往前看輸入。5.2 上下文污染的隱形殺手5.3 上下文污染的隱形殺手上下文污染是我排查時(shí)最頭疼的問(wèn)題它隱蔽在正常輸出之下極難察覺(jué)。最常見(jiàn)的污染源有三個(gè)第一是系統(tǒng)提示詞里殘留了之前實(shí)驗(yàn)用的示例內(nèi)容比如你測(cè)試過(guò)旅游推薦忘了刪掉示例后面所有回答都被帶出旅游行業(yè)的味道第二是工具調(diào)用的結(jié)果殘留Agent 調(diào)用搜索引擎后把大段 HTML 或 JSON 結(jié)果留在上下文中后面幾輪即使與搜索無(wú)關(guān)這段噪聲也在持續(xù)影響模型第三是檢索片段之間的矛盾知識(shí)庫(kù)不同版本對(duì)同一問(wèn)題的回答沖突模型為了調(diào)和兩者產(chǎn)出了一個(gè)看似合理但兩邊都不沾的幻覺(jué)答案。針對(duì)污染問(wèn)題我的排查經(jīng)驗(yàn)是把發(fā)送給模型的完整消息列表可視化逐條標(biāo)出每條消息的來(lái)源標(biāo)簽。標(biāo)注來(lái)源后污染源通常一眼就能看出來(lái)。修復(fù)手段不是簡(jiǎn)單刪除而是給非必要的工具結(jié)果和低置信度檢索片段設(shè)置過(guò)時(shí)淘汰機(jī)制——比如工具結(jié)果只保留最近一輪檢索片段在規(guī)定輪數(shù)后自動(dòng)移除。干凈上下文是模型輸出的地基這遠(yuǎn)比優(yōu)化模型參數(shù)管用。5.4 排查利器上下文可視化與最小復(fù)現(xiàn)分享兩個(gè)非常有效但很少被人提到的排查手段。第一個(gè)是上下文可視化抓包在代碼里封裝一個(gè) debug 開(kāi)關(guān)把每次實(shí)際發(fā)給模型的消息按照 system/user/assistant 分段打印標(biāo)注每段的 token 占比和來(lái)源。線上開(kāi)啟之后問(wèn)題復(fù)現(xiàn)時(shí)我能在幾秒內(nèi)看到模型到底看了什么。第二個(gè)是最小復(fù)現(xiàn)法拿到故障請(qǐng)求后不斷裁剪上下文直到問(wèn)題從復(fù)現(xiàn)變成不復(fù)現(xiàn)被裁剪掉的部分就是可疑信息。用二分法手工刪除通常七八次定位就能找到根因。這兩個(gè)手段比反復(fù)修改 Prompt 要高效得多。我自己有個(gè)切身體會(huì)有一次模型反復(fù)輸出格式錯(cuò)誤的 JSON我調(diào)了二十多版提示詞都沒(méi)用最后用最小復(fù)現(xiàn)法發(fā)現(xiàn)問(wèn)題出在歷史消息里有一條被截?cái)嗔税雮€(gè)字的舊用戶消息觸發(fā)了模型的補(bǔ)救心理導(dǎo)致它在 JSON 里塞了額外的解釋文本。這種坑只有把輸入側(cè)完完整整攤開(kāi)看才能發(fā)現(xiàn)。5.5 三個(gè)省 Token 的實(shí)操技巧最后聊一下大家最關(guān)心的成本問(wèn)題。在上下文預(yù)算里省錢(qián)核心不是一味壓縮而是減少無(wú)效注入。我長(zhǎng)期使用三個(gè)技巧第一個(gè)是去重再注入。檢索出的 Top K 片段之間經(jīng)常有大量重復(fù)內(nèi)容比如同一份文檔的多個(gè)分塊互相重疊。在拼接進(jìn)上下文之前先做一次基于 MinHash 或簡(jiǎn)單文本哈希的相似度去重通常能砍掉 10% 到 20% 的 token而且回答質(zhì)量不降反升。第二個(gè)是指令瘦身。系統(tǒng)提示詞里經(jīng)常堆了大量示例和冗長(zhǎng)的邊界描述但模型根本用不到那么多。把到一個(gè)穩(wěn)定版本后你可以做一次精簡(jiǎn)把每條指令拿掉跑一遍回歸測(cè)試集如果輸出質(zhì)量不變就永久拿掉這個(gè)過(guò)程反復(fù)迭代幾次系統(tǒng)提示詞能縮到 60%。第三個(gè)是把歷史降采樣做到策略里。對(duì)早期對(duì)話不需要每一條都保留完整原文每隔 N 條取一條快照就能維持連續(xù)性再配合摘要兜底。這個(gè)方案比全量摘要更便宜也比純截?cái)喔踩?。我目前的主力?xiàng)目就是這個(gè)策略在支撐長(zhǎng)會(huì)話場(chǎng)景成本比最初的全量歷史方案下降了差不多 40%用戶的失憶投訴基本消失。6. 寫(xiě)在最后的幾條個(gè)人體會(huì)做上下文管理這一年多有個(gè)很深的體會(huì)很多人把大模型應(yīng)用的核心競(jìng)爭(zhēng)力押在模型選擇和 Prompt 上但上線之后真正拉開(kāi)體驗(yàn)差距的往往是 context-mode 這種輸入側(cè)基建。模型再?gòu)?qiáng)看不到該看的信息輸出也是空中樓閣上下文組織得好哪怕是通用模型也能在復(fù)雜任務(wù)里表現(xiàn)得像定制模型。如果你現(xiàn)在正要開(kāi)始做一個(gè) AI 應(yīng)用我的建議很簡(jiǎn)單先花一晚上把上下文預(yù)算公式寫(xiě)出來(lái)再選擇一個(gè) ContextManager 骨架給每一種模式做好插槽不要急著在第一個(gè)版本里就把所有策略都寫(xiě)滿。從截?cái)嚅_(kāi)始加上水位數(shù)統(tǒng)計(jì)當(dāng)數(shù)據(jù)證明你需要摘要和檢索時(shí)再逐步引入。這個(gè)演進(jìn)路線比一開(kāi)始就堆復(fù)雜方案要穩(wěn)妥得多。最后再說(shuō)一個(gè)小技巧給每條進(jìn)入上下文的消息打一個(gè)來(lái)源標(biāo)簽字段它可以是一條 debug 注釋也可以是一個(gè)結(jié)構(gòu)化元數(shù)據(jù)。這個(gè)習(xí)慣看起來(lái)不起眼但在你排查幻覺(jué)問(wèn)題、分析 token 消耗、優(yōu)化策略路由時(shí)能讓你少走無(wú)數(shù)彎路。上下文管理是細(xì)活所有省下的時(shí)間最后都會(huì)以故障的形式還回來(lái)。把基礎(chǔ)打牢比臨時(shí)抱佛腳調(diào) Prompt 有用一萬(wàn)倍。