AI应用架构设计:从LLM、Agent到MCP的分层实践与避坑指南
1. 从一张架构图说起:为什么AI应用架构值得单独拎出来讲
很多人第一次接触AI应用开发,脑子里想的都是"调个API就完事了"。我刚开始也是这么想的——拿个大模型接口,套个前端页面,能对话就算跑通了。直到真正接手一个要上线的项目,才发现事情远没有这么简单:模型响应忽快忽慢、多轮对话上下文爆炸、工具调用动不动就超时、多个Agent之间互相甩锅……这些问题没有一个能靠"调API"解决,它们全部指向同一个东西——架构设计。
AI应用架构设计和传统软件架构最大的区别在于:传统架构里,输入输出是确定的,你写个函数传3返回6,它永远返回6;但AI应用里,同一个输入可能得到完全不同的输出,模型可能幻觉、可能超时、可能拒绝回答。这意味着架构要额外处理不确定性——这是传统后端工程师最容易低估的地方。
这篇内容适合三类人看:一是正在从传统开发转向AI应用开发的工程师,你需要知道架构层面多了哪些东西;二是已经能跑通Demo但不知道怎么产品化的开发者,你需要把"能跑"变成"稳定跑";三是技术负责人,你需要判断一个AI应用方案到底靠不靠谱。我会围绕LLM、Agent、MCP这几个核心概念,把AI应用架构从底到顶拆一遍,配上我自己踩过的坑和实际可用的设计思路。
先给一个全局视角。一个完整的AI应用架构,从下到上大致分四层:模型层(LLM本身,包括推理、微调、量化部署)、能力层(RAG检索、工具调用、记忆管理)、编排层(Agent逻辑、多Agent协作、任务规划)、应用层(对话界面、业务流程、权限控制)。MCP则是一个横切关注点,它解决的是"能力层怎么标准化对接外部工具"的问题。下面逐层展开。
2. 模型层:LLM选型和部署里那些没人告诉你的取舍
2.1 闭源API和本地部署,不是"哪个好"而是"哪个合适"
模型层最基础的决策就是:用云端API还是本地部署?这个问题被问烂了,但很多回答都停留在"本地部署数据安全"这种层面,不够具体。我按实际项目经验给一个判断框架。
先看几个硬指标。云端API的优势是开箱即用、模型能力强、免运维;劣势是数据出域、按量计费成本不可控、网络延迟不可控。本地部署的优势是数据不出门、成本固定、可深度定制;劣势是硬件投入大、模型能力通常弱于顶级闭源模型、需要专人维护。
但真正做决策时,我建议先问三个问题:
第一,你的数据敏感度到底有多高?很多团队一上来就说"我们数据不能出域",结果仔细一聊,发现要处理的只是公开的产品文档。这种情况用云端API完全没问题。真正需要本地部署的,是涉及个人隐私、商业机密、或者有明确合规要求的场景。
第二,你的调用量有多大?算一笔账:假设你用某云端模型,每百万token输入收费几块钱,一天调用100万token,一个月就是几百块。这个量级下本地部署一张消费级显卡的成本可能两三年都收不回来。但如果一天调用上亿token,本地部署的边际成本优势就出来了。
第三,你对延迟的容忍度是多少?云端API的网络往返通常在几百毫秒到几秒,本地部署可以做到几十毫秒。如果是实时对话场景,这个差异用户能明显感知。
我自己的做法是混合架构:核心敏感数据走本地小模型,通用任务走云端大模型,中间加一层路由。这样既控制了成本,又保证了敏感数据不出域。路由逻辑可以很简单,比如按任务类型分流,也可以复杂一点,用小模型先判断任务复杂度再决定用哪个模型。
2.2 量化部署:让大模型跑在消费级硬件上的关键手段
本地部署绕不开量化。所谓量化,说白了就是把模型权重从高精度(比如16位浮点)压缩到低精度(比如4位整数),代价是精度损失,收益是显存占用大幅下降。
常见的量化方案有这么几档:
| 量化级别 | 显存占用(以7B模型为例) | 精度损失 | 适用场景 |
|---|---|---|---|
| FP16 | 约14GB | 无 | 有专业显卡,追求最高质量 |
| INT8 | 约7GB | 极小 | 平衡选择 |
| INT4 | 约4GB | 可感知 | 消费级显卡、边缘设备 |
| 2-3bit | 约2-3GB | 明显 | 极端资源受限场景 |
实测下来,INT4量化在大多数对话和问答任务上,效果和FP16的差距普通用户基本感知不到,但显存占用直接砍掉三分之二。如果你的场景是结构化信息抽取或者代码生成,量化带来的精度损失会更明显一些,建议至少用INT8。
量化工具方面,主流的有llama.cpp自带的量化、GPTQ、AWQ等。llama.cpp的GGUF格式对CPU推理友好,适合没有独立显卡的场景;GPTQ和AWQ对GPU推理优化更好。选哪个取决于你的硬件环境,没有绝对优劣。
注意:量化后的模型在长文本生成时更容易出现重复、跑题的问题。如果你的应用涉及长文写作,建议做一轮量化前后的对比测试,别直接上线。
2.3 模型层的容错设计:别把鸡蛋放在一个篮子里
生产环境里,单一模型源是不可接受的。云端API会限流、会宕机、会涨价;本地模型会OOM、会卡死。架构上必须做多模型冗余。
具体做法:维护一个模型池,每个模型标注能力标签(擅长对话、擅长代码、擅长抽取)和健康状态。请求进来时,路由器根据任务类型选主模型,主模型失败则自动降级到备用模型。降级策略要提前定义好,比如主模型超时3秒就切备用,备用也失败就返回兜底话术。
这里有个容易忽略的点:不同模型的输出格式可能不一样。比如同样要求返回JSON,模型A返回的是标准JSON,模型B可能带markdown代码块标记。所以路由层之后需要加一层输出规范化,把不同模型的输出统一成应用层能处理的格式。这层不做,后面会出各种诡异的解析错误。
3. 能力层:RAG、工具调用和记忆管理怎么搭才不塌
3.1 RAG不是"向量库+大模型"这么简单
RAG(检索增强生成)现在几乎成了AI应用的标配,但很多人对它的理解停留在"把文档切块、存进向量库、查询时召回、拼进prompt"。这个流程能跑通Demo,但上线后会遇到一堆问题。
第一个问题是切块策略。固定长度切块是最省事的,但效果往往最差。一份技术文档,按500字硬切,很可能把一个完整的函数说明切成两半,召回时只召回上半部分,模型拿到残缺信息自然答不对。我的经验是按语义结构切块:Markdown按标题层级切,代码按函数/类切,合同按条款切。切完之后给每个块加上元数据(来源文件、所属章节、时间戳),召回时可以按元数据过滤。
第二个问题是召回质量。纯向量检索对语义相似但用词不同的查询效果好,但对精确匹配(比如查一个特定的错误码)效果差。实际项目里我一般用混合检索:向量检索+关键词检索(BM25),两路结果合并后重排序。重排序可以用一个小的交叉编码器模型,也可以用大模型做相关性打分。实测混合检索比纯向量检索的召回准确率能提升20%以上。
第三个问题是上下文组装。召回了一堆文档块,不能一股脑全塞进prompt。一是token有限,二是无关信息会干扰模型。我的做法是:先按相关性排序,取Top-K(通常3-5个),然后做去重和压缩——把重复内容去掉,把长块压缩成摘要,最后按"问题-证据-指令"的结构组装prompt。
3.2 工具调用:让LLM从"会说"变成"会做"
LLM本身只能生成文本,要让它查数据库、发邮件、调API,就得靠工具调用(Function Calling)。工具调用的架构设计有几个关键决策。
工具描述怎么写。模型是根据工具的名称和描述来决定调不调的。描述写得太简单,模型不知道什么时候该用;写得太复杂,占token还容易让模型困惑。我的经验是:名称用动词开头(如search_orders、send_email),描述里写清楚"什么时候用"和"参数含义",并给一两个调用示例。比如:
{ "name": "search_orders", "description": "根据用户ID和日期范围查询订单。当用户询问订单状态、订单金额、订单时间时使用此工具。", "parameters": { "user_id": "用户唯一标识,字符串", "start_date": "起始日期,格式YYYY-MM-DD", "end_date": "结束日期,格式YYYY-MM-DD" } }工具执行的安全边界。模型可能调用任何它认为合适的工具,包括删除数据的工具。所以架构上必须做权限校验:每个工具标注风险等级,高风险工具(写操作、删除操作)需要二次确认或人工审批。我见过一个案例,模型在用户说"帮我清理一下"的时候直接调了删除接口,把用户数据删了。这种坑一定要在架构层面堵住。
工具调用的错误处理。工具执行可能失败——网络超时、参数错误、权限不足。失败信息要返回给模型,让模型决定是重试、换工具还是告诉用户。这里有个细节:错误信息不要直接抛原始堆栈给模型,要转成自然语言描述,否则模型看不懂。
3.3 记忆管理:短期记忆、长期记忆和它们各自的坑
AI应用的"记忆"分两种:短期记忆(当前对话的上下文)和长期记忆(跨对话的用户偏好、历史事实)。
短期记忆的核心问题是上下文窗口有限。多轮对话聊久了,token会爆。解决方案有几种:滑动窗口(只保留最近N轮)、摘要压缩(把早期对话总结成一段话)、关键信息提取(只保留实体和意图)。我一般组合使用:最近5轮保留原文,5轮之前的做摘要,实体信息单独存一张表。
长期记忆的核心问题是写入和召回的准确性。什么时候该记?记什么?怎么在需要的时候想起来?我的做法是:对话结束后用一个小模型做信息抽取,把值得记的事实(用户偏好、重要日期、已确认的决策)存进结构化存储;下次对话开始时,根据当前话题召回相关记忆,拼进system prompt。注意不要把所有记忆都塞进去,只召回相关的,否则会干扰模型。
提示:记忆管理最容易出的问题是"记了不该记的"。比如用户随口说了一句"我今天心情不好",如果被当成长期记忆存下来,下次对话模型可能莫名其妙地安慰你。所以信息抽取时要加过滤规则,只记事实性、稳定性强的信息。
4. 编排层:Agent架构和多Agent协作的真实玩法
4.1 单Agent的经典架构:ReAct、Plan-and-Execute怎么选
Agent的本质是"让LLM自己决定下一步做什么"。最经典的架构是ReAct(Reasoning + Acting):模型先思考(Thought),再决定行动(Action),执行后观察结果(Observation),循环直到任务完成。
ReAct的优点是灵活、实现简单,适合步骤不确定的探索性任务。缺点是容易陷入循环——模型可能反复调同一个工具,或者在一个死胡同里打转。所以生产环境里必须加最大步数限制和循环检测:如果连续两步调了同一个工具且参数相同,就强制中断。
另一种架构是Plan-and-Execute:先让模型制定完整计划,再逐步执行。优点是全局观强、不容易跑偏;缺点是计划可能一开始就错了,而且执行过程中遇到意外不好调整。我的经验是:任务步骤明确、可预先规划的场景用Plan-and-Execute;需要边做边看的场景用ReAct。也可以混合:先规划大步骤,每个步骤内部用ReAct。
4.2 多Agent协作:什么时候需要,怎么避免互相甩锅
多Agent协作听起来很酷,但很多场景其实不需要。我见过不少项目,明明一个Agent加几个工具就能搞定,非要拆成"规划Agent+执行Agent+审核Agent",结果三个Agent之间通信开销比任务本身还大,还容易互相甩锅——执行Agent说规划Agent给的任务不清晰,规划Agent说执行Agent理解能力差。
判断要不要多Agent,我的标准是:如果子任务需要不同的系统提示词、不同的工具集、或者不同的模型,才考虑拆Agent。比如一个"客服Agent"和一个"技术文档Agent",前者需要亲和力,后者需要严谨,提示词差异大,拆开合理。如果只是任务步骤多,单Agent加工具就够了。
多Agent协作的架构模式主要有两种:中心化(一个协调者Agent分配任务给执行者Agent)和去中心化(Agent之间平等通信)。中心化模式可控性强,适合大多数场景;去中心化模式灵活但容易出现死锁和消息风暴,除非有明确需求,否则不建议。
通信协议方面,Agent之间传递的消息要结构化,包含任务ID、状态、结果、错误信息。不要传自然语言,否则解析起来全是坑。我一般用JSON,字段固定,每个Agent都按这个格式读写。
4.3 MCP:工具对接的标准化尝试
MCP(Model Context Protocol)是最近很热的一个概念,它要解决的问题是:每个AI应用都要自己实现一遍工具对接,重复造轮子。MCP定义了一套标准协议,工具提供方按协议暴露能力,AI应用按协议调用,双方解耦。
从架构角度看,MCP的价值在于把工具层标准化。以前你接一个数据库,要写连接、查询、结果解析;接一个文件系统,又要写一套。有了MCP,这些工具如果有现成的MCP Server,你直接接就行。对工具提供方来说,实现一次MCP Server,所有支持MCP的AI应用都能用。
但MCP目前还在演进中,实际使用有几个注意点:一是MCP Server的质量参差不齐,有些实现有性能问题或安全漏洞,接入前要评估;二是MCP的权限模型还在完善,生产环境用的话要自己在外面加一层权限控制;三是不是所有工具都值得MCP化,如果只是内部用的一两个工具,直接写代码调用可能更简单。
我自己的判断是:MCP适合工具生态丰富、需要频繁对接新工具的场景。如果你的应用就固定用三五个工具,没必要为了MCP而MCP。
5. 应用层与横切关注点:那些决定产品能不能上线的细节
5.1 流式输出和中断处理:用户体验的关键
用户等一个回答等10秒和等3秒但能看到字一个个蹦出来,体验天差地别。流式输出(Streaming)是AI应用的标配,但实现起来有几个坑。
第一个坑是流式输出和工具调用的冲突。模型可能在流式输出到一半时决定调用工具,这时候前面的文本已经发给用户了,工具执行完还要接着输出,怎么衔接?我的做法是:工具调用前的文本作为"思考过程"展示,工具执行后重新开始一段输出。前端要做好状态管理,别让用户看到混乱的拼接。
第二个坑是中断处理。用户可能在模型输出到一半时发送新消息,或者点击停止。这时候要能干净地中断当前生成,释放资源,并且保证对话历史的一致性。我见过一些实现,中断后历史记录里存了半截回答,下次对话模型看到半截内容会困惑。正确做法是:中断时把已生成的部分标记为"未完成",或者直接丢弃。
5.2 成本控制:token是要花钱的
AI应用的成本大头是token消耗。控制成本要从架构层面入手,不能靠事后省。
几个实用手段:缓存——相同或相似的问题直接返回缓存结果,尤其是FAQ类场景,命中率能到30%以上;模型分级——简单问题用小模型,复杂问题用大模型,用一个小分类器做路由;prompt压缩——定期审查system prompt,去掉冗余描述,我见过一个项目的system prompt写了2000字,压缩到500字后效果没变,成本降了四分之三;输出长度限制——给模型设置max_tokens,防止它滔滔不绝。
还有一个容易被忽略的点:重试的成本。模型调用失败重试,每次重试都是钱。所以重试策略要合理,不要无脑重试三次。我的做法是:网络错误重试,模型返回格式错误不重试(重试大概率还是错),而是走降级逻辑。
5.3 可观测性:出了问题怎么查
AI应用的可观测性比传统应用难做,因为输出是不确定的。传统应用看日志就知道哪行代码出了问题,AI应用可能同样的输入这次对下次错,你根本不知道发生了什么。
我的做法是全链路记录:每次请求记录输入、检索到的文档、组装的prompt、模型原始输出、工具调用记录、最终输出。这些数据存下来,出问题时可以回放。同时做质量监控:用规则或小模型对输出做自动评估(比如是否包含敏感词、是否格式正确、是否答非所问),异常时告警。
注意:记录prompt和输出时要注意隐私合规。用户输入可能包含个人信息,存储前要脱敏,存储期限要明确。
6. 我在实际项目里踩过的几个典型坑
第一个坑是过度设计。刚开始做AI应用时,我总想把架构做得"完整"——加缓存、加队列、加多Agent、加监控,结果系统复杂度上去了,开发速度下来了,很多功能根本用不上。后来我学乖了:先跑通最小闭环,再按需加东西。一个对话应用,先做到"能稳定回答",再考虑缓存和监控。
第二个坑是忽视prompt的版本管理。prompt是AI应用的核心资产,但很多人把它硬编码在代码里,改一次要发一次版。我现在的做法是:prompt存在配置中心或数据库里,支持热更新,每次修改记录版本和效果指标。这样调prompt不用发版,A/B测试也方便。
第三个坑是对模型能力估计过高。有些任务模型确实做不好,比如精确计算、复杂逻辑推理。硬让模型做,不如把任务拆解,该用代码的用代码,该用模型的用模型。架构设计的一个原则是:能用确定性代码解决的,不要交给模型。
第四个坑是忽略冷启动。新上线的AI应用没有历史数据,缓存命中率低,检索效果差。这时候要么先用规则兜底,要么用合成数据预热。我一般会准备一批常见问题,上线前先跑一遍,把结果缓存起来。
7. 一套可落地的AI应用架构参考
把上面的内容串起来,给一个我实际用过的架构参考。这套架构适合中等规模的AI应用,日调用量在十万到百万级别。
接入层:API网关,负责鉴权、限流、路由。所有请求先到这里,根据用户等级和任务类型分发。
编排层:Agent调度器,维护会话状态,决定用哪个Agent处理。单Agent场景下这层很薄,多Agent场景下负责任务分配和结果汇总。
能力层:RAG检索服务、工具调用服务、记忆服务。每个服务独立部署,通过内部接口通信。RAG服务包含向量库、关键词索引、重排序模型;工具服务包含工具注册、权限校验、执行引擎;记忆服务包含短期记忆存储和长期记忆存储。
模型层:模型路由 + 模型池。路由根据任务类型和模型健康状态选择模型,模型池包含云端API和本地部署模型。
基础设施:日志、监控、缓存、消息队列。这些是支撑性的,但缺了哪个都会出问题。
这套架构不是唯一解,也不是最优解,但它是我在多个项目里验证过能跑通的。你可以根据自己的场景裁剪——小项目可能只需要接入层+模型层,大项目可能还要加数据飞轮、模型微调流水线。
最后说一个我越来越深的体会:AI应用架构的核心不是技术选型,而是对不确定性的管理。模型会出错、工具会失败、用户会问奇怪的问题,架构的价值就是让这些不确定性在可控范围内。把这一点想清楚,很多设计决策就自然清晰了。