上下文工程:構(gòu)建透明推理架構(gòu)引擎的實(shí)踐指南)
1. 從提示詞調(diào)優(yōu)到上下文工程多智能體系統(tǒng)正在經(jīng)歷什么如果你最近半年一直在折騰多智能體系統(tǒng)大概率會有一種很強(qiáng)烈的割裂感一方面單個智能體的提示詞已經(jīng)被你打磨到近乎完美角色設(shè)定、輸出格式、few-shot 示例全都齊了另一方面一旦把三五個智能體放進(jìn)同一個任務(wù)流里整個系統(tǒng)就開始精神分裂——A 智能體輸出的結(jié)構(gòu)化 JSON 被 B 智能體當(dāng)成自然語言理解C 智能體的中間推理過程污染了 D 智能體的上下文窗口最后匯總出來的結(jié)果連你自己都不敢認(rèn)。這不是提示詞的問題這是上下文工程的問題。我在過去一年里陸續(xù)搭過七八套多智能體協(xié)作系統(tǒng)從最簡單的規(guī)劃者-執(zhí)行者-審查者三件套到帶 RAG 檢索、帶工具調(diào)用、帶長期記憶的復(fù)雜編排踩過的坑幾乎都指向同一個根因大多數(shù)人把多智能體系統(tǒng)當(dāng)成多個提示詞的疊加而它本質(zhì)上是一個上下文在多個推理單元之間流動、變形、衰減的架構(gòu)問題。提示詞工程解決的是單個智能體怎么想上下文工程解決的是多個智能體之間怎么傳遞、隔離、壓縮、重建上下文讓推理過程透明可追溯。這一篇是多智能體系統(tǒng)上下文工程系列的第五篇前面幾篇分別聊了上下文窗口的預(yù)算分配、RAG 檢索結(jié)果如何注入、智能體間消息協(xié)議的設(shè)計(jì)、以及長期記憶的讀寫策略。這一篇我想把視角拉高一層專門講上下文與推理的透明架構(gòu)引擎——也就是當(dāng)系統(tǒng)里同時存在多個智能體、多輪推理、多源上下文時你怎么設(shè)計(jì)一套引擎讓每一次推理的輸入是什么、輸出是什么、為什么這么決策全部可觀測、可回放、可調(diào)試。關(guān)鍵詞里出現(xiàn)了 RAG、提示詞工程、推理架構(gòu)、多智能體系統(tǒng)還有一堆關(guān)于鵜鶘騎自行車提示詞這類測試用例的熱詞。這些熱詞其實(shí)反映了一個很真實(shí)的現(xiàn)狀大家測提示詞的方式還停留在單點(diǎn)測試——給一個模型一個刁鉆的提示詞看它能不能畫出鵜鶘騎自行車。但多智能體系統(tǒng)的測試復(fù)雜度是單點(diǎn)的指數(shù)級你沒法靠喂一個提示詞看輸出來判斷系統(tǒng)好壞你需要的是推理鏈路的透明化。這篇文章適合誰看如果你已經(jīng)寫過至少一個能跑通的多智能體 demo但一到真實(shí)任務(wù)就各種翻車如果你在用 LangChain、LangGraph、AutoGen、CrewAI 這類框架但總覺得框架幫我做了太多黑盒決策如果你正在做 RAG 知識庫發(fā)現(xiàn)檢索回來的內(nèi)容塞進(jìn)多智能體流程后效果反而變差——那這篇就是寫給你的。我會盡量少講抽象概念多講我實(shí)際搭引擎時的結(jié)構(gòu)設(shè)計(jì)、參數(shù)取舍和踩坑記錄。2. 透明架構(gòu)引擎的四層結(jié)構(gòu)為什么不能只靠框架默認(rèn)編排2.1 大多數(shù)框架默認(rèn)編排的黑盒到底黑在哪先說一個我自己的真實(shí)經(jīng)歷。早期我用某個主流多智能體框架搭了一個研究員-分析師-寫手的流水線跑簡單任務(wù)時效果驚艷但一旦任務(wù)變復(fù)雜問題就來了寫手輸出的內(nèi)容里混進(jìn)了分析師的中間推理草稿研究員檢索到的原始網(wǎng)頁片段被原封不動塞進(jìn)了最終報(bào)告而且我完全不知道是哪一步出的問題。框架的日志只告訴我Agent B 調(diào)用了 Agent C但沒告訴我Agent B 傳給 Agent C 的上下文里到底有什么。這就是黑盒編排的典型癥狀??蚣転榱送ㄓ眯阅J(rèn)幫你做了幾件事自動拼接歷史消息、自動傳遞上一步輸出、自動管理對話輪次。這些自動在 demo 階段是便利在生產(chǎn)階段是災(zāi)難因?yàn)樯舷挛牡拿恳淮纹唇?、截?cái)?、傳遞都是一次信息的有損變換而你對這些變換一無所知。我后來總結(jié)一個透明的上下文架構(gòu)引擎必須顯式管理四層結(jié)構(gòu)缺一層都會導(dǎo)致調(diào)試時抓瞎層級職責(zé)不顯式管理的后果上下文采集層決定每個智能體能看到哪些信息檢索結(jié)果、歷史記憶無差別注入窗口爆炸上下文變換層壓縮、摘要、結(jié)構(gòu)化、脫敏原始噪聲污染下游推理推理執(zhí)行層單次 LLM 調(diào)用的輸入輸出快照無法回放無法定位是哪次調(diào)用出錯追溯與回放層記錄完整推理鏈路支持重放出問題只能靠猜無法復(fù)現(xiàn)這四層不是框架給你的是你必須自己設(shè)計(jì)的??蚣芸梢詭湍阕鰣?zhí)行但上下文的語義邊界必須由你定義。2.2 上下文采集層每個智能體應(yīng)該看到什么而不是能拿到什么采集層的核心原則只有一句話按需注入而非全量傳遞。我見過太多系統(tǒng)把整個對話歷史、所有檢索結(jié)果、所有工具返回值一股腦塞給每個智能體理由是信息越多越好。這是錯的。上下文窗口是有限資源而且更關(guān)鍵的是無關(guān)信息會稀釋相關(guān)信息的注意力權(quán)重。我的做法是給每個智能體定義一個上下文契約Context Contract明確聲明它需要哪幾類信息# 上下文契約示例分析師智能體 analyst_context_contract { required: [task_goal, research_summary], # 必須注入 optional: [raw_sources], # 按需注入 forbidden: [other_agents_reasoning_trace], # 禁止注入 max_tokens: 4000, priority: [task_goal, research_summary, raw_sources] }注意forbidden這一項(xiàng)。其他智能體的推理草稿reasoning trace是最容易被誤注入、也最容易造成污染的內(nèi)容。研究員的我覺得這個來源可能不太可靠但先記下來這種內(nèi)心獨(dú)白如果被注入到寫手的上下文里寫手可能會莫名其妙地在報(bào)告里寫這個來源不太可靠。推理草稿應(yīng)該被隔離在產(chǎn)生它的智能體內(nèi)部只把結(jié)論傳遞給下游。采集層還有一個容易被忽略的點(diǎn)上下文的時效性標(biāo)記。RAG 檢索回來的內(nèi)容、長期記憶里讀出的內(nèi)容、當(dāng)前任務(wù)實(shí)時產(chǎn)生的中間結(jié)果這三類信息的可信度和時效性完全不同。我會給每條上下文打上來源標(biāo)簽和時間戳讓下游智能體知道這條信息是三天前從知識庫檢索的可能已經(jīng)過時。2.3 上下文變換層壓縮不是刪減是語義重建變換層是四層里技術(shù)含量最高的一層。很多人理解的上下文壓縮就是截?cái)嗷蛘哒嬲淖儞Q應(yīng)該是語義重建——把冗長的原始上下文重建成下游智能體真正需要的、結(jié)構(gòu)化的、無歧義的信息。我常用的三種變換策略第一種是結(jié)構(gòu)化提取。比如研究員檢索回來一堆網(wǎng)頁不要直接把網(wǎng)頁文本傳給分析師而是讓一個輕量的提取步驟把網(wǎng)頁內(nèi)容轉(zhuǎn)成結(jié)構(gòu)化字段{claim, evidence, source, confidence}。這樣分析師拿到的是干凈的斷言列表而不是一堆 HTML 噪聲。第二種是分層摘要。對于長對話歷史不要用一次摘要壓到底而是做分層最近三輪保留原文三到十輪做段落級摘要十輪以上做要點(diǎn)級摘要。這樣既保留了近期上下文的細(xì)節(jié)又控制了總長度。第三種是沖突消解。當(dāng)多個來源的信息互相矛盾時RAG 檢索到 A 說 X長期記憶里 B 說非 X變換層要顯式標(biāo)記沖突而不是讓下游智能體自己糾結(jié)。我會生成一個conflicts字段把矛盾點(diǎn)列出來讓下游智能體知道這里存在不確定性。提示變換層最容易犯的錯是過度壓縮。我踩過一次坑把檢索結(jié)果壓縮得太狠導(dǎo)致關(guān)鍵數(shù)字被摘要掉了下游智能體基于錯誤信息推理整個任務(wù)跑偏。后來我定了個規(guī)矩涉及數(shù)字、日期、專有名詞的內(nèi)容壓縮時必須原樣保留不允許摘要改寫。2.4 推理執(zhí)行層與追溯層讓每一次 LLM 調(diào)用都可回放執(zhí)行層的關(guān)鍵是快照。每一次 LLM 調(diào)用都要完整記錄輸入 messages變換后的最終版本、模型參數(shù)、輸出內(nèi)容、token 消耗、耗時。這不是為了日志好看而是為了回放調(diào)試。我搭的引擎里每次調(diào)用會生成一個trace_id結(jié)構(gòu)大概是這樣{ trace_id: task_2024_xxx_step_3, agent: analyst, input_snapshot: { messages: [...], context_sources: [research_summary_v2, task_goal], token_count: 3820 }, output: {...}, latency_ms: 4200, model: xxx }有了這個當(dāng)最終結(jié)果不對時我可以沿著 trace 鏈一路回溯是采集層注入了錯誤信息是變換層壓縮丟了關(guān)鍵內(nèi)容還是執(zhí)行層模型本身推理出錯沒有快照你只能靠猜有了快照問題定位從小時級降到分鐘級。追溯層還要支持重放。我實(shí)現(xiàn)過一個簡化版重放給定某個 trace_id用完全相同的輸入重新調(diào)用一次模型看輸出是否一致。這能幫我區(qū)分是上下文問題還是是模型隨機(jī)性問題。如果重放輸出一致說明是上下文設(shè)計(jì)的問題如果不一致說明是模型本身的方差需要調(diào)溫度參數(shù)或加約束。3. RAG 在多智能體上下文里的正確接入姿勢3.1 為什么 RAG 塞進(jìn)多智能體后效果反而變差關(guān)鍵詞里 RAG 出現(xiàn)頻率極高還有rag瓶頸rag檢索增強(qiáng)rag智能體這些詞。我自己的觀察是單智能體 RAG 效果通常不錯但多智能體 RAG 經(jīng)常翻車。原因有三個。第一檢索時機(jī)錯位。單智能體里檢索通常發(fā)生在回答前一步檢索結(jié)果直接進(jìn)上下文。但多智能體里如果每個智能體都各自檢索一遍會出現(xiàn)重復(fù)檢索、檢索結(jié)果不一致、檢索結(jié)果互相覆蓋的問題。我見過一個系統(tǒng)研究員檢索了 5 篇文檔分析師又檢索了 5 篇寫手再檢索 5 篇最后上下文里塞了 15 篇文檔其中大量重復(fù)窗口直接爆掉。第二檢索結(jié)果沒有經(jīng)過變換就注入。原始檢索片段是給人看的不是給智能體看的。它包含大量導(dǎo)航欄、頁腳、無關(guān)段落。直接注入會嚴(yán)重稀釋有效信息。第三檢索結(jié)果的可信度沒有傳遞。RAG 檢索回來的內(nèi)容質(zhì)量參差不齊但下游智能體不知道哪條可信。如果檢索層不標(biāo)注置信度下游就會把低質(zhì)量內(nèi)容和高質(zhì)量內(nèi)容同等對待。3.2 我的 RAG 接入方案檢索一次變換后分發(fā)我的做法是把 RAG 檢索從智能體內(nèi)部抽出來變成引擎級別的一個共享服務(wù)。整個任務(wù)流只檢索一次或按需檢索少數(shù)幾次檢索結(jié)果經(jīng)過變換層統(tǒng)一處理然后按各智能體的上下文契約分發(fā)。具體流程統(tǒng)一檢索入口任務(wù)開始時由引擎根據(jù)任務(wù)目標(biāo)生成檢索 query調(diào)用 RAG 服務(wù)拿到原始結(jié)果。變換層處理對原始結(jié)果做結(jié)構(gòu)化提取、去重、置信度打分、沖突標(biāo)記。按契約分發(fā)研究員拿到完整結(jié)構(gòu)化結(jié)果分析師拿到摘要版寫手只拿到高置信度的結(jié)論。這樣做的直接好處是檢索結(jié)果在整個系統(tǒng)里是一致的不會出現(xiàn)研究員看到 A 文檔分析師看到 B 文檔的割裂。而且檢索只做一次token 成本和延遲都大幅下降。關(guān)于rag知識庫能存儲圖片嘛這個熱詞我順帶說一句多模態(tài) RAG 是趨勢但在多智能體上下文里圖片的處理要格外小心。圖片的 token 消耗遠(yuǎn)高于文本而且圖片描述本身就需要一次額外的模型調(diào)用。我的建議是圖片在檢索層轉(zhuǎn)成結(jié)構(gòu)化描述caption 關(guān)鍵屬性以文本形式進(jìn)入上下文原圖只在必要時按引用傳遞不要直接把圖片塞進(jìn)每個智能體的上下文。3.3 檢索結(jié)果與推理鏈的綁定讓引用可追溯一個經(jīng)常被忽略的細(xì)節(jié)下游智能體的輸出應(yīng)該能追溯到具體的檢索來源。我在變換層會給每條檢索結(jié)果分配一個穩(wěn)定的source_id并要求下游智能體在引用時帶上這個 id。這樣最終輸出里如果出現(xiàn)事實(shí)錯誤我可以直接定位到是哪條檢索結(jié)果的問題而不是籠統(tǒng)地說RAG 效果不好。這個機(jī)制在調(diào)試時價(jià)值巨大。有一次最終報(bào)告里出現(xiàn)了一個錯誤的數(shù)字我通過 source_id 一路回溯發(fā)現(xiàn)是檢索層把兩個相似文檔的段落拼接錯了。如果沒有這個綁定我可能要花半天才能找到根因。4. 推理透明化讓智能體的思考過程可觀測但不污染4.1 推理草稿該不該暴露給其他智能體這是多智能體設(shè)計(jì)里爭議最大的問題之一。一派觀點(diǎn)認(rèn)為推理草稿比如思維鏈應(yīng)該共享因?yàn)樽屜掠沃郎嫌卧趺聪氲挠兄趨f(xié)作。另一派認(rèn)為推理草稿是噪聲會污染下游。我的實(shí)踐結(jié)論是推理草稿應(yīng)該被記錄但不應(yīng)該被默認(rèn)注入下游上下文。記錄是為了調(diào)試和追溯不注入是為了避免污染。這兩件事不矛盾。具體做法是每個智能體的推理草稿寫入追溯層但采集層默認(rèn)不把它作為下游的上下文來源。如果某個下游智能體確實(shí)需要理解上游的推理邏輯比如審查者需要判斷分析師的推理是否合理那就通過一個顯式的推理摘要變換把草稿壓縮成結(jié)構(gòu)化的推理要點(diǎn)再注入。這樣既保留了信息又控制了噪聲。4.2 用推理契約約束輸出格式透明化的另一個關(guān)鍵是輸出格式的約束。如果每個智能體輸出自由文本下游根本沒法可靠解析。我要求每個智能體的輸出必須符合預(yù)定義的推理契約通常包含這幾個字段{ conclusion: 最終結(jié)論, reasoning_steps: [步驟1, 步驟2], evidence_refs: [source_id_1, source_id_2], confidence: 0.85, uncertainties: [不確定的點(diǎn)] }這個契約的好處是conclusion給下游用reasoning_steps給追溯用evidence_refs給引用綁定用confidence給沖突消解用uncertainties給審查者用。一個結(jié)構(gòu)化的輸出同時服務(wù)了四個下游需求比自由文本高效得多。注意約束輸出格式時不要用過于復(fù)雜的嵌套結(jié)構(gòu)。我試過讓智能體輸出五層嵌套的 JSON結(jié)果模型經(jīng)常漏字段或者格式錯誤。后來簡化為兩層穩(wěn)定性大幅提升。格式約束的復(fù)雜度要和模型的指令遵循能力匹配。4.3 置信度傳遞讓不確定性在系統(tǒng)里流動多智能體系統(tǒng)里最危險(xiǎn)的情況是上游不確定下游卻當(dāng)成確定事實(shí)用。比如研究員檢索到的信息本身置信度只有 0.6但傳給分析師時沒標(biāo)注分析師當(dāng)成 0.95 的事實(shí)推理寫手再當(dāng)成鐵定結(jié)論寫進(jìn)報(bào)告。錯誤就這樣被逐級放大。我的解決方案是讓置信度成為上下文的一等公民。每條信息都帶置信度每個智能體的輸出也帶置信度而且下游的置信度不能超過上游證據(jù)的置信度上限。如果分析師基于 0.6 置信度的證據(jù)得出 0.9 置信度的結(jié)論引擎會標(biāo)記這個異常提示置信度躍升不合理。這個機(jī)制幫我抓出過很多隱蔽問題。有一次審查者發(fā)現(xiàn)分析師的置信度異常高回溯后發(fā)現(xiàn)是分析師忽略了證據(jù)里的不確定性標(biāo)記。如果沒有置信度傳遞這個錯誤會一路傳到最終輸出。5. 實(shí)戰(zhàn)踩坑三個讓我重構(gòu)引擎的真實(shí)案例5.1 案例一上下文窗口的隱性溢出早期我的引擎沒有做 token 預(yù)算的硬約束只是盡量控制。結(jié)果有一次任務(wù)跑到第七步模型突然開始輸出亂碼。排查后發(fā)現(xiàn)上下文已經(jīng)累積到超出模型窗口框架自動做了截?cái)嗟財(cái)嗟氖侵虚g的關(guān)鍵證據(jù)導(dǎo)致模型基于殘缺信息推理。修復(fù)方案是引入硬性 token 預(yù)算每個智能體的上下文契約里明確max_tokens采集層在注入前先算總 token超了就按優(yōu)先級裁剪。裁剪順序是先裁 optional 里優(yōu)先級最低的再裁 required 里可摘要的最后才動核心目標(biāo)。永遠(yuǎn)不允許框架自動截?cái)嘟財(cái)啾仨氂梢骘@式控制。5.2 案例二RAG 檢索結(jié)果的幽靈重復(fù)有一次最終報(bào)告里同一段話出現(xiàn)了三次措辭略有不同。排查發(fā)現(xiàn)檢索層返回了三個來源內(nèi)容高度相似同一篇文檔的不同鏡像變換層沒有去重三個來源都被注入了上下文模型把它們當(dāng)成三條獨(dú)立證據(jù)分別引用了一次。修復(fù)方案是在變換層加語義去重對檢索結(jié)果做 embedding相似度超過閾值的合并為一條保留置信度最高的來源。這個改動讓上下文長度平均下降了 30%而且消除了重復(fù)引用的問題。5.3 案例三智能體間的指令漂移最隱蔽的一個坑。任務(wù)開始時我給的指令是生成一份客觀的技術(shù)分析報(bào)告但經(jīng)過研究員、分析師、寫手三個智能體傳遞后最終輸出變成了強(qiáng)烈推薦使用 X 方案。排查發(fā)現(xiàn)分析師在推理草稿里寫了一句這個方案明顯更好這句話被注入到寫手的上下文里寫手把它當(dāng)成了任務(wù)指令的一部分。修復(fù)方案是嚴(yán)格區(qū)分任務(wù)指令和中間推理。任務(wù)指令只在采集層注入一次且標(biāo)記為system級別中間推理永遠(yuǎn)不進(jìn)入下游的 system 消息只能作為user或assistant消息的參考內(nèi)容。這個區(qū)分看似簡單但能避免大量指令漂移問題。6. 引擎落地的工程細(xì)節(jié)從原型到可用6.1 用 LangGraph 還是自己寫編排關(guān)鍵詞里出現(xiàn)了 langchain4j、rag框架這些詞說明很多人在糾結(jié)框架選型。我的經(jīng)驗(yàn)是原型階段用框架生產(chǎn)階段自己寫編排層。框架LangGraph、AutoGen 等在快速驗(yàn)證時很香但它們的抽象層次和你的上下文契約往往對不齊。當(dāng)你需要精細(xì)控制上下文注入、變換、追溯時框架的默認(rèn)行為反而成了阻礙。我的做法是用框架做底層的 LLM 調(diào)用和工具調(diào)用但編排層、上下文采集層、變換層、追溯層全部自己實(shí)現(xiàn)。這樣既享受了框架的便利又保留了架構(gòu)的透明性。編排層其實(shí)不復(fù)雜核心就是一個狀態(tài)機(jī)加一個上下文管理器幾百行代碼就能搞定。6.2 狀態(tài)管理上下文不是全局變量一個常見的反模式是把上下文當(dāng)成全局變量所有智能體共享讀寫。這會導(dǎo)致競態(tài)、污染、難以追溯。正確做法是每個智能體有獨(dú)立的上下文視圖視圖由采集層根據(jù)契約生成智能體只能讀自己的視圖寫自己的輸出。輸出經(jīng)過變換層處理后才能進(jìn)入下一個智能體的視圖。這種視圖隔離的設(shè)計(jì)讓每個智能體的輸入輸出都是確定的、可快照的。調(diào)試時你可以精確知道每個智能體看到了什么而不是面對一個不斷變化的全局狀態(tài)。6.3 性能與成本的平衡透明化是有成本的。每次調(diào)用都做快照、每次變換都做結(jié)構(gòu)化處理會增加延遲和存儲。我的經(jīng)驗(yàn)是追溯層可以異步寫入不阻塞主流程變換層的結(jié)構(gòu)化提取可以用小模型不必用主模型快照可以采樣存儲比如只存最近 N 次調(diào)用的完整快照更早的只存摘要。在成本敏感的場景下我會把追溯層做成可開關(guān)的開發(fā)調(diào)試階段全開生產(chǎn)環(huán)境只記錄關(guān)鍵節(jié)點(diǎn)。這樣既保證了調(diào)試能力又控制了運(yùn)行成本。7. 關(guān)于透明架構(gòu)我最后想說的幾點(diǎn)經(jīng)驗(yàn)搭了這么多套多智能體系統(tǒng)我最大的體會是透明性不是錦上添花而是系統(tǒng)能否從 demo 走向可用的分水嶺。一個不透明的系統(tǒng)你永遠(yuǎn)不知道它為什么成功也不知道它為什么失敗只能靠反復(fù)試錯效率極低。而一個透明的系統(tǒng)每次失敗都能定位到具體的上下文環(huán)節(jié)每次優(yōu)化都有明確的方向。如果你現(xiàn)在正在搭多智能體系統(tǒng)我建議你從第一天就把追溯層建起來哪怕只是最簡單的日志。因?yàn)榈鹊较到y(tǒng)復(fù)雜了再補(bǔ)追溯成本會高得多。另外不要迷信框架的自動編排上下文的語義邊界必須由你親手定義這是框架替代不了的。關(guān)于 RAG 和多智能體的結(jié)合我的核心建議是把檢索抽出來做成共享服務(wù)檢索一次變換后按契約分發(fā)。不要讓每個智能體各自檢索那是上下文爆炸和結(jié)果不一致的根源。最后分享一個小技巧我在引擎里加了一個上下文審計(jì)功能每次任務(wù)結(jié)束后自動生成一份報(bào)告列出每個智能體的上下文來源、token 消耗、置信度變化、沖突點(diǎn)。這份報(bào)告在復(fù)盤時特別有用能幫你快速發(fā)現(xiàn)系統(tǒng)性的上下文設(shè)計(jì)問題而不是每次都從頭排查。這個功能實(shí)現(xiàn)起來不難但價(jià)值極高強(qiáng)烈建議你試試。