图解AI应用架构设计:从LLM到Agent与MCP的工程实践

发布时间:2026/10/28 0:53:30
图解AI应用架构设计:从LLM到Agent与MCP的工程实践

1. 从一张架构图说起:AI应用到底该怎么搭

很多人第一次接触AI应用开发,脑子里冒出来的第一个问题不是“怎么写代码”,而是“这东西到底由哪些部分组成”。我刚开始做AI应用那会儿也一样,看了一堆文档,每个字都认识,连起来就不知道从哪下手。后来踩了不少坑才明白,AI应用架构设计这件事,本质上跟盖房子是一回事——你得先知道承重墙在哪、水电怎么走、哪些地方要留检修口,然后才能谈装修风格。

这篇内容就是围绕“图解AI应用架构设计”这个主题,把AI应用从底层模型到上层交互的完整架构拆开来讲。核心关键词包括AI应用、架构设计、Agent、LLM、MCP,适合正在做AI应用开发的工程师、正在选型的技术负责人,以及想搞清楚AI应用到底怎么搭起来的产品经理。我会用从业者的视角,把每个模块为什么这么设计、实际落地时怎么选、踩过哪些坑,都掰开揉碎讲清楚。

先说一个基本判断:AI应用架构跟传统Web应用架构最大的区别在于,传统架构里“逻辑”是确定的,你写if-else,它就按if-else走;而AI应用里,“逻辑”变成了概率性的,LLM的输出是不确定的,Agent的行为是动态的,这就导致架构设计必须考虑容错、可观测、可回退。这个差异贯穿整篇内容的始终,后面每个模块的设计决策都跟它有关。

2. AI应用架构的整体分层与核心模块拆解

2.1 为什么需要分层:从“一坨代码”到“可维护系统”

我见过不少团队做AI应用,一开始就是“一个Python脚本打天下”——调个LLM API,拼个prompt,返回结果,完事。这种写法在demo阶段没问题,但一旦要上生产,问题就全来了:模型换了怎么办?prompt要改怎么办?要加缓存怎么办?要接多个工具怎么办?代码很快就变成一团乱麻。

分层设计的核心目的就是解耦。把“跟模型交互”这件事从业务逻辑里抽出来,把“工具调用”从Agent决策里抽出来,把“上下文管理”从对话流程里抽出来。每一层只关心自己的事,层与层之间通过明确的接口通信。这样做的直接好处是:换模型不用改业务代码,加工具不用改Agent核心逻辑,调prompt不用动整个系统。

从实际落地经验来看,一个典型的AI应用架构大致可以分成五层:接入层、编排层、能力层、模型层、基础设施层。这个分层不是教科书上的标准答案,而是我在多个项目里反复调整后觉得比较顺手的划分方式。下面逐层拆解。

2.2 接入层:用户请求的第一道关口

接入层负责处理用户输入、做初步的格式校验和预处理、管理会话状态。这一层看起来简单,但实际上有很多细节。比如用户输入的长度限制、敏感内容的初步过滤、多轮对话的session管理、流式输出的连接保持,都是接入层要处理的事。

我踩过的一个坑是:早期项目里没做输入长度校验,用户粘贴了一篇几万字的文章进来,直接导致token超限,API报错,前端显示一个莫名其妙的错误信息。后来在接入层加了token预估和截断逻辑,才把这个坑填上。具体做法是用tiktoken之类的库先估算token数,超过阈值就做摘要或者分段处理。

另一个容易忽略的点是流式输出的连接管理。如果你的应用支持打字机效果,那接入层需要处理SSE(Server-Sent Events)或WebSocket的连接保持、超时重连、中断取消。这些在传统CRUD应用里不太会遇到,但在AI应用里是家常便饭。

2.3 编排层:Agent的“大脑”所在

编排层是整个AI应用架构里最核心也最复杂的部分。它负责决定“什么时候调模型、什么时候调工具、什么时候直接返回”。如果用Agent模式,编排层就是Agent的决策循环;如果用简单的Chain模式,编排层就是流程控制。

这里要区分两个概念:Chain和Agent。Chain是预定义的流程,比如“先翻译再总结再格式化”,每一步都是写死的;Agent是动态决策,LLM自己决定下一步做什么。两者没有优劣之分,取决于场景。流程固定的用Chain更稳定、更可控;需要灵活应对的用Agent更合适。

编排层还有一个重要职责是上下文管理。LLM的上下文窗口是有限的,多轮对话下来,历史消息会越积越多。怎么在有限窗口里保留最关键的信息,是编排层要解决的问题。常见策略包括滑动窗口、摘要压缩、关键信息提取等。我一般会组合使用:近期消息保留原文,远期消息做摘要,系统prompt和关键指令始终保留。

