LLM - 通过案例轻松理解MCP、Tool Calling、Agent

文章目录

  • 一、MCP 是什么?
  • 二、MCP 解决了哪些痛点?
  • 三、什么是 Tool Calling?
  • 四、对比案例一:Tool Calling vs MCP
  • 五、对比案例二:Agent vs Agent+MCP
  • 六:使用场景理解Agent→Tool Calling→MCP
    • 场景一:智能助手帮你整理工作安排(重构版)
      • Agent 的理解与规划
      • Tool Calling 的执行流程
      • MCP 的幕后支撑
    • 场景二:智能电商客服处理订单异常
      • Agent 的理解与规划
      • Tool Calling 的执行流程
      • MCP 的幕后支撑
    • 异常重试与多轮对话示例
      • 1. 重试伪码
      • 2. 多轮对话流程
      • 3. 在代码中集成
    • 基于 Redis 的上下文持久化
      • 1. Redis 配置
      • 2. AiChatService 中接入 Redis 存取
    • 基于关系型数据库(JPA)的上下文持久化
      • 1. 定义实体与仓库
      • 2. AiChatService 中使用 JPA
      • 对比与适用
  • 七 总结: MCP、Tool Calling、Agent

一、MCP 是什么?

  • MCP 全称:Model Context Protocol(模型上下文协议)。

  • 由谁提出:2024 年 11 月,Anthropic 公司正式开源发布。

  • 核心目标:为 AI 模型与外部工具、数据源之间,搭建一条“高速公路”,让模型不仅“会说话”,更会“动手做事”。

  • 技术特点

    • 基于 JSON‑RPC 2.0 的标准化消息格式
    • 支持双向、富上下文信息传输
    • 灵活扩展、自定义工具接入
    • 内置权限、安全控制
    • 高并发、低延迟,跨平台兼容

这样,AI 模型就不再是“孤岛”,而是真正的“数字助手”。

二、MCP 解决了哪些痛点?

  1. 孤立性问题:传统 LLM 只能靠训练数据“闭门造车”,无法获取实时信息。
  2. 上下文丢失:各种工具之间各说各话,信息割裂。
  3. 集成复杂:每接入一个 API 都要重头写文档、维护签名,开发成本高。
  4. 能力受限:模型只能“输出文字”,无法“执行任务”。

MCP 的魔法

  • 统一接口,所有工具一套协议对接
  • 全程上下文管理,工具间无缝传递信息
  • 动态发现可用工具,运行时就能调用
  • 严密权限管控,安全可靠

应用示例:AI 直接读取代码、执行数据库查询、发起运维命令……开发效率大幅提升,体验也更“聪明”自然。

三、什么是 Tool Calling?

Tool Calling(函数调用)是 LLM 从“写字生”迈向“指令行小帮手”的关键能力。

  1. 工作流程

    1. 意图识别:理解用户要干啥
    2. 函数选择:匹配最合适的函数
    3. 参数提取:抓取参数、校验类型
    4. 结构化输出:生成 JSON 调用指令
    5. 执行反馈:拿到结果,反馈给用户
  2. 举个小例子

    • 用户问:“帮我查下今天北京天气?”

    • 模型选中 get_weather(city, date)

    • 生成:

      {
        "function": "get_weather",
        "parameters": {"city":"北京","date":"2025-07-18"}
      }
      
    • 后端执行拿到数据,模型再用自然语言回复:“今天北京晴转多云……”。

  3. 优势与挑战

    • 优点:无需手写 API,智能处理参数;多轮对话上下文保持。
    • 难点:意图理解要精准,防止“调错函数”;要做好安全和大规模函数管理。

四、对比案例一:Tool Calling vs MCP

场景:开发者希望 AI 帮忙给项目打包并上传制品。

  • 传统 Tool Calling

    1. LLM 识别“打包”→ 调用 build_project()
    2. 再识别“上传”→ 调用 upload_artifact(path, repo)
    3. 中间上下文靠模型记忆,偶尔忘参数得重问。
    4. 接入新工具时,必须更新函数列表、改模型配置。
  • MCP 场景

    1. MCP 启动时自动注册 CI/CD 工具、仓库管理工具、存储服务等。

    2. LLM 发出“打包并上传”意图,经 MCP 统一管理:

      • MCP 调度 build 模块、publish 模块
      • 自动带上上下文:项目路径、版本号、权限凭证
    3. 无需多轮确认,端到端执行;新增工具只要实现 MCP 接口即可即刻可用。

