Spring AI 高级实战:ChatMemory、Tool Calling 与 MCP 架构解析

在大模型工程化落地中,记忆(State) 、工具调用(Action) 与 标准协议(Connection) 构成了应用与模型交互的三驾马车。本文从底层存储到协议规范,为你拆解生产级避坑指南与面试高频考点。


一、 ChatMemory:策略层与存储层分离

Spring AI 将"记忆"优雅地拆分成了两层,很多开发者只停留在第一层,容易在深度面试中失分:

  • ChatMemory(策略层):决定留多少条、怎么裁剪。

  • ChatMemoryRepository(存储层):决定存在哪、怎么序列化。

1. 默认配置的"三个致命问题"

默认情况下,Spring AI 使用 MessageWindowChatMemory 配合 InMemoryChatMemoryRepository(基于 ConcurrentHashMap 存在 JVM 堆内存)。

这只能用于 Demo,生产环境必踩以下三个坑:

  1. 重启即丢:服务发布一次,用户多轮对话全清空。

  2. 多实例不共享:网关负载均衡到另一台,历史断层。

  3. OOM 风险 :conversationId 只增不删,内存无限膨胀。

2. 配置方式一:JDBC 与它的"巨坑"

官方内置了 JDBC 存储方案。配置如下:

yaml

复制代码
spring:
  ai:
    chat:
      memory:
        repository:
          jdbc:
            initialize-schema: always # 自动建表;生产建议 never,走 DB 变更脚本

🚨 必须知道的巨坑:

官方默认表结构(SPRING_AI_CHAT_MEMORY)只存 content + type + timestamp,不存 toolCalls 和 metadata。

  • 后果 :如果你的会话发生过工具调用,从 JDBC 恢复出来的是"残缺"的 AssistantMessage(只有文本,没有 toolCalls),后续发请求直接 400(与 ToolResponseMessage 无法配对)。

  • 解法:使用了 Tool Calling 的场景,要么自定义存储结构,要么将工具轮次从记忆中剔除。

3. 配置方式二:自定义存储(企业级方案:Redis)

实现 ChatMemoryRepository 接口,核心方法包括 findByConversationId、saveAll 等。

java

复制代码
@Override
public void saveAll(String cid, List<Message> messages) {
    String key = "ai:chat:memory:" + cid;
    // 序列化必须保留完整 Message(含 toolCalls、metadata),不能只存纯文本
    redis.opsForList().rightPushAll(key, messages.stream().map(this::toJson).toList());
    redis.expire(key, Duration.ofHours(2));        // 必须设 TTL
    redis.opsForList().trim(key, -MAX_MESSAGES, -1); // 配合策略层做条数裁剪
}

4. 挂载与最佳实践

java

复制代码
ChatMemory memory = MessageWindowChatMemory.builder()
        .chatMemoryRepository(redisRepository) // 替换默认内存实现
        .maxMessages(20)
        .build();

ChatClient client = ChatClient.builder(chatModel)
        .defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build())
        .build();

client.prompt()
        .user(q)
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, orderId)) // 会话 ID 由业务决定
        .call().content();
  • Advisor 的区别 :MessageChatMemoryAdvisor(历史作为多条 Message 插入,语义最准) vs PromptChatMemoryAdvisor(历史拼成一段文本塞进 system,兼容性做法但损失结构)。

  • 会话 ID 选择 :必须用业务主键 (如工单号、订单号),绝对不要用 sessionId 或随机 UUID------否则刷新页面历史就断,且无法做客服回溯。


二、 Tool Calling:明确安全边界与降级策略

LLM 只输出"调用意图",从不执行 。执行权在应用侧(Spring AI 的 ToolCallback),这是核心安全边界。

1. 触发时机与标准降级链

调用工具不只是在"模型想调"的时候,常见场景包括:实时数据(库存、汇率)、精确计算、结构化查库、副作用操作(下单)。

标准降级链(面试重点):

text

复制代码
向量检索 → 命中则直接答
         → 空/低分 → 调联网搜索工具 → 结果回填 LLM 组织答案
         → 仍为空 → 明确告知“资料未覆盖”,不编造

2. 结果的两种去向

去向 实现方式 适用场景
回填给 LLM 二次组织 默认行为,框架自动再调一次模型 需要模型消化、总结、多工具串联
直接返回给用户 配置 @Tool(returnDirect = true) 结果本身就是答案:跳转链接、订单卡片、无需模型改写

3. 工程落地 8 条军规

  1. description 是 Prompt:写得模糊,模型就会乱调、传错参。

  2. 服务端强校验 :模型会传 null、负数、超长字符串,绝不信任模型参数。

  3. 幂等性:模型可能重复调用同一个工具。

  4. 超时与熔断:外部 API 设 3~5 秒超时,防拖死整个对话。

  5. 身份隔离 :使用 toolContext 传用户身份,绝不用模型参数传 userId(防伪造)。

  6. 循环上限:限制连续调用轮次(如 5 次),防死循环烧 Token。

  7. 结果截断:工具返回 10 万字会直接打爆上下文,必须截断 + 摘要。

  8. 人在环(HITL):下单、退款等危险操作,必须拦截并让用户人工确认。