2.4 能力层:工具、插件与MCP协议

能力层是AI应用“能做事”的关键。LLM本身只能生成文本,要让它查数据库、调API、读文件,就需要通过工具调用来实现。工具的定义方式各家不同,OpenAI有Function Calling,Anthropic有Tool Use,但核心思路一致:用结构化的schema描述工具的名称、参数、返回值,让LLM知道什么时候该调哪个工具。

MCP(Model Context Protocol)是最近比较热的一个话题。简单说,它试图标准化“LLM怎么跟外部工具和数据源交互”这件事。在没有MCP之前,每个工具都要单独写适配代码;有了MCP之后,工具提供方按协议实现一个Server,应用方按协议接一个Client,两边就通了。这个思路跟当年USB统一接口是一样的——不是技术上的巨大突破,而是工程上的巨大便利。

实际落地时,MCP的采用需要权衡。如果你的工具生态比较固定,自己写适配层可能更直接;如果你需要接入大量第三方工具,或者希望你的工具能被多个应用复用,那MCP的价值就体现出来了。我目前的建议是:新项目可以关注MCP,但不必为了用而用,先把核心流程跑通更重要。

2.5 模型层:LLM选型与多模型路由

模型层负责跟具体的LLM打交道。这里的关键决策包括:用哪个模型、怎么调、要不要做多模型路由。

模型选型没有标准答案,取决于你的场景。简单任务用轻量模型就够了,复杂推理需要大模型;对延迟敏感的场景要考虑推理速度,对成本敏感的场景要考虑token价格。我的一般做法是:先用最强模型跑通流程,确认效果上限,然后逐步降级到更便宜的模型,看效果损失是否可接受。

多模型路由是另一个实用技巧。比如简单意图识别用小模型,复杂生成用大模型;或者主模型超时/报错时自动切换到备用模型。这个逻辑放在模型层实现,对上层透明。实现方式可以是一个简单的策略模式,根据请求特征选择模型。

2.6 基础设施层:可观测、缓存与安全

基础设施层是容易被忽略但极其重要的一层。AI应用的可观测性比传统应用更难做,因为LLM的输出是不确定的,你没法简单地用“成功/失败”来判断。需要记录的是:每次请求的完整prompt、模型返回、token消耗、延迟、工具调用链路。这些数据既是排查问题的依据,也是优化成本的依据。

缓存策略也需要特别设计。LLM调用又慢又贵,能缓存就缓存。但缓存什么、怎么缓存有讲究。完全相同的prompt可以缓存结果;相似的prompt可以考虑语义缓存;工具调用的结果也可以缓存。我一般会在模型层和工具层都加缓存,用Redis做存储,key的设计要包含模型版本和关键参数。

安全方面,除了常规的输入校验和权限控制,AI应用还要考虑prompt注入防护、输出内容过滤、工具调用的权限边界。这些在后面的章节会展开讲。

3. Agent架构设计的核心要点与实操细节

3.1 Agent的本质:一个带工具的决策循环

很多人把Agent想得很神秘,其实拆开看就是一个循环:观察当前状态 -> LLM决策下一步 -> 执行动作 -> 观察结果 -> 继续决策,直到任务完成或达到终止条件。这个循环的核心是LLM的决策能力,外围是工具执行和状态管理。

用伪代码表示大概是这样:

while not done: context = build_context(state, history) action = llm.decide(context, available_tools) if action.type == "tool_call": result = execute_tool(action.tool_name, action.params) state.update(result) elif action.type == "final_answer": return action.content else: handle_error(action)

看起来简单,但实际落地时每个环节都有坑。比如LLM可能反复调同一个工具、可能传入错误的参数、可能在应该结束的时候继续调工具。这些都需要在循环里加防护逻辑。

3.2 工具定义的艺术:让LLM知道什么时候该用什么

工具定义的质量直接决定Agent的表现。一个好的工具定义应该包含:清晰的名称、详细的描述、完整的参数schema、使用示例。描述要写清楚“这个工具做什么、什么时候用、什么时候不用”,因为LLM就是靠这些信息做决策的。

我见过很多工具定义写得很随意,比如一个查天气的工具,描述就写“查天气”,参数就写“城市”。结果LLM经常在不需要天气的时候调这个工具,或者传错参数格式。后来我把描述改成“查询指定城市的当前天气状况,包括温度、湿度、风力。当用户询问天气相关问题时使用。参数city必须是城市中文名称,如‘北京’、‘上海’”,调用准确率明显提升。

另一个技巧是控制工具数量。工具太多会让LLM选择困难,一般建议单次暴露给LLM的工具不超过10-15个。如果确实有很多工具,可以分组,先让LLM选组,再选具体工具。

