生成智能體框架的設(shè)計與實現(xiàn))
在搭建智能體Agent應(yīng)用時最讓人頭疼的往往不是大模型本身而是 Agent 的“骨架”怎么搭工具列表寫死、任務(wù)流程寫死、上下文越堆越長……一旦業(yè)務(wù)需求變化靜態(tài)配置的 Agent 就要改代碼、發(fā)版本鏈路還特別容易斷。JIT-Agent 正是為了解決這類問題出現(xiàn)的一種動態(tài)生成智能體框架的實現(xiàn)思路。這里所說的“模型”并不是指某一個預(yù)訓(xùn)練大模型而是一種框架層面的設(shè)計模型JITJust-In-Time即時生成。它借鑒了 JIT 編譯“按需生成、運行時執(zhí)行”的思想把 Agent 中的工具選擇、提示詞組裝、執(zhí)行計劃從啟動階段推遲到每一次任務(wù)請求發(fā)生時動態(tài)完成。本文會從核心概念講起再手把手實現(xiàn)一個輕量 JIT-Agent 框架最后給出常見問題和工程建議。1. 背景與核心概念1.1 什么是 JIT-AgentJIT-Agent 可以理解為一種“動態(tài)生成智能體框架”的設(shè)計模型。通常一個 Agent 由三部分組成大模型LLM、工具集Tools、執(zhí)行策略Execution Strategy。傳統(tǒng)做法是在系統(tǒng)啟動時把全部工具和固定提示詞注冊進去之后所有請求都走同一套邏輯。這種模式實現(xiàn)簡單但靈活性不足。JIT-Agent 改變了生成時機當用戶請求到達時框架先分析當前任務(wù)確定“這個任務(wù)需要用到哪些工具”再去注冊表中動態(tài)加載對應(yīng)的工具元數(shù)據(jù)和執(zhí)行函數(shù)同時生成針對性的系統(tǒng)提示詞最后執(zhí)行任務(wù)并返回結(jié)果。整個過程像 JIT 編譯一樣不是提前把一切準備好而是“用到什么才生成什么”。這樣做的好處很明顯減少上下文中的工具描述節(jié)省 Token模型不容易被無關(guān)工具干擾新增工具時無需修改主流程只要在注冊表中登記即可可以根據(jù)不同用戶、不同場景生成不同形態(tài)的 Agent。1.2 JIT-Agent 解決什么問題靜態(tài) Agent 在實際項目中會遇到幾個突出問題。第一工具全量注冊導(dǎo)致上下文膨脹。假設(shè)一個 Agent 注冊了 30 個工具每次調(diào)用大模型時都要把 30 個工具的描述傳給模型即使本次任務(wù)只需要其中 2 個。隨著工具數(shù)量增長Token 開銷會越來越大模型出錯的概率也會上升。第二任務(wù)流程固定難以應(yīng)對開放性問題。很多 Agent 是“定義好 Function、定義好 Prompt、執(zhí)行固定流程”對簡單任務(wù)勉強可用但遇到無法預(yù)料的用戶輸入就會顯得僵化。第三擴展成本高。每次新增工具都要修改 Agent 的初始化代碼甚至要調(diào)整提示詞模板維護成本非常高。JIT-Agent 的核心思路是“以任務(wù)為中心”先把所有工具注冊到一個中心注冊表每次請求到來時由規(guī)劃器決定加載哪些工具再動態(tài)生成執(zhí)行計劃。這樣一來Agent 的結(jié)構(gòu)與具體業(yè)務(wù)解耦新增工具只需要寫一個函數(shù)并注冊無需改動主框架。當然動態(tài)生成也不是沒有代價。運行時分析任務(wù)、動態(tài)加載工具都會帶來額外延遲而且規(guī)劃結(jié)果可能不穩(wěn)定。所以 JIT-Agent 更適合任務(wù)類型豐富、工具數(shù)量較多、需要快速擴展的場景。1.3 典型應(yīng)用場景JIT-Agent 的適用面很廣常見的有下面幾類。智能客服系統(tǒng)用戶問“我的訂單什么時候到”后臺動態(tài)加載訂單查詢工具用戶問“退款流程是什么”后臺加載退款相關(guān)文檔工具。數(shù)據(jù)分析平臺用戶輸入“幫我畫一個 7 月銷售額折線圖”Agent 動態(tài)加載數(shù)據(jù)查詢工具、圖表生成工具不需要把所有 BI 能力全部暴露給模型。自動化運維助手根據(jù)告警關(guān)鍵字動態(tài)選擇日志查詢、服務(wù)重啟、健康檢查等工具避免把所有危險操作都暴露在同一個上下文中。個人助理根據(jù)用戶當前需求動態(tài)決定調(diào)用天氣、日歷、導(dǎo)航還是音樂播放工具。這些場景有一個共同特征工具數(shù)量多、任務(wù)類型分散、單次請求實際用到的工具很少。如果全部靜態(tài)加載效率和安全都不理想。2. 環(huán)境準備與版本說明2.1 運行環(huán)境本文的示例代碼使用 Python 編寫核心部分只依賴 Python 標準庫不需要安裝額外第三方包方便讀者直接復(fù)制運行。操作系統(tǒng)Windows / macOS / Linux 均可Python 版本建議 3.9 及以上IDEVS Code、PyCharm 均可能運行 Python 腳本即可可選依賴如果后續(xù)要接入真實大模型需要根據(jù)你使用的模型服務(wù)安裝對應(yīng) SDK版本以官方文檔為準。示例項目結(jié)構(gòu)如下jit_agent/ ├── agent/ │ ├── __init__.py │ ├── registry.py │ ├── planner.py │ ├── executor.py │ └── runtime.py ├── tools/ │ ├── __init__.py │ ├── weather.py │ └── calculator.py └── main.pyagent包負責框架核心邏輯tools包存放具體業(yè)務(wù)工具main.py是入口。這樣分層的好處是框架與業(yè)務(wù)工具分離新增工具不需要改動框架代碼。2.2 版本說明關(guān)于 Python 版本建議使用 3.9 以上因為代碼中用到了類型注解等特性更低版本可能需要調(diào)整。如果你要在生產(chǎn)環(huán)境中接入真實大模型版本需要根據(jù)你實際使用的模型服務(wù)調(diào)整本文示例重點演示框架實現(xiàn)思路。3. JIT 動態(tài)生成的核心原理拆解3.1 從“靜態(tài) Agent”到“動態(tài)生成”先看一個靜態(tài) Agent 的典型實現(xiàn)。# 靜態(tài)注冊啟動時寫死 def get_weather(city: str): return f{city} 天氣晴 def calculate(expression: str): return eval(expression) TOOLS { get_weather: get_weather, calculate: calculate, }這種方式在工具數(shù)量少時沒有問題但如果工具越來越多問題就來了每個請求都要把所有工具的描述拼進 Prompt工具間如果有依賴關(guān)系靜態(tài)加載容易遺漏或沖突新增工具需要修改TOOLS字典和 Prompt 模板。JIT-Agent 的做法是引入“注冊表 規(guī)劃器”兩層結(jié)構(gòu)。注冊表保存所有工具的元數(shù)據(jù)和執(zhí)行函數(shù)規(guī)劃器在運行時根據(jù)用戶輸入決定加載哪些工具。核心思想是讓“工具選擇”變成動態(tài)過程。3.2 工具注冊表動態(tài)生成的基礎(chǔ)注冊表是 JIT-Agent 的地基。它維護一個全局字典每個工具條目至少包含四個信息工具名稱、工具描述、參數(shù) Schema、執(zhí)行函數(shù)。名稱用于唯一標識描述用于給規(guī)劃器做選擇參數(shù) Schema 用于校驗和生成調(diào)用參數(shù)執(zhí)行函數(shù)是真正運行的業(yè)務(wù)邏輯。Python 中可以用裝飾器實現(xiàn)一個極簡注冊表。# agent/registry.py TOOL_REGISTRY {} def register_tool(name, description, parameters_schemaNone): def decorator(func): TOOL_REGISTRY[name] { name: name, description: description, parameters: parameters_schema or {}, func: func, } return func return decorator def get_tool(name): return TOOL_REGISTRY.get(name) def list_tools(): return [ { name: tool[name], description: tool[description], parameters: tool[parameters], } for tool in TOOL_REGISTRY.values() ]這里的關(guān)鍵點是list_tools只返回元數(shù)據(jù)不返回執(zhí)行函數(shù)。這樣在把工具列表發(fā)給大模型時不會暴露內(nèi)部實現(xiàn)。執(zhí)行函數(shù)只在真正執(zhí)行時才通過get_tool獲取這是動態(tài)生成的關(guān)鍵一步。3.3 任務(wù)規(guī)劃動態(tài)選擇工具的入口注冊表準備好了剩下的問題是怎么知道當前任務(wù)需要哪些工具這就是“規(guī)劃器”的職責。規(guī)劃器的輸入是用戶請求文本輸出是一個或多個“工具調(diào)用計劃”每個計劃包含工具名和參數(shù)。最簡單的方式是關(guān)鍵詞匹配適合演示更通用的方式是調(diào)用大模型的 function calling 能力讓模型自己從工具列表中選擇合適的工具。不管是哪種方式規(guī)劃器的核心邏輯都一致先用當前注冊表中的工具元數(shù)據(jù)生成候選列表再根據(jù)用戶輸入選擇工具并提取參數(shù)。這就是“動態(tài)生成”的核心體現(xiàn)工具列表不是固定的而是隨著請求變化。4. 完整實戰(zhàn)手寫一個輕量 JIT-Agent 框架4.1 定義業(yè)務(wù)工具先創(chuàng)建tools/weather.py定義一個查詢天氣的工具。# tools/weather.py from agent.registry import register_tool register_tool( nameget_weather, description查詢指定城市的天氣情況, parameters_schema{ type: object, properties: { city: {type: string, description: 城市名稱例如北京} }, required: [city], }, ) def get_weather(city: str) - str: # 真實項目中可替換為天氣 API 請求 return f{city} 今天晴氣溫 25 攝氏度空氣質(zhì)量良。再創(chuàng)建tools/calculator.py定義一個計算器工具。# tools/calculator.py from agent.registry import register_tool register_tool( namecalculate, description計算簡單的四則運算表達式, parameters_schema{ type: object, properties: { expression: {type: string, description: 數(shù)學表達式例如 35} }, required: [expression], }, ) def calculate(expression: str) - str: # 演示代碼直接使用 eval生產(chǎn)環(huán)境請使用安全解析庫 result eval(expression) return f{expression} {result}到這里工具定義已經(jīng)完成。注意兩個文件都通過register_tool裝飾器把函數(shù)注冊到全局注冊表但主流程代碼并沒有直接引用它們。這意味著將來新增工具時只需要新增一個文件并導(dǎo)入一次即可。4.2 實現(xiàn)工具注冊表之前已經(jīng)給出registry.py的代碼這里再補充一個細節(jié)工具注冊順序會影響規(guī)劃器看到的候選列表。實際項目中建議在入口文件統(tǒng)一導(dǎo)入所有工具模塊保證注冊完成后再啟動 Agent。# agent/__init__.py這個文件保持為空用于標識agent是一個 Python 包。4.3 實現(xiàn)任務(wù)規(guī)劃器接下來實現(xiàn)agent/planner.py。為了做到“零依賴即可運行”演示版本用關(guān)鍵詞匹配生成計劃。# agent/planner.py class Planner: def __init__(self, tools): self.tools tools def plan(self, user_input: str) - list: 根據(jù)用戶輸入生成執(zhí)行計劃。 演示版本使用關(guān)鍵詞匹配生產(chǎn)環(huán)境可替換為 LLM function calling。 plans [] if 天氣 in user_input: city 北京 for candidate in [北京, 上海, 廣州, 深圳]: if candidate in user_input: city candidate break plans.append({tool: get_weather, arguments: {city: city}}) if 計算 in user_input or 算一下 in user_input: expression user_input.replace(計算, ).replace(算一下, ).strip() plans.append({tool: calculate, arguments: {expression: expression}}) return plansPlanner接收tools參數(shù)但在關(guān)鍵詞匹配版本中沒有直接使用。保留這個參數(shù)是為了后續(xù)替換成 LLM 規(guī)劃器時可以基于工具列表生成 function calling 請求。如果用戶輸入同時包含“天氣”和“計算”比如“先看看北京天氣再計算 35”規(guī)劃器會生成兩個計劃按順序執(zhí)行這已經(jīng)具備多步任務(wù)的雛形。4.4 實現(xiàn)執(zhí)行引擎執(zhí)行引擎負責真正調(diào)用工具函數(shù)。它從計劃中取出工具名和參數(shù)從注冊表中獲取執(zhí)行函數(shù)然后執(zhí)行并返回結(jié)果。# agent/executor.py from agent.registry import get_tool class Executor: def execute(self, plan: dict) - str: tool_name plan[tool] tool get_tool(tool_name) if not tool: return f錯誤工具 {tool_name} 未注冊 try: result tool[func](**plan.get(arguments, {})) return result except Exception as e: return f工具執(zhí)行失敗{e}這里做了兩層保護第一層判斷工具是否存在第二層捕獲執(zhí)行異常。實際項目中還應(yīng)該增加參數(shù)校驗確保調(diào)用參數(shù)符合工具的parameters_schema避免惡意輸入或異常參數(shù)傳入工具函數(shù)。4.5 實現(xiàn)運行時裝配運行時是整個 JIT-Agent 的入口它把注冊表、規(guī)劃器、執(zhí)行器組裝在一起對外提供run方法。# agent/runtime.py from agent.registry import list_tools from agent.planner import Planner from agent.executor import Executor class JITAgent: def __init__(self): self.tools list_tools() self.planner Planner(self.tools) self.executor Executor() def run(self, user_input: str): print(f用戶輸入{user_input}) print(當前可用工具) for tool in self.tools: print(f - {tool[name]}: {tool[description]}) plans self.planner.plan(user_input) if not plans: return 沒有找到合適的工具來處理該請求 outputs [] for plan in plans: print(f動態(tài)生成計劃{plan[tool]} {plan.get(arguments, {})}) result self.executor.execute(plan) outputs.append(result) return \n.join(outputs)這個類體現(xiàn)了一個核心思想JITAgent在初始化時只獲取工具元數(shù)據(jù)不直接綁定任何具體業(yè)務(wù)函數(shù)。每次運行請求時規(guī)劃器和執(zhí)行器動態(tài)配合完成“選工具、調(diào)工具”的過程。4.6 編寫入口并運行驗證最后創(chuàng)建main.py統(tǒng)一導(dǎo)入工具模塊然后啟動 Agent。# main.py from agent.runtime import JITAgent import tools.weather # noqa: F401 確保工具模塊被導(dǎo)入并注冊 import tools.calculator # noqa: F401 def main(): agent JITAgent() print(agent.run(北京天氣怎么樣)) print(---) print(agent.run(計算 35)) if __name__ __main__: main()在項目根目錄運行python main.py預(yù)期輸出類似用戶輸入北京天氣怎么樣 當前可用工具 - get_weather: 查詢指定城市的天氣情況 - calculate: 計算簡單的四則運算表達式 動態(tài)生成計劃get_weather {city: 北京} 北京 今天晴氣溫 25 攝氏度空氣質(zhì)量良。 --- 用戶輸入計算 35 當前可用工具 - get_weather: 查詢指定城市的天氣情況 - calculate: 計算簡單的四則運算表達式 動態(tài)生成計劃calculate {expression: 35} 35 8到這里一個最簡 JIT-Agent 已經(jīng)可以運行。接下來可以思考如何把關(guān)鍵詞規(guī)劃器替換成真正的大模型規(guī)劃器。4.7 接入大模型 function calling 的改造思路真實生產(chǎn)環(huán)境中規(guī)劃器應(yīng)該讓大模型來承擔。以常見的 function calling 接口為例改造思路如下。# agent/llm_planner.py # 示例思路需按實際模型服務(wù)調(diào)整 def plan_with_llm(user_input: str, tools: list) - list: messages [{role: user, content: user_input}] functions [ { type: function, function: { name: tool[name], description: tool[description], parameters: tool[parameters], }, } for tool in tools ] # 調(diào)用你使用的模型服務(wù)這里以偽代碼示意 # response chat_completion( # modelyour-model, # messagesmessages, # toolsfunctions, # ) # # 解析響應(yīng)中的 tool_calls轉(zhuǎn)換為 plan 列表 # plans [ # { # tool: call.function.name, # arguments: json.loads(call.function.arguments), # } # for call in response.choices[0].message.tool_calls # ] return []核心點是把list_tools()返回的元數(shù)據(jù)直接作為functions傳給模型模型返回工具調(diào)用計劃后執(zhí)行引擎不用改動即可執(zhí)行。這就是 JIT-Agent 最大的優(yōu)勢工具不斷新增Agent 框架代碼不變模型每次都能看到最新的工具列表。5. 常見問題與排查思路下面列出 JIT-Agent 在實踐中最常見的幾類問題。問題現(xiàn)象常見原因解決思路工具未注冊執(zhí)行時報錯工具模塊沒有被導(dǎo)入在入口統(tǒng)一導(dǎo)入所有工具模塊大模型選錯工具工具描述含糊、相似工具太多優(yōu)化描述增加示例減少相似工具參數(shù)解析報錯function calling 返回的 JSON 格式不穩(wěn)定增加 JSON 解析兜底做參數(shù) Schema 校驗上下文超長動態(tài)生成后仍然傳入過多工具限制候選工具數(shù)量分批選擇并發(fā)下狀態(tài)混亂工具函數(shù)修改了全局狀態(tài)工具函數(shù)盡量無狀態(tài)避免共享可變對象動態(tài)加載延遲較高每次請求都重新分析任務(wù)增加緩存、預(yù)加載熱工具下面逐個展開說明。工具未注冊。這是使用注冊表模式最容易踩的坑。register_tool裝飾器在模塊導(dǎo)入時執(zhí)行如果某個工具模塊沒有被 import它的注冊代碼就不會運行。解決方法是把全部工具模塊在main.py中統(tǒng)一導(dǎo)入或使用importlib按目錄自動加載。大模型選錯工具。工具列表是動態(tài)生成的但如果工具描述寫得含糊模型仍然可能選錯。比如get_weather和get_temperature功能相似模型很難區(qū)分。建議在描述里寫清楚邊界必要時在參數(shù) Schema 的description中補充示例。參數(shù)格式不穩(wěn)。不同模型對 function calling 的響應(yīng)格式有差異有的模型返回的是 JSON 字符串有的返回結(jié)構(gòu)化對象。建議在規(guī)劃器中增加統(tǒng)一解析層解析失敗時給模型一個“重新生成”的機會而不是直接報錯。上下文超長。JIT-Agent 已經(jīng)能減少無關(guān)工具但工具數(shù)量特別多時即使每個工具只描述一兩行全部傳給模型也會撐爆上下文??梢园垂ぞ叻纸M或先用模型做一次粗篩再加載候選工具的詳細描述。并發(fā)狀態(tài)混亂。如果工具函數(shù)操作了全局變量多線程場景下容易出現(xiàn)互相覆蓋。JIT-Agent 中工具函數(shù)應(yīng)該盡量保持無狀態(tài)所有輸入通過參數(shù)傳入輸出通過返回值帶出。動態(tài)加載延遲。每次請求都動態(tài)分析確實會帶來額外耗時。在延遲敏感的場景中可以基于歷史請求做熱工具緩存把高頻工具提前加載到內(nèi)存。6. 最佳實踐與工程建議6.1 工具注冊表的命名與版本管理工具名稱是全局唯一標識建議用“動詞 名詞”的格式例如query_order、create_ticket、send_email。避免使用模糊的do_task、run之類名稱。工具數(shù)量增多后還要考慮版本管理??梢栽谧员項l目中增加version字段重要工具升級時保留舊版本方便灰度切換。注冊表本身可以增加enabled開關(guān)讓運營或開發(fā)臨時下線有問題的工具而不用重新發(fā)布代碼。6.2 配置與代碼分離JIT-Agent 的動態(tài)特性容易讓人把業(yè)務(wù)邏輯寫進框架代碼里這是需要避免的。工具列表、模型名稱、超時時間、緩存策略等都應(yīng)該放在配置文件中通過配置中心或環(huán)境變量管理。這樣工具上線、參數(shù)調(diào)整不需要改代碼。6.3 安全邊界與權(quán)限控制動態(tài)生成框架最大的風險是“工具暴露面變大”。如果所有工具都注冊到一個全局注冊表一旦規(guī)劃器被誘導(dǎo)就可能調(diào)用危險工具。因此需要做到以下幾點工具函數(shù)內(nèi)部做輸入校驗不輕信規(guī)劃器傳來的參數(shù)高危操作需要二次確認比如刪除資源、發(fā)起支付不要在生產(chǎn)環(huán)境使用eval或exec計算器示例只是為了演示原理對大模型的工具選擇結(jié)果做白名單校驗只允許調(diào)用當前任務(wù)允許范圍內(nèi)的工具對用戶輸入做提示注入防護避免用戶通過自然語言誘導(dǎo)模型調(diào)用未授權(quán)工具。6.4 日志與可觀測性動態(tài)生成意味著每次請求的行為都可能不同必須把規(guī)劃結(jié)果記錄下來。建議至少記錄以下內(nèi)容用戶原始輸入本次請求加載了哪些工具規(guī)劃器生成的工具調(diào)用計劃每個工具的執(zhí)行結(jié)果和執(zhí)行耗時如果規(guī)劃器調(diào)用大模型記錄模型返回的原始響應(yīng)。有了這些日志才能快速定位“模型選錯工具”“參數(shù)錯誤”“工具執(zhí)行異?!钡葐栴}。6.5 性能優(yōu)化動態(tài)生成雖然靈活但性能也需要關(guān)注。常見優(yōu)化手段包括工具元數(shù)據(jù)緩存避免每次請求都重新遍歷注冊表熱工具預(yù)加載把高頻工具的執(zhí)行函數(shù)提前放入內(nèi)存規(guī)劃結(jié)果緩存對于相同或相似請求直接復(fù)用之前的工具調(diào)用計劃并發(fā)控制避免大量請求同時觸發(fā)大模型規(guī)劃導(dǎo)致上游服務(wù)超時。6.6 生產(chǎn)環(huán)境注意事項上線前建議先做一次“全量工具掃描”確保所有工具模塊都能正常導(dǎo)入、注冊、執(zhí)行。還要準備一個“最小工具集”兜底當規(guī)劃器沒有返回任何計劃或返回的工具全部執(zhí)行失敗時Agent 應(yīng)該能給用戶一個明確回復(fù)而不是靜默失敗。如果使用真實大模型規(guī)劃器要關(guān)注模型服務(wù)的限流和成本??梢越o每次任務(wù)的規(guī)劃調(diào)用設(shè)置超時時間并在超時后降級到關(guān)鍵詞規(guī)劃器保證核心鏈路可用。7. 總結(jié)與學習路線這篇文章主要圍繞 JIT-Agent 展開核心內(nèi)容可以總結(jié)為三點第一JIT-Agent 是一種動態(tài)生成智能體框架的設(shè)計模型核心是把工具選擇和流程組裝推遲到運行時第二實現(xiàn)一個輕量 JIT-Agent 的關(guān)鍵是注冊表加規(guī)劃器注冊表負責管理工具元數(shù)據(jù)規(guī)劃器負責按任務(wù)選擇工具第三動態(tài)生成框架在靈活性上優(yōu)于靜態(tài)注冊但同時要關(guān)注安全、日志、性能和可觀測性。如果你對智能體框架感興趣下一步可以繼續(xù)學習三塊內(nèi)容一是 ReAct 模式搞清楚 Agent 如何在大模型推理和工具調(diào)用之間循環(huán)二是多 Agent 協(xié)作了解多個 JIT-Agent 如何分工配合三是工具自治讓 Agent 在運行時自動生成工具描述甚至動態(tài)生成新的工具函數(shù)。無論走哪個方向都可以先把本文這套最小框架跑通再逐步往里面加入真實模型、持久化存儲和更完善的工具管理能力。實際項目中優(yōu)先關(guān)注的風險點有兩個一個是工具暴露面變大帶來的安全風險另一個是動態(tài)生成導(dǎo)致的可觀測性變差。只要把注冊表權(quán)限控制和日志鏈路做好JIT-Agent 就能在日常業(yè)務(wù)中發(fā)揮很大的靈活性。如果你正在設(shè)計自己的 Agent 框架不妨從一個小場景開始把一兩個工具接入動態(tài)注冊表跑通后再逐步擴展。如果這篇文章對你有幫助可以收藏備用也歡迎在評論區(qū)交流你在動態(tài)生成 Agent 時踩過的坑。