
1. 从一张架构图说起AI应用到底该怎么搭很多人第一次接触AI应用开发脑子里冒出来的第一个问题就是我到底该从哪儿下手是直接调个大模型接口就完事还是得搞一套完整的工程体系我刚开始做AI应用那会儿也有这个困惑后来踩了不少坑才慢慢理清楚——AI应用架构设计这件事本质上不是让你去造一个多复杂的系统而是让你想明白数据从哪来、模型怎么调、结果怎么用这三件事怎么串起来。“图解AI应用架构设计”这个主题核心就是要把这套串联逻辑用可视化的方式讲清楚。它解决的不是某个具体算法问题而是帮你在动手写代码之前先在脑子里画出一张完整的路线图。适合谁来参考我觉得三类人最需要一是刚转行做AI应用开发的程序员二是在传统软件团队里被安排去“接个大模型”的工程师三是想搞清楚Agent、LLM、MCP这些概念之间到底是什么关系的技术负责人。这篇文章我会从架构分层讲起把LLM、Agent、MCP这几个核心组件在架构中的位置和职责拆开揉碎然后给出可落地的实操方案最后把我自己踩过的坑和排查经验整理出来。你不需要有很深的AI背景只要写过代码、理解基本的API调用逻辑就能跟着往下走。2. AI应用架构的核心分层与组件拆解2.1 为什么需要分层从“一把梭”到“各司其职”我见过太多一开始“一把梭”的AI应用前端直接调大模型API拿到结果就渲染。这种写法在demo阶段没问题但一旦要加记忆、要接工具、要做多轮对话代码就会变成一团乱麻。架构分层的意义就在这儿——让每一层只关心自己的事层与层之间通过明确的接口通信。一个典型的AI应用架构我会把它分成四层接入层、编排层、能力层、基础设施层。接入层负责和用户交互编排层负责决定“什么时候调什么”能力层提供LLM调用、工具执行、记忆存储等具体能力基础设施层则是模型服务、向量数据库、日志监控这些底座。这么分的理由很简单编排逻辑是最容易变的今天用这个模型明天换那个模型后天加一个新工具如果编排和具体能力耦合在一起每次改动都是牵一发动全身。把编排独立出来之后换模型只需要改能力层的适配器编排层几乎不用动。2.2 LLM在架构中的角色不只是“文本生成器”很多人把LLM当成一个“输入文本、输出文本”的黑盒这在简单场景下够用但在架构设计层面就太粗糙了。LLM在AI应用里其实承担了三个角色推理引擎、决策核心、格式转换器。作为推理引擎它负责理解用户意图、做逻辑判断作为决策核心它决定要不要调用某个工具、调用哪个工具作为格式转换器它把非结构化输入转成结构化输出比如把一段自然语言转成JSON。理解这三个角色很重要因为它直接决定了你在架构里怎么设计LLM的调用接口——如果只当文本生成器接口就很简单如果要当决策核心就需要把工具描述、上下文、历史对话都传进去。实际做的时候我会在能力层封装一个统一的LLM适配器对外暴露的方法不只是generate(prompt)而是reason(context, tools, memory)这种更贴近实际需求的接口。这样编排层调用的时候就不用关心底层是哪个模型、用什么参数。2.3 Agent的定位编排层的“大脑”Agent这个词现在被用得有点泛但在架构设计语境下它的定位很明确Agent是编排层的核心执行单元。它接收用户输入结合记忆和工具决定下一步做什么然后执行再根据结果决定是否继续循环。Agent和普通LLM调用的区别在于“自主性”。普通调用是你告诉它做什么它就做什么Agent是你告诉它目标它自己决定怎么做。这个区别在架构上的体现就是Agent需要一个循环控制机制需要工具注册和发现机制需要状态管理。我自己的经验是Agent的复杂度要控制。见过一些项目Agent里塞了十几个工具、五六层嵌套决策最后调试的时候根本不知道是哪一步出了问题。我的建议是单个Agent的工具数量控制在5到8个超过这个数就考虑拆成多个Agent协作。2.4 MCP的位置能力层的“标准化插座”MCP这个概念这两年被讨论得很多但很多人还是不太清楚它在架构里到底放在哪。我的理解是MCP是能力层的一种标准化协议它让工具和资源的接入变得像插插座一样统一。在没有MCP之前每接一个外部工具都要写一套适配代码工具A用HTTP、工具B用SDK、工具C用命令行编排层要处理各种不同的调用方式。MCP做的事情就是把这些差异屏蔽掉对外提供统一的工具描述和调用接口。编排层只需要知道“有一个工具叫search接受query参数”不用关心它底层是怎么实现的。在架构图上MCP通常画在能力层和外部资源之间作为一个协议适配层。它不负责决策只负责“把请求翻译成外部工具能懂的形式把结果翻译回编排层能懂的形式”。2.5 一张完整的架构图应该包含什么把上面这些串起来一张完整的AI应用架构图应该包含以下元素层级核心组件职责接入层Web/App/API Gateway用户交互、鉴权、限流编排层Agent Runtime、Workflow Engine决策、循环控制、状态管理能力层LLM Adapter、Tool Registry、Memory Store、MCP Client模型调用、工具执行、记忆读写基础设施层Model Serving、Vector DB、Logging、Monitoring模型托管、向量检索、可观测性画图的时候我习惯用从左到右的数据流方向用户请求从左边进来经过接入层、编排层、能力层最后到基础设施层响应再反向流回去。这样画的好处是任何一层出问题你都能快速定位是“进来的路上”还是“出去的路上”出了问题。3. 核心组件深度解析与选型实操3.1 LLM选型别只看跑分要看场景匹配度选LLM这件事我的原则是“场景优先成本其次跑分最后”。跑分高的模型不一定适合你的场景比如有些模型在通用benchmark上很强但在特定领域的术语理解上可能不如一个经过精调的 smaller model。具体怎么选我会从四个维度评估任务类型是生成类、分类类、抽取类还是推理类生成类对模型创造力要求高抽取类对格式遵循要求高。延迟要求实时对话场景要求首token延迟低离线批处理场景可以接受高延迟。成本预算按token计费的话要估算日均调用量和平均token数。部署方式是调云端API还是本地部署本地部署要考虑显存和量化方案。我自己的做法是先用云端API快速验证效果确定prompt和流程没问题之后再评估是否需要用本地模型替换。本地模型的优势是数据不出域、成本可控劣势是维护成本高、效果可能打折扣。3.2 Agent框架选型从“够用”出发Agent框架现在选择很多但我建议从“够用”出发不要一上来就上最复杂的。我按复杂度分三档第一档单Agent 工具调用。适合大多数场景一个Agent注册几个工具循环执行直到任务完成。实现简单调试方便。第二档多Agent协作。适合需要分工的场景比如一个Agent负责规划、一个负责执行、一个负责审核。复杂度上升但可控。第三档带记忆和反思的Agent。适合长期运行、需要积累经验的场景。实现复杂度最高调试也最麻烦。我的建议是除非你的场景确实需要否则从第一档开始。我见过太多项目一上来就搞多Agent结果连单Agent的prompt都没调明白。3.3 MCP接入实操以工具注册为例MCP的接入流程我用一个具体的工具注册例子来说明。假设你要接入一个“天气查询”工具通过MCP协议暴露给Agent。第一步定义工具描述。MCP要求工具描述包含名称、描述、参数schema。这个描述会传给LLM让LLM知道有这个工具可用。{ name: get_weather, description: 查询指定城市的当前天气, parameters: { type: object, properties: { city: { type: string, description: 城市名称如北京、上海 } }, required: [city] } }第二步实现工具执行逻辑。这部分是实际调用天气API的代码MCP不关心你怎么实现只要求你返回符合格式的结果。第三步注册到MCP Client。编排层通过MCP Client发现这个工具在需要的时候调用。注意工具描述的质量直接影响LLM的调用准确率。描述要简洁明确参数说明要写清楚格式要求。我踩过的坑是描述写得太模糊LLM经常传错参数类型。3.4 记忆系统设计短期、长期、工作记忆记忆系统是很多AI应用容易忽略的部分但它是让应用“越用越聪明”的关键。我把记忆分成三类短期记忆当前对话的上下文通常就是消息列表。实现简单但受限于模型上下文窗口。长期记忆跨对话持久化的信息比如用户偏好、历史事实。通常用向量数据库存储检索时按相似度召回。工作记忆Agent在执行任务过程中的中间状态比如已经完成了哪些步骤、当前在做什么。这个通常存在Agent的运行时状态里。设计记忆系统的时候关键问题是“什么时候写入、什么时候读取、什么时候遗忘”。我的经验是写入要克制不是所有对话都值得存读取要精准用相似度阈值过滤遗忘要有策略定期清理低价值记忆。3.5 可观测性别等出问题了才想起来AI应用的可观测性和传统软件不太一样因为LLM的输出是不确定的你没法用简单的断言来判断对错。我通常会记录以下几类数据每次LLM调用的完整输入输出包括prompt、参数、返回结果、token消耗、延迟。Agent的决策轨迹每一步的思考、选择了哪个工具、工具返回了什么。工具调用的成功率和延迟哪些工具经常失败哪些工具响应慢。用户反馈点赞、点踩、重新生成这些是评估效果的重要信号。这些数据我一般会存到结构化的日志系统里方便后续做分析和回放。回放功能特别有用当用户反馈某个case有问题时你可以把当时的完整调用链调出来看是哪一步出了问题。4. 从零搭建一个AI应用的完整实操4.1 环境准备与项目结构假设我们要搭建一个“智能客服助手”能回答产品问题、查询订单状态、转接人工。我用的技术栈是Python FastAPI 一个LLM API 向量数据库。项目结构我会这样组织smart-assistant/ ├── app/ │ ├── main.py # FastAPI入口 │ ├── api/ │ │ └── chat.py # 对话接口 │ ├── orchestrator/ │ │ ├── agent.py # Agent核心逻辑 │ │ └── workflow.py # 工作流定义 │ ├── capabilities/ │ │ ├── llm_adapter.py # LLM适配器 │ │ ├── tool_registry.py # 工具注册 │ │ └── memory.py # 记忆管理 │ ├── tools/ │ │ ├── order_query.py # 订单查询工具 │ │ └── faq_search.py # FAQ检索工具 │ └── config.py # 配置 ├── tests/ └── requirements.txt这个结构的好处是职责清晰编排逻辑在orchestrator里能力实现在capabilities里具体工具在tools里。加新工具只需要在tools里加文件在tool_registry里注册。4.2 LLM适配器的实现LLM适配器的作用是屏蔽不同模型API的差异。我通常会定义一个基类然后为每个模型实现一个子类。class BaseLLMAdapter: def reason(self, messages, toolsNone, **kwargs): raise NotImplementedError class OpenAIAdapter(BaseLLMAdapter): def reason(self, messages, toolsNone, **kwargs): # 调用OpenAI API处理tools参数 response client.chat.completions.create( modelself.model, messagesmessages, toolstools, **kwargs ) return self._parse_response(response)这样编排层调用的时候只需要llm.reason(messages, tools)不用关心底层是哪个模型。换模型的时候只需要换适配器。4.3 Agent循环的实现Agent的核心是一个循环思考、行动、观察、再思考。我用伪代码说明def run_agent(user_input, max_steps10): messages build_initial_messages(user_input) for step in range(max_steps): response llm.reason(messages, toolstool_registry.get_schemas()) if response.has_tool_call(): tool_result execute_tool(response.tool_call) messages.append(tool_result) else: return response.content return 达到最大步数限制这个循环里关键点是max_steps的设置。设太小复杂任务完不成设太大可能陷入死循环。我的经验是设8到12之间同时加一个“连续两次调用同一工具且参数相同”的检测如果出现就强制退出。4.4 工具注册与MCP接入工具注册我用一个注册表模式每个工具是一个类实现execute方法同时提供schema描述。class ToolRegistry: def __init__(self): self.tools {} def register(self, name, tool): self.tools[name] tool def get_schemas(self): return [tool.schema for tool in self.tools.values()] def execute(self, name, params): return self.tools[name].execute(**params)MCP接入的话我会把MCP Client封装成一个特殊的工具它内部维护了所有通过MCP协议暴露的工具列表。当Agent调用这个工具时MCP Client负责转发到实际的外部工具。4.5 记忆的读写实现短期记忆就是消息列表这个不用多说。长期记忆我用向量数据库实现写入的时候把对话摘要embedding后存入读取的时候用当前输入做相似度检索。class LongTermMemory: def __init__(self, vector_store): self.store vector_store def write(self, content, metadata): embedding embed(content) self.store.upsert(embedding, content, metadata) def read(self, query, top_k3, threshold0.7): embedding embed(query) results self.store.search(embedding, top_k) return [r for r in results if r.score threshold]阈值设置很关键太低会召回不相关的内容干扰LLM太高会漏掉有用信息。我一般从0.7开始调根据实际效果微调。4.6 完整调用链的串联把上面这些串起来一次完整的对话流程是这样的用户输入到达接入层鉴权、限流后转发到编排层。编排层从长期记忆里检索相关历史和当前输入一起构建messages。Agent进入循环调用LLM做决策。如果需要调工具通过工具注册表执行结果追加到messages。循环直到LLM给出最终回复。回复返回给用户同时异步写入长期记忆和日志。这个流程里第2步的记忆检索和第6步的记忆写入是异步的不阻塞主流程。日志记录也是异步的避免影响响应延迟。5. 常见问题与排查技巧实录5.1 LLM调用失败从报错信息定位问题LLM调用失败是最常见的问题报错信息通常能给出线索。我整理了一个速查表报错关键词可能原因排查方向rate limit调用频率超限检查QPS加退避重试context length上下文超长检查messages长度做截断或摘要invalid schema工具schema格式错误检查JSON schema是否符合规范timeout网络或模型响应慢加超时设置考虑换模型content filter内容被过滤检查输入输出是否触发安全策略我踩过的一个坑是工具schema里用了LLM不支持的JSON schema特性比如oneOf嵌套太深导致LLM解析失败。后来我把schema简化成扁平结构就好了。5.2 Agent陷入死循环识别与打断Agent死循环的表现是反复调用同一个工具或者在不同工具之间来回跳转。识别方法是记录每一步的工具调用如果连续N步的工具调用模式重复就判定为死循环。打断策略我一般用两个一是设置最大步数二是检测重复模式。重复模式的检测可以用简单的哈希把工具名和参数拼起来做哈希如果连续三次哈希相同就退出。提示死循环很多时候是prompt的问题。检查一下工具描述是否清晰是否给了LLM足够的退出条件说明。5.3 工具调用参数错误schema设计与prompt双管齐下工具调用参数错误的表现是LLM传了错误的参数类型或缺少必填参数。解决方法有两个层面schema层面参数类型要明确枚举值要列全必填项要标注。我习惯在参数描述里加示例比如city: {type: string, description: 城市名称如北京}。prompt层面在系统提示里说明工具的使用场景和参数要求。比如“查询订单时order_id必须是纯数字”。5.4 记忆检索不准确阈值与embedding的双重调优记忆检索不准确通常有两个原因阈值设置不当或者embedding模型不适合你的领域。阈值的问题前面说过了embedding的问题需要换模型或者做微调。我的做法是先用通用embedding模型跑一批测试case看召回的相关性。如果相关性差考虑换一个在中文或特定领域上表现更好的模型。如果换模型还不行就需要考虑用领域数据做微调。5.5 响应延迟过高从链路各环节找瓶颈响应延迟高的时候我会按链路逐段排查接入层鉴权和限流是否耗时编排层记忆检索是否同步阻塞能力层LLM调用延迟多少工具调用延迟多少基础设施层向量检索延迟多少大部分情况下瓶颈在LLM调用。优化手段包括用更小的模型、减少上下文长度、开启流式输出让用户先看到部分结果。5.6 几个我踩过的坑和对应技巧坑一prompt里工具描述太长导致token浪费。我一开始把每个工具的完整文档都塞进prompt结果token消耗巨大。后来改成只放工具名和一句话描述详细说明放在工具执行时的参数校验里。坑二异步写入记忆导致数据不一致。有次用户刚说完偏好下一轮对话却没读到。原因是异步写入还没完成。后来改成写入完成后再返回响应或者用消息队列保证顺序。坑三日志记录太多导致存储成本高。全量记录LLM输入输出很占空间。后来我改成只记录摘要和关键字段完整内容按需采样。坑四工具版本升级导致schema不兼容。工具升级后参数变了但Agent还在用旧schema。后来我加了版本号schema变更时同步更新注册表。这些经验都是实际项目中积累的文档里通常不会写但真正做起来的时候会反复遇到。希望对你搭建自己的AI应用有帮助。