
使用 Whisper API 通过设备麦克风把语音转录为文本
可以发现 SpringAI 把 FunctionCall 已经标记为废弃状态,真的让人大吃一惊,仔细深入查看文档发现如今的 Spring 更为推荐使用 Tool Calling 感兴趣的读者可以阅读下这篇文章 docs.spring.io/spring-ai/r…[2] 简单来说,Tool Calling 和 FunctionCall 的作用大致相同,只不过换用了一套注解,使用 Tool Calling 之后 可以像下面的代码这样去绑定到 ChatClient 上面
class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
// 注册工具到大模型上面
ChatModel chatModel = ...
ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(dateTimeTools)
.build();
Prompt prompt = new Prompt("What day is tomorrow?", chatOptions);
chatModel.call(prompt);
那为什么好好的 FunctionCall 需要改为 ToolCall,我猜测背后的原因主要是 SpringAI 想去支持 MCP 协议,MCP 协议中规定了服务端程序需要具备 Prompt、Resource、Tools 三个要素(后文会有详细讲解),所以把 FunctionCall 的 API 全部修改为 ToolCall,读者可以参考:docs.spring.io/spring-ai/r…[3] 有两种 API 的使用对比,改造的规模还是挺大的,不得不说以后升级 SpringAI 大版本又不知道会让多少研发陷入 API 改造的深渊。
MCP(Model Context Protocol)又被称之为模型上下文协议,MCP 就像是 AI 世界中的 “通用插座”,为 AI 模型与外部数据源和工具之间搭建起沟通的桥梁,致力于标准化应用程序为 LLM 提供上下文的方式,让不同的 AI 组件和系统能够顺畅连接、协同工作。这个就有点像 Jdbc 协议一样,通过定义规范,让中间件厂商进行实现。我们看几张来自 SpringAI 官网的图示:
在上图中我们可以发现,我们开发的大模型应用程序可以通过 MCPClient 去访问外部资源,并且是典型的 Client-Server 架构。实现了大模型工具实现和工具调用的分离,就像通过一个第三方管家,来统一管理函数调用,在我看来 MCP 就是比 FunctionCalling 的更高一级抽像,也是实现智能体 Agent 的基础。
在上面的图示中我们可以发现目前的 MCP 服务端存在两种类型一种是标准 IO,一种是 SSE 模式,对于标准 IO 的服务端和大模型应用是一对一的对应关系,而通过 SSE 方式进行客户端服务端通信的模型,服务端则可以与客户端进行一对多通信也许在不久的未来,目前的服务端开发人员,不再需要和前端页面对接只需要和大模型的 McpClient 对接了,即一切服务端程序都需要接入 MCP 协议以更好的服务大模型和智能体。
下面我们开发一个简单 demo,去体验一下 SpringAI 的 MCP 能力,我们计划通过 MCPClient 让大模型具备访问数据库和本地文件系统的能力,实际效果如下图所示:
这个 demo 中我们先不开发 MCP 服务端应用程序,而是利用 modelcontextprotocol.io/introductio…[4] 中提供的一些开源 MCP 服务端实现。❝此外额外说明下版本问题,考虑到目前大部分人使用的都是 SpringAI 1.0.0-M5 版本,所以本节先使用 SpringAI 1.0.0-M5 版本做演示,在第四节将具体演示 SpringAI 1.0.0-M6 的功能,也可以给大家做一个对比,方便大家比较两个大版本的 API 改动。❞
npx 是 nodeJs 下的一个工具可以执行一些 Ts 或者 JS 脚本甚至应用程序,uvx 的功能则和他很类似是 python 环境下执行脚本的工具,因为我们使用的文件服务器 MCP 服务端是使用 Ts 代码写的,而访问数据库的 MCP 服务端程序是用 Python 写的所以我们必须去安装下这两个命令以支持 MCPClient 调用,简单来说,使用 TypeScript 编写的 MCP server 可以通过 npx 命令来运行,使用 Python 编写的 MCP server 可以通过 uvx 命令来运行。uvx 安装命令如下:
# 确保你已经安装了python
pip install uvx
npx 则是随着 npm 一起安装的,一般情况下只要安装了 npm 则也就自动安装了 npx 工具。
这一步就比较简单,笔者是使用 DeepSeek 生成的前端页面,但是需要注意的是 Prompt 需要反复打磨,Deepseek 具备识图能力也可以直接发图给 Deepseek,记住需要在提示词里面告诉大模型,不要用 Vue 或者 React, 因为我们的功能非常简单,所以不需要上这种重量级框架,此外可以告诉大模型需要使用 EventSource 来接收服务端的数据以实现打字机效果
@Configuration
public class McpConfig {
@Bean
public List functionCallbacks(List mcpSyncClients) {
List list = new ArrayList();
for (McpSyncClient mcpSyncClient : mcpSyncClients) {
list.addAll(mcpSyncClient.listTools(null)
.tools()
.stream()
.map(tool -> new McpFunctionCallback(mcpSyncClient, tool))
.toList());
}
return list;
}
@Bean(destroyMethod = "close")
public McpSyncClient mcpFileSysClient() {
// 把这里的路径记得改为自己的真实路径
var stdioParams = ServerParameters.builder("D:\software\nodeJs\npx.cmd")
.args("-y", "@modelcontextprotocol/server-filesystem", "D:\工作日志")
.build();
var mcpClient = McpClient.using(new StdioClientTransport(stdioParams))
.requestTimeout(Duration.ofSeconds(10)).sync();
var init = mcpClient.initialize();
System.out.println("mcpFileSysClient loading init=" + init);
return mcpClient;
}
@Bean(destroyMethod = "close")
public McpSyncClient mcpDbClient() {
// 把这里的路径记得改为自己的真实路径
var stdioParams = ServerParameters.builder("D:\Program Files\python3.12.3\Scripts\uvx.exe")
.args("mcp-server-sqlite", "--db-path", "D:\work-space-study\spring-ai-mcp-demo\mcp-client\src\main\resources\test.db")
.build();
var mcpClient = McpClient.using(new StdioClientTransport(stdioParams))
.requestTimeout(Duration.ofSeconds(10)).sync();
var init = mcpClient.initialize();
System.out.println("mcpDbClient loading init=" + init);
return mcpClient;
}
}
在上面的代码中我们声明了两个 McpClient 使用 stdIo(标准 IO)的方式去链接 MCP 服务端,并通过 @Bean(destroyMethod = "close") 让 Spring 容器关闭的时候一并释放客户端占用,最后我们把两个服务端提供的 tools 全部转为 List 集合中,这个集合会在后面定义 ChatClient 中使用到。
public FileSysController(ChatClient.Builder chatClientBuilder,
List functionCallbacks) {
this.chatClient = chatClientBuilder
// 注意这里
.defaultFunctions(functionCallbacks.toArray(new McpFunctionCallback[0]))
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build())
.build();
}
在这里我们创建 ChatClient 并利用 Spring 的依赖注入能力注入了在之前 McpConfig 类中声明的 functionCallbacks,至此这个大模型 ChatClient 就具备了调用 MCP 服务端的能力。使用大模型生成的前端页面输入问题访问下面的 Controller 接口即可进行测试。
@RequestMapping(value = "/generate", method = RequestMethod.GET)
public Flux generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) {
response.setCharacterEncoding("UTF-8");
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
return this.chatClient.prompt(prompt)
.advisors(messageChatMemoryAdvisor).stream().chatResponse();
}
在前面的开发环节中,我们使用的是开源的 MCP 服务端,如何自己用 Java 语言开发一个 MCP 服务端程序呢?本节将给出一个案例介绍使用 SpringAI 开发 MCPserver 的主要步骤❝注意到这里你需要将你的 SpringAI 升级到 1.0.0-M6 版本,低版本的无法很好的支持 MCP 服务端调用❞
开发 MCPserver 需要引入 SpringAI 下面三种依赖中的一种,取决于你打算使用什么通信方式
org.springframework.ai
spring-ai-mcp-server-spring-boot-starter
org.springframework.ai
spring-ai-mcp-server-webmvc-spring-boot-starter
org.springframework.ai
spring-ai-mcp-server-webflux-spring-boot-starter
一个标准的 MCP 服务端程序需要包含三个主要信息分别为 Tools、Prompts、Resources资源(Resources):资源是 AI 可以读取的数据,比如文件内容、数据库查询结果或 API 的响应。 例如,AI 可能通过资源获取你的日历事件列表。工具(Tools):工具是 AI 可以调用的函数,用于执行特定操作,比如添加新任务或发送邮件,使用工具时,通常需要用户先批准,以确保安全。提示词(Prompts):提示词是服务器提供给 AI 的预写消息或模板,帮助 AI 理解如何使用资源和工具,例如,服务器可能告诉 AI:“你可以添加任务,试试说‘添加任务:买牛奶’”,从而帮助用户更轻松地完成任务。提示词虽然直接提供给 AI,但实际上是通过 AI 间接帮助用户,比如 AI 会根据提示词告诉用户如何操作。这三个要素在 SpringAI 中对应的 API 如下:
@Configuration
@EnableWebMvc
public class McpServerConfig implements WebMvcConfigurer {
@Bean
public ToolCallbackProvider bookServiceTools(BookService bookService) {
// …… 注册工具
return MethodToolCallbackProvider.builder().toolObjects(bookService).build();
}
@Bean
public List resourceRegistrations() {
// …… 注册资源
return List.of(resourceRegistration);
}
@Bean
public List promptRegistrations() {
// ……注册Prompt
return List.of(promptRegistration);
}
}
上面的代码中 BookService 可以是你自己写的一个服务,比如可以执行查询 DB 或者调用其他微服务等代码实现。最后还需要再 application.yaml 中进行服务暴露
# Using spring-ai-mcp-server-webmvc-spring-boot-starter
spring:
ai:
mcp:
server:
name: webmvc-mcp-server
version: 1.0.0
type: SYNC
sse-message-endpoint: /mcp/messages
至此你的服务端基本就开发完毕了。
在 4.1 中我们开发了我们自己的 MCP-server 那么怎么去让大模型去调用它呢?相比 SpringAI1.0.0-M5 版本的 McpClient 集成方式,在 SpringAI 1.0.0-M6 那就发生了翻天覆地的变化。总体来说配置变得更加简单:首先我们需要在 application.yaml 中做下述配置:
server:
port: 9999
spring:
ai:
mcp:
client:
enabled: true
name: call-mcp-server
sse:
connections:
server1:
# 这里是你的Mcp服务端的暴露地址
url: http://127.0.0.1:8080
dashscope:
api-key: {通义千问API-Key}
接着你需要在 pom 文件中引入下面的依赖:
org.springframework.ai
spring-ai-mcp-client-spring-boot-starter
1.0.0-SNAPSHOT
最后我们就可以直接使用了,详细可以看下下面的代码
@RestController
@RequestMapping("/dashscope/chat-client")
public class ChatController {
private final ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
// 直接使用自动装配就会自动注入McpClient
public ChatController(ChatClient.Builder chatClientBuilder,
List mcpSyncClients,
ToolCallbackProvider tools) {
this.chatClient = chatClientBuilder
// 注意这里API发生了变化 不再使用defaultFunctions
.defaultTools(tools)
.defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build())
.build();
}
@RequestMapping(value = "/generate_stream", method = RequestMethod.GET)
public Flux generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) {
response.setCharacterEncoding("UTF-8");
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
return this.chatClient.prompt(prompt)
.advisors(messageChatMemoryAdvisor)
.stream()
.chatResponse();
}
}
我们对比下之前在 SpringAI1.0.0-M5 的客户端实现方式就可以发现,现在我们不需要自己去定义 McpClient,由于我们引入了 spring-ai-mcp-client-spring-boot-starter 就可以自动识别我们配置的 mcpClient 信息,进行自动装配,此外我们也可以发现我们创建 ChatClient 使用的 API 也不再是 defaultFunctions 而是 defaultTools。那就有聪明的读者要问了那这样配置的话我的超时时间怎么配置呢?SpringAI 提供了 McpSyncClientCustomizer 接口这个接口里面提供了定制逻辑,可以实现该接口做一些 McpClient 定制工作。最后我们先启动 Mcp-Server 然后启动我们的 Mcp-client 应用程序,开始提问,实际效果如下:
在上图中大模型通过了 McpClient 调用了我在 Mcp-Server 提供了 BookService,然后按照名字查询到了对应的书籍,这些书籍信息是我在代码中预制的信息,大模型输出这个信息也很好的证明了确实调用到了我们的 Mcp 服务端接口。
本文介绍了下目前比较火热的 MCP 协议设计内容以及关键思想,同时基于 SpringAI 的两个版本分别给出了具体的实现方案,希望对各位读者有所帮助,由于 SpringAI 发展很快,内容如有错误欢迎沟通。也欢迎大家在评论区交流,源码已经放于 github.com/AHUCodingBe…[5] ,运行之前请前往阿里云百炼平台[6]申请自己的 API-Key,目前 SpringAI 最新 1.0.0-M6 版本的网上资源比较少,如果源码对你有帮助,欢迎 Star。内容创作不易,转载请务必注明出处,谢谢。点击关注公众号,“技术干货” 及时达!