MCP 全称:Model Context Protocol(模型上下文协议)。
由谁提出:2024 年 11 月,Anthropic 公司正式开源发布。
核心目标:为 AI 模型与外部工具、数据源之间,搭建一条“高速公路”,让模型不仅“会说话”,更会“动手做事”。
技术特点:
这样,AI 模型就不再是“孤岛”,而是真正的“数字助手”。
MCP 的魔法:
应用示例:AI 直接读取代码、执行数据库查询、发起运维命令……开发效率大幅提升,体验也更“聪明”自然。
Tool Calling(函数调用)是 LLM 从“写字生”迈向“指令行小帮手”的关键能力。
工作流程
举个小例子
用户问:“帮我查下今天北京天气?”
模型选中 get_weather(city, date)
生成:
{
"function": "get_weather",
"parameters": {"city":"北京","date":"2025-07-18"}
}
后端执行拿到数据,模型再用自然语言回复:“今天北京晴转多云……”。
优势与挑战
场景:开发者希望 AI 帮忙给项目打包并上传制品。
传统 Tool Calling
build_project()
upload_artifact(path, repo)
MCP 场景
MCP 启动时自动注册 CI/CD 工具、仓库管理工具、存储服务等。
LLM 发出“打包并上传”意图,经 MCP 统一管理:
build
模块、publish
模块无需多轮确认,端到端执行;新增工具只要实现 MCP 接口即可即刻可用。
收益:流程更连贯、接入更简单、维护更省心。
场景:一个智能运维 Agent,负责监控服务状态并自动修复。
仅 Tool Calling 的 Agent
check_status()
、restart_service()
等函数基于 MCP 的 Agent
收益:Agent 逻辑更聚焦“决策+编排”,底层工具管理交给 MCP,系统更灵活可扩展。
用户需求:“帮我整理今天的工作安排,看看有哪些最重要的待办事项。”
意图识别:用户要一个“今天工作计划”概览。
数据来源:日历、邮件、任务清单。
分析目标:筛选出关键事件与高优先级待办。
行动计划:
步骤 1:获取日历事件
调用函数:getCalendarEvents(date="2025-07-19")
返回:
步骤 2:拉取未读邮件
调用函数:fetchUnreadEmails(folder="inbox")
返回:
步骤 3:获取待办事项
调用函数:listTasks(status="pending")
返回:
整理输出:Agent 将上述结果按时间和优先级排序,生成最终清单。
用户需求:“我的订单被取消了,帮我检查原因并重新下单。”
意图识别:用户订单出现异常,需要检查并补救。
数据来源:订单系统、库存服务、支付服务、客服工单系统。
分析目标:
行动计划:
步骤 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:决策与补救
createSupportTicket(customerId="C001", issue="库存不足,订单被取消")
ticketId="T1001"
或 若库存充足,则 Agent 可继续调用:
createOrder(customerId="C001", productId="SKU-789", quantity=1)
orderId
、customerId
及权限信息给所有工具。当调用外部服务遇到网络抖动或短暂故障时,Agent 不应立刻放弃,而是内置重试机制,并在必要时向用户发起澄清或决策确认,从而保证流程的可靠性和交互的自然性。
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
)进行重试下面展示一个基于用户和 Agent 间的多轮对话示例,其中包含重试失败后的用户确认环节。
用户:我的订单 123456 被取消了,帮我检查原因并重新下单。
Agent:好的,我先查询订单状态……(第一次重试成功)
Agent:订单状态是已取消,原因:库存不足。我再尝试检查库存……(重试3次均超时)
Agent:不好意思,库存查询服务目前不可用。您要我现在为您创建客服工单,还是稍后再试?
如果用户选择“创建工单”:
用户:创建工单吧。
Agent:已为您生成工单 T1001,客服将尽快与您联系。
如果用户选择“稍后再试”:
用户:稍后再试吧。
Agent:明白,我将在 5 分钟后自动重试库存检查,再次反馈给您。
以 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
方法封装了重试逻辑// 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;
}
}
// 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 或其他唯一标识
// 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);
}
// 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,自动过期 | 需要手动清理历史记录 |
审计合规/报表 | 不便 | 方便,易统计 |
层次关系
协作机制
相互依赖