三、 MCP:破除误区与架构视角

1. 核心纠偏:MCP 不需要 Python/Node 环境

错误认知 :"接入 MCP 需要 Python 或 Node 环境"。

正确认知 :MCP 是协议(JSON-RPC 2.0),与语言无关。官方/社区 SDK 覆盖 Java、Python、TS、Go 等。

  • 之所以感觉"需要 npx / uvx",是因为部分现成的社区 Server 恰好是 Node/Python 写的。

  • Spring AI 支持 :官方提供了 spring-ai-starter-mcp-server-webmvc 等,Java 完全可以直接写 MCP Server,跟现有微服务共用同一套部署和鉴权体系。

2. MCP 的本质:M×N 降为 M+N

MCP 不是"模型的协议",而是**"应用 ↔ 能力提供方"的协议**。类比 LSP(Language Server Protocol)之于编辑器。

  • 没有 MCP:M 个 AI 应用 × N 个数据源 = M×N 个定制集成。

  • 有了 MCP :M 个 Client + N 个 Server = M+N 个实现。

    (注:MCP 并不替代 Function Calling,Function Calling 是"模型让应用调什么",MCP 是"应用去哪找工具、怎么描述工具"。)

3. Server 原语与传输方式

  • 三类原语 :Tools(可调用函数,有副作用)、Resources(只读数据)、Prompts(可复用模板)。

  • 传输方式:

    • stdio:本地子进程通信,IDE 插件常用。

    • Streamable HTTP(含旧版 SSE):远程服务,支持鉴权、可水平扩展,企业集成首选。

4. Spring AI 集成代码

写 Server(Java 纯后端):

java

复制代码
@Configuration
public class McpServerConfig {
    @Bean
    ToolCallbackProvider dtcTools(DtcToolService svc) {
        // 加了 @Tool 的方法自动变成 MCP Tool
        return MethodToolCallbackProvider.builder().toolObjects(svc).build(); 
    }
}

接 Client(远程调用):

yaml

复制代码
spring.ai.mcp.client.sse.connections.ats-dtc.url: https://mcp.internal/ats/dtc

连上之后,MCP 的工具会自动注册成 ToolCallback,直接传给 ChatClient 使用。这就是完整的"业务能力 → MCP Server → ToolCallback → 模型调用"链路。

5. MCP 的安全红线

  1. 防工具投毒 :description 也是 Prompt,可能被注入。

  2. 远程必须鉴权:OAuth 2.1 / mTLS,不能裸奔。

  3. stdio 命令防注入 :command 绝不能由用户控制。

  4. 路径穿越校验 :Resources 读取要做 ../ 校验。

  5. 能力最小化:一个 Server 只暴露一类能力,别做万能网关。


四、 面试 60 秒高能串讲版

"ChatMemory 分策略和存储两层。策略默认按条数裁剪,存储默认在 JVM 堆里------重启丢、多实例不共享,只能 Demo 用。生产环境我换 JDBC 或自定义 Redis 实现,会话 ID 用业务主键,设 TTL,且必须完整序列化 Message 含 toolCalls ,否则工具调用恢复后请求非法。

Tool Calling 里,模型只输出调用意图,执行在应用侧 ,这是安全边界。触发场景常见于知识库召回为空时的联网兜底。结果默认回填模型二次组织,returnDirect = true 则直接返回。工程上守住:服务端参数校验、幂等、超时熔断、通过 toolContext 传身份。

MCP 的本质是应用与能力提供方的标准协议(JSON-RPC 2.0),把 M×N 集成降成 M+N。它跨语言,用 Spring AI 我完全可以用 Java 写 Server,走 Streamable HTTP 传输,和我们现有的微服务鉴权体系无缝集成。连上后工具自动注册为 ToolCallback,实现模型侧完全解耦。"

相关推荐
vinijack1 小时前
1天干穿1个周额度!手握17个 opencode-go 账号的我,开源了 Claude Code 无感切号代理 OpenSelect
人工智能
zhidian_AI1 小时前
Claude Code 装到一半总超时?其实是下载了两次
人工智能
国科安芯1 小时前
长期在轨的计算底座:商业空间站与在轨服务飞行器健康管理中的抗辐射MCU—— 面向可维护空间平台的分层容错与安全通信综述
人工智能·单片机·嵌入式硬件·安全·低轨卫星·抗辐射
加宝的起点雷达1 小时前
40 行原子写入:为什么临时文件必须写在同一个目录
人工智能·代码规范
科技新资讯2 小时前
AI如何批量处理Excel文件? 从数据合并、清洗到自动生成报表,看看 AI Agent 怎么完成任务
大数据·人工智能·excel
解构Shopify2 小时前
专题 01 | 展示面自持、凭据隔离与技术选型权衡
架构
ZhengEnCi2 小时前
Agent 自动化系统怎么搭:从 Super-agent 架构到事件驱动调度(全文可跑)
后端·架构
郝学胜-神的一滴2 小时前
AI 编程智能体 22:解析器赋能与实战落地全指南
人工智能·程序人生·游戏·unity·游戏引擎