上下文管理:解決“跑著跑著就失憶”的工程方案)
在實際 AI Agent 開發(fā)里長任務(wù)“跑著跑著就丟了上下文”是最高頻的坑之一。所謂長任務(wù)是指需要多輪調(diào)用模型才能完成的執(zhí)行過程比如批量處理文檔、按步驟重構(gòu)代碼模塊、生成報告后再逐項校驗任務(wù)通常持續(xù)幾分鐘到幾十分鐘。問題在于模型本身沒有記憶上下文只來自你本次請求傳入的內(nèi)容任務(wù)一旦超過窗口長度或者進程重啟、會話切換早期結(jié)論、用戶約束、已完成列表就會丟失后續(xù)步驟開始“失憶”。prime-agent 正是圍繞這個問題設(shè)計的上下文管理方案它把模型層無狀態(tài)的對話補齊為有狀態(tài)的長任務(wù)執(zhí)行上下文讓 Agent 在 token 受限的前提下仍然能引用早期信息。這篇文章會沿著一條完整鏈路展開先講清楚上下文為什么會丟再拆解 prime-agent 這類方案的核心機制然后給出一套最小可運行的工程示例說明環(huán)境準(zhǔn)備、關(guān)鍵參數(shù)、驗證方法和排查路徑。適合正在開發(fā) Agent 應(yīng)用、做 LLM 應(yīng)用工程化或者被長任務(wù)輸出不穩(wěn)定困擾的開發(fā)者閱讀。1. 長任務(wù)為什么會“跑著跑著就丟了上下文”1.1 上下文窗口不是無限的token 上限與截斷機制大語言模型的上下文窗口是一個有限資源。常見模型從幾萬 token 到幾十萬 token 不等但不管窗口多大長任務(wù)只要持續(xù)產(chǎn)出中間結(jié)果就有可能在某一輪把窗口占滿。窗口占滿后發(fā)生什么取決于調(diào)用方式平臺側(cè)靜默截斷中間內(nèi)容只保留開頭和結(jié)尾。請求直接報錯提示上下文長度超出限制??蚣茏詣幼龌瑒哟翱趤G棄最早的消息。這三種情況都會讓 Agent“失憶”。更隱蔽的是靜默截斷程序沒有報錯后面步驟還在繼續(xù)但關(guān)鍵約束已經(jīng)被裁掉了最終結(jié)果看似正常實際已經(jīng)偏離目標(biāo)。1.2 上下文丟失的三種典型形態(tài)把丟上下文的問題歸納成三種形態(tài)排查時會更快定位形態(tài)表現(xiàn)典型觸發(fā)場景靜默截斷程序正常執(zhí)行但早期約束失效循環(huán)內(nèi)不斷追加歷史消息中間內(nèi)容被裁剪會話重置狀態(tài)完全清空重新從“零”開始進程重啟、會話超時、任務(wù)被調(diào)度系統(tǒng)重新拉起引用漂移信息還在但后續(xù)步驟引用錯誤把多段內(nèi)容壓縮后丟失時間順序或歸屬關(guān)系1.3 普通對話補全為什么不能直接用于長任務(wù)普通對話補全的 API 是無狀態(tài)的。你發(fā)一條請求模型返回一條響應(yīng)服務(wù)端不會替你保存這次對話。所謂“多輪對話”本質(zhì)上是客戶端把歷史消息逐條拼進下一次請求再發(fā)出去。這種模式在短對話里夠用但放到長任務(wù)里會出現(xiàn)兩個問題每次請求都攜帶全部歷史token 消耗線性增長很快觸頂。任務(wù)執(zhí)行到一半需要暫?;蚧謴?fù)時歷史消息存在哪里、如何加載、如何保證不丟都需要自己設(shè)計。prime-agent 這類上下文管理組件解決的正是這兩個問題它把“歷史消息”升級為“執(zhí)行上下文”并給上下文加上持久化、壓縮、檢索和恢復(fù)能力。2. prime-agent 的核心思路把無狀態(tài)對話變成有狀態(tài)執(zhí)行2.1 它在任務(wù)鏈路中的位置可以把 prime-agent 理解成 Agent 和模型之間的一層上下文管理層。任務(wù)開始時它負責(zé)初始化上下文每執(zhí)行完一個步驟它負責(zé)把關(guān)鍵信息寫回存儲后續(xù)步驟需要信息時它負責(zé)把最相關(guān)的內(nèi)容組裝進新的模型請求。這種設(shè)計讓“模型沒有記憶”這個底層限制不再直接暴露給業(yè)務(wù)代碼。業(yè)務(wù)側(cè)只關(guān)心當(dāng)前任務(wù)的目標(biāo)和結(jié)果不關(guān)心上一輪到底傳了多少歷史消息。2.2 四個核心機制機制作用解決什么上下文快照把當(dāng)前任務(wù)的關(guān)鍵狀態(tài)持久化到外部存儲會話重置、進程重啟后的恢復(fù)摘要壓縮把冗長歷史壓縮成結(jié)構(gòu)化摘要長文本超過窗口上限關(guān)鍵信息抽取從步驟結(jié)果中提取約束、結(jié)論、待辦項中間結(jié)果信息過載按需檢索當(dāng)前請求只組裝最相關(guān)的片段token 浪費和信息漂移2.3 和“把上下文全部塞進 Prompt”的本質(zhì)區(qū)別很多人遇到丟上下文第一反應(yīng)是把所有歷史都塞進 Prompt。這個做法在任務(wù)短的時候有效任務(wù)一長就會撞上窗口上限而且會引入大量無關(guān)信息干擾模型輸出。prime-agent 的思路是“有所取舍”把必須記住的信息抽出來保存把不需要的細節(jié)丟棄把臨時需要的信息按需檢索回來。本質(zhì)上是拿“存儲 檢索”換“上下文窗口”讓長任務(wù)不再被窗口大小鎖死。3. 環(huán)境與項目結(jié)構(gòu)學(xué)習(xí)環(huán)境和生產(chǎn)環(huán)境先分開下面示例用于說明工程思路實際實現(xiàn)要結(jié)合自己的語言、框架和模型服務(wù)調(diào)整。如果原始材料沒有給出明確版本落地前先確認(rèn)你的模型 SDK、向量庫和運行環(huán)境版本。3.1 最小運行環(huán)境組件建議說明語言Python 3.10示例代碼基于 Python其他語言同理模型服務(wù)任意外部 LLM API 或本地模型關(guān)注請求接口和返回結(jié)構(gòu)存儲本地文件或 SQLite 即可先跑通再換 Redis、PostgreSQL可選向量數(shù)據(jù)庫只有用到按需檢索時才需要學(xué)習(xí)環(huán)境可以用本地文件存儲快照省去中間件部署。生產(chǎn)環(huán)境至少要換成具備高可用和事務(wù)能力的外部存儲。3.2 推薦的項目結(jié)構(gòu)agent_app/ ├── main.py # 任務(wù)入口 ├── context/ │ ├── store.py # 快照持久化 │ ├── compress.py # 摘要壓縮 │ └── retrieve.py # 按需檢索 ├── steps/ │ ├── plan.py # 任務(wù)步驟定義 │ └── executor.py # 步驟執(zhí)行循環(huán) └── llm/ └── client.py # 模型調(diào)用封裝這個結(jié)構(gòu)把上下文管理、任務(wù)執(zhí)行、模型調(diào)用拆成三層。實際項目可以根據(jù)規(guī)模調(diào)整但“上下文管理獨立成模塊”這一點不要省否則后期排查會非常痛苦。3.3 核心數(shù)據(jù)模型一個長任務(wù)上下文至少需要保存以下信息# 示意數(shù)據(jù)模型實際字段按任務(wù)擴展 dataclass class TaskContext: task_id: str # 任務(wù)唯一標(biāo)識 goal: str # 初始目標(biāo)壓縮時不可丟棄 constraints: list[str] # 用戶約束逐條保留 completed_steps: list[str] # 已完成步驟 pending_steps: list[str] # 待執(zhí)行步驟 latest_summary: str # 當(dāng)前壓縮摘要 raw_events: list[dict] # 原始步驟記錄可被壓縮設(shè)計這個模型時要記住壓縮可以丟細節(jié)但目標(biāo)、約束、已完成/待辦狀態(tài)是任務(wù)能否繼續(xù)的關(guān)鍵必須單獨列出不能混在長摘要里。4. 最小案例從樸素循環(huán)升級到帶上下文管理的 Agent4.1 場景假設(shè)假設(shè)要執(zhí)行一個三步任務(wù)設(shè)定目標(biāo)為產(chǎn)品寫一篇技術(shù)文檔。生成文檔初稿。根據(jù)約束校驗并修改。這個任務(wù)只有三步看似不需要上下文管理但把它循環(huán)執(zhí)行 10 次、20 次時問題就暴露出來了。下面先用樸素實現(xiàn)演示問題再給出改進版。4.2 樸素實現(xiàn)為什么普通循環(huán)會丟上下文# 樸素實現(xiàn)每次請求都攜帶全部歷史 def naive_run(llm, steps, user_goal): messages [{role: user, content: user_goal}] for step in steps: # 把所有歷史消息都發(fā)給模型 content step.execute(llm, messages) messages.append({role: assistant, content: content}) # 問題messages 無限增長窗口遲早占滿這里的錯誤在于歷史消息只增不減也沒有任何持久化。任務(wù)在第 15 步時前面 14 步的完整輸出都堆在請求里中間步驟很容易被截斷。4.3 prime-agent 風(fēng)格的上下文管理實現(xiàn)# 帶上下文管理的執(zhí)行循環(huán) def context_aware_run(llm, context_store, task_id, steps, user_goal): ctx context_store.load(task_id) if ctx is None: ctx TaskContext( task_idtask_id, goaluser_goal, constraintsextract_constraints(user_goal), completed_steps[], pending_stepssteps, latest_summary, raw_events[] ) for step in steps: if step.name in ctx.completed_steps: continue # 恢復(fù)時跳過已完成步驟 # 組裝當(dāng)前請求目標(biāo) 約束 摘要 當(dāng)前步驟輸入 prompt build_prompt( goalctx.goal, constraintsctx.constraints, summaryctx.latest_summary, stepstep.input ) result llm.call(prompt) # 執(zhí)行后把關(guān)鍵信息寫回上下文 ctx.completed_steps.append(step.name) ctx.raw_events.append({step: step.name, result: result}) # 超過閾值時做壓縮而不是無限擴展 if estimate_tokens(ctx.raw_events) COMPRESS_THRESHOLD: ctx.latest_summary compress_summary(llm, ctx.latest_summary, ctx.raw_events) ctx.raw_events [] # 壓縮后釋放原始事件 context_store.save(ctx) # 每步都落盤保證可恢復(fù) return ctx.latest_summary關(guān)鍵點有三個每步完成后立刻保存上下文進程崩潰后可以從最后一步繼續(xù)。請求里只放“目標(biāo) 約束 摘要 當(dāng)前步驟”不塞全部歷史。原始事件超過閾值就壓縮壓縮后清空避免 token 增長失控。4.4 為什么這樣能保住上下文模型側(cè)看到的上下文變小了但任務(wù)最關(guān)鍵的信息沒有丟目標(biāo)、約束、摘要都穩(wěn)定存在。中間過程的完整文本被扔掉代價是“細節(jié)丟失”收益是“關(guān)鍵信息始終可引用”。長任務(wù)真正需要的不是全部歷史而是“夠用的歷史”。5. 關(guān)鍵參數(shù)與壓縮策略選型5.1 關(guān)鍵參數(shù)說明參數(shù)含義常見值調(diào)大影響調(diào)小影響COMPRESS_THRESHOLD原始事件觸發(fā)壓縮的 token 閾值窗口上限的 30%-50%保留更多細節(jié)token 消耗更高壓縮更頻繁細節(jié)丟失更多SNAPSHOT_INTERVAL快照保存頻率每步一次恢復(fù)粒度更細寫入開銷高恢復(fù)時可能丟失最近幾步RETRIEVE_TOP_K按需檢索返回片段數(shù)3-5上下文更充分可能引入噪音信息不足模型重復(fù)提問SUMMARY_MAX_TOKENS壓縮摘要長度上限窗口的 10%-20%摘要信息更全占用更多空間摘要過短丟失細節(jié)參數(shù)沒有絕對的最優(yōu)值要結(jié)合任務(wù)類型調(diào)整。步驟產(chǎn)出很長、關(guān)鍵信息少的任務(wù)閾值可以調(diào)低盡早壓縮步驟之間強依賴、經(jīng)常引用早期結(jié)論的任務(wù)摘要要留足空間。5.2 壓縮策略怎么選策略做法適合場景風(fēng)險全量摘要把全部歷史壓縮成一段摘要階段性結(jié)論明確、步驟線性早期細節(jié)丟失滑動窗口只保留最近 N 輪完整消息近鄰步驟強依賴早期約束失效分層摘要按步驟或主題分塊壓縮可檢索長文檔、多子任務(wù)實現(xiàn)復(fù)雜度高混合策略關(guān)鍵信息單獨保存非關(guān)鍵信息壓縮大多數(shù)生產(chǎn)場景需要定義“關(guān)鍵”規(guī)則推薦組合是目標(biāo)、約束、待辦單獨保存步驟結(jié)果做分層摘要模型請求時再通過檢索把相關(guān)片段拉回來。這是目前長任務(wù)工程里最常見的做法。6. 運行驗證怎么確認(rèn)上下文真的保住了6.1 驗證步驟代碼跑通不代表上下文保住。建議按以下順序驗證檢查快照文件是否按預(yù)期生成內(nèi)容是否包含目標(biāo)、約束、已完成步驟。讓任務(wù)執(zhí)行到第 N 步后手動中斷再重新啟動確認(rèn)能從第 N 步繼續(xù)。在第 1 步設(shè)置一個明確約束比如“禁止使用某個術(shù)語”到第 20 步時觀察輸出是否仍遵守。查看每次請求實際發(fā)送的 token 數(shù)確認(rèn)沒有隨步驟數(shù)線性增長。對比樸素實現(xiàn)和改進版在相同任務(wù)上的最終結(jié)果質(zhì)量。6.2 預(yù)期日志輸出INFO tasktask_001 stepwrite_draft done INFO tasktask_001 raw_events_tokens12800 compress_triggeredTrue INFO tasktask_001 snapshot_saved offset6 INFO tasktask_001 stepreview_draft prompt_tokens3210看到compress_triggeredTrue和snapshot_saved交替出現(xiàn)說明壓縮和持久化都在正常工作。如果只有執(zhí)行日志沒有快照日志就要懷疑上下文根本沒有被保存。6.3 驗證指標(biāo)指標(biāo)關(guān)注點請求 token 數(shù)是否被控制在穩(wěn)定區(qū)間而不是隨步驟增長中斷恢復(fù)成功率重啟后能否從斷點繼續(xù)約束保持率早期約束在后續(xù)步驟的執(zhí)行率最終結(jié)果一致性同輸入兩次執(zhí)行結(jié)果是否穩(wěn)定7. 常見問題與排查鏈路7.1 常見問題速查表問題現(xiàn)象常見原因檢查方式處理建議所有歷史塞進 Prompt窗口很快滿沒有做壓縮或檢索觀察請求 token 數(shù)引入摘要壓縮和按需檢索任務(wù)重啟后從開頭重新執(zhí)行快照未保存或加載失敗檢查快照文件和日志每步落盤增加恢復(fù)邏輯壓縮后早期約束失效摘要只寫了結(jié)論沒寫約束查看 latest_summary 內(nèi)容約束單獨字段保存檢索回來的片段不對片段缺少時間和順序信息檢查檢索返回結(jié)果給片段加時間戳、步驟號快照保存失敗但沒有報錯異常被吞掉關(guān)閉異常吞沒查看日志保存失敗要告警不能靜默7.2 標(biāo)準(zhǔn)排查鏈路按順序排查不要跳步先確認(rèn)上下文存儲有沒有寫入檢查快照文件、數(shù)據(jù)庫記錄。再確認(rèn)加載邏輯重啟后是否讀到了正確的 task_id。接著看壓縮摘要里是否包含約束和待辦。然后看檢索召回片段是否為當(dāng)前步驟真正需要的內(nèi)容。最后看請求組裝實際發(fā)給模型的 Prompt 里到底有什么。7.3 三個必須避開的坑坑一把所有歷史都塞回 Prompt。這是最常見的錯誤。以為自己做了上下文管理實際只是每次把 raw_events 全量拼進請求token 照樣爆炸。正確做法是只保留摘要、約束、待辦和當(dāng)前步驟。坑二摘要只寫“做了什么”不寫“決定了什么”。壓縮時模型傾向于復(fù)述過程忽略結(jié)論和約束。建議壓縮 Prompt 里明確要求必須保留所有用戶約束、目標(biāo)、未完成事項??尤煺毡4媸”煌痰舢惓?。很多 Agent 框架里上下文保存失敗只是 catch 后打一行日志任務(wù)繼續(xù)跑結(jié)果所有狀態(tài)都沒落盤。生產(chǎn)環(huán)境必須把保存失敗當(dāng)成嚴(yán)重錯誤處理寧可暫停任務(wù)也不能讓狀態(tài)丟失。8. 最佳實踐與擴展方向8.1 長任務(wù)上下文管理檢查清單目標(biāo)是否單獨保存不參與壓縮丟棄。用戶約束是否逐條保存并在每次請求內(nèi)重復(fù)注入。已完成任務(wù)列表是否持久化重啟后能跳過。原始事件是否設(shè)置了 token 閾值并觸發(fā)壓縮。快照是否每步或每隔固定步數(shù)落盤。模型請求是否只包含目標(biāo)、約束、摘要、當(dāng)前步驟。保存失敗是否有告警是否阻斷任務(wù)繼續(xù)。測試用例是否包含“中斷后恢復(fù)”場景。8.2 生產(chǎn)環(huán)境還要額外做什么學(xué)習(xí)環(huán)境跑通后進入生產(chǎn)環(huán)境至少要補齊使用 Redis、PostgreSQL 等外部存儲替代本地文件避免多實例任務(wù)狀態(tài)不一致。給上下文管理增加監(jiān)控指標(biāo)請求 token 數(shù)、壓縮次數(shù)、快照耗時、恢復(fù)成功率。對模型調(diào)用增加重試和熔斷避免單次超時導(dǎo)致整個任務(wù)失敗。敏感信息要在寫入快照前脫敏防止上下文落盤帶來數(shù)據(jù)泄露風(fēng)險。保留每個任務(wù)版本的歷史快照方便回溯和分析。8.3 下一步擴展方向如果長任務(wù)場景更復(fù)雜可以在 prime-agent 的思路上繼續(xù)擴展把摘要升級為向量索引任務(wù)規(guī)模大時用相似度檢索替代關(guān)鍵詞匹配。對超長任務(wù)做多級記憶分層工作記憶、任務(wù)記憶、長期記憶。在關(guān)鍵節(jié)點插入人工確認(rèn)點由人來確認(rèn)進度后再繼續(xù)。讓壓縮策略可配置不同任務(wù)類型使用不同閾值和摘要模板。長任務(wù)丟上下文不是模型能力問題而是工程架構(gòu)問題。只要把“模型無狀態(tài)”這個事實接受下來圍繞持久化、壓縮、檢索幾個方向設(shè)計好上下文管理層任務(wù)跑得再久也能保持關(guān)鍵信息在線。對新手來說從這個最小案例開始改造自己的 Agent 循環(huán)比直接上復(fù)雜框架更值得先做。