MCP vs RAG vs AI Agent: giải thích chi tiết kèm ví dụ

Bài này có bản video ngắn:YouTubeTikTok

1. Tổng quan

Ba cái tên MCP, RAG, AI Agent xuất hiện khắp nơi khi nói về ứng dụng LLM, và rất dễ nhầm chúng là “ba lựa chọn cạnh tranh nhau”. Thực ra chúng giải quyết ba vấn đề khác nhau: RAG giúp LLM trả lời đúng dựa trên dữ liệu riêng, AI Agent giúp LLM tự lên kế hoạch và hành động, còn MCP là chuẩn giao tiếp để LLM app kết nối với tool/dữ liệu một cách thống nhất. Đọc xong bài này, bạn sẽ phân biệt rõ ba khái niệm, biết khi nào dùng cái nào, và có vài đoạn code Java/Spring AI chạy được để hình dung thực tế.

2. RAG là gì

RAG (Retrieval-Augmented Generation) là kỹ thuật “nhúng” dữ liệu bên ngoài vào prompt trước khi gọi LLM, để model trả lời dựa trên dữ liệu thật thay vì chỉ dựa vào kiến thức đã học lúc huấn luyện.

2.1. Luồng hoạt động của RAG

  1. Chuyển văn bản nguồn (policy, tài liệu, FAQ) thành embedding (vector số) và lưu vào vector database.
  2. Khi user hỏi, chuyển câu hỏi thành embedding, tìm các đoạn văn bản gần nhất (similarity search).
  3. Gắn các đoạn tìm được vào prompt, rồi mới gửi cho LLM để generate câu trả lời.

Ví dụ: OrderService cần trả lời câu hỏi “chính sách đổi trả đơn hàng online là gì?” dựa trên tài liệu chính sách công ty, không phải kiến thức chung của model.

<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. Ví dụ code RAG đơn giản

@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) {
        List<Document> relevant = vectorStore.similaritySearch(
                SearchRequest.query(question).withTopK(3));

        String context = relevant.stream()
                .map(Document::getContent)
                .collect(Collectors.joining("\n---\n"));

        String prompt = """
                Dựa vào tài liệu chính sách dưới đây, trả lời câu hỏi của khách hàng.
                Nếu không có thông tin, nói "tôi không chắc".

                Tài liệu:
                %s

                Câu hỏi: %s
                """.formatted(context, question);

        return chatClient.prompt(prompt).call().content();
    }
}
  • vectorStore.similaritySearch trả về 3 đoạn tài liệu gần nghĩa nhất với câu hỏi.
  • Prompt được “tăng cường” (augmented) bằng context lấy từ vector store trước khi gửi LLM.
  • Nếu tài liệu không chứa thông tin liên quan, model được hướng dẫn trả lời “tôi không chắc” thay vì bịa (hallucination).
Input:  "Tôi mua áo online 5 ngày trước, có đổi được không?"
Output: "Theo chính sách, bạn được đổi trả trong 7 ngày kể từ ngày nhận hàng, 
         miễn sản phẩm còn tag. Áo của bạn vẫn còn trong thời hạn."

Lưu ý: RAG chỉ giúp model “biết thêm dữ liệu”. Nó không giúp model tự hành động (gọi API, cập nhật database). Nếu bạn cần model làm gì đó chứ không chỉ trả lời, bạn cần Agent.

3. AI Agent là gì

AI Agent là một hệ thống dùng LLM làm “bộ não” để lập kế hoạch, chọn tool phù hợp, gọi tool, quan sát kết quả, rồi lặp lại cho đến khi đạt mục tiêu — khác với RAG chỉ dừng ở việc trả lời một câu hỏi.

3.1. Vòng lặp reason → act → observe

Mô hình phổ biến nhất là ReAct: model suy nghĩ (reasoning), chọn một tool để gọi (action), nhận kết quả (observation), rồi suy nghĩ tiếp dựa trên kết quả đó.

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 "Kiểm tra trạng thái đơn hàng theo orderId"; }

    public String execute(String orderId) {
        return orderRepository.findById(orderId)
                .map(o -> "Đơn %s đang %s".formatted(orderId, o.getStatus()))
                .orElse("Không tìm thấy đơn " + orderId);
    }
}