收益:流程更连贯、接入更简单、维护更省心。

五、对比案例二:Agent vs Agent+MCP

场景:一个智能运维 Agent,负责监控服务状态并自动修复。

  • 仅 Tool Calling 的 Agent

    • Agent 编写好调用 check_status()restart_service() 等函数
    • 需要手工维护函数注册、调用逻辑;新增监控项要重新编写函数脚本
  • 基于 MCP 的 Agent

    1. 通过 MCP 自动发现监控平台、日志分析服务、运维脚本库
    2. Agent 编排任务时,直接通过 MCP 消息发起调用
    3. 如果接入新的监控工具,只需实现 MCP Adapter,Agent 即刻识别并使用

收益:Agent 逻辑更聚焦“决策+编排”,底层工具管理交给 MCP,系统更灵活可扩展。


六:使用场景理解Agent→Tool Calling→MCP

场景一:智能助手帮你整理工作安排(重构版)

用户需求:“帮我整理今天的工作安排,看看有哪些最重要的待办事项。”

Agent 的理解与规划

  1. 意图识别:用户要一个“今天工作计划”概览。

  2. 数据来源:日历、邮件、任务清单。

  3. 分析目标:筛选出关键事件与高优先级待办。

  4. 行动计划

    • 步骤 1:查询日历事件
    • 步骤 2:拉取未读邮件、筛选紧急邮件
    • 步骤 3:获取任务系统中的待办
    • 步骤 4:合并并按优先级排序输出清单

Tool Calling 的执行流程

  • 步骤 1:获取日历事件

    • 调用函数:getCalendarEvents(date="2025-07-19")

    • 返回:

      • 09:00 团队例会
      • 15:00 客户电话
      • 19:00 在线培训
  • 步骤 2:拉取未读邮件

    • 调用函数:fetchUnreadEmails(folder="inbox")

    • 返回:

      • “项目进度询问”来自老板
      • 客户反馈邮件
  • 步骤 3:获取待办事项

    • 调用函数:listTasks(status="pending")

    • 返回:

      • 撰写周报
      • 准备演示材料
      • 回复客户邮件
  • 整理输出:Agent 将上述结果按时间和优先级排序,生成最终清单。

MCP 的幕后支撑

  • 统一协议:通过 MCP,Agent 对接 Google Calendar、Outlook,无需为每个日历写不同代码。
  • 上下文传递:同一请求中,MCP 带上用户身份、日期、权限,确保跨工具操作语义一致。
  • 动态扩展:后续若接入“团队白板”或“会议纪要”工具,只需实现 MCP 接口即可被 Agent 调用。

场景二:智能电商客服处理订单异常

用户需求:“我的订单被取消了,帮我检查原因并重新下单。”

Agent 的理解与规划

  1. 意图识别:用户订单出现异常,需要检查并补救。

  2. 数据来源:订单系统、库存服务、支付服务、客服工单系统。

  3. 分析目标

    • 找出订单取消原因
    • 判断是否可重新下单
    • 自动或指导用户完成重新下单
  4. 行动计划

    • 步骤 1:查询订单状态
    • 步骤 2:检查库存可用性
    • 步骤 3:验证支付记录
    • 步骤 4:生成客服工单或自动重新下单

Tool Calling 的执行流程

  • 步骤 1:查询订单状态

    • 调用:getOrderStatus(orderId="123456")
    • 返回:status="cancelled", reason="库存不足"
  • 步骤 2:检查库存可用

    • 调用:checkInventory(productId="SKU-789", quantity=1)
    • 返回:available=false
  • 步骤 3:验证支付记录

    • 调用:getPaymentInfo(orderId="123456")
    • 返回:paidAmount=0, paymentStatus="refunded"
  • 步骤 4:决策与补救

    • Agent 判断“库存不足”无法自动下单
    • 调用:createSupportTicket(customerId="C001", issue="库存不足,订单被取消")
    • 返回:ticketId="T1001"
  • 若库存充足,则 Agent 可继续调用:

    • createOrder(customerId="C001", productId="SKU-789", quantity=1)

