設計:從模型層到應用層的五層架構(gòu)與Agent實操)
1. 從一張架構(gòu)圖說起AI應用到底該怎么搭這兩年我參與過不少AI應用項目的評審和落地發(fā)現(xiàn)一個特別普遍的現(xiàn)象很多人一上來就急著寫Prompt、調(diào)API、接模型結(jié)果做到一半發(fā)現(xiàn)整個系統(tǒng)像一團亂麻——模型換了要改幾十處代碼加個新工具要動核心邏輯想做個多輪對話狀態(tài)管理得從頭造輪子。說到底問題出在動手之前沒有把架構(gòu)想清楚?!皥D解AI應用架構(gòu)設計”這個主題核心就是解決一件事用可視化的方式把AI應用從底層模型到上層交互的完整鏈路拆解清楚讓開發(fā)者知道每一層該放什么、層與層之間怎么通信、哪些東西該抽象、哪些東西該固化。它適合正在做AI應用開發(fā)的工程師、正在設計Agent系統(tǒng)的架構(gòu)師也適合剛?cè)腴T想搞清楚“一個AI應用到底由哪些部分組成”的學習者。我個人的判斷是2024年之后AI應用開發(fā)已經(jīng)從“能不能跑通”進入“能不能扛住”的階段。早期大家寫個腳本調(diào)一下LLM接口就算AI應用了現(xiàn)在要考慮多模型切換、工具調(diào)用編排、上下文管理、并發(fā)控制、安全防護、可觀測性等等。這些需求疊加在一起如果沒有一個清晰的架構(gòu)設計代碼會迅速腐化。所以這篇文章我會從架構(gòu)分層、核心組件、實操搭建、問題排查幾個維度把AI應用架構(gòu)設計這件事講透盡量做到你看完能直接對照著畫自己的架構(gòu)圖。2. AI應用架構(gòu)的整體分層與設計思路2.1 為什么不能把AI應用寫成一個“大函數(shù)”我見過最簡陋的AI應用是這樣的一個Python文件里面一個函數(shù)接收用戶輸入拼接Prompt調(diào)用OpenAI接口返回結(jié)果。這種寫法在Demo階段沒問題但一旦要加功能——比如支持多輪對話、支持工具調(diào)用、支持切換模型——這個函數(shù)會迅速膨脹到幾百行改一處崩三處。架構(gòu)設計的本質(zhì)是分離關注點。AI應用和傳統(tǒng)Web應用最大的區(qū)別在于傳統(tǒng)應用的核心邏輯是確定的if-else、循環(huán)、數(shù)據(jù)庫查詢而AI應用的核心邏輯包含了一個不確定的組件——LLM。LLM的輸出不可預測、有延遲、有成本、可能失敗。所以架構(gòu)設計的第一原則就是把LLM當作一個不可靠的外部依賴來對待圍繞它做隔離、降級、重試和監(jiān)控?;谶@個原則我把AI應用的架構(gòu)分為五層從下往上依次是模型層LLM、Embedding模型、Rerank模型等負責推理和生成能力層Prompt管理、上下文管理、工具/函數(shù)注冊、記憶系統(tǒng)編排層Agent邏輯、工作流引擎、任務規(guī)劃、多步推理控制接口層API網(wǎng)關、鑒權(quán)、限流、請求路由應用層對話界面、業(yè)務邏輯、數(shù)據(jù)持久化、監(jiān)控告警每一層只和相鄰層通信層內(nèi)可以替換實現(xiàn)而不影響其他層。比如你把GPT-4換成Claude只需要改模型層的適配器編排層和接口層完全不用動。這就是分層架構(gòu)的價值。2.2 模型層別把雞蛋放在一個籃子里模型層看起來簡單——不就是調(diào)API嗎但實際項目中模型層要處理的問題非常多。首先是多模型適配。不同廠商的API格式不一樣參數(shù)命名不一樣返回結(jié)構(gòu)不一樣。如果你在業(yè)務代碼里直接寫openai.ChatCompletion.create(...)那換模型的時候就得全局搜索替換。正確的做法是定義一個統(tǒng)一的模型接口抽象比如class BaseLLM: def chat(self, messages: list, tools: list None, **kwargs) - LLMResponse: raise NotImplementedError class OpenAILLM(BaseLLM): def chat(self, messages, toolsNone, **kwargs): # 適配OpenAI格式 ... class ClaudeLLM(BaseLLM): def chat(self, messages, toolsNone, **kwargs): # 適配Claude格式 ...這樣上層只需要依賴BaseLLM具體用哪個模型通過配置決定。我實測下來這個抽象層大概多寫200行代碼但后續(xù)換模型、加模型、做A/B測試的時候能省掉大量重構(gòu)時間。其次是模型路由。不同任務對模型的要求不一樣。簡單的意圖分類用便宜的小模型就夠了復雜的推理任務才需要上大模型。你可以在模型層做一個路由策略根據(jù)任務類型、輸入長度、歷史成功率動態(tài)選擇模型。這不只是為了省錢也是為了控制延遲——小模型的響應速度通常快很多。注意模型路由策略一定要有fallback。我踩過的坑是某次主力模型服務抖動因為沒有配置備用模型整個應用直接不可用。后來加了自動降級邏輯主模型連續(xù)失敗3次就切到備用模型同時發(fā)告警。2.3 能力層Prompt、上下文和工具注冊能力層是AI應用架構(gòu)中最“AI”的部分也是最容易寫亂的部分。Prompt管理不是簡單地把字符串存在代碼里。一個成熟的Prompt管理系統(tǒng)應該支持模板變量替換、多版本管理、A/B測試、按場景切換。我通常會把Prompt存在數(shù)據(jù)庫或配置文件中每個Prompt有唯一ID和版本號代碼里通過ID引用。這樣產(chǎn)品經(jīng)理改Prompt不需要發(fā)版運營可以做實驗對比不同Prompt的效果。上下文管理是另一個重災區(qū)。LLM有上下文窗口限制對話輪次多了之后必須做截斷或摘要。常見的策略有幾種滑動窗口保留最近N輪、摘要壓縮把早期對話用LLM總結(jié)成一段話、關鍵信息提取只保留實體和意圖。我一般會組合使用——最近5輪保留原文更早的做摘要系統(tǒng)指令永遠保留。工具注冊是Agent能力的基礎。每個工具需要定義名稱、描述、參數(shù)Schema、執(zhí)行函數(shù)。描述非常重要LLM就是靠描述來判斷什么時候該調(diào)用哪個工具。我見過很多工具調(diào)用失敗的情況排查下來都是描述寫得太模糊LLM根本分不清什么時候該用。tools [ { name: search_knowledge_base, description: 在內(nèi)部知識庫中搜索相關文檔。當用戶詢問產(chǎn)品功能、操作步驟、常見問題時使用此工具。, parameters: { type: object, properties: { query: {type: string, description: 搜索關鍵詞} }, required: [query] } } ]2.4 編排層Agent的大腦編排層決定了AI應用是“單輪問答”還是“能自主完成復雜任務”。簡單場景下編排層就是一個線性流程接收輸入→組裝Prompt→調(diào)用LLM→解析輸出→返回結(jié)果。但Agent場景下編排層要復雜得多。一個典型的Agent循環(huán)是這樣的LLM思考當前狀態(tài)→決定下一步行動調(diào)用工具/直接回答→執(zhí)行行動→把結(jié)果喂回LLM→繼續(xù)思考。這個循環(huán)可能跑幾輪甚至幾十輪直到任務完成或達到最大輪次限制。編排層需要處理的核心問題包括任務規(guī)劃把復雜任務拆成子任務、工具選擇從注冊的工具中選合適的、結(jié)果解析從LLM輸出中提取結(jié)構(gòu)化信息、錯誤恢復工具調(diào)用失敗后怎么辦、輪次控制防止無限循環(huán)。我個人的經(jīng)驗是編排層不要寫得太“聰明”。很多開發(fā)者想讓Agent完全自主決策結(jié)果行為不可預測調(diào)試極其困難。更穩(wěn)妥的做法是混合編排——關鍵路徑用確定性代碼控制只在需要靈活性的環(huán)節(jié)交給LLM決策。比如“先查知識庫查不到再調(diào)搜索引擎”這種邏輯用代碼寫死而“根據(jù)用戶問題判斷該查哪個知識庫”交給LLM。2.5 接口層與應用層別忘了這是給用戶用的接口層負責對外暴露服務核心要考慮的是鑒權(quán)、限流、請求校驗、流式輸出。流式輸出特別重要——LLM生成完整回復可能要好幾秒如果等全部生成完再返回用戶體驗很差。用SSE或WebSocket做流式推送用戶能看到文字逐字出現(xiàn)感知延遲大幅降低。應用層則是業(yè)務邏輯的歸宿。用戶會話管理、歷史記錄存儲、計費統(tǒng)計、內(nèi)容審核、監(jiān)控告警都在這一層。我特別想強調(diào)的是可觀測性——AI應用比傳統(tǒng)應用更需要日志和追蹤。每次LLM調(diào)用的輸入輸出、耗時、Token消耗、工具調(diào)用鏈路都要記錄下來否則出了問題根本沒法排查。3. 核心組件深度拆解與實操要點3.1 Agent架構(gòu)ReAct還是Plan-ExecuteAgent架構(gòu)目前主流的有兩種模式。ReAct模式是“思考-行動-觀察”循環(huán)每一步都讓LLM決定下一步做什么。優(yōu)點是靈活能應對不確定的環(huán)境缺點是LLM調(diào)用次數(shù)多延遲高而且容易陷入循環(huán)。Plan-Execute模式是先讓LLM制定完整計劃然后按計劃逐步執(zhí)行。優(yōu)點是效率高、可控性強缺點是計劃可能不準確執(zhí)行過程中遇到意外不好調(diào)整。我的建議是任務步驟少于5步、環(huán)境確定性高的場景用Plan-Execute任務復雜、需要根據(jù)中間結(jié)果動態(tài)調(diào)整的場景用ReAct。實際項目中也可以混合——先用Plan-Execute生成大致計劃每個步驟內(nèi)部用ReAct處理細節(jié)。Agent的并發(fā)問題也值得單獨說。當多個用戶同時請求Agent服務時每個Agent實例都有自己的狀態(tài)對話歷史、中間結(jié)果必須做好隔離。我通常用會話ID作為key每個會話維護獨立的狀態(tài)對象存在Redis或內(nèi)存緩存中。要注意設置合理的過期時間否則內(nèi)存會爆。3.2 MCP協(xié)議工具調(diào)用的標準化嘗試MCPModel Context Protocol是最近很熱的一個話題它試圖解決的是工具調(diào)用的標準化問題。在沒有MCP之前每個AI應用接入工具的方式都不一樣——有的用OpenAI的Function Calling格式有的自己定義JSON Schema有的直接拼字符串。MCP定義了一套標準協(xié)議讓工具提供方和使用方解耦。MCP的核心概念是Server工具提供方暴露資源Resource和工具ToolClientAI應用通過標準協(xié)議發(fā)現(xiàn)和調(diào)用這些工具。這樣你寫一個MCP Server所有支持MCP的AI應用都能直接用不需要為每個應用單獨適配。實際使用中MCP的配置通常長這樣{ mcpServers: { knowledge-base: { command: python, args: [mcp_server_kb.py], env: { KB_API_KEY: your-key } } } }提示MCP目前還在快速演進中不同實現(xiàn)的兼容性參差不齊。生產(chǎn)環(huán)境使用前一定要做充分的兼容性測試特別是流式輸出和錯誤處理部分。3.3 上下文窗口管理Token就是錢LLM的上下文窗口是有限資源而且直接關系到成本。GPT-4的128K上下文聽起來很大但如果每輪對話都塞滿成本會高得離譜。上下文管理的目標是在保留關鍵信息和控制Token消耗之間找平衡。我的實操策略是這樣的系統(tǒng)Prompt控制在500 Token以內(nèi)只放最核心的角色定義和輸出格式要求最近3-5輪對話保留原文更早的對話用LLM做摘要摘要控制在200 Token以內(nèi)工具調(diào)用結(jié)果只保留關鍵字段不要把整個API返回的JSON都塞進去。還有一個技巧是動態(tài)上下文。根據(jù)當前用戶問題從歷史對話中檢索最相關的幾輪而不是簡單按時間截取。這需要用到Embedding和向量檢索但效果比固定窗口好很多。3.4 安全防護Agent也會被“投毒”Agent安全是一個容易被忽視但非常重要的領域。所謂Agent投毒AgentPoison指的是攻擊者通過污染Agent的記憶或知識庫讓Agent在后續(xù)對話中產(chǎn)生惡意行為。比如攻擊者在用戶輸入中植入一段看似正常但實際包含指令的文本Agent把它存到記憶里下次檢索出來執(zhí)行。防護措施包括輸入過濾檢測并剝離可疑的指令注入、記憶隔離不同用戶的記憶嚴格隔離、工具權(quán)限控制敏感工具需要額外鑒權(quán)、輸出審核對Agent的輸出做安全檢查。這些措施會增加一些延遲但安全底線不能破。4. 從零搭建一個AI應用的完整實操4.1 環(huán)境準備與技術選型假設我們要搭建一個企業(yè)知識庫問答Agent支持多輪對話、工具調(diào)用和流式輸出。技術選型如下組件選型理由開發(fā)語言Python 3.11AI生態(tài)最完善庫最多Web框架FastAPI異步支持好自帶OpenAPI文檔LLMGPT-4o / Claude 3.5綜合能力強工具調(diào)用穩(wěn)定向量庫Qdrant輕量部署簡單性能好緩存Redis會話狀態(tài)和限流前端React SSE流式渲染體驗好安裝核心依賴pip install fastapi uvicorn openai anthropic qdrant-client redis sse-starlette4.2 模型適配層實現(xiàn)先定義統(tǒng)一的模型接口然后實現(xiàn)OpenAI和Claude兩個適配器。關鍵點是統(tǒng)一消息格式和統(tǒng)一工具調(diào)用格式。OpenAI的tool_calls和Claude的tool_use結(jié)構(gòu)不同需要在適配器里做轉(zhuǎn)換。from abc import ABC, abstractmethod from dataclasses import dataclass dataclass class LLMResponse: content: str tool_calls: list usage: dict class BaseLLM(ABC): abstractmethod async def chat(self, messages, toolsNone, streamFalse): pass class OpenAIAdapter(BaseLLM): def __init__(self, api_key, modelgpt-4o): self.client AsyncOpenAI(api_keyapi_key) self.model model async def chat(self, messages, toolsNone, streamFalse): resp await self.client.chat.completions.create( modelself.model, messagesmessages, toolstools, streamstream ) return self._parse(resp)這個適配層的價值在于上層編排邏輯完全不關心底層用的是哪個模型換模型只需要改配置。4.3 工具注冊與調(diào)用鏈路工具注冊我用裝飾器模式寫起來簡潔讀起來也清楚TOOL_REGISTRY {} def register_tool(name, description, parameters): def decorator(func): TOOL_REGISTRY[name] { function: func, schema: { type: function, function: { name: name, description: description, parameters: parameters } } } return func return decorator register_tool( namesearch_kb, description搜索企業(yè)知識庫適用于產(chǎn)品功能、操作流程類問題, parameters{ type: object, properties: { query: {type: string, description: 搜索關鍵詞} }, required: [query] } ) async def search_kb(query: str): # 向量檢索邏輯 results await vector_store.search(query, top_k3) return \n.join([r.text for r in results])工具調(diào)用的完整鏈路是LLM返回tool_calls→編排層解析出工具名和參數(shù)→從注冊表找到對應函數(shù)→執(zhí)行→把結(jié)果作為tool消息追加到對話→再次調(diào)用LLM。這個循環(huán)要設置最大輪次我一般設5輪防止無限調(diào)用。4.4 流式輸出與會話管理流式輸出用SSE實現(xiàn)FastAPI這邊用StreamingResponse。關鍵是要把LLM的流式響應和工具調(diào)用的中間狀態(tài)都推給前端讓用戶知道Agent在干什么。from sse_starlette.sse import EventSourceResponse app.post(/chat) async def chat(request: ChatRequest): async def event_generator(): async for event in agent.run(request.session_id, request.message): yield {event: event.type, data: json.dumps(event.data)} return EventSourceResponse(event_generator())會話管理用Redis存對話歷史key是session:{session_id}value是消息列表的JSON。設置30分鐘過期每次訪問刷新過期時間。要注意消息列表不能無限增長超過20條就觸發(fā)摘要壓縮。4.5 部署與監(jiān)控部署我推薦用Docker Compose起步服務不多的時候夠用。核心服務包括API服務、Redis、Qdrant、監(jiān)控Prometheus Grafana。API服務至少起2個實例做負載均衡避免單點故障。監(jiān)控指標重點關注LLM調(diào)用延遲P95、Token消耗速率、工具調(diào)用成功率、Agent平均輪次、錯誤率。這些指標能幫你快速定位問題——比如工具調(diào)用成功率突然下降可能是某個外部API掛了Agent平均輪次飆升可能是Prompt出了問題導致LLM反復調(diào)用工具。5. 常見問題與排查技巧實錄5.1 LLM返回格式不符合預期怎么辦這是最常見的問題。你要求LLM返回JSON它偏偏給你加一段“好的以下是JSON”的前綴。解決辦法有幾個層次第一層是Prompt層面在系統(tǒng)指令里明確要求“只輸出JSON不要任何其他文字”并給出示例。第二層是解析層面用正則提取JSON部分容忍一定程度的格式偏差。第三層是重試層面解析失敗時把錯誤信息喂回LLM讓它重新生成。第四層是約束解碼如果用的模型支持JSON Mode或Structured Output直接開啟。我一般四層都上實測下來格式錯誤率能從15%降到1%以下。5.2 Agent陷入循環(huán)調(diào)用怎么破Agent反復調(diào)用同一個工具、或者在兩個工具之間來回跳這是ReAct模式的經(jīng)典問題。排查思路先看LLM的思考過程如果記錄了的話判斷是工具描述有歧義還是任務本身無法完成。如果是描述問題優(yōu)化工具描述如果是任務問題設置最大輪次強制退出并返回一個兜底回復。還有一個技巧是在Prompt里加入“如果你已經(jīng)調(diào)用過某個工具且結(jié)果不理想不要重復調(diào)用嘗試其他方法或直接告知用戶”。這句話能顯著減少無效循環(huán)。5.3 并發(fā)上來之后響應變慢LLM API本身有速率限制并發(fā)高了之后請求會排隊。解決辦法請求隊列優(yōu)先級。用Redis做隊列重要請求優(yōu)先處理批量合并短時間內(nèi)相同或相似的請求合并成一次LLM調(diào)用緩存對常見問題緩存LLM回復命中緩存直接返回。緩存要注意設置合理的TTL和失效策略。知識庫更新后相關緩存要主動清除否則用戶會拿到過時的答案。5.4 常見問題速查表問題現(xiàn)象可能原因排查方向解決方案LLM調(diào)用超時網(wǎng)絡問題/模型服務抖動檢查網(wǎng)絡和模型服務狀態(tài)增加超時重試配置備用模型工具調(diào)用參數(shù)錯誤工具描述不清/參數(shù)Schema有誤檢查工具定義和LLM輸出優(yōu)化描述增加參數(shù)示例上下文超限對話輪次過多/工具結(jié)果太大統(tǒng)計Token消耗摘要壓縮截斷工具結(jié)果回復內(nèi)容不安全輸入注入/模型幻覺檢查輸入和輸出加輸入過濾和輸出審核流式輸出中斷網(wǎng)絡不穩(wěn)定/服務重啟檢查SSE連接狀態(tài)前端加重連后端加心跳5.5 幾個我踩過的坑第一個坑是過度依賴LLM做決策。早期我讓LLM自己決定要不要調(diào)用工具、調(diào)用哪個工具結(jié)果行為很不穩(wěn)定。后來改成混合模式——簡單規(guī)則用代碼判斷復雜情況才交給LLM穩(wěn)定性大幅提升。第二個坑是忽略Token成本。上線第一個月賬單出來嚇了一跳排查發(fā)現(xiàn)是工具返回結(jié)果太長每次都把整個JSON塞進上下文。后來改成只提取關鍵字段成本降了60%。第三個坑是沒有做會話隔離。測試階段兩個用戶同時對話發(fā)現(xiàn)A用戶能看到B用戶的歷史記錄。原因是會話狀態(tài)存在了全局變量里。改成Redis按session_id隔離后解決。這個坑很危險涉及數(shù)據(jù)安全一定要在架構(gòu)設計階段就考慮清楚。第四個坑是Prompt硬編碼。一開始Prompt寫在代碼里每次調(diào)整都要重新部署。后來遷移到配置中心產(chǎn)品經(jīng)理自己就能改迭代速度快了很多。6. 架構(gòu)演進從單體到分布式的思考6.1 什么時候該拆服務一開始不要拆。單體應用開發(fā)快、部署簡單、調(diào)試方便。當出現(xiàn)以下信號時再考慮拆分不同模塊的伸縮需求差異大比如模型調(diào)用需要GPU而業(yè)務邏輯不需要、團隊規(guī)模變大需要獨立發(fā)布、某個模塊成為性能瓶頸。拆分的第一刀通常切在模型服務上。把LLM調(diào)用獨立成一個服務統(tǒng)一管理API Key、限流、緩存、監(jiān)控。這樣多個AI應用可以共享模型服務也方便做統(tǒng)一的成本核算。6.2 多Agent協(xié)作的架構(gòu)模式當單個Agent搞不定復雜任務時就需要多Agent協(xié)作。常見的模式有主管模式一個Orchestrator Agent分配任務給Worker Agent、流水線模式Agent按順序處理前一個的輸出是后一個的輸入、辯論模式多個Agent給出方案互相評審后選最優(yōu)。多Agent架構(gòu)的復雜度比單Agent高一個數(shù)量級通信協(xié)議、狀態(tài)同步、錯誤傳播都是難點。我的建議是除非單Agent確實無法完成否則不要上多Agent。很多場景下把單Agent的Prompt和工具優(yōu)化好效果比多Agent更好。6.3 架構(gòu)設計的取舍原則最后分享幾條我在架構(gòu)設計中的取舍原則。簡單優(yōu)于靈活——能用一個Agent解決的不要用兩個能用代碼寫死的不要交給LLM決策??捎^測優(yōu)于性能——寧可多花一點性能在日志和追蹤上也不要出了問題兩眼一抹黑。隔離優(yōu)于復用——不同用戶的會話、不同任務的上下文嚴格隔離不要為了省內(nèi)存而共享狀態(tài)。漸進優(yōu)于一步到位——先跑通最小閉環(huán)再逐步加功能不要一開始就設計一個“完美”的架構(gòu)。這些原則聽起來簡單但在實際項目中堅持下來不容易。我見過太多項目因為一開始追求“大而全”的架構(gòu)結(jié)果三個月都沒上線。反而是那些從簡單開始、快速迭代的項目最終跑得更遠。架構(gòu)設計這件事紙上談兵容易落地踩坑才是常態(tài)。我自己的體會是每次做完一個AI應用回頭看架構(gòu)圖都會發(fā)現(xiàn)可以優(yōu)化的地方。這不是壞事說明你在進步。重要的是保持架構(gòu)的可演進性——今天的設計不一定要完美但一定要讓明天的修改成本足夠低。