3.2. Agent chọn và gọi 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) {
        return chatClient.prompt()
                .user(userMessage)
                .tools(tools.toArray()) // Spring AI tự map method execute() thành function-call
                .call()
                .content();
    }
}
  • tools(tools.toArray()) cho model biết nó có thể gọi các tool này; Spring AI tự sinh schema cho function-calling.
  • Model sẽ tự quyết định: nếu câu hỏi liên quan đến trạng thái đơn hàng, nó gọi checkOrderStatus với orderId suy ra từ câu hỏi, nhận kết quả, rồi diễn giải lại bằng ngôn ngữ tự nhiên.
User: "Đơn ORD-2024-891 của tôi sao rồi?"
Agent: (gọi tool checkOrderStatus("ORD-2024-891"))
       → "Đơn ORD-2024-891 đang được giao, dự kiến đến trong 2 ngày nữa."

3.3. Test hành vi chọn 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");
}

Test này không gọi LLM thật, chỉ xác nhận logic tool hoạt động đúng — phần “model chọn tool nào” nên test riêng bằng eval set, không nên unit test cứng nhắc vì output LLM không deterministic.

Lưu ý: Agent mạnh nhưng rủi ro cao hơn RAG — nếu tool có quyền ghi (refund, update), một reasoning sai của model có thể gây hậu quả thật. Luôn thêm bước xác nhận (confirmation) hoặc giới hạn quyền cho các tool nhạy cảm.

4. MCP là gì

MCP (Model Context Protocol) là một chuẩn mở định nghĩa cách một LLM application (gọi là host) kết nối với các nguồn dữ liệu và tool bên ngoài (gọi là server) thông qua giao thức thống nhất, thay vì mỗi integration lại viết code riêng.

4.1. Vấn đề MCP giải quyết

Trước MCP, nếu bạn có 5 LLM app và muốn mỗi app dùng được 10 nguồn dữ liệu/tool (database, CRM, file system…), bạn phải viết tới 50 integration riêng lẻ — bài toán N×M quen thuộc. MCP biến nó thành N+M: mỗi server viết một lần, mọi host tuân theo chuẩn MCP đều gọi được.

4.2. Kiến trúc MCP

  • Host: ứng dụng chứa LLM (chatbot, IDE assistant…).
  • Client: thành phần trong host, giữ kết nối 1-1 với một MCP server.
  • Server: tiến trình expose 3 loại “capability”:
    • Resources: dữ liệu đọc được (giống GET endpoint).
    • Tools: hành động có thể gọi (giống POST/action).
    • Prompts: template prompt dùng lại được.

4.3. Ví dụ: viết một MCP server bằng Java

<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", "Tra cứu đơn hàng theo 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(...) dựng server dùng transport stdio (host chạy server như subprocess và nói chuyện qua stdin/stdout).
  • tool(orderLookupTool()) đăng ký một tool getOrderById với JSON schema mô tả input, để bất kỳ host nào theo chuẩn MCP cũng đọc được “tool này cần field gì”.
  • Khi client gọi getOrderById, server trả về CallToolResult chứa nội dung text.

4.4. Gọi MCP server từ Spring AI client

<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 tự khởi động MCP server (order-mcp-server.jar) như subprocess, giữ kết nối qua stdio.
  • toolCallbackProvider.getToolCallbacks() lấy tất cả tool mà server expose (ở đây là getOrderById) và đưa vào model như function-calling, giống hệt cách bạn đăng ký AgentTool thủ công ở mục 3 — chỉ khác là tool này do một tiến trình độc lập cung cấp theo chuẩn chung.

Lưu ý: MCP không thay thế function-calling của LLM. Nó là lớp vận chuyển/chuẩn hóa nằm dưới function-calling, giúp tool có thể viết một lần và dùng lại ở nhiều host (Claude Desktop, IDE, app của bạn) mà không cần sửa code tích hợp mỗi lần.

5. So sánh MCP, RAG và AI Agent