MCP 的幕后支撑

  • 多系统接入:MCP 统一封装了订单、库存、支付、工单等微服务接口。
  • 上下文管理:在一次会话中,MCP 传递 orderIdcustomerId 及权限信息给所有工具。
  • 权限与安全:MCP 会校验 Agent 是否有查询支付和库存权限,防止越权操作。
  • 可扩展性:新增“优惠券”工具时,只要实现 MCP 接口,Agent 即可查询并应用优惠。

异常重试与多轮对话示例

当调用外部服务遇到网络抖动或短暂故障时,Agent 不应立刻放弃,而是内置重试机制,并在必要时向用户发起澄清或决策确认,从而保证流程的可靠性和交互的自然性。

1. 重试伪码

function safeCall(toolName, method, params, maxRetries=3):
    for attempt in 1..maxRetries:
        try:
            return mcp.callTool(toolName, method, params)
        except TransientError as e:
            log("第 " + attempt + " 次调用失败,原因:" + e.message)
            if attempt < maxRetries:
                sleep(500ms)  // 等待再试
            else:
                throw e

// 在订单异常处理流程中使用 safeCall
statusInfo = safeCall("orderService", "getOrderStatus", {orderId})
if statusInfo.status == "cancelled":
    // 继续后续处理
  • 要点

    • 对短暂的网络或服务错误(TransientError)进行重试
    • 重试次数和间隔可配置
    • 如果最终仍失败,再切换到“多轮对话”

2. 多轮对话流程

下面展示一个基于用户和 Agent 间的多轮对话示例,其中包含重试失败后的用户确认环节。

用户:我的订单 123456 被取消了,帮我检查原因并重新下单。
Agent:好的,我先查询订单状态……(第一次重试成功)
Agent:订单状态是已取消,原因:库存不足。我再尝试检查库存……(重试3次均超时)
Agent:不好意思,库存查询服务目前不可用。您要我现在为您创建客服工单,还是稍后再试?  
  • 如果用户选择“创建工单”

    用户:创建工单吧。
    Agent:已为您生成工单 T1001,客服将尽快与您联系。
    
  • 如果用户选择“稍后再试”

    用户:稍后再试吧。
    Agent:明白,我将在 5 分钟后自动重试库存检查,再次反馈给您。
    

3. 在代码中集成

以 Spring AI 示例为基础,加入重试与多轮对话的关键片段:

// 在 AiChatService 中
private <T> T safeCall(String tool, String method, Map<String,Object> args) throws Exception {
    int max = 3;
    for (int i = 1; i <= max; i++) {
        try {
            return mcp.callTool(tool, method, args);
        } catch (TransientException ex) {
            log.warn("调用 {}.{} 第 {} 次失败: {}", tool, method, i, ex.getMessage());
            if (i == max) throw ex;
            Thread.sleep(500);
        }
    }
    return null; // 不会到这里
}

public String chat(String userInput) throws Exception {
    // 1. 第一次对话,获取 FunctionCall
    ChatCompletionResponse resp = chatClient.complete(buildRequest(userInput));
    ChatMessage msg = resp.getChoices().get(0).getMessage();

    if (msg.getFunctionCall() != null) {
        FunctionCall fc = msg.getFunctionCall();
        Map<String,Object> args = objectMapper.readValue(fc.getArguments(), Map.class);
        Object result;
        try {
            // 使用 safeCall 重试调用
            result = safeCall(fc.getName(), fc.getName(), args);
        } catch (TransientException tex) {
            // 重试失败,进入多轮对话:请求用户决策
            return "库存查询服务暂时不可用,您要现在创建客服工单,还是稍后再试?";
        }

        // 正常流程:回传结果,生成最终回答
        ChatMessage fnMsg = ChatMessage.function(fc.getName(), objectMapper.writeValueAsString(result));
        ChatCompletionResponse followup = chatClient.complete(
            ChatCompletionRequest.builder()
                .model("gpt-4o-mini")
                .messages(msg, fnMsg)
                .build()
        );
        return followup.getChoices().get(0).getMessage().getContent();
    }
    // 处理用户对上一轮提示的回复
    if (userInput.contains("创建工单")) {
        Object ticket = mcp.callTool("supportService", "createSupportTicket", Map.of(
            "customerId", currentCustomer, "issue", "库存查询失败"));
        return "已创建工单:" + ticket.toString() + ",客服会尽快跟进。";
    }
    if (userInput.contains("稍后再试")) {
        // 异步定时任务示例(略)
        scheduleRetry(...);
        return "好的,我会在 5 分钟后自动重试并反馈给您。";
    }

    return msg.getContent();
}
  • 说明

    • safeCall 方法封装了重试逻辑
    • 重试失败后,通过返回提示引导用户进行下一步选择
    • 根据用户的多轮回复(“创建工单”或“稍后再试”),Agent 再次触发相应的 Tool Calling