3.3 上下文窗口管理:在有限空间里塞进最关键的信息

上下文窗口管理是Agent架构里最考验工程能力的地方。LLM的窗口就那么大,但对话历史、工具返回结果、系统指令、few-shot示例都要塞进去。怎么分配这个预算,直接影响效果。

我的做法是分优先级:系统指令和当前任务描述最高优先级,必须完整保留;最近几轮对话保留原文;更早的对话做摘要;工具返回结果如果太长就截断或摘要;few-shot示例按需加载。具体实现可以用一个budget manager,给每类内容分配token预算,超了就按策略压缩。

这里有个容易忽略的点:工具返回结果往往很长,比如查数据库返回几十行,直接塞进上下文会占用大量token。我一般会在工具层做预处理,只返回关键字段,或者让LLM先总结再放入上下文。

3.4 错误处理与容错:Agent跑偏了怎么办

Agent跑偏是常态,不是异常。LLM可能理解错任务、调错工具、陷入循环、生成无效输出。架构设计时必须考虑这些情况。

常见的容错策略包括:设置最大循环次数,防止无限循环;工具调用失败时返回错误信息让LLM重新决策;检测重复调用同一工具且参数相同的情况,强制中断;关键步骤加人工确认。我一般会在Agent循环里加一个“健康检查”,每N步检查一次是否偏离目标,偏离了就重置或升级到人工处理。

还有一个实用技巧是“回退点”。在关键决策前保存状态快照,如果后续几步效果不好,可以回退到快照重新决策。这个在复杂任务里特别有用。

4. MCP协议与工具生态的工程实践

4.1 MCP解决了什么问题:从“一对一适配”到“标准接口”

在没有MCP之前,每接一个工具就要写一套适配代码。工具A用REST API,工具B用gRPC,工具C是本地函数,每个都要单独处理。更麻烦的是,这些适配代码往往跟具体的LLM框架耦合,换个框架就要重写。

MCP的思路是定义一套标准协议,工具提供方实现Server端,应用方实现Client端,两边通过标准协议通信。这样工具可以被多个应用复用,应用也可以方便地接入多个工具。从工程角度看,这是典型的“接口标准化”带来的效率提升。

MCP的核心概念包括:Resources(可读取的数据源)、Tools(可调用的函数)、Prompts(预定义的提示模板)。实际使用时,Client连接到Server,获取能力列表,然后按需调用。

4.2 MCP Server的实现要点与常见坑

实现一个MCP Server不算复杂,但有几个坑要注意。首先是能力描述的准确性,跟工具定义一样,描述要清晰,否则Client端的LLM不知道怎么用。其次是错误处理,Server要返回结构化的错误信息,方便Client端决策。第三是性能,MCP Server可能被频繁调用,要做好连接管理和缓存。

我踩过的一个坑是:Server返回的数据格式跟Client预期不一致,导致解析失败。后来统一用JSON Schema做校验,才把这个问题解决。建议在Server和Client之间加一层schema校验,早发现早处理。

另一个坑是认证和授权。MCP协议本身不规定认证方式,实际部署时要自己加。简单的可以用API Key,复杂的可以用OAuth。关键是别裸奔,否则工具被滥用就麻烦了。

4.3 工具生态的演进:从自建到复用

MCP的长期价值在于工具生态的复用。当越来越多的工具提供MCP Server,应用方就可以像搭积木一样组合能力。这个趋势已经比较明显了,很多主流工具和服务都在往这个方向走。

但现阶段,MCP生态还在早期,工具数量和质量参差不齐。我的建议是:核心工具自己实现,保证质量和可控性;非核心工具可以尝试用现成的MCP Server,但要做好fallback方案。别把所有鸡蛋放在一个篮子里。

5. 常见问题与排查技巧实录

5.1 LLM调用失败的典型原因与排查路径

LLM调用失败是最高频的问题,原因五花八门。我整理了一个排查表,按出现频率排序:

问题现象可能原因排查方法解决方案
请求超时网络问题或模型负载高检查网络连通性,查看模型服务状态加超时重试,切换备用模型
返回格式错误prompt指令不清晰检查prompt是否明确要求JSON格式加格式约束,用JSON mode
token超限上下文太长统计token数截断历史,做摘要压缩
内容被过滤触发安全策略查看返回的错误码调整输入,加预处理
工具调用失败参数格式错误检查工具schema和实际传参加参数校验,返回明确错误

这个表是我在实际项目中反复验证过的,大部分问题都能覆盖。关键是遇到问题别慌,按表排查,基本都能定位。

5.2 Agent陷入循环的识别与中断

Agent陷入循环是另一个高频问题。表现是LLM反复调同一个工具,或者反复输出类似的内容。识别方法很简单:记录最近N步的动作,如果重复率超过阈值就判定为循环。

