ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

LLM、RAG、MCP、Agent:AI应用开发四层架构与实战落地

LLM、RAG、MCP、Agent:AI应用开发四层架构与实战落地 简介围绕LLM、MCP、RAG与Agent四者关系解析的项目代码包是一份适合AI初学者、技术编辑及软件开发者使用的轻量可视化资料用于厘清大模型应用系统中的组件边界与协作方式直观理解各层能力的定位。压缩包共3个文件以HTML可视化页面为核心清晰展示四者层级结构图、功能对比表与IDE Agent应用流程并配有inscode运行配置和gitignore过滤规则可在本地或在线开发环境中直接打开浏览。整套资源仅6KB体量小巧、内容聚焦重点呈现LLM作为推理引擎、RAG负责知识增强、MCP提供工具接口、Agent统筹规划的关键逻辑目前已有89人学习下载。页面结合IDE Agent案例演示了从用户请求、MCP工具调用、RAG知识检索到任务完成的完整协作流程帮助读者理解多种AI技术如何在实际系统中组合落地。代码包结构简洁、可读性强既可作为技术笔记也可用于教学分享、技术写作或二次开发参考。 如果你最近在搞 AI 应用开发大概率绕不开这四个词LLM、RAG、MCP、Agent。我自己项目里最开始只是接了一个大模型 API跑了个简单问答后来业务需求越来越复杂——要回答私有知识库的问题要调用内部系统查工单还要能自动执行多步操作。技术选型的时候差点被这四个概念绕晕网上的文章各说各的有的只讲 RAG有的只讲 Agent很少有文章把四个东西放在同一条链路里讲清楚。这篇博文就基于我最近在项目里落地的一套代码骨架把 LLM、RAG、MCP、Agent 到底谁管什么、怎么协作、在项目结构里怎么组织一次说清楚。内容不搞纯理论尽量贴实际代码适合正在做 AI 应用开发、被各种名词轰炸到头痛的开发者参考。1. 先对齐概念四个词分别解决什么问题1.1 LLM它是大脑但只有大脑还不够LLM大语言模型是整个 AI 应用的核心引擎。你给它一段输入它根据海量训练数据中学到的模式生成回答。但只用 LLM 做产品很快会遇到三个绕不开的短板第一知识有时效性。模型的训练数据截止在某个时间点你问它上周新发布的某个功能它只会一本正经地编。第二它不知道你的私有数据。公司内部的制度文档、项目复盘、线上事故记录模型一概不知。第三它只能说不能做。它可以告诉你应该发一封邮件给负责人但它自己不会发。所以从工程视角看LLM 只是一个推理内核你得给它配记忆、配工具、配调度才能变成一个能干活的产品。这正好引出后面的三个词。1.2 RAG给模型接上可更新的外部记忆RAGRetrieval-Augmented Generation检索增强生成解决的是模型不知道但业务需要的问题。它的思路很直白不逼模型记住所有知识而是每次回答前先从外部知识库检索出相关文档片段把这些片段拼进提示词里再让模型基于这些片段回答。整个链路是文档加载 → 文本切分 → 向量化Embedding→ 存入向量库 → 用户提问时做相似度检索 → 把检索结果注入 Prompt → 模型生成。我在项目里用的方案是文档切块后用 Embedding 模型转成向量存进本地向量库查询时取出 Top-K 相关片段。这个方案最大的好处是知识可随时更新文档改了重新跑一遍索引就行不用重新训练模型。这也是 RAG 目前在企业知识库场景里几乎是标配的原因。1.3 MCP把工具接入变成USB-C标准MCPModel Context Protocol是最近讨论度很高的一个协议。用大白话说它解决的是模型怎么统一调用外部工具的标准化问题。在没有 MCP 之前接一个工具就要写一套胶水代码。比如接订单系统要写一套函数调用接飞书机器人又要写另一套每个工具的参数格式、鉴权方式、返回结构都不一样项目里的集成代码越堆越多。MCP 做的事情是定义了一套统一协议每个工具通过 MCP Server 对外暴露应用侧通过 MCP Client 去发现、调用这些工具。工具的能力描述、入参出参、错误信息全部标准化。你可以把它理解为AI 世界的 USB-C 接口——过去每个设备都有自己的充电线现在统一成一个标准只要两端都支持协议插上就能用。项目里常见的 MCP Server 有查数据库的、操作浏览器的、读文件的、连设计工具的比如蓝湖、Figma、跑 Playwright 的这些在社区里都有现成实现。1.4 Agent从回答问题升级到完成任务Agent 是这四个概念里最容易被神化、也最容易被误解的一个。它不是某个具体的库而是一种循环决策的运行机制模型在每轮迭代里先观察当前状态再思考下一步该干什么然后调用工具执行动作观察结果再进入下一轮思考直到任务完成。经典的 Agent 循环是 ReAct 模式Reason Act。一个简化的循环大概是系统拿到用户目标 → 让 LLM 决策要不要检索知识库 / 要不要调用某个 MCP 工具 → 执行动作 → 把结果返回给 LLM → LLM 判断任务是否结束。如果没结束继续下一轮。所以 Agent 更像一个调度中枢它决定什么时候用 RAG、什么时候调 MCP、什么时候直接回答。它不是取代 RAG 或 MCP而是站在它们上面做编排。2. 四者关系拆解一张链路看清协作方式2.1 关系总览从个体到系统先给一张总览表把四个概念的核心定位放在一起对比概念核心定位解决什么问题典型产出依赖关系LLM推理内核理解语言、生成内容、做决策回答、计划、代码无RAG外部知识私有数据、时效知识、降低幻觉检索片段、增强后的回答依赖 LLM 做生成MCP工具总线统一接入外部系统和工具标准化的工具调用能力依赖 LLM 做工具选择Agent任务调度多步任务、动态决策、自动执行完整任务闭环依赖 LLM RAG MCP从这张表能看出来四者并不是并列关系而是分层关系。拿一个真实场景举例用户问帮我查一下上季度各区域的销量并生成一份摘要发到工作群。Agent 收到任务后先让 LLM 拆解计划LLM 判断需要查数据库于是通过 MCP 调用数据库查询工具查出来的原始数据可能很杂Agent 再决定是否从 RAG 知识库检索公司对季度销量摘要的格式要求最后 LLM 生成摘要Agent 再调 MCP 里的消息推送工具发送。整个流程里每个概念各管一段缺一不可。2.2 Agentic RAG不是 RAG 的替代而是 RAG 的升级热词里有 Agentic RAG 和 RAG 框架顺便说下两者的关系。传统 RAG 是固定流程用户提问系统检索一次注入生成结束。不管问题简单还是复杂检索次数、检索策略都是写死的。Agentic RAG 则是把检索这件事交给 Agent 决策如果问题简单模型直接回答不检索如果问题复杂Agent 决定拆成多个子问题分别检索如果第一次检索结果不够Agent 可以改写关键词再检一次。这种模式在多跳问答场景里特别明显。比如跟我们有合作关系的供应商里哪些在 Q3 交付过不良品这个问题需要先查出合作供应商清单再拿清单到质量记录里去查传统 RAG 一次检索根本搞不定Agent 循环天然适合这种场景。所以不要把 Agent 和 RAG 对立起来。RAG 负责提供相关资料Agent 负责判断什么时候要资料、要几次、怎么组合。两者是配合关系。2.3 边界判断什么场景该上什么技术实际做项目选型时我一般按这个逻辑判断如果需求是基于一批固定文档回答问题直接上 RAG不需要 Agent。比如企业制度问答助手文档就那些问题模式也固定。如果需求是调用外部系统完成一次性操作比如帮我把这个 Bug 的截图发给产品经理直接接 MCP 工具或者简单的函数调用就行不需要完整 Agent 循环。如果需求是需要根据用户意图动态决定查询哪些数据、调哪些工具、多步操作这时候才需要 Agent 编排。比如智能工单助手用户说查一下 XX 服务器的告警顺便看看有没有对应的处理手册然后生成一条处置建议这里面有数据库查询、知识库检索、文本生成三步而且步骤依赖前一步的结果Agent 是合适的载体。一句话总结边界能用固定流程解决的别硬上 Agent只用模型知识就够的别硬接 RAG没有外部工具需求的别引 MCP。每多一层技术就多一层维护成本。3. 项目代码实战一个可运行的最小骨架3.1 项目结构与技术选型先说明下面这个骨架是我从生产项目里抽出来的教学级最小版本目的是展示四者的接线方式不是完整的工业级实现。生产环境还需要补异步、重试、可观测、权限控制等东西。项目结构按功能分层框架层和应用层分开llm-rag-mcp-agent-demo/ ├── app/ │ ├── agent/ │ │ ├── loop.py # Agent 主循环 │ │ └── prompt.py # Agent 提示词模板 │ ├── rag/ │ │ ├── loader.py # 文档加载 │ │ ├── splitter.py # 文本切分 │ │ ├── embedder.py # Embedding 封装 │ │ └── retriever.py # 检索器 │ ├── mcp_client/ │ │ └── client.py # MCP 客户端封装 │ ├── tools/ │ │ └── registry.py # 工具注册表 │ └── llm/ │ └── chat.py # LLM 调用封装 ├── data/ │ └── docs/ # 知识库原始文档 ├── index.py # 建立向量索引 └── main.py # 入口演示完整链路选型上我用的都是相对通用的组件LLM 部分封装成OpenAI 兼容接口这样既能接云端模型也能切换本地 vLLM 或 Ollama 起的服务向量库用的是 Chroma本地跑起来快不需要额外起服务Embedding 用的是 text-embedding-3-small 级别的模型。之所以这样选是希望读者在本地最快跑通不用被复杂部署卡住。3.2 核心代码RAG 检索模块先看 RAG 的检索部分。这部分代码负责把用户问题转成向量 → 从库里找最相关的片段# app/rag/retriever.py from typing import List from chromadb import Collection def retrieve(collection: Collection, query: str, top_k: int 5) - List[str]: # 把用户问题向量化与库里的文档向量做相似度比较 # 返回 top_k 个相关文本片段 results collection.query( query_texts[query], n_resultstop_k, include[documents, distances] ) docs results[documents][0] distances results[distances][0] # 按距离排序距离越小越相关 ranked sorted(zip(docs, distances), keylambda x: x[1]) return [doc for doc, _ in ranked]这里有一个容易被忽略的点query_texts传入的是原始文本Chroma 内部会调用你配置的 Embedding 函数做向量化。所以建索引和查询时Embedding 模型必须保持一致否则检索结果会非常离谱。我在项目里踩过这个坑——本地调试时换了一个 Embedding 模型忘了重新建索引检索出来的相关度惨不忍睹。文档切分splitter也很关键。我常用的策略是固定长度切块每个块 500 到 800 个字符带 50 到 100 字符的重叠。重叠的目的是避免一句话正好被切成两半导致语义断裂。如果你处理的文档有清晰的章节结构优先按标题层级切效果比纯按字数切好很多。# app/rag/splitter.py from langchain_text_splitters import RecursiveCharacterTextSplitter # 按分隔符优先级递归切分先按段落、再按句子、最后按固定长度 splitter RecursiveCharacterTextSplitter( chunk_size600, chunk_overlap80, separators[\n\n, \n, 。, , , ., ] )RecursiveCharacterTextSplitter的想法是优先在语义边界处切实在找不到边界再用长度兜底。相比纯固定长度切分这个策略能让检索到的片段更完整。3.3 核心代码MCP 客户端封装MCP 客户端的核心是发现工具 → 调用工具两个动作。用官方 Python SDK 的示意代码# app/mcp_client/client.py import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client class McpClient: def __init__(self, server_command: str, args: List[str]): self.server_params StdioServerParameters(commandserver_command, argsargs) async def list_tools(self) - list: # 通过 stdio 启动 MCP Server并列出它提供的工具 async with stdio_client(self.server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result await session.list_tools() return [{name: t.name, description: t.description} for t in result.tools] async def call_tool(self, tool_name: str, args: dict): async with stdio_client(self.server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result await session.call_tool(tool_name, argumentsargs) return result.content实际项目里MCP Server 有两种形态一种是本地进程通过 stdio 通信上面的代码就是这种另一种是远程服务器走 HTTP 或 SSE 通信。本地进程适合数据库查询、文件操作这类工具远程服务适合团队共享的工具网关。初期建议先跑通本地 stdio 模式把协议逻辑搞清楚再考虑远程部署。3.4 核心代码Agent 主循环Agent 主循环是整个骨架里最重点的部分它决定调用哪个工具、何时结束任务。我写了一个极简的 ReAct 循环# app/agent/loop.py import json from app.llm.chat import llm_call from app.tools.registry import execute_tool SYSTEM_PROMPT 你是一个任务调度助手。你有两个能力 1. 调用工具执行动作工具的调用格式是 JSON{tool: 工具名, args: {...}} 2. 当任务完成时直接输出最终回复。 规则 - 如果任务需要外部知识先调用 rag_retrieval 工具检索知识库 - 如果任务需要查系统状态或执行动作调用对应的 MCP 工具 - 一次只做一步等待工具返回后再决定下一步 def run_agent(user_query: str, max_iterations: int 8): messages [{role: system, content: SYSTEM_PROMPT}, {role: user, content: user_query}] for step in range(max_iterations): response llm_call(messages) text response[content] if response.get(is_final): return text # 解析模型输出的工具调用指令 try: action json.loads(text) tool_name action[tool] tool_result execute_tool(tool_name, action.get(args, {})) except Exception as e: tool_result f工具调用失败{str(e)} # 把工具结果追加回对话上下文进入下一轮 messages.append({role: assistant, content: text}) messages.append({role: tool, content: str(tool_result)}) return 已超过最大迭代次数任务终止注意这段代码有个故意简化的地方模型返回的结构不是严格的 JSON有的模型喜欢在文本里夹杂解释。生产级实现里要加结构化输出约束或者让模型先把 JSON 提取出来。不过思路是清楚的Agent 的本质就是在循环里反复调用 LLM把工具结果不断喂回去直到模型认为任务完成。max_iterations这个参数一定要设置。没有它遇到复杂任务模型可能陷入无限循环既浪费 token 又把接口打爆。我一般根据任务复杂程度设 5 到 10 次。3.5 数据流演示一次完整问答走四层用入口脚本串起整个链路。假设用户的问题是查一下 XX 项目的上线状态如果还在测试中就查一下知识库里的验收标准整理成一条待办提醒。# main.py from app.agent.loop import run_agent if __name__ __main__: result run_agent(查一下 XX 项目的上线状态如果在测试中请检索知识库中的验收标准整理成待办提醒。) print(result)执行时内部的真实走向是第 1 轮Agent 收到问题LLM 判断需要查项目状态于是调用 MCP 里的query_project_status工具。工具返回状态测试中。第 2 轮Agent 看到状态是测试中按计划调用rag_retrieval工具检索知识库里XX 项目验收标准相关的文档片段。第 3 轮模型拿到检索片段后结合测试中状态生成一条待办提醒作为最终回答返回。这个例子里 RAG 和 MCP 都被 Agent 调度到了实际生产场景会更复杂比如查询结果为空时要不要换个姿势再查一次多个工具结果冲突时听谁的这些都要在 Agent 的脚本和提示词里写清楚。4. 实操中的坑与排查心得4.1 工具注册不上、调用超时怎么查热词里有 Figma MCP 在 Codex 中总是工具注册不上这个我太有体会了。MCP 工具注册失败绝大多数情况不是协议本身的问题而是环境问题。我遇到过的原因按出现频率排序原因典型表现排查方式MCP Server 没启动连接超时client 日志无响应先手动启动 server确认进程存活鉴权未通过返回 401/403或工具列表为空检查 OAuth token 是否过期环境变量是否正确注入环境变量缺失server 报错找不到 API Key确认 server 进程是否读取到了配置工具名称对不上模型生成了错误工具名调list_tools打印真实工具名比对大小写超时时间太短工具实际能跑但 client 提前放弃调长 MCP 调用的超时阈值比如从 30s 调到 120s排查顺序有讲究先确认 server 端能不能独立工作再去看 client 端日志最后才怀疑模型生成参数的问题。不要一上来就改模型提示词那样查不到根因。4.2 RAG 检索质量上不去指标怎么看很多人把 RAG 想象成文档丢进去就能回答实际上跑起来发现效果很差。顺着热词RAG 知识库指标有哪些如何理解各指标说一下。衡量 RAG 效果我重点看四个指标指标含义我的判断标准召回率RecallK正确答案是否出现在 Top-K 结果里至少要达到 80% 以上否则检索策略有问题MRR平均倒数排名正确答案排在第几越靠前越好0.7 以上算及格忠实度Faithfulness模型回答是否严格基于检索片段回答中不能有片段里没有的信息答案相关性Answer Relevancy回答是否切题靠人工评测或 LLM 打分如果召回率低大概率是切分策略或 Embedding 模型的问题如果召回率正常但忠实度低问题可能在提示词——模型把检索片段当参考而不是唯一依据。后者的修法是在 Prompt 里明确写只根据提供的片段回答不要使用内部知识片段里没有就回答不知道。另外一个高频问题检索到了错误的上下文。这种情况比检索不到更隐蔽因为模型很可能基于错误片段一本正经地胡说。我的排查方法是把每次检索的片段和距离分数都打出来看如果相关片段排得太靠后就考虑调整top_k或者上重排模型Reranker做二次过滤。4.3 Agent 循环失控与安全边界热词里有一条 the agent execution provider did not respond in time以及 Agent 超时相关的问题。Agent 循环常见的失控场景有三种陷入无限循环、工具反复调用失败、模型擅自执行危险操作。我的应对经验第一在系统提示词里明确工具调用失败后最多重试一次如果仍然失败就放弃该步骤并告诉用户。让模型自己决定何时止损比在外面写死重试逻辑更灵活。第二给工具做权限分级。MCP 工具接入的时候不要一股脑全暴露给 Agent。比如删除数据库记录发送消息这类高影响操作我一般会加一个confirm参数Agent 必须要求用户确认后才真正执行。工具最小权限原则在 Agent 场景里尤其重要因为模型自主调用工具时你很难预料它每一步的动作。第三做好审计日志。每次 Agent 循环的决策、工具调用参数、返回值都记录下来。线上出问题的时候没有日志你根本不知道模型为什么要调那个工具。我在项目里把每一轮 LLM 输出和工具结果都写到 JSONL 文件里排查效率提升非常明显。4.4 常见问题速查表再整理一份通用速查表方便直接照着查问题现象可能原因解决建议RAG 回答经常编造Prompt 没约束只基于片段回答在 Prompt 中明确知识边界RAG 检索结果相关度差Embedding 模型不一致或切块过大统一模型调整 chunk 大小增加重叠MCP 工具列表为空服务未启动 / 鉴权失败手动测试 server检查 tokenAgent 任务执行到一半停止超时或迭代次数耗尽调大超时时间和 max_iterationsAgent 调错工具工具描述不清楚优化 MCP 工具的描述让模型容易理解调用 LLM 接口频繁 429并发过高或限额不足加限流重试升级配额5. 最后聊聊我的实际体会把这个项目从零搭起来之后我对这四个概念最深的感受是它们不是四个可以任选其一的方案而是四个互相咬合的层次。LLM 是能力基础RAG 是知识扩展MCP 是工具通道Agent 是任务调度。你可以在只有 LLM 的层面做出简单应用但想做出真正能干活的产品后面三层迟早要补上。如果非要说一个学习顺序我建议新手先别急着搞 Agent。先把 RAG 做好把检索→生成这条链路调通再接入一两个 MCP 工具感受一下工具调用最后再看 Agent 循环。因为 Agent 的调试复杂度是几何级上升的它把前面所有环节的不确定性叠加在了一起。在 RAG 和 MCP 没调稳之前上 Agent你根本分不清回答出错是检索的问题、工具的问题还是调度的问题。这几年 AI 应用开发最大的变化就是从写好一个 Prompt 调模型变成了搭一条能自主决策和行动的管道。别被概念本身吓住拆开看每一层都不复杂。先跑通最小闭环再往上加东西这是我一直觉得最靠谱的路线。本文还有配套的精品资源点击获取
返回列表