基于 Redis 的上下文持久化

1. Redis 配置

// com.example.springai.config.RedisConfig.java
package com.example.springai.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class RedisConfig {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // 默认本地 6379,生产环境请外部配置
        return new LettuceConnectionFactory("localhost", 6379);
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(LettuceConnectionFactory cf) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(cf);
        return template;
    }
}

2. AiChatService 中接入 Redis 存取

// com.example.springai.service.AiChatService.java
package com.example.springai.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class AiChatService {

    private static final String KEY_PREFIX = "chat:session:";

    @Autowired private ChatCompletionClient chatClient;
    @Autowired private List<FunctionDefinition> functionDefinitions;
    @Autowired private ObjectMapper objectMapper;
    @Autowired private RedisTemplate<String, String> redisTemplate;

    public String chat(String sessionId, String userInput) throws Exception {
        String redisKey = KEY_PREFIX + sessionId;

        // 1. 拉取历史对话
        List<ChatMessage> history = Optional.ofNullable(
            redisTemplate.opsForValue().get(redisKey)
        ).map(json -> {
            try {
                return objectMapper.readValue(
                    json, new TypeReference<List<ChatMessage>>() {});
            } catch (Exception e) {
                return new ArrayList<ChatMessage>();
            }
        }).orElse(new ArrayList<>());

        // 2. 添加用户消息
        ChatMessage userMsg = new ChatMessage(ChatRole.USER, userInput);
        history.add(userMsg);

        // 3. 调用模型
        ChatCompletionRequest req = ChatCompletionRequest.builder()
                .model("gpt-4o-mini")
                .messages(history)
                .functions(functionDefinitions)
                .functionCall("auto")
                .build();
        ChatCompletionResponse resp = chatClient.complete(req);
        ChatMessage botMsg = resp.getChoices().get(0).getMessage();

        // 4. 将模型回复/函数调用加入历史
        history.add(botMsg);
        if (botMsg.getFunctionCall() != null) {
            // 如果是函数调用,再执行并添入结果
            FunctionCall fc = botMsg.getFunctionCall();
            Map<String,Object> args = objectMapper.readValue(
                fc.getArguments(), new TypeReference<Map<String,Object>>(){});
            // 这里省略权限检查和实际调用,假设调用后 resultStr 得到结果
            String resultStr = /* 调用工具 */ "{}";
            ChatMessage fnReply = ChatMessage.function(fc.getName(), resultStr);
            history.add(fnReply);
        }

        // 5. 保存到 Redis(以 JSON 字符串形式)
        String updated = objectMapper.writeValueAsString(history);
        redisTemplate.opsForValue().set(redisKey, updated);

        // 6. 最终返回最新的模型回复文本
        return botMsg.getContent();
    }
}

要点

  • 使用 RedisTemplate 将整个对话列表序列化存储
  • 每次调用前拉取、调用后更新,保持多轮上下文
  • sessionId 可用用户 ID、会话 ID 或其他唯一标识

基于关系型数据库(JPA)的上下文持久化

1. 定义实体与仓库

// com.example.springai.entity.ChatMessageEntity.java
package com.example.springai.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "chat_messages")
public class ChatMessageEntity {
    @Id @GeneratedValue private Long id;
    private String sessionId;
    private String role;        // "user"、"assistant"、"function"
    @Column(columnDefinition = "TEXT")
    private String content;     // 文本或函数调用参数/结果
    private LocalDateTime timestamp;

