用到記憶管理的智能體能力擴(kuò)展實(shí)踐)
做AI應(yīng)用這一年多我越來越強(qiáng)烈地感覺到一個(gè)矛盾大模型的智商越來越高但每個(gè)Agent真正能“摸到”的東西卻少得可憐。你給它一個(gè)任務(wù)它的大腦確實(shí)足夠聰明可手邊只有一把螺絲刀再聰明也只能反復(fù)擰螺絲。所謂Agent-Reach說白了就是研究怎么把這只“手”伸得足夠遠(yuǎn)、足夠準(zhǔn)——讓Agent能觸達(dá)它需要的工具、數(shù)據(jù)和協(xié)作對(duì)象。這個(gè)項(xiàng)目我前后迭代了三輪從最開始只能調(diào)兩三個(gè)API的小玩具做到現(xiàn)在能同時(shí)調(diào)度幾十個(gè)工具、跨多個(gè)數(shù)據(jù)源取數(shù)、還能在出錯(cuò)時(shí)自己換方案的工程化系統(tǒng)。這篇文章把完整的設(shè)計(jì)思路、踩過的坑、以及我反復(fù)調(diào)整后的最終架構(gòu)都記錄下來給同樣在做Agent落地、尤其是被工具調(diào)用和上下文管理折磨的朋友一些參考。我要先說清楚一個(gè)容易被忽視的事實(shí)Agent的能力上限很多時(shí)候不是模型決定的而是它的“觸角”決定的。模型再聰明看不到的數(shù)據(jù)就是不知道調(diào)不到的工具就是干不了。Agent-Reach的核心工作就三件事讓Agent看得更廣數(shù)據(jù)接入、夠得更遠(yuǎn)工具調(diào)用、記得更牢記憶管理。下面我從頭到尾拆一遍。1. 為什么“智能體的觸角”決定能力上限1.1 一個(gè)業(yè)務(wù)員的類比想象你手底下有個(gè)特別能干的業(yè)務(wù)員口才一流、腦子轉(zhuǎn)得快、談判技巧拉滿。但如果你只給他一部通訊錄里面就三個(gè)號(hào)碼他能談成的生意注定有限。你給他加一個(gè)行業(yè)人脈庫他能約到更多客戶再給他配一個(gè)產(chǎn)品報(bào)價(jià)系統(tǒng)他能在現(xiàn)場直接給方案再讓他能隨時(shí)調(diào)用財(cái)務(wù)、法務(wù)、供應(yīng)鏈的同事他就能處理真正復(fù)雜的訂單。Agent-Rach做的就是這件事——給模型這個(gè)“超級(jí)業(yè)務(wù)員”配上足夠多的號(hào)碼、系統(tǒng)和協(xié)作通道。這個(gè)類比不是隨便打的。我見過太多團(tuán)隊(duì)把精力花在調(diào)prompt、換更強(qiáng)模型上結(jié)果瓶頸根本不在推理能力而在Agent的外部接口太少、太脆。你讓它寫一份行業(yè)分析報(bào)告它寫得挺好你讓它去查實(shí)時(shí)行情、調(diào)內(nèi)部數(shù)據(jù)接口、把結(jié)果整理成表格發(fā)給指定郵箱它直接卡死——因?yàn)楹竺孢@些動(dòng)作需要“夠得著”外部世界。1.2 我在第一版踩的跟頭我最早做的Agent-Reach原型特別簡陋五個(gè)工具兩個(gè)數(shù)據(jù)庫查詢一個(gè)HTTP請求封裝再加一個(gè)文件讀寫。當(dāng)時(shí)測試效果很不錯(cuò)模型基本指哪打哪。于是我很膨脹一口氣把工具加到二十多個(gè)結(jié)果準(zhǔn)確率肉眼可見地往下掉。當(dāng)時(shí)的表現(xiàn)很典型模型開始搞混相似工具比如把“查詢用戶訂單”調(diào)成“查詢用戶資料”有時(shí)候壓根不調(diào)用工具直接憑訓(xùn)練記憶瞎編數(shù)據(jù)還有的時(shí)候它選擇了正確的工具但參數(shù)格式傳錯(cuò)了。我一開始以為是模型不夠聰明后來排查才發(fā)現(xiàn)是我自己犯了個(gè)經(jīng)典錯(cuò)誤——工具一多選擇難度非線性上升而我沒有任何路由機(jī)制幫模型做決策。這個(gè)經(jīng)歷讓我重新理解了Agent-Reach的真正含義不是把工具堆得越多越好而是要建立一套機(jī)制讓Agent在龐大的工具集和數(shù)據(jù)源里用最低的試錯(cuò)成本找到正確的那一個(gè)。單純的“多”沒有意義“準(zhǔn)”才有。1.3 “Reach”的定義一個(gè)可度量的能力空間在工程上我給Agent-Reach下了一個(gè)可執(zhí)行的定義一個(gè)Agent實(shí)例能夠直接訪問、并通過動(dòng)作影響的外部空間集合。它包含四個(gè)維度工具層能調(diào)用的API、函數(shù)、服務(wù)比如訂單查詢、天氣接口、內(nèi)部工單系統(tǒng)數(shù)據(jù)層能讀取的數(shù)據(jù)庫、文檔庫、文件系統(tǒng)、網(wǎng)頁內(nèi)容記憶層能跨會(huì)話保留和檢索的歷史信息、項(xiàng)目上下文、用戶偏好協(xié)作層能觸達(dá)的其他Agent或人工審核通道這個(gè)定義最大的好處是它把“這個(gè)Agent強(qiáng)不強(qiáng)”從玄學(xué)變成了可測量的問題。你可以給每個(gè)維度打分然后精準(zhǔn)定位系統(tǒng)短板如果模型經(jīng)常答非所問可能是數(shù)據(jù)層不夠如果模型推理正確但執(zhí)行失敗可能是工具層封裝有問題如果同一件事?lián)Q個(gè)時(shí)間問它就不記得了那是記憶層缺失。后續(xù)所有優(yōu)化都是圍繞這四個(gè)維度展開的。2. 從“單點(diǎn)Agent”到“網(wǎng)狀Reach”整體架構(gòu)拆解2.1 單體Agent為什么扛不住很多入門教程教你寫的Agent其實(shí)是單體結(jié)構(gòu)一個(gè)循環(huán)模型思考→調(diào)用工具→觀察結(jié)果→再思考。這種結(jié)構(gòu)在工具少、任務(wù)單一的時(shí)候完全夠用但一旦Reach范圍擴(kuò)大就會(huì)出三個(gè)問題。第一決策與執(zhí)行耦合。模型既要決定“調(diào)哪個(gè)工具”又要負(fù)責(zé)“怎么調(diào)”兩步都擠在一個(gè)上下文里容易互相干擾。第二上下文迅速膨脹。每調(diào)用一個(gè)工具結(jié)果都要塞回對(duì)話歷史多調(diào)幾次上下文窗口就滿了早期的關(guān)鍵信息被擠掉。第三錯(cuò)誤無法隔離。一個(gè)工具超時(shí)或返回臟數(shù)據(jù)會(huì)污染整個(gè)鏈條模型接下來的每一步都在錯(cuò)誤的基礎(chǔ)上做推理。所以我在第二版直接推倒重來把架構(gòu)拆成了清晰的層次。2.2 Agent-Reach的最終架構(gòu)整體分五層從上往下看層級(jí)職責(zé)核心組件協(xié)調(diào)層拆解任務(wù)、決定下一步行動(dòng)、判斷是否完成Planner、Orchestrator路由層從工具注冊中心里選最合適的工具、校驗(yàn)參數(shù)Router、Tool Registry執(zhí)行層真實(shí)調(diào)用外部API、數(shù)據(jù)庫、腳本Tool Executor數(shù)據(jù)層統(tǒng)一各種數(shù)據(jù)源的接入方式屏蔽差異Data Adapter記憶層分層存儲(chǔ)和檢索會(huì)話、項(xiàng)目、長期知識(shí)Memory Manager數(shù)據(jù)流的走向是這樣的用戶任務(wù)進(jìn)入?yún)f(xié)調(diào)層Planner先生成一個(gè)大致的執(zhí)行計(jì)劃路由層根據(jù)當(dāng)前這一步的需求去工具注冊中心檢索候選工具選定后由執(zhí)行層真實(shí)調(diào)用調(diào)用結(jié)果經(jīng)過數(shù)據(jù)層標(biāo)準(zhǔn)化后寫回記憶層再返給協(xié)調(diào)層判斷下一步。關(guān)鍵改動(dòng)在于——模型不再直接面對(duì)幾十個(gè)工具的原始描述它面對(duì)的是路由層篩選后的兩三個(gè)候選決策負(fù)擔(dān)大大減輕。2.3 為什么“決策”和“執(zhí)行”必須分離這個(gè)設(shè)計(jì)理念是我踩坑踩出來的。第一版里模型的prompt里塞了所有工具的JSON Schema讓它自己挑。工具少的時(shí)候沒事工具一多模型的選擇準(zhǔn)確率急劇下降而且每次推理都要重新“讀”一遍所有工具描述token消耗也高。把“選工具”這個(gè)動(dòng)作單獨(dú)抽出來做成路由層之后有三個(gè)立竿見影的好處。第一模型上下文里只需要出現(xiàn)“這一步要做什么”而不是“所有可能的做法”信息密度更高。第二路由層可以獨(dú)立優(yōu)化比如用本地小模型做粗篩、用向量檢索做召回、再用規(guī)則校驗(yàn)兜底而不用動(dòng)主模型。第三路由層可以記錄每次選擇的命中率形成反饋數(shù)據(jù)慢慢調(diào)優(yōu)工具描述和權(quán)重。三層分離之后整個(gè)系統(tǒng)的可維護(hù)性上了個(gè)大臺(tái)階。以前改一個(gè)工具要重新測一整套流程現(xiàn)在只需要在注冊中心改一條元數(shù)據(jù)。3. 工具注冊與動(dòng)態(tài)路由讓Agent學(xué)會(huì)“夠得遠(yuǎn)”這一層是整個(gè)Agent-Reach的地基也是我投入時(shí)間最多的部分。工具注冊中心看著不起眼但它直接決定模型能不能在關(guān)鍵時(shí)刻選對(duì)工具。3.1 用統(tǒng)一Schema描述工具拒絕自由發(fā)揮我見過很多團(tuán)隊(duì)的工具描述寫得特別隨意有的寫“這個(gè)函數(shù)用來獲取用戶信息”有的干脆只貼一個(gè)函數(shù)簽名。這在工具少的時(shí)候無所謂工具一多模型根本分不清“獲取用戶信息”和“獲取用戶訂單”到底有什么區(qū)別參數(shù)也容易傳錯(cuò)。我在Agent-Reach里給每個(gè)工具定義了一份結(jié)構(gòu)化元數(shù)據(jù)用JSON Schema承載包含工具名稱、一句話功能摘要、詳細(xì)描述什么時(shí)候該用、什么時(shí)候不該用、參數(shù)定義類型、必填、枚舉值、示例、返回值結(jié)構(gòu)、錯(cuò)誤碼表。TOOL_SCHEMA { name: query_user_orders, summary: 查詢用戶的歷史訂單列表, description: ( 當(dāng)需要獲取某個(gè)用戶在某段時(shí)間內(nèi)的訂單記錄時(shí)使用。 注意該接口只返回已支付訂單不含未支付或已取消的單。 如果需要查詢訂單的物流狀態(tài)請使用 query_order_logistics。 ), parameters: { type: object, properties: { user_id: {type: string, description: 用戶唯一標(biāo)識(shí)}, start_date: {type: string, format: date, description: 開始日期如2024-01-01}, end_date: {type: string, format: date, description: 結(jié)束日期如2024-06-30} }, required: [user_id] }, return_schema: { type: array, items: { type: object, properties: { order_id: {type: string}, amount: {type: number}, status: {type: string, enum: [paid, refunded, shipped]} } } } }這份Schema看起來啰嗦但它至少解決三個(gè)問題一是給路由層提供了檢索和排序的依據(jù)二是給模型提供了足夠的語義信息來區(qū)分相似工具三是執(zhí)行層的參數(shù)校驗(yàn)可以直接復(fù)用這套定義不用另寫一套。我建議所有工具都按這個(gè)模板補(bǔ)齊特別是“什么時(shí)候不該用”和“和相似工具的差異”這兩項(xiàng)能顯著減少模型選錯(cuò)工具的概率。3.2 路由層先粗篩、再精排、最后兜底路由層的設(shè)計(jì)是我調(diào)了很久才穩(wěn)定下來的目前是三段式流水線。第一段是粗篩用關(guān)鍵詞匹配加常用工具優(yōu)先從注冊中心里快速撈出一批候選。這一步不追求精確只求別漏掉正確工具。第二段是精排把這一步的任務(wù)描述和候選工具的summary、description做向量相似度計(jì)算按分?jǐn)?shù)排序取Top 3。第三段是規(guī)則校驗(yàn)檢查這Top 3里有沒有參數(shù)明顯對(duì)不上的比如任務(wù)里根本沒有user_id這個(gè)字段而某個(gè)工具必填參數(shù)就是user_id那就把分?jǐn)?shù)往下壓。粗篩和精排的代碼實(shí)現(xiàn)大概是這樣的def route_tool(task_step_description: str, registry: ToolRegistry, top_k: int 3): # 第一階段粗篩用關(guān)鍵詞和標(biāo)簽縮小范圍 candidates registry.keyword_search(task_step_description) if not candidates: candidates registry.get_default_tools() # 第二階段精排向量相似度計(jì)算 query_embedding embed(task_step_description) scored [] for tool in candidates: tool_embedding embed(tool.summary tool.description) score cosine_similarity(query_embedding, tool_embedding) # 第三階段規(guī)則兜底參數(shù)匹配度作為懲罰項(xiàng) param_penalty 0.0 required_params tool.parameters.get(required, []) for p in required_params: if p not in task_step_description: param_penalty 0.1 scored.append((tool.name, score - param_penalty)) scored.sort(keylambda x: x[1], reverseTrue) return [name for name, _ in scored[:top_k]]這套三段式方案上線后工具選擇準(zhǔn)確率從之前的六成多恢復(fù)到九成以上。而且它還有一個(gè)好處每段都可以用不同的模型粗篩和精排甚至可以用不上大模型的本地算法完成只有最終把Top 3候選塞進(jìn)主模型prompt時(shí)才需要花錢推理。3.3 工具描述的藝術(shù)別寫說明書寫決策卡寫工具描述的時(shí)候大部分人容易陷入兩種極端要么太簡略比如“獲取訂單信息”要么太啰嗦把實(shí)現(xiàn)細(xì)節(jié)、歷史原因全寫進(jìn)去。我的經(jīng)驗(yàn)是工具描述本質(zhì)是給模型看的“決策卡”它需要的是“什么場景選我、什么場景別選我”而不是“我內(nèi)部是怎么實(shí)現(xiàn)的”。舉個(gè)例子“查詢用戶訂單”和“查詢用戶訂單詳情”這兩個(gè)工具如果描述都只寫“查訂單”模型完全分不清。我后來把前者寫成“返回用戶訂單列表只包含訂單編號(hào)、金額、狀態(tài)等概要信息適合展示列表”把后者寫成“基于訂單ID返回單個(gè)訂單的完整詳情包括商品明細(xì)、收貨地址、支付流水適合需要完整信息的場景”。改動(dòng)之后選擇準(zhǔn)確率立竿見影。我還養(yǎng)成了一個(gè)習(xí)慣每跑一批任務(wù)就把路由層選錯(cuò)的case收集起來分析是描述問題還是參數(shù)問題。描述問題就改元數(shù)據(jù)參數(shù)問題就改Schema積累兩個(gè)月工具描述的迭代速度會(huì)非??臁?. 記憶與上下文擴(kuò)展越過窗口限制拿數(shù)據(jù)4.1 上下文窗口是Agent最硬的物理瓶頸不管你用哪個(gè)模型上下文窗口都是有限度的。你可以說“無限上下文”是趨勢但在實(shí)際工程里塞得越多推理越慢、費(fèi)用越高、準(zhǔn)確率反而下降。所以Agent-Reach的第四個(gè)重點(diǎn)維度是讓Agent在有限窗口內(nèi)拿到它真正需要的信息。我見過不少項(xiàng)目把整份對(duì)話歷史一股腦塞給模型美其名曰“保留上下文”結(jié)果幾千個(gè)token全是廢話。解決這個(gè)問題的思路不是無腦堆窗口而是把記憶分層每層各司其職。4.2 三層記憶架構(gòu)工作記憶、項(xiàng)目記憶、檔案記憶我最終落地的方案是三層記憶每一層的存取策略完全不同。工作記憶Short-term只放當(dāng)前任務(wù)正在處理的中間狀態(tài)比如已經(jīng)執(zhí)行完哪些工具、當(dāng)前還差哪些信息、下一步計(jì)劃是什么。它的特點(diǎn)是短小精悍每次迭代都會(huì)更新最多不超過當(dāng)前任務(wù)范圍。項(xiàng)目記憶Project-level跨會(huì)話保留某個(gè)項(xiàng)目的重要決策和約束。比如“這個(gè)客戶的訂單查詢必須用V2接口”“這個(gè)報(bào)表的金額字段單位是分展示時(shí)要轉(zhuǎn)成元”。項(xiàng)目記憶是針對(duì)性的只在下一次會(huì)話開始時(shí)注入避免每次都要重新交代背景。檔案記憶Long-term把歷史對(duì)話、文檔、工具調(diào)用日志向量化后存入數(shù)據(jù)庫按需檢索而不是全量注入。這一步本質(zhì)上是一個(gè)小型的檢索增強(qiáng)生成RAG系統(tǒng)只不過檢索的對(duì)象是Agent自己的歷史經(jīng)驗(yàn)。class MemoryManager: def __init__(self, vector_store): self.working {} self.project ProjectMemory.load() self.archive vector_store def get_prompt_context(self, task_id: str, task_description: str): # 工作記憶當(dāng)前任務(wù)的執(zhí)行狀態(tài) working_ctx self.working.get(task_id, {}) # 項(xiàng)目記憶和當(dāng)前任務(wù)相關(guān)的項(xiàng)目約束 project_ctx self.project.get_relevant(task_description) # 檔案記憶檢索歷史相似任務(wù)的執(zhí)行經(jīng)驗(yàn)和結(jié)果 archive_ctx self.archive.search(task_description, top_k5) return { working: working_ctx, project: project_ctx, archive: archive_ctx } def update_after_step(self, task_id: str, step_result: dict): self.working[task_id][last_step] step_result if self.working[task_id].should_archive(): self.archive.add(self.working[task_id])三層記憶分開管理之后我發(fā)現(xiàn)prompt的體積平均下降了百分之四十以上而任務(wù)完成率反而升了。原因是模型不再被海量無關(guān)信息干擾注意力更集中。4.3 讓Agent“忘記”有時(shí)比記住更重要這里說一個(gè)反直覺的經(jīng)驗(yàn)記憶系統(tǒng)最難的其實(shí)不是“存”,而是“刪”。如果你不主動(dòng)清理過期的、錯(cuò)誤的、重復(fù)的信息會(huì)像垃圾一樣堆積污染每一次推理。我踩過一個(gè)具體的坑有一次系統(tǒng)把某客戶三個(gè)月前的訂單狀態(tài)當(dāng)成最新狀態(tài)返回給用戶用戶投訴之后我一查發(fā)現(xiàn)是檔案記憶里的一條舊記錄被檢索出來而且它的相關(guān)度分?jǐn)?shù)比新記錄還高。問題出在向量檢索只比相似度不比時(shí)間新鮮度。修復(fù)方案是給每條記憶加了時(shí)間字段檢索時(shí)把時(shí)間衰減系數(shù)乘進(jìn)去。這個(gè)改動(dòng)看起來小但直接避免了一整類數(shù)據(jù)新鮮度事故。所以現(xiàn)在我的記憶層有一條鐵律檢索結(jié)果必須帶時(shí)間權(quán)重工作記憶必須高保真檔案記憶必須可丟棄。寧可讓模型說“我不知道”也不能讓它拿著過期數(shù)據(jù)一本正經(jīng)地胡說。5. 實(shí)戰(zhàn)踩坑工具調(diào)用超時(shí)、上下文污染與幻覺的排查記錄寫這套系統(tǒng)的過程本質(zhì)上就是一段接一段的排錯(cuò)史。我覺得把這部分單獨(dú)拿出來講比直接給一個(gè)“完美架構(gòu)”更有價(jià)值因?yàn)榭硬攀沁@個(gè)項(xiàng)目真正的老師。5.1 工具調(diào)用超時(shí)模型為什么會(huì)在一個(gè)失敗點(diǎn)上反復(fù)橫跳第一次做Agent-Reach的時(shí)候我給工具調(diào)用設(shè)了一個(gè)很簡單的封裝直接調(diào)用等返回結(jié)果。有一次一個(gè)第三方API響應(yīng)特別慢超過了模型的等待閾值返回的是個(gè)超時(shí)錯(cuò)誤。這里我本來以為模型會(huì)換個(gè)方案結(jié)果它做了一件特別蠢的事——用一模一樣的參數(shù)再調(diào)一次同一個(gè)工具然后又超時(shí)又調(diào)循環(huán)了好幾次白白浪費(fèi)了幾分鐘和一堆token。排查下來問題不在于模型笨而在于我沒有給它足夠的信息來做出“換方案”的判斷。超時(shí)錯(cuò)誤只說了“request timeout”模型不知道這個(gè)超時(shí)是暫時(shí)的還是永久的也不知道有沒有備用接口可用它只能機(jī)械地重試。修復(fù)分兩部分第一給所有工具調(diào)用包了一層統(tǒng)一的執(zhí)行器內(nèi)置超時(shí)控制和重試策略第二把異常信息轉(zhuǎn)換成對(duì)模型友好的結(jié)構(gòu)化反饋。import time import concurrent.futures def execute_with_fallback(tool_func, params, timeout10, max_retries2): for attempt in range(max_retries): try: with concurrent.futures.ThreadPoolExecutor() as pool: future pool.submit(tool_func, **params) result future.result(timeouttimeout) return {success: True, result: result} except concurrent.futures.TimeoutError: if attempt max_retries - 1: time.sleep(2) continue return { success: False, error: TIMEOUT, suggestion: 接口響應(yīng)超時(shí)??蓢L試1. 縮小查詢時(shí)間范圍2. 改用batch接口3. 直接告知用戶稍后重試。 } except Exception as e: return {success: False, error: str(e)}改動(dòng)之后模型看到的是“接口超時(shí)建議縮小查詢時(shí)間范圍”它就真的會(huì)去縮小參數(shù)范圍再試而不是原地打轉(zhuǎn)。這個(gè)教訓(xùn)讓我明白了一件事給模型反饋錯(cuò)誤的時(shí)候一定要帶上可執(zhí)行的下一步建議否則它只能瞎猜。5.2 上下文污染同一個(gè)Agent會(huì)話里執(zhí)行風(fēng)馬牛不相及的任務(wù)會(huì)怎樣第二個(gè)大坑和記憶層有關(guān)。早期版本里我把所有任務(wù)的中間結(jié)果都存在同一個(gè)工作記憶區(qū)想著“反正對(duì)話是連著的共享天經(jīng)地義”。結(jié)果有一次同一個(gè)會(huì)話里先讓Agent分析了一份財(cái)務(wù)報(bào)告又讓它寫了一段營銷文案。兩個(gè)任務(wù)完全不相關(guān)但寫文案的時(shí)候模型居然引用了財(cái)務(wù)報(bào)告里的數(shù)據(jù)還煞有介事地編了個(gè)用戶畫像出來。這就是很典型的上下文污染。排查之后我發(fā)現(xiàn)根源在于工作記憶分區(qū)太粗不同任務(wù)的中間狀態(tài)混在一起。模型的注意力機(jī)制天然會(huì)去關(guān)聯(lián)最近的信息一旦它發(fā)現(xiàn)“上一個(gè)任務(wù)提過這個(gè)數(shù)字”就可能把它當(dāng)成當(dāng)前任務(wù)的上下文。修復(fù)方案是把工作記憶按task_id做嚴(yán)格隔離每次任務(wù)切換時(shí)清空上一層的工作記憶只保留項(xiàng)目記憶里明確標(biāo)記為“全局可用”的約束。此外我在prompt里也加了一條指令“如果當(dāng)前任務(wù)和之前任務(wù)主題不同不要在推理中引用之前的任何數(shù)據(jù)”。雙重保障之后類似問題基本絕跡。5.3 幻覺出一個(gè)不存在的工具這是最讓我哭笑不得的一個(gè)bug。有段時(shí)間模型頻繁調(diào)用一個(gè)我從來沒注冊過的工具叫“search_weather_in_city”而我整個(gè)系統(tǒng)里根本沒有天氣相關(guān)接口。一開始我以為是自己注冊遺漏了翻遍代碼也沒有。后來問模型為什么選這個(gè)工具它的回答是“根據(jù)函數(shù)名推斷這個(gè)工具應(yīng)該存在”。這句話一下點(diǎn)醒了我當(dāng)工具庫里工具數(shù)量膨脹、描述又不夠清晰時(shí)模型會(huì)把“功能名”當(dāng)成“真實(shí)存在的東西”。它在語義上覺得天氣查詢應(yīng)該有個(gè)工具于是就在幻覺里生成了一個(gè)。這個(gè)坑的修復(fù)分三層。第一層路由層兜底任何工具調(diào)用必須經(jīng)過工具注冊中心驗(yàn)證名字不在注冊表里的直接拒絕不能放行到執(zhí)行層。第二層傳遞給模型的工具列表只保留Top候選而不是全部從源頭減少“幻覺空間”。第三層在系統(tǒng)提示詞里加一條硬規(guī)則“只能調(diào)用工具列表中明確存在的工具如果列表中沒有合適工具必須明確說明無法完成不得臆造工具名?!贝撕筮@個(gè)問題從頻發(fā)變成了偶發(fā)再后來基本消失。我把這三個(gè)排查案例放在一起是想說一個(gè)共性Agent的很多“蠢行為”根源不在模型而在系統(tǒng)沒有提供足夠清晰的邊界信息。你把邊界劃清楚模型自然就走準(zhǔn)了。6. 性能與成本權(quán)衡擴(kuò)大Reach的同時(shí)別讓賬單爆炸6.1 Reach范圍擴(kuò)大后成本為什么失控工具越多、記憶越多、路由越復(fù)雜單次任務(wù)的token消耗和延遲都會(huì)上臺(tái)階。我有段時(shí)間只盯著功能正確率完全沒管成本結(jié)果月底看賬單嚇了一跳一個(gè)看起來并不復(fù)雜的“用戶訂單分析”任務(wù)因?yàn)榉磸?fù)調(diào)用工具、每次把大量歷史記錄回灌進(jìn)prompt一次就要燒掉接近兩塊錢人民幣。這在生產(chǎn)環(huán)境是根本跑不起的。后來我逼著自己把一個(gè)完整任務(wù)的成本拆開看才發(fā)現(xiàn)大頭不在于主模型的推理而在于三件事工具調(diào)用結(jié)果反復(fù)注入上下文、路由層的多次向量檢索被重復(fù)計(jì)算、以及失敗重試帶來的額外輪次。6.2 實(shí)測成本拆解一次多步驟任務(wù)的賬單明細(xì)以“查詢某個(gè)用戶的訂單統(tǒng)計(jì)每月消費(fèi)金額并生成一段總結(jié)”這個(gè)任務(wù)為例我記錄了改造前后的實(shí)際token消耗和預(yù)估成本按常見商用模型價(jià)格估算粗略折算環(huán)節(jié)改造前Token消耗改造后Token消耗原因系統(tǒng)提示詞工具列表4200900只注入Top工具而非全部任務(wù)規(guī)劃600600不變工具路由1200300路由獨(dú)立到小模型不占主模型上下文工具調(diào)用結(jié)果35001500只保留結(jié)構(gòu)化摘要不塞完整原始報(bào)文歷史記憶回灌2800700按需檢索搞掂時(shí)間衰減最終生成800800不變合計(jì)131004800約節(jié)省63%這個(gè)對(duì)比不是說改造前后功能變了而是同樣的功能用更聰明的架構(gòu)省掉了那些“模型不需要看但還是塞進(jìn)去”的token。6.3 我用的四個(gè)降本手段第一提示詞緩存。系統(tǒng)提示詞和工具Schema基本是常量用支持緩存的服務(wù)端方案后這部分token不再重復(fù)計(jì)費(fèi)。第二結(jié)構(gòu)化摘要替代原始報(bào)文。工具返回的原始數(shù)據(jù)往往又長又雜我在執(zhí)行層過濾一遍只保留任務(wù)相關(guān)的字段并壓縮成要點(diǎn)再喂給模型。第三路由分層。粗篩和精排用本地模型或純向量計(jì)算只有需要模型決策的最后一步才走大模型。第四并發(fā)與批處理。獨(dú)立工具調(diào)用之間沒有依賴關(guān)系時(shí)用并發(fā)方式一起發(fā)出去既省時(shí)間又省重試成本。這幾件事做下來同樣任務(wù)的平均成本降了大半延遲也從七八秒降到三四秒。這里我給一個(gè)建議做Agent項(xiàng)目一定要把token消耗分成“必要消耗”和“浪費(fèi)消耗”兩類。必要消耗是模型真正推理要花的浪費(fèi)消耗是信息冗余、重復(fù)注入、失敗重試造成的。你優(yōu)化的方向不是讓模型“少思考”而是把該給它的給它不該給的堅(jiān)決不給。6.4 什么情況下值得擴(kuò)大Reach什么情況下不應(yīng)該最后說一點(diǎn)關(guān)于邊界的思考。Agent-Reach不是越大越好它是有邊際效應(yīng)的。我見過有些團(tuán)隊(duì)逢接口必接、逢數(shù)據(jù)必連結(jié)果工具列表幾百個(gè)路由層自己都跑不動(dòng)了。我的判斷標(biāo)準(zhǔn)是一個(gè)工具或者一個(gè)數(shù)據(jù)源如果在可預(yù)見的三個(gè)月內(nèi)不會(huì)被用到三次以上就不值得接入。讓它作為可動(dòng)態(tài)加載的插件留在倉庫里比塞進(jìn)在線系統(tǒng)的注冊中心要明智得多。反過來如果某個(gè)數(shù)據(jù)源是核心業(yè)務(wù)流程反復(fù)依賴的那就必須接得深一點(diǎn)包括緩存、監(jiān)控、降級(jí)方案都要配齊。Reach的深度應(yīng)該跟著業(yè)務(wù)價(jià)值走而不是跟著好奇心走。最后聊兩句Agent-Reach這個(gè)項(xiàng)目做到現(xiàn)在要說最值錢的心得不是某個(gè)架構(gòu)多優(yōu)雅而是一句很樸素的話Agent的邊界是你畫的它的能力只能在你畫的邊界里兌現(xiàn)。你想讓它觸達(dá)更多就要把觸達(dá)的路徑鋪得足夠清晰你想讓它記得更牢就要把記憶的層次分得足夠干凈。如果你也正在被工具調(diào)用不準(zhǔn)、上下文塞不下、成本越跑越高這些問題折磨不妨先別急著換更強(qiáng)的模型回頭把Reach這套體系重新捋一遍很多問題會(huì)自己解開。這條路我還在繼續(xù)走后面有新的進(jìn)展再來更新。