1. 为什么说 Spring AI 和 MCP 是当前企业级 Agent 落地的最优解
做后端开发的同学应该都感受到了,从 2024 年下半年开始,Agent 这个词已经从技术公众号的标题里走到了真正的生产环境。但当你真正上手去做一个 Agent 项目,会发现最头疼的往往不是模型本身有多聪明,而是怎么让模型安全、稳定、可控地去调用你企业内部那几十个微服务、查询那些分散在多个数据源里的业务数据。
我在这条路上踩了不少坑。最早的时候团队自己定义了一套工具调用规范,用 JSON Schema 描述每个工具的入参出参,然后在代码里写死一套路由,模型输出一个 tool_call 就映射到对应的 Service 方法。前几个月还能撑住,等到工具数量超过二十个,各种幺蛾子就出来了:参数校验不统一、鉴权逻辑散落各处、新服务接入要改一堆核心代码,而且每个项目组都有自己的工具描述格式,A 组写的工具 B 组根本没法复用。这个时候 MCP 的出现就像是一剂对症的药。
MCP(Model Context Protocol)本质上解决的是一个非常朴素的问题——让 AI 应用和外部工具、数据源之间有一套统一的对话标准。你可以把它理解成 AI 世界的 USB-C 接口,不管你是鼠标、键盘还是显示器,只要都支持 USB-C,插上就能用。在 MCP 的体系里,模型应用是 Host,工具提供方是 Server,两者通过标准化的 JSON-RPC 消息进行交互,开发一个 MCP Server 就像写一个独立的微服务,只负责把能力暴露出去,至于谁在调用、怎么编排、怎么鉴权,那是上层的事。
而 Spring AI 这边,作为 Spring 生态官方推出的 AI 框架,最大的价值在于它把 AI 能力像 Spring Data、Spring Security 一样变成了 Java 开发者熟悉的编程模型。你不需要了解 LangChain 那种 Python 生态的思维方式,不需要自己封装 HTTP 调用大模型 API,直接用 ChatClient、ToolCallingManager 这些组件,配合 Spring Boot 的自动装配,就能把 MCP Server 接入到你的业务流程里。
这两个东西加在一起,等于说你既拿到了 MCP 的标准化连接能力,又保留了 Spring 生态里成熟的事务管理、配置中心、监控体系。对于做企业级系统开发的团队来说,这个组合几乎是当前技术条件下最务实的选择。这篇博客我会从架构设计、核心实现、生产环境坑位三个维度,把我实际构建 Agent 生态的完整过程拆开来讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 企业级 Agent 的核心诉求与架构选型
2.1 企业级场景下 Agent 不能只会"聊天"
在谈技术方案之前,先想清楚一个问题:企业级 Agent 到底和你在 GitHub 上看到的那些 demo 项目差在哪里。
我见过不少团队做的 Agent 看起来什么都能聊,但真正放到生产环境就废了。原因很简单:聊天场景下模型输出不合理,用户骂一句也就过去了;但企业环境下,Agent 生成的内容可能直接触发一笔资金转账、下发一条生产指令、修改一条核心配置,这种情况下模型的"创造力"就是灾难。
所以企业级 Agent 的第一诉求其实是可控。可控体现在几个层面:模型的输出结构要严格符合业务要求,不能让它自由发挥;Agent 调用的每一个工具都要有完整的日志链路,出了问题能回溯;权限控制要精确到用户级别,不同角色能触达的能力边界必须清晰;还有一点很重要,流程中的关键节点要有人工审批的插入点,不能全自动。
第二诉求是集成。企业内部几乎不可能是绿地项目,你身边一定有一套跑了七八年的老系统,数据库是 Oracle,接口是 SOAP,还有一堆 Excel 导出的报表。Agent 要发挥作用,就必须能对接这些存量系统,而不是停留在调用几个公开 API 的玩具阶段。MCP 的价值在这里就非常明显了——它提供的是标准化的接入方式,你可以给老系统写一个 MCP Server 适配层,把那些丑陋的 SOAP 接口包装成标准的 MCP Tool,Agent 不需要关心底层协议细节。
第三诉求是性能。企业级系统往往有 SLA 要求,Agent 的响应时间不能太离谱。这里涉及两个层面:一是模型本身的响应速度,这通常靠选型解决,比如复杂任务用更强的模型,简单查改用轻量模型;二是 Agent 编排的耗时,如果一次任务要串行调用三四个工具,每个工具还要经过网络往返,总延迟可能会到几十秒,这就需要架构上支持并行调用、缓存、结果复用这些优化手段。
2.2 Spring AI 在 Java 技术栈里的不可替代性
Java 团队做 AI 应用,选型上其实没有太多选择。Python 生态的 LangChain、LlamaIndex 确实能力丰富,但让一个后端团队去维护一套 Python 服务,要考虑部署链路、监控体系、人员技能匹配,成本相当高。Spring AI 的价值在于它不是一个孤立的东西,而是长在 Spring Boot 这个无数企业已经在用的框架之上。
用 Spring AI 你得到的是一套统一的 AI 编程模型。不管底层接的是 OpenAI、通义千问还是本地部署的开源模型,对业务代码来说差异很小,切换模型只需改配置。ChatClient 的使用方式和 RestTemplate、WebClient 很像,Java 开发者几乎没有学习曲线。加上 Spring AI 支持同步、流式、响应式三种调用方式,写流式对话体验和传统 WebFlux 开发没什么两样。
更关键的是 Spring AI 在工具调用上做得很顺手。它用 @Tool 注解标记一个 Java 方法,运行时自动生成参数描述传给模型,模型决策需要调用时,框架自动把参数反序列化然后反射调用。这与 Spring 开发者习惯的声明式编程风格完全一致,不需要像 Python 生态那样用字典去定义 schema。如果你的项目里已经有 Spring Cloud 体系——Nacos 注册发现、Sentinel 限流、OpenFeign 服务调用,Spring AI 可以直接嵌进这条链路,不会产生架构割裂感。
2.3 MCP 协议解决了工具调用的哪些历史遗留问题
在没有 MCP 之前,一个 Agent 项目要接入多个工具,通常的做法是团队自己定一套规则。比如定义 Tool 接口,里面有 name、description、parameters 三个字段,然后写一个中心化的注册表,硬编码把每个工具的路由写死。这种做法的问题在于:
工具定义与业务逻辑高度耦合,每加一个工具都要改动注册表和调用逻辑,很多项目维护到后期,注册表里上百个工具谁都不敢乱动。
不同团队、不同项目的工具格式五花八门,想跨项目复用几乎不可能。A 部门写了一个查询订单的工具,B 部门要用就得复制代码然后改结构,一个工具 N 份实现。
每个工具都要单独做鉴权、限流、审计,公共能力没法沉淀,安全团队审查的时候要翻遍每一个工具的代码。
MCP 的出现改变了这个局面。它把"工具提供方"和"工具使用方"彻底解耦:MCP Server 只负责暴露能力和实现业务逻辑,MCP Client(在 Spring AI 里就是框架本身)负责统一管理连接、发现工具、转发调用。这样新工具的接入就变成两个环节:开发一个 MCP Server 然后启动它,在配置里登记一下地址。至于鉴权、限流、审计,这些可以做成公共的 MCP 中间件层,不用每个工具重复实现。
MCP 的另一个亮点是它支持多种传输模式。开发环境下可以用 stdio 在本地起一个子进程,省去网络部署;生产环境用 SSE(Server-Sent Events)以 HTTP 的方式提供服务;从较新的协议版本开始还支持 streamable HTTP 这种流式传输模式,Agent 需要长时间执行工具时不会被连接超时打断。这种灵活的设计,让它既能跑在开发者的笔记本上做调试,也能真正部署到 K8s 集群里承担企业级流量。
3. 基于 Spring AI 的 MCP 工具链完整搭建
3.1 快速启动一个项目骨架
我用 Spring Boot 3.2 + JDK 17 + Spring AI 1.0.0-M6 这套组合做了验证,先把最基础的依赖关系理清楚。
在 pom.xml 里需要引入以下核心模块:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
</dependencies>
注意 spring-ai-starter-mcp-client 和 spring-ai-starter-mcp-server 要同时引入,因为在实际项目中,你的应用既可能是别人 Agent 的 Server,也需要作为 Client 去调用别的 MCP Server。前者对应的是"我在 Spring Boot 应用里用 @Tool 注解暴露方法";后者是"我的应用要去连接其他能力源"。
配置文件里最核心的是模型地址和 MCP 服务地址:
yaml复制spring:
ai:
openai:
base-url: https://your-model-gateway.example.com
api-key: ${MODEL_API_KEY}
chat:
options:
model: qwen-plus
mcp:
client:
connections:
order-server:
type: sse
url: http://mcp-order-server:8080/sse
enabled: true
user-server:
type: stdio
command: "java"
args: ["-jar", "user-mcp-server.jar"]
这里设计上有一个值得关注的细节:模型网关地址我配置的是团队的统一网关,因为企业环境很少直接连外部模型厂商的 API,通常是走内网网关做密钥管理和审计。AI 应用的模型访问也应该像数据库连接一样走统一出口,而不是每个服务自己留一份密钥。
3.2 用 @Tool 注解写一个企业内部的 MCP Server
Spring AI 让开发 MCP Server 变得近乎无感。你不需要理解 JSON-RPC 的报文细节,不需要手动处理协议握手,只需要用 @Tool 标记一个普通 Java 方法。
以一个企业内部的"订单查询服务"为例,我在 McpServerConfig 中配置:
java复制@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider orderTools(OrderService orderService) {
return MethodToolCallbackProvider.builder()
.toolObjects(orderService)
.build();
}
}
然后 OrderService 里定义具体的可调用方法:
java复制@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Tool(name = "queryOrderById", description = "根据订单ID查询订单详细信息")
public OrderVO queryOrderById(String orderId) {
return orderRepository.findByOrderId(orderId);
}
@Tool(name = "listOrdersByCustomerId", description = "查询指定客户最近N笔订单")
public List<OrderVO> listOrdersByCustomerId(
@ToolParam(description = "客户ID") String customerId,
@ToolParam(description = "订单数量,默认10") Integer limit) {
return orderRepository.findByCustomerIdOrderByCreateTimeDesc(customerId)
.stream()
.limit(limit == null ? 10 : limit)
.toList();
}
}
这段代码看起来就是一个普通的 Service,但 Spring AI 会在运行时自动生成 MCP 协议要求的 Tool Schema,包括方法名、描述、参数类型和注释。当你启动应用后,通过 MCP 协议的 tools/list 请求,就能看到这两个工具已经被注册到了 MCP 的目录里。
我实际使用中的心得是:@Tool 的 description 字段写得越详细,模型调用准确率越高。模型没有"读源码"的能力,它只能靠你给的描述来决定什么情况下用这个工具。所以描述里应该包含这个工具适用什么场景、数据来自哪里、有什么限制,比如"查询用户订单列表,数据最终一致,延迟可能有5秒"这种信息,模型会据此判断查询类任务是否适合走这个工具。参数名和描述也尽量用业务语言而不是技术语言。
3.3 客户端接入与 ChatClient 的 Tool 调用机制
当你的应用作为 MCP Client 去连接其他服务时,Spring AI 的 ChatClient 提供了非常丝滑的工具调用体验。我在一个"企业知识库助手"的模块里这样配置:
java复制@Configuration
public class AgentConfig {
private final ChatClient.Builder chatClientBuilder;
public AgentConfig(ChatClient.Builder chatClientBuilder) {
this.chatClientBuilder = chatClientBuilder;
}
@Bean
public ChatClient agentChatClient(List<ToolCallback> toolCallbacks) {
return chatClientBuilder
.defaultTools(toolCallbacks)
.build();
}
}
List<ToolCallback> 是 Spring AI 自动收集的——所有通过 MCP Client 连接进来的远程工具,以及本地用 @Tool 标注的方法对应的工具回调,都会注入到这个 List 里。当模型决定调用某个工具时,ChatClient 会自动完成整个 Request-Response 循环:
- 把用户问题发送给模型
- 模型返回一个
tool_call指令,附带参数 JSON - Spring AI 根据函数名找到对应的
ToolCallback - 框架反序列化参数,调用你写的方法
- 把方法返回值作为新的系统消息发给模型
- 模型基于工具返回结果生成最终回答
这个过程对业务代码完全透明。你在 ChatClient 上调用 .chat("帮我查一下订单 20240001 的物流状态"),底层的工具调用循环框架自动替你处理到位。
流式场景下更是如此,用 .stream() 方法替换 .call(),其余逻辑不用动,模型边生成工具调用、边处理工具结果、边输出最终答案的流式效果就出来了。在企业项目里,用户感知影响比较大的就是流式输出,配合 SSE 推给前端,体验比转圈等几十秒强太多了。
3.4 结构化输出:让模型不只是说话,而是交出合格的 JSON
聊完工具调用,再聊一个我花了很大功夫才搞明白的点——结构化输出。在企业级场景里,你不能让模型自由发挥写一段话交给下游系统解析。比如让 Agent 提取合同里的关键要素,你要的是一个固定结构的 JSON,字段名、类型、嵌套关系都得符合预期。
Spring AI 在这一块提供了 StructuredOutputConverter 机制。你定义好实体类:
java复制public record ContractInfo(
String contractNo,
String partyA,
String partyB,
LocalDate signDate,
BigDecimal amount,
List<String> keyClauses
) {}
然后在调用模型时声明输出格式:
java复制ChatClient chatClient = ...;
ContractInfo result = chatClient.prompt()
.system("你是一个专业的合同审查助手,从用户提供的合同中提取关键信息。")
.user("这是一份合同扫描件,请识别关键字段。")
.call()
.entity(ContractInfo.class);
entity() 方法内部会调用模型按指定 schema 生成输出,如果模型返回的不是合法 JSON,还会自动重试解析。但这里我要提醒几个实际生产中的坑:
模型的 JSON 生成能力和模型本身智识水平强相关。小模型或通用模型经常会在复杂嵌套结构上出错,比如生成多余字段或者类型不对。实测下来,要保证复杂业务对象稳定输出,最好用支持 JSON mode 的模型,并且把字段说明写详细,尽量用 record 定义结构,避免 Map<String, Object> 这种模糊类型。
另外,输出结构的字段名如果使用下划线风格,而 Java 类是驼峰,需要用 Jackson 的 @JsonProperty 做映射,不然模型返回的 contract_no 和 contractNo 对不上,反序列化结果全是 null。这是实操中非常容易踩的坑,而且问题出在序列化层,模型本身并不会提示你,排查看日志才能发现。
4. Skill 与 MCP 的边界划分,以及 Spring AI Alibaba 带来的新增量
4.1 Skill 和 MCP 到底有什么区别
很多刚开始接触 Agent 的同事都会问一个问题——Skill 和 MCP 看起来不都是给模型"加技能"吗,为什么要两套东西。我用一个实际例子来解释。
在 Spring AI 里,system 提示词中配置的一段精心设计的解释引导,让模型能按特定方式完成某类任务(比如"你是财务分析助手,回答时引用数据来源,并用表格输出"),这叫 Skill。它本质是提示词层面的能力注入,不需要写代码,不需要部署服务,修改成本极低,适合沉淀那些"模型靠知识推理就能做好"的任务。
但如果你需要模型进行精确的数值计算,或者查询一个实时更新的订单数据库,光靠提示词是不行的。你必须在系统里写一个服务,暴露一个接口,然后让模型知道"当用户问订单状态时,调用 queryOrderById 这个工具,参数是订单 ID"。这是代码层面的能力接入,要部署、要鉴权、要监控,这就是 MCP 的领域。
两者并不冲突,而是互补。我在实际项目里是把 Skill 用做"行为规范"层,MCP 用做"能力接入"层。Skill 规定模型怎么说话、怎么组织答案、什么情况下必须拒绝回答;MCP 负责让模型能触及实时数据、执行业务动作。一个 Agent 既有遵守业务合规的边界感,又具备完成实际业务操作的能力,这才是完整的企业级 Agent。
4.2 Spring AI Alibaba 在微服务架构里的角色
Spring AI Alibaba 的出现让 Spring AI 在国内企业级落地的路径更顺畅,尤其是那些已经深度绑定 Spring Cloud Alibaba 生态的团队。
最直观的变化是接入通义系列模型变得极其顺手。本来 Spring AI 的 OpenAI 兼容模式也能用,但 Spring AI Alibaba 把通义百炼的更多特性(比如流式工具调用、上下文记忆管理)都封装好了。它的 DashScopeChatModel 支持通义千问 Max、Turbo 系列,配置方式跟标准 Spring AI 完全统一。
另一个很实在的功能是它提供了一套比较完善的 Agent 开发框架,包括 Graph 模式。在 Java 领域,想实现复杂的 Agent 编排(比如多轮人机协作、条件分支、循环迭代),过去你基本只能自己写状态机或者硬编码流程。Spring AI Alibaba 的 Graph 项目借鉴了 LangGraph 的思路,用 Java 定义了 StateGraph,把 Agent 的行为建模成一张有向图——节点是处理逻辑,边是转移条件。我在项目中用它做了一个客服工单流水线:节点一判断工单类型,节点二调用不同业务系统查询,节点三生成回复草稿,节点四根据用户反馈决定是结束还是转人工。这种显式的图形化流程比一堆 if-else 清晰得多,而且可观测性强,哪个节点耗时多、哪条路径卡住了,一目了然。
4.3 企业级知识库与向量存储的实操方案
知识库是 Agent 项目里另一个高频需求。你要让模型能回答私有领域的问题,通常不能指望模型本身知道,而是先把知识文档切碎、向量化、存进向量数据库,查询时做向量检索把相关片段拿出来作为上下文。
Spring AI 提供了 VectorStore 抽象,底层支持多种实现。我在项目里选了 Elasticsearch 做向量存储,原因很实际:公司已有 ES 集群,熟悉度高,不用为了一个 Agent 项目再引入一套 Milvus。接入方式也很简单:
yaml复制spring:
ai:
vectorstore:
elasticsearch:
index-name: enterprise_knowledge
dimensions: 1024
但这里我踩过一个非常典型的坑:默认的写入策略是追加,每次把文档向量化写入 ES 时都会新增一个文档,直接导致同一个知识文档被多次重复添加。调用侧如果没做去重,检索结果里全是重复片段,回答质量急剧下降。解决方案是在写入前先按业务 ID 做一次删除,再执行写入。具体写法是:
java复制public void updateDocument(String docId, List<String> chunks) {
// 先按业务ID删除旧片段
var filter = new Filter.Expression("docId", Filter.ComparisonType.EQ, docId);
vectorStore.delete(filter);
// 再写入新片段
List<Document> docs = chunks.stream()
.map(chunk -> Document.builder()
.text(chunk)
.metadata(Map.of("docId", docId))
.build())
.toList();
vectorStore.add(docs);
}
这个模式其实就是"覆盖写"语义。知识库场景下,文档经常需要更新,如果只追加不清理,索引会越来越脏。后来我干脆封装了一套 KnowledgeBaseService,内部控制先删后增的逻辑,内部调用方不用关心底层存储细节。
5. 生产环境落地中的常见问题与排查经验
5.1 工具注册不上与"连接中断"问题
在实际部署过程中,我遇到的第一类高频问题是 MCP Client 连接不上,或者工具注册不上。症状通常是在 Spring Boot 启动日志里看不到来自某个 MCP Server 的 Tool 注册信息,或者运行时模型明明想调用工具,但框架报找不到对应的 ToolCallback。
排查的顺序我建议这样来:
先确认 MCP Server 本身是活的。用命令行工具或 Postman 直接打 SSE 接口(curl http://server:port/sse),看是否正常建立连接。对于 stdio 模式的 Server,本地手工执行一下启动命令,看有没有启动报错。
再看配置文件里的地址和参数。由于 MCP 协议的 SSE 传输需要保持长连接,端口没开放、防火墙拦截、负载均衡超时都会导致注册失败。尤其是如果你把 MCP Server 放在 Nginx 后面,需要确认 Nginx 配置里关了缓冲并且设置了足够长的 proxy_read_timeout。
第三个排查点很隐蔽——IDEA 里本地调试 stdio 模式时,经常发现 MCP Server 没起来,是因为工具类没有被 Spring 扫描到。@Tool 注解的方法所在的类如果没放在主类的包扫描路径下,MCP Server 会启动,但工具列表是空的。这个问题在本地开发最容易遇到,因为它不像连不上那样有显眼的报错,启动日志一切正常,就是工具不出现。
上述问题都排查完还是不行,还有一个被不少人忽视的原因——MCP Server 返回的协议格式和 Spring AI 版本不兼容。MCP 协议本身在演进,Spring AI 1.0.0-M6 支持的是 2024-11-05 版本的协议,如果你是拿最新协议版本的 Agent 产品对接,有可能出现 tools/list 拿到结果但解析失败的情况,日志里还往往不会直接报协议版本错误,而是报一个泛泛的 JSON 解析异常。这时候可以抓一下网络报文看看返回格式,确认字段是否和服务端预期的一致。
5.2 Agent 响应超时与重连策略
企业级场景里,模型响应慢、工具调用超时、连接中断重连这三个问题基本是绕不开的。我总结了一套比较实用的工程级应对策略,分享一下。
对模型调用,必须在 Client 侧设置超时时间和重试次数。Spring AI 的 OpenAI 兼容配置里可以直接设置:
yaml复制spring:
ai:
openai:
chat:
options:
timeout: 30s
max-retries: 2
但对于 Agent 任务来说,一次任务可能包含多轮工具调用,单轮模型响应 30 秒,整个任务可能要几分钟甚至更久。这时候要处理好 HTTP 层的连接超时和流式会话的保持。实践中我建议对耗时任务使用流式调用而不是同步等待,配合 SSE 实时推给前端,用户能看到 Agent 的思考过程在推进,而不是干等一个结果。
对 MCP Client 与 Server 之间的连接重连,Spring AI 目前提供的基础能力是:SSE 连接如果断开,下一次调用会尝试重新建立连接。但如果你在中间用了一层网关,连接状态的维护就不那么透明了。我的经验是做一个单独的 McpConnectionHealthIndicator,定期发送 tools/list 请求做健康检查,连不上就告警并尝试重建。这样运维层面至少能第一时间感知到 MCP Server 不可用。
还有一个比较隐蔽的问题:服务重启后旧连接没有正常释放,导致 MCP Server 端出现连接数堆积。排查日志时能看到"too many connections"之类的错误。解决方式是在关闭钩子里显式调用 McpClient.destroy() 或者用 @PreDestroy 注解关闭连接池。Spring AI 的自动配置其实已经包含了这部分逻辑,但如果你手动创建了多个客户端实例,就得自己管理生命周期,避免连接泄漏。
5.3 模型输出与工具参数的兼容性问题
工具调用中参数不匹配是让很多新手崩溃的坑。具体有两种表现:一是模型传了一个字符串类型,但业务方法参数是 Integer,反序列化直接失败;二是模型认为某个参数是必填的,但业务方法里没有这个字段,调用时报方法签名错误。
解决这类问题,除了把 @ToolParam 描述写清楚之外,我强烈建议在工具方法内部做好防御性参数校验。模型不是一个严谨的程序员,它在极端情况下可能传空串、传超出枚举范围的值、传错误格式的日期。你必须在方法开头做参数校验,校验失败就返回一个友好的错误字符串给模型,让模型知道该换一种方式重试或直接向用户说明原因。
我经历过一个真实案例:一个生成月度报表的 Agent,模型传入了 2024-2-30 这样的非法日期,校验失败后模型又自己"脑补"了另一种格式传入,还是失败,来回了三四次,最终超时。后来我在工具描述里增加了"日期格式必须为 yyyy-MM-dd,且为合法日期,当日数据不存在时返回空报表"这样的说明,故障立刻大幅减少。写工具描述的投入,对最终的准确率回报率极高。
6. 实操心得:Agent 生态建设的几个关键认知
项目做到中期,我对 Agent 生态建设这件事情有了更深的理解,有几个认知层面的收获,可能比具体的代码技巧更有价值。
第一,Agent 不是模型能力的堆叠,而是工程能力的汇聚。模型本身的智商是底座,但底座再强,没有一个可靠的工具层去触达真实业务数据,没有稳定的基础设施去支撑高并发调用,Agent 终究只能停留在玩一玩的程度。我在项目里最耗精力的不是写 Agent 逻辑,而是解决 MCP Server 的高可用、链路追踪、参数校验这些看似琐碎但决定成败的细节。企业级 Agent 的本质是"用工程化的方式,把模型能力约束在业务边界内",这个定位要想清楚。
第二,MCP 的标准化带来的最大收益不是开发效率,而是组织效率。以前不同小组各自定义工具调用方式,接口文档五花八门,联调全靠吼。现在大家统一按 MCP 协议暴露能力,业务团队不用关心调用方是谁,只需要维护好自己的 MCP Server,按协议暴露并做好版本管理。这就是生态的雏形,而生态的搭建依赖标准先行。我在项目里推动的第一件事,就是把 MCP 协议规范、工具命名规范、参数描述规范定成团队级强制标准。
第三,从实际自动化效果来看,Spring AI 这套体系确实把 Java 开发者接入 AI 的门槛降到了极低的水平。团队里一位完全没有接触过 AI 开发的同事,从零开始,花了一周时间就独立给内部知识库做了一个带 MCP 工具调用的问答 Agent。这个效率背后靠的不是他能力强,而是框架把 AI 开发的思维方式和 Java Web 开发的思维方式对齐了,让熟悉 Spring 的人能凭惯性上手 AI。
至于未来的演进方向,我目前比较关注 Spring AI Alibaba 的 Graph 能力在复杂流程编排上的表现,以及 MCP 协议在鉴权、权限管理方面的新进展。Agent 生态的竞争,短期看模型能力,中期看工程化水平,长期看生态标准。你提前把 MCP 这套标准用好、把这套工程基础打牢,模型换一代、能力升级一代,你的业务系统都能借助标准化快速跟上。