    // getters & setters
}
// com.example.springai.repository.ChatMessageRepository.java
package com.example.springai.repository;

import com.example.springai.entity.ChatMessageEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface ChatMessageRepository extends JpaRepository<ChatMessageEntity, Long> {
    List<ChatMessageEntity> findBySessionIdOrderByTimestampAsc(String sessionId);
}

2. AiChatService 中使用 JPA

// com.example.springai.service.AiChatService.java
package com.example.springai.service;

import com.example.springai.entity.ChatMessageEntity;
import com.example.springai.repository.ChatMessageRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.chat.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;

@Service
public class AiChatService {

    @Autowired private ChatCompletionClient chatClient;
    @Autowired private List<FunctionDefinition> functionDefinitions;
    @Autowired private ObjectMapper objectMapper;
    @Autowired private ChatMessageRepository messageRepo;

    public String chat(String sessionId, String userInput) throws Exception {
        // 1. 从数据库加载历史
        List<ChatMessageEntity> entities =
            messageRepo.findBySessionIdOrderByTimestampAsc(sessionId);
        List<ChatMessage> history = new ArrayList<>();
        for (var e : entities) {
            history.add(new ChatMessage(
                ChatRole.valueOf(e.getRole().toUpperCase()), e.getContent()));
        }

        // 2. 保存用户请求
        messageRepo.save(makeEntity(sessionId, "user", userInput));

        // 3. 调用模型
        ChatCompletionRequest req = ChatCompletionRequest.builder()
                .model("gpt-4o-mini")
                .messages(history)
                .functions(functionDefinitions)
                .functionCall("auto")
                .build();
        ChatCompletionResponse resp = chatClient.complete(req);
        ChatMessage botMsg = resp.getChoices().get(0).getMessage();

        // 4. 保存模型/函数调用
        messageRepo.save(makeEntity(sessionId,
            botMsg.getFunctionCall() != null ? "function" : botMsg.getRole().name(),
            botMsg.getFunctionCall() != null
                ? objectMapper.writeValueAsString(botMsg.getFunctionCall())
                : botMsg.getContent()));

        // 5. 如有函数调用,再保存函数执行结果
        if (botMsg.getFunctionCall() != null) {
            // 执行函数逻辑省略
            String resultStr = /* 调用 */ "{}";
            messageRepo.save(makeEntity(sessionId, "function", resultStr));
        }

        return botMsg.getContent();
    }

    private ChatMessageEntity makeEntity(String sessionId, String role, String content) {
        var e = new ChatMessageEntity();
        e.setSessionId(sessionId);
        e.setRole(role);
        e.setContent(content);
        e.setTimestamp(LocalDateTime.now());
        return e;
    }
}

要点

  • 每条消息作为一行记录入库,支持审计与回溯
  • 适合对话量大、需持久化、审计合规的场景
  • 可以做分表、按日期或会话做归档优化

对比与适用

特性 Redis 关系型数据库(JPA)
读写性能 极高,适合高并发短期会话 较 Redis 略慢,适合稳定、需持久审计的对话
存储模型 列表/字符串,便于快速读写 结构化表格,便于复杂查询、分析
会话过期管理 可配置 TTL,自动过期 需要手动清理历史记录
审计合规/报表 不便 方便,易统计

LLM - 通过案例轻松理解MCP、Tool Calling、Agent_第1张图片

七 总结: MCP、Tool Calling、Agent

  • 层次关系

    • 基础层:MCP——通信协议和上下文框架
    • 能力层:Tool Calling——模型提取意图并结构化调用
    • 应用层:Agent——智能系统,负责规划和执行任务
  • 协作机制

    1. MCP 提供标准通信通道
    2. Tool Calling 产生具体调用指令
    3. Agent 统筹任务流程,驱动 Tool Calling,经由 MCP 与工具交互
  • 相互依赖

    • 没有 MCP,Tool Calling 无法统一接入各类工具
    • 没有 Tool Calling,Agent 失去执行具体操作的“动作能力”
    • 没有 Agent,用户只能手动发起 Tool Calling,体验不连贯

LLM - 通过案例轻松理解MCP、Tool Calling、Agent_第2张图片

你可能感兴趣的:(【LLM大模型】,MCP,Function,Call,Agent,Tool,Calling)