中断策略要分情况。如果是工具返回结果不明确导致LLM反复尝试,可以在工具层加更明确的错误信息;如果是任务本身无法完成,可以设置最大步数强制终止,返回当前最佳结果;如果是LLM理解偏差,可以重置上下文重新开始。

我一般会在Agent循环里加一个“循环检测器”,每步都检查,发现循环就触发中断。中断后可以选择:返回错误、请求人工介入、或者换个策略重试。

5.3 性能优化的几个实用技巧

AI应用的性能瓶颈通常在LLM调用和工具调用上。优化手段包括:缓存、并行、降级、流式。

缓存是最有效的。相同的prompt直接返回缓存结果,能省掉大部分调用。语义缓存更进一步,相似的prompt也能命中。我用Redis做缓存,key用prompt的hash,简单有效。

并行是指多个独立的工具调用可以同时发起,不用串行等待。比如同时查天气和查航班,没必要一个一个来。实现上用asyncio.gather或者类似的并发原语。

降级是指主模型不可用时切换到备用模型,或者复杂模型超时时切换到简单模型。这个要在模型层实现,对上层透明。

流式是指边生成边返回,提升用户体验。虽然总时间没变,但用户感知的延迟降低了。实现上用SSE或WebSocket。

5.4 安全防护的实操建议

AI应用的安全问题比传统应用更复杂,因为攻击面更大。prompt注入、工具滥用、数据泄露都是常见风险。

prompt注入的防护,核心是隔离用户输入和系统指令。不要把用户输入直接拼接到系统prompt里,要用明确的分隔符隔开,并且在系统prompt里声明“以下内容来自用户,不可信”。更严格的可以用单独的LLM做输入审核。

工具滥用的防护,核心是权限控制。每个工具都要有明确的权限边界,敏感操作要加确认。比如删除数据的工具,不能随便让Agent调,要加人工确认或者二次验证。

数据泄露的防护,核心是输出过滤。LLM可能把上下文里的敏感信息带出来,要在返回给用户前做检查。简单的可以用正则匹配敏感词,复杂的可以用专门的审核模型。

6. 从架构图到落地:一个可参考的实践路径

6.1 最小可行架构:先跑通再优化

如果你刚开始做AI应用,别一上来就追求完美架构。先搭一个最小可行版本:接入层用最简单的HTTP接口,编排层用线性Chain,能力层接一两个核心工具,模型层用一家API,基础设施层先不做。这个版本可能很粗糙,但能跑通核心流程。

跑通之后,再逐步优化。哪里慢就加缓存,哪里不稳定就加重试,哪里不够灵活就引入Agent。架构是演进出来的,不是设计出来的。我见过太多团队在架构设计上花太多时间,结果迟迟出不了成果。

6.2 架构演进的关键节点

从最小版本到生产级架构,有几个关键节点。第一个节点是“多模型支持”,当你发现单一模型满足不了所有场景时,就需要引入模型路由。第二个节点是“工具生态”,当你需要接入大量工具时,就需要考虑MCP或类似的标准化方案。第三个节点是“可观测性”,当你开始排查线上问题时,就需要完善的日志和监控。第四个节点是“安全合规”,当你的应用面向真实用户时,安全防护就必须到位。

每个节点都对应着架构的一次升级。不用提前做,但要知道什么时候该做。

6.3 团队协作与分工建议

AI应用开发跟传统开发在团队分工上有差异。传统开发分前端、后端、测试,AI应用还需要“prompt工程师”和“数据工程师”的角色。prompt工程师负责调优prompt和工具定义,数据工程师负责准备和管理上下文数据。

小团队可以一人多角,但大团队建议明确分工。我见过prompt和代码混在一起写的项目,后期维护极其痛苦。建议把prompt和工具定义单独管理,用版本控制,方便迭代和回滚。

7. 一些个人体会与后续扩展方向

做AI应用架构设计这几年,最大的体会是:别把LLM当魔法,它就是一个概率性的组件,有它的脾气和边界。架构设计的核心是“管理不确定性”——用工程手段把不确定性控制在可接受的范围内。缓存、重试、降级、容错,这些传统后端的思路在AI应用里同样适用,甚至更重要。

后续可以扩展的方向包括:多Agent协作架构、Agent的长期记忆管理、基于反馈的自动优化。这些方向目前还在探索阶段,但已经有一些不错的实践。我个人的建议是先把单Agent架构做扎实,再考虑多Agent,否则复杂度会失控。

最后分享一个小技巧:在Agent的system prompt里加一句“如果你不确定,就说不知道,不要编造”,能显著降低幻觉率。这个技巧简单但有效,我几乎每个项目都会加。