Tiêu chí RAG AI Agent MCP
Giải quyết vấn đề gì Model thiếu dữ liệu riêng/mới Model không tự hành động, không lập kế hoạch nhiều bước Tool/dữ liệu bị integration code trùng lặp, không chuẩn hóa
Đơn vị hoạt động Truy xuất + augment prompt Vòng lặp reason-act-observe Protocol kết nối host ↔ server
Có gọi tool/action không Không (chỉ đọc) Có Có (expose tool cho agent gọi)
Mức độ rủi ro Thấp (chỉ trả lời sai nếu retrieval kém) Cao (có thể thực thi hành động sai) Phụ thuộc tool bên dưói, bản thân protocol trung lập
Quan hệ với nhau Là một “nguồn dữ liệu” Agent có thể dùng Có thể dùng RAG như một tool, dùng MCP để gọi tool chuẩn hóa Là “đường ống” để Agent gọi RAG/tool một cách thống nhất

Nói ngắn: RAG cho model kiến thức, Agent cho model khả năng hành động, MCP là chuẩn giao tiếp để việc hành động/truy xuất đó không phải viết lại mỗi lần đổi host hoặc đổi tool.

6. Kết hợp cả ba trong một hệ thống thực tế

Thực tế các dự án lớn không chọn một trong ba, mà xếp chồng chúng lên nhau:

User → AI Agent (lập kế hoạch, ReAct loop)
          ├─ gọi MCP Client → MCP Server "knowledge-base" → trả kết quả RAG similarity search
          ├─ gọi MCP Client → MCP Server "order-service"  → tool getOrderById
          └─ gọi MCP Client → MCP Server "payment-service" → tool refundPayment (cần confirm)

Trong kiến trúc này:

  • RAG nằm bên trong một MCP server riêng (ví dụ server “knowledge-base” expose resource là kết quả similarity search).
  • Agent là phần điều phối (orchestrator) chạy vòng lặp reasoning, không quan tâm tool được viết bằng gì, chỉ cần tool tuân theo MCP.
  • MCP là lớp giao tiếp thống nhất giữa Agent và tất cả server, giúp bạn thêm server mới (ví dụ “shipping-service”) mà không phải sửa code Agent.

7. Lỗi hay gặp

  • Dùng RAG khi cần hành động: nhồi dữ liệu vào prompt nhưng kỳ vọng model tự cập nhật database — RAG không làm được việc này, bạn cần Agent + tool có quyền ghi.
  • Agent không giới hạn quyền tool: cho agent gọi trực tiếp tool deleteOrder hay refundPayment mà không có bước xác nhận con người, dẫn đến thiệt hại khi model reasoning sai.
  • Nhầm MCP là một “AI framework”: MCP chỉ là protocol kết nối, không tự suy luận, không tự retrieve. Nếu server của bạn không expose tool/resource nào hữu ích, có MCP cũng không giúp model thông minh hơn.
  • Top-k quá nhỏ hoặc quá lớn trong RAG: top-k nhỏ (1-2) dễ thiếu context, top-k lớn (10+) làm prompt dài, tốn token và loãng tín hiệu quan trọng. Nên bắt đầu với 3-5 và đo lại bằng eval set thực tế.
  • Không versioning schema của MCP tool: khi đổi input schema của tool mà không tăng version, các host khác đang dùng server cũ sẽ gọi sai tham số.

8. Kết luận

RAG, AI Agent và MCP không cạnh tranh nhau — chúng nằm ở ba lớp khác nhau của một hệ thống LLM: RAG cấp dữ liệu, Agent cấp khả năng ra quyết định và hành động, MCP cấp chuẩn kết nối để việc đó không phải viết lại mỗi lần đổi tool hay đổi host. Khi thiết kế hệ thống AI thực tế, bạn nên bắt đầu từ câu hỏi “model cần biết thêm gì” (RAG), “model cần làm gì” (Agent), rồi mới tính đến “làm sao để tool/dữ liệu đó tái dùng được ở nhiều nơi” (MCP) — thứ tự này giúp tránh over-engineering ngay từ đầu.

mcpragai-agentspring-aillm