ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI应用架构设计实战:从分层到Agent与MCP的完整指南

AI应用架构设计实战:从分层到Agent与MCP的完整指南 1. 从一张架构图说起AI应用到底该怎么搭第一次接触“AI应用架构设计”这个词很多人脑子里浮现的是一堆框和箭头用户输入、大模型、向量库、工具调用、输出。画出来挺好看但真到动手搭一个能跑、能维护、能扩展的AI应用时才发现图上的每个框背后都有一堆需要拍板的决策。我过去两年参与过几个从零到一的AI应用项目也帮朋友救过几个“Demo很惊艳、上线就崩溃”的烂摊子踩过的坑足够写一本小册子。这篇文章就把我理解的AI应用架构设计拆开揉碎讲清楚从整体分层到核心组件从Agent设计到MCP协议再到实际落地时的参数选择和排查技巧尽量做到看完就能照着搭。先明确一下范围。这里说的AI应用指的是以LLM大语言模型为核心推理引擎结合外部工具、数据源、记忆机制完成特定任务的软件系统。它可以是简单的问答机器人也可以是复杂的多Agent协作系统。架构设计的核心目标就三个让模型输出可控、让系统运行可靠、让后续迭代可扩展。这三个目标听起来像废话但实际做的时候大部分问题都出在只顾着让Demo跑通忽略了这三件事。适合谁看如果你是有一定开发经验、准备把AI能力集成到产品里的工程师这篇文章能帮你少走弯路。如果你是产品经理或技术负责人想理解AI应用的技术边界和成本结构也能从中拿到可用的判断依据。如果你刚入门建议先把前两章读透后面的实操细节可以边做边查。2. 整体架构分层把复杂系统切成四块2.1 为什么分层是架构设计的第一步任何复杂系统如果不分层最后都会变成一团乱麻。AI应用尤其如此因为它同时涉及传统软件工程、机器学习推理、数据管理和人机交互。我见过最离谱的一个项目把Prompt拼接、API调用、数据库查询、前端渲染全写在一个Python文件里两千多行改一个标点都要重新部署整个服务。这种代码活不过三个月。分层的本质是控制变更的影响范围。当你想换一个模型供应商时只改模型接入层当你想调整Prompt策略时只改编排层当你想加一个新工具时只改工具层。每一层通过明确的接口通信层与层之间不关心对方的内部实现。这样做的好处是你可以在不影响其他部分的情况下独立测试、独立部署、独立扩展每一层。2.2 四层架构的具体划分我习惯把AI应用分成四层从下往上依次是基础设施层包括计算资源、存储资源、网络资源。这一层决定了你的应用能跑多快、能扛多少并发、能存多少数据。对于大多数中小型AI应用起步阶段用云服务商的托管方案就够了没必要一上来就自建GPU集群。我见过一个团队产品还没验证就买了八张A100结果模型调用量每天不到一千次资源利用率不到百分之五纯属浪费。模型接入层负责与LLM交互包括API调用、流式输出处理、重试机制、限流控制、成本统计。这一层的核心设计原则是抽象隔离。你不能让业务代码直接调用某家厂商的SDK否则换模型时就要改遍全项目。正确的做法是定义一个统一的模型接口所有厂商的适配器都实现这个接口业务层只依赖接口。编排层这是AI应用的大脑负责决定什么时候调用模型、调用哪个模型、传什么参数、如何处理返回结果。Agent逻辑、Prompt模板管理、工具调用决策、多轮对话状态管理都属于这一层。编排层的设计质量直接决定了应用的智能程度和可靠性。应用层面向最终用户的功能实现包括界面渲染、用户输入处理、结果展示、权限控制等。这一层与传统Web应用差别不大但需要特别处理流式输出的展示和长时间任务的进度反馈。2.3 层与层之间的通信约定分层之后层与层之间怎么通信是个关键问题。我的经验是同步调用用于实时交互异步消息用于耗时任务。比如用户问一个问题编排层需要调用模型生成回答这是同步的因为用户在等。但如果用户上传了一个文档需要向量化入库这就是异步的应该丢到消息队列里慢慢处理处理完了再通知用户。另外每一层对外暴露的接口都要有明确的输入输出定义。我推荐用Pydantic这类数据校验库来定义接口契约这样可以在开发阶段就发现类型不匹配的问题而不是等到运行时才报错。接口定义还有一个好处是它强迫你思考每个组件的职责边界避免出现“这个函数既查数据库又调模型还发邮件”的混乱情况。3. 核心组件拆解LLM、Agent、MCP各管什么3.1 LLM接入别把模型当黑盒LLM是整个应用的核心推理引擎但很多开发者对它的理解停留在“输入文字、输出文字”的层面。实际上要做好LLM接入你需要关注至少五个参数温度、最大输出长度、Top-P、频率惩罚、存在惩罚。这些参数不是随便设的它们直接影响输出的质量和稳定性。温度控制随机性。温度越低输出越确定温度越高输出越多样。对于需要精确回答的场景比如客服问答、代码生成温度建议设在0.1到0.3之间。对于创意写作、头脑风暴可以调到0.7到1.0。我见过有人把温度设成1.5结果模型开始胡言乱语还以为是模型能力不行。最大输出长度决定了模型一次能生成多少内容。这个参数需要根据你的应用场景来定。如果是短问答设512就够了如果是长文生成至少要设4096。但要注意输出长度越长延迟越高成本也越高。我一般会先估算典型场景下的输出长度然后留出百分之三十的余量。Top-P是另一种控制随机性的方式它从概率最高的词开始累加直到累积概率超过P值。通常和温度配合使用温度调高时Top-P可以适当降低避免输出过于发散。频率惩罚和存在惩罚用来控制重复内容。频率惩罚根据词出现的次数来惩罚存在惩罚只要词出现过就惩罚。对于需要避免重复的场景比如列表生成可以适当调高这两个参数。注意不同厂商的模型对参数的支持程度不同有些模型不支持频率惩罚和存在惩罚。在切换模型时一定要先确认参数兼容性否则可能出现参数被静默忽略的情况。3.2 Agent设计从“能回答”到“能做事”Agent是这两年最热的概念之一但很多人对它的理解有偏差。Agent不是简单的“LLM加几个工具”而是一个具备自主决策能力的执行体。它需要能够理解任务目标、拆解任务步骤、选择合适的工具、执行操作、评估结果、必要时调整策略。一个典型的Agent循环包括四个阶段感知、规划、执行、反思。感知阶段接收用户输入和环境状态规划阶段决定下一步做什么执行阶段调用工具或模型完成任务反思阶段评估执行结果决定是继续还是结束。Agent架构设计中最容易出问题的地方是循环控制。如果没有设置合理的终止条件Agent可能陷入无限循环不断调用工具却永远不给出最终答案。我的做法是设置三层保护最大迭代次数、最大Token消耗、最大执行时间。任何一层触发就强制终止并返回当前最好的结果。另一个关键点是工具设计。工具是Agent与外部世界交互的接口工具的质量直接影响Agent的能力上限。好的工具应该具备三个特征功能单一、参数明确、错误信息友好。功能单一意味着一个工具只做一件事不要设计“万能工具”参数明确意味着每个参数的类型和含义都要清晰定义错误信息友好意味着当工具执行失败时返回的错误信息要能帮助Agent理解问题并调整策略。3.3 MCP协议工具调用的标准化尝试MCPModel Context Protocol是最近讨论度很高的一个话题。简单来说它试图定义一套标准协议让不同的AI应用能够以统一的方式发现、调用、管理外部工具和数据源。你可以把它理解为“AI应用的工具接口标准”。在没有MCP之前每个AI应用都要自己定义工具的描述格式和调用方式。这导致两个问题一是工具提供方需要为每个AI应用单独适配成本很高二是AI应用切换工具时需要重写大量代码。MCP的出现就是为了解决这两个问题。MCP的核心概念包括服务器、客户端、资源、工具、提示模板。服务器暴露能力和资源客户端连接服务器并调用这些能力。资源是只读的数据工具是可执行的操作提示模板是可复用的Prompt片段。实际使用MCP时有几个坑需要注意。首先是授权问题很多MCP服务器需要配置访问凭证如果配置不正确客户端会连接失败但错误信息不明确。其次是版本兼容性MCP协议还在演进中不同版本之间可能存在不兼容的变更。最后是性能开销MCP的标准化是以一定的性能损耗为代价的对于延迟敏感的场景需要评估是否值得。提示如果你只是想快速验证一个想法不必一上来就上MCP。先用简单的函数调用把流程跑通等工具体系稳定了再考虑标准化。4. 实操过程从零搭一个可用的AI应用骨架4.1 环境准备与依赖选择动手之前先把环境理清楚。我推荐的技术栈是Python 3.11以上、FastAPI做Web框架、Pydantic做数据校验、Redis做缓存和会话存储、PostgreSQL做持久化。模型接入方面如果追求灵活性可以用LiteLLM这类统一接口库如果追求性能可以直接用厂商SDK。依赖管理用Poetry或PDM不要用pip加requirements.txt后者在依赖冲突时很难排查。虚拟环境用venv或conda都行关键是每个项目独立环境避免版本污染。# 用Poetry初始化项目 poetry init poetry add fastapi uvicorn pydantic redis sqlalchemy litellm poetry add --group dev pytest httpx ruff mypy配置文件用环境变量加YAML的方式。敏感信息如API密钥放环境变量其他配置放YAML。我习惯在项目根目录建一个config目录里面放default.yaml和development.yaml、production.yaml通过环境变量切换。4.2 模型接入层的实现模型接入层的核心是定义一个抽象基类然后为每个模型厂商实现一个适配器。抽象基类定义统一的方法签名适配器负责把统一调用转换成厂商特定的调用。from abc import ABC, abstractmethod from typing import AsyncIterator class BaseLLMProvider(ABC): abstractmethod async def generate(self, messages: list[dict], **kwargs) - str: pass abstractmethod async def stream_generate(self, messages: list[dict], **kwargs) - AsyncIterator[str]: pass abstractmethod def count_tokens(self, text: str) - int: pass实现适配器时要特别注意错误处理。模型调用可能因为网络问题、限流、余额不足等原因失败。我的做法是定义一个统一的异常体系把不同厂商的错误码映射到统一的异常类型这样上层代码可以用一致的方式处理错误。重试策略也很重要。对于限流错误应该用指数退避重试对于参数错误重试没有意义应该直接抛出对于网络超时可以重试但要有次数上限。我一般设置最多重试三次第一次等1秒第二次等2秒第三次等4秒。4.3 编排层的Agent循环实现编排层是逻辑最复杂的部分。我以一个简单的ReAct风格Agent为例说明核心循环的实现。async def agent_loop(task: str, tools: list, max_iterations: int 10): messages [{role: system, content: SYSTEM_PROMPT}, {role: user, content: task}] for i in range(max_iterations): response await llm.generate(messages) if is_final_answer(response): return extract_answer(response) tool_call parse_tool_call(response) if tool_call: result await execute_tool(tool_call, tools) messages.append({role: assistant, content: response}) messages.append({role: tool, content: result}) else: messages.append({role: assistant, content: response}) messages.append({role: user, content: 请继续或给出最终答案。}) return 达到最大迭代次数未能完成任务。这个循环看起来简单但实际实现时有很多细节要处理。比如工具调用的解析模型可能返回格式不正确的JSON需要做容错处理。再比如上下文长度管理随着对话轮次增加消息列表会越来越长最终超出模型的上下文窗口。我的做法是设置一个Token阈值超过阈值时对历史消息做摘要压缩。4.4 工具层的设计与注册工具层的设计目标是让添加新工具变得简单。我采用装饰器加注册表的方式。TOOL_REGISTRY {} def tool(name: str, description: str, parameters: dict): def decorator(func): TOOL_REGISTRY[name] { function: func, description: description, parameters: parameters } return func return decorator tool( namesearch_database, description根据关键词搜索数据库中的记录, parameters{ type: object, properties: { keyword: {type: string, description: 搜索关键词}, limit: {type: integer, description: 返回结果数量上限} }, required: [keyword] } ) async def search_database(keyword: str, limit: int 10): # 实际搜索逻辑 return results这种设计的好处是工具的定义和使用在同一个地方添加新工具只需要写一个函数加一个装饰器。工具的描述和参数定义会自动转换成模型能理解的格式。4.5 会话管理与状态持久化多轮对话场景下会话管理是必须的。我一般用Redis存储会话状态key是会话IDvalue是消息列表和元数据。设置合理的过期时间比如24小时避免无用数据堆积。async def save_session(session_id: str, messages: list, ttl: int 86400): await redis.setex( fsession:{session_id}, ttl, json.dumps(messages, ensure_asciiFalse) ) async def load_session(session_id: str) - list: data await redis.get(fsession:{session_id}) return json.loads(data) if data else []对于需要长期保存的会话比如用户主动保存的对话再落库到PostgreSQL。Redis和数据库之间做好同步策略避免数据不一致。5. 常见问题与排查技巧实录5.1 模型输出格式不稳定怎么办这是最常见的问题。模型有时候返回JSON有时候返回Markdown有时候在JSON外面包一层解释文字。解决方法有三层第一层是在Prompt中明确要求输出格式并给出示例第二层是在解析时做容错比如用正则提取JSON部分第三层是如果解析失败把错误信息返回给模型让它重新生成。我实测下来最有效的是给出具体的输出示例。不要只说“请返回JSON格式”而是给出一个完整的示例包括字段名和值的类型。这样模型的理解准确率会高很多。5.2 工具调用失败如何优雅处理工具调用失败的原因很多参数错误、网络超时、权限不足、资源不存在。处理原则是能自动恢复的自动恢复不能自动恢复的返回明确错误信息给模型。参数错误通常可以通过让模型重新生成参数来解决。网络超时可以重试。权限不足和资源不存在属于不可恢复错误应该把错误信息返回给模型让模型决定是换一个工具还是告知用户。注意不要把原始的系统错误信息直接返回给模型比如堆栈跟踪。这些信息对模型没有帮助还可能泄露敏感信息。应该转换成人类可读的错误描述。5.3 上下文超长怎么处理上下文超长是长对话场景的必然问题。处理方法有几种滑动窗口、摘要压缩、关键信息提取。滑动窗口最简单只保留最近N轮对话但会丢失早期信息。摘要压缩用模型对早期对话做摘要保留关键信息但会增加一次模型调用。关键信息提取是从对话中抽取实体和意图用结构化数据代替原始文本。我的做法是组合使用最近三轮对话保留原文更早的对话做摘要同时维护一个关键信息表记录用户提到的实体和偏好。这样既控制了上下文长度又保留了必要信息。5.4 成本失控怎么排查AI应用的成本主要来自模型调用。成本失控通常有三个原因调用次数过多、单次Token量过大、使用了过于昂贵的模型。排查方法先加日志记录每次调用的模型、输入Token数、输出Token数、耗时。然后分析日志找出调用量最大的场景和Token消耗最多的环节。常见优化手段包括缓存重复问题的答案、用小模型做意图分类再路由到大模型、压缩Prompt长度、设置单次调用的Token上限。我见过一个项目每次用户提问都会把整个知识库塞进Prompt导致单次调用消耗几万Token。后来改成先用向量检索找出相关片段再塞进PromptToken消耗降了百分之九十效果反而更好。5.5 常见问题速查表问题现象可能原因排查方向解决方案模型返回空内容触发了内容过滤检查输入是否包含敏感词调整输入或更换模型工具调用参数错误工具描述不清晰检查工具的参数定义补充参数示例和类型说明响应延迟突然增大模型服务限流查看调用日志和错误码增加重试和降级策略多轮对话丢失上下文会话存储过期检查Redis的TTL设置调整过期时间或改用持久化存储Agent陷入死循环终止条件缺失检查循环控制逻辑增加最大迭代次数和时间限制流式输出中断网络不稳定检查客户端和服务端日志增加断线重连和续传机制6. 架构演进从单体到多Agent协作6.1 什么时候需要多Agent单Agent能解决大部分问题但遇到复杂任务时单Agent的局限性就暴露了。比如一个任务需要同时进行信息检索、数据分析、报告撰写单Agent很难在一个循环里高效完成所有步骤。这时候就需要多Agent协作。多Agent的核心思想是分工与协作。每个Agent负责一个子任务通过消息传递协调工作。常见的协作模式有主从模式、流水线模式、辩论模式。主从模式是一个主Agent负责任务拆解和结果汇总从Agent负责执行具体子任务。流水线模式是多个Agent按顺序处理前一个的输出是后一个的输入。辩论模式是多个Agent对同一问题给出不同答案然后通过讨论达成一致。6.2 多Agent通信的坑多Agent系统最大的坑是通信开销。Agent之间传递消息需要序列化和反序列化如果消息格式设计不当会导致大量Token浪费在格式转换上。我的经验是Agent之间传递结构化数据而不是自然语言。比如传递一个JSON对象而不是一段描述。另一个坑是状态同步。多个Agent可能同时修改共享状态导致数据不一致。解决方案是引入一个协调者所有状态修改都通过协调者进行或者使用乐观锁机制。6.3 从架构图到可运行系统的检查清单最后给一份检查清单帮你确认架构设计是否完整模型接入层是否有统一的抽象接口切换模型是否需要改业务代码编排层是否有明确的终止条件是否设置了最大迭代次数和Token上限工具层是否有统一的注册机制添加新工具是否需要改动核心代码会话管理是否有过期策略是否支持持久化是否有完整的日志记录能否追踪每次模型调用的输入输出和耗时是否有成本监控能否按场景统计Token消耗是否有降级策略模型服务不可用时能否返回兜底结果是否有安全过滤是否对用户输入和模型输出做了内容检查这份清单看起来简单但每一条背后都是实际踩过的坑。我建议在项目启动阶段就把这些检查项过一遍而不是等到上线后出了问题再补。我个人在实际操作中的体会是AI应用架构设计最难的从来不是技术选型而是对不确定性的管理。模型输出不确定、工具执行不确定、用户输入不确定架构设计的核心就是把这些不确定性控制在可接受的范围内。好的架构不是让系统永远不出错而是出错时能快速定位、快速恢复、不影响整体可用性。这个思路想清楚了具体用什么框架、什么模型反而没那么重要了。
返回列表