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