MCP vs RAG vs AI Agent: 详细解释与代码示例
1. 概览
MCP、RAG 和 AI Agent 这三个名字在讨论 LLM 应用时随处可见,很容易误认为它们是“三种相互竞争的选择”。实际上它们解决三个不同的问题:RAG 帮助 LLM 基于私有数据给出准确答案,AI Agent 帮助 LLM 自主规划和行动,而 MCP 是一种通信标准,让 LLM 应用能够以统一的方式连接 tool 和数据源。读完这篇文章后,你将能够清晰区分这三个概念,知道何时使用哪一个,并拥有几段可运行的 Java/Spring AI 代码来理解实际情况。
2. 什么是 RAG
RAG(检索增强生成,Retrieval-Augmented Generation)是一种技术,在调用 LLM 前将外部数据“嵌入”到 prompt 中,使模型能够基于真实数据而不是仅依赖训练时学到的知识来回答问题。
2.1. RAG 的工作流程
- 将源文本(政策、文档、FAQ)转换为 embedding(数值向量)并存储在向量数据库中。
- 当用户提问时,将问题转换为 embedding,寻找最相似的文本段落(相似度搜索)。
- 将找到的段落附加到 prompt 中,然后发送给 LLM 生成答案。
例如:OrderService 需要根据公司政策文档而不是模型的通用知识来回答“网上订单的退货政策是什么?“这样的问题。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
2.2. 简单的 RAG 代码示例
@Service
public class ReturnPolicyAssistant {
private final VectorStore vectorStore;
private final ChatClient chatClient;
public ReturnPolicyAssistant(VectorStore vectorStore, ChatClient.Builder builder) {
this.vectorStore = vectorStore;
this.chatClient = builder.build();
}
public String answer(String question) {
// 返回与问题语义最接近的 3 个段落
List<Document> relevant = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(3));
String context = relevant.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n---\n"));
String prompt = """
根据下面的政策文档,回答客户的问题。
如果没有相关信息,说"我不确定"。
文档:
%s
问题:%s
""".formatted(context, question);
return chatClient.prompt(prompt).call().content();
}
}
vectorStore.similaritySearch返回与问题语义最接近的 3 个文档段落。- 在发送给 LLM 前,prompt 被“增强”(augmented)了从向量存储中获取的上下文。
- 如果文档不包含相关信息,模型被指示回答“我不确定”而不是生成虚假信息(hallucination)。
输入: "我 5 天前在线购买了一件衣服,可以退换吗?"
输出: "根据政策,从收货之日起 7 天内可以退换,前提是产品还有标签。
你的衣服仍在有效期内。"
注意: RAG 仅帮助模型“了解更多数据”。它不能帮助模型自主行动(调用 API、更新数据库)。如果你需要模型做某件事而不仅仅是回答问题,你需要 Agent。
3. 什么是 AI Agent
AI Agent 是一个系统,使用 LLM 作为“大脑”来规划、选择合适的 tool、调用 tool、观察结果,然后重复此过程直到达成目标——不同于 RAG 仅停留在回答单个问题。
3.1. reason → act → observe 循环
最常见的模型是 ReAct:模型进行推理(reasoning),选择并调用一个 tool(action),接收结果(observation),然后根据结果继续推理。
public interface AgentTool {
String name();
String description();
String execute(String input);
}
@Component
public class CheckOrderStatusTool implements AgentTool {
private final OrderRepository orderRepository;
public CheckOrderStatusTool(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public String name() { return "checkOrderStatus"; }
public String description() { return "按 orderId 查询订单状态"; }
public String execute(String orderId) {
return orderRepository.findById(orderId)
.map(o -> "订单 %s 状态为 %s".formatted(orderId, o.getStatus()))
.orElse("未找到订单 " + orderId);
}
}
3.2. Agent 选择并调用 tool
@Service
public class SupportAgent {
private final ChatClient chatClient;
private final List<AgentTool> tools;
public SupportAgent(ChatClient.Builder builder, List<AgentTool> tools) {
this.chatClient = builder.build();
this.tools = tools;
}
public String handle(String userMessage) {
// Spring AI 自动将 execute() 方法映射为 function-call
return chatClient.prompt()
.user(userMessage)
.tools(tools.toArray())
.call()
.content();
}
}
tools(tools.toArray())让模型知道它可以调用这些 tool;Spring AI 会自动生成 function-call 的 schema。- 模型将自动决策:如果问题涉及订单状态,它会调用
checkOrderStatus并从问题中推断出orderId、接收结果,然后用自然语言解释结果。
用户: "订单 ORD-2024-891 怎么样了?"
Agent:(调用 tool checkOrderStatus("ORD-2024-891"))
→ "你的订单 ORD-2024-891 正在配送中,预计还需 2 天送达。"
3.3. 测试 tool 选择行为
@Test
void shouldCallCheckOrderStatusWhenAskingAboutOrder() {
AgentTool tool = new CheckOrderStatusTool(orderRepository);
when(orderRepository.findById("ORD-1")).thenReturn(Optional.of(shippedOrder));
String result = tool.execute("ORD-1");
assertThat(result)
.contains("ORD-1")
.contains("SHIPPED");
}
这个测试不调用真实的 LLM,仅确认 tool 的逻辑正确——模型选择哪个 tool 的部分应该单独用 eval set 测试,不应该用死板的单元测试,因为 LLM 输出不是确定性的。
注意: Agent 功能强大但风险也高——如果 tool 拥有写权限(退款、更新),模型的一个推理错误可能导致真实后果。始终为敏感 tool 添加确认步骤(confirmation)或限制其权限。
4. 什么是 MCP
MCP(模型上下文协议,Model Context Protocol)是一个开放标准,定义了 LLM 应用(称为主机)如何通过统一的协议连接外部数据源和 tool(称为服务器),而不是每个集成都要单独编写代码。
4.1. MCP 解决的问题
在 MCP 出现前,如果你有 5 个 LLM 应用,每个应用都想使用 10 个数据源/tool(数据库、CRM、文件系统等),你需要编写 50 个单独的集成——一个典型的 N×M 问题。MCP 将其转变为 N+M:每个服务器编写一次,所有遵循 MCP 标准的主机都能调用它。
4.2. MCP 架构
- 主机:包含 LLM 的应用(chatbot、IDE 助手等)。
- 客户端:主机内的组件,与一个 MCP 服务器保持 1-1 的连接。
- 服务器:暴露 3 种“能力”的进程:
- Resources:可读的数据(类似 GET 端点)。
- Tools:可调用的操作(类似 POST/action)。
- Prompts:可重用的 prompt 模板。
4.3. 例子:用 Java 编写 MCP 服务器
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.8.0</version>
</dependency>
public class OrderMcpServer {
public static void main(String[] args) {
McpSyncServer server = McpServer.sync(StdioServerTransport.builder().build())
.serverInfo("order-service", "1.0.0")
.tool(orderLookupTool())
.build();
server.start();
}
private static McpServerFeatures.SyncToolSpecification orderLookupTool() {
var schema = """
{
"type": "object",
"properties": { "orderId": { "type": "string" } },
"required": ["orderId"]
}
""";
return new McpServerFeatures.SyncToolSpecification(
new Tool("getOrderById", "根据 orderId 查询订单", schema),
(exchange, args) -> {
String orderId = (String) args.get("orderId");
String status = OrderLookup.findStatus(orderId);
return new CallToolResult(List.of(new TextContent(status)), false);
});
}
}
McpServer.sync(...)创建一个服务器,使用stdiotransport(主机将服务器作为子进程运行,通过 stdin/stdout 通信)。tool(orderLookupTool())注册一个getOrderByIdtool,附带 JSON schema 描述输入,使任何遵循 MCP 标准的主机都能读懂“这个 tool 需要什么字段”。- 当客户端调用
getOrderById时,服务器返回包含文本内容的CallToolResult。
4.4. 从 Spring AI 客户端调用 MCP 服务器
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
spring:
ai:
mcp:
client:
stdio:
connections:
order-service:
command: java
args: ["-jar", "order-mcp-server.jar"]
@Service
public class McpAgentBridge {
private final SyncMcpToolCallbackProvider toolCallbackProvider;
private final ChatClient chatClient;
public McpAgentBridge(SyncMcpToolCallbackProvider toolCallbackProvider,
ChatClient.Builder builder) {
this.toolCallbackProvider = toolCallbackProvider;
this.chatClient = builder.build();
}
public String ask(String question) {
return chatClient.prompt()
.user(question)
.tools(toolCallbackProvider.getToolCallbacks())
.call()
.content();
}
}
- Spring AI 自动启动 MCP 服务器(
order-mcp-server.jar)作为子进程,通过 stdio 保持连接。 toolCallbackProvider.getToolCallbacks()获取服务器暴露的所有 tool(这里是getOrderById)并像 function-calling 一样提供给模型,与你在第 3 节手动注册AgentTool的方式完全相同——唯一区别是这个 tool 由一个独立进程按统一标准提供。
注意: MCP 不会替代 LLM 的 function-calling。它是位于 function-calling 下方的传输层/标准化层,使 tool 可以编写一次并在多个主机(Claude Desktop、IDE、你的应用)中重复使用,无需每次集成都修改代码。
5. MCP、RAG 和 AI Agent 的对比
| 标准 | RAG | AI Agent | MCP |
|---|---|---|---|
| 解决什么问题 | 模型缺少私有/新数据 | 模型无法自主行动、无法进行多步规划 | Tool/数据集成代码重复、无标准化 |
| 工作单位 | 检索 + 增强 prompt | reason-act-observe 循环 | 连接协议(主机 ↔ 服务器) |
| 是否调用 tool/action | 否(仅读取) | 是 | 是(向 agent 暴露 tool) |
| 风险等级 | 低(仅在检索差时回答错误) | 高(可能执行错误的操作) | 取决于下层 tool,协议本身中立 |
| 相互关系 | 可作为 Agent 的一个“数据源” | 可使用 RAG 作为 tool,使用 MCP 以标准化方式调用 tool | 是“管道”,使 Agent 以统一方式调用 RAG/tool |
简单说:RAG 给模型知识,Agent 给模型行动能力,MCP 是通信标准,使得这些行动和检索不必每次切换主机或 tool 时都重新编写。
6. 在实际系统中综合使用三者
实际的大型项目不会只选择其中一个,而是将三者堆叠在一起:
用户 → AI Agent(规划、ReAct 循环)
├─ 调用 MCP 客户端 → MCP 服务器"knowledge-base" → 返回 RAG 相似度搜索结果
├─ 调用 MCP 客户端 → MCP 服务器"order-service" → tool getOrderById
└─ 调用 MCP 客户端 → MCP 服务器"payment-service" → tool refundPayment(需确认)
在这个架构中:
- RAG 位于一个独立的 MCP 服务器内(如“knowledge-base”服务器暴露相似度搜索结果作为资源)。
- Agent 是协调者(orchestrator),运行推理循环,不关心 tool 用什么语言编写,只要 tool 遵循 MCP。
- MCP 是 Agent 和所有服务器之间的统一通信层,让你能够添加新服务器(如“shipping-service”)而无需修改 Agent 代码。
7. 常见错误
- 需要行动时仅用 RAG:向 prompt 中加入数据但期望模型自动更新数据库——RAG 做不到这件事,你需要 Agent + 具有写权限的 tool。
- Agent 的 tool 权限无限制:让 agent 直接调用
deleteOrder或refundPayment而没有人工确认步骤,当模型推理出错时导致实际损失。 - 把 MCP 当成“AI 框架”:MCP 只是连接协议,不自主推理,不自动检索。如果你的服务器不暴露有用的 tool/资源,有 MCP 也无法让模型更聪明。
- RAG 中的 top-k 太小或太大:top-k 太小(1-2)容易上下文不足,top-k 太大(10+)使 prompt 过长、消耗 token 并削弱关键信息。建议从 3-5 开始,用真实 eval set 调整。
- 未对 MCP tool 的 schema 进行版本管理:改动 tool 的输入 schema 而不提高版本号时,使用旧服务器的其他主机会传递错误的参数。
8. 结论
RAG、AI Agent 和 MCP 不是相互竞争的——它们位于 LLM 系统的三个不同层:RAG 提供数据,Agent 提供决策和行动能力,MCP 提供通信标准,使这些行动和检索不必每次改变 tool 或主机时都重新编写。在设计真实的 AI 系统时,应该从“模型需要了解什么”(RAG)开始,然后是“模型需要做什么”(Agent),最后才考虑“如何让这些 tool/数据在多个地方重用”(MCP)——这个顺序能帮助你从一开始就避免过度设计。