观察Springboot AI-Function Tools 执行过程
- [Springboot AI-Function Tools 执行流程](#Springboot AI-Function Tools 执行流程)
Springboot AI-Function Tools 执行流程

构造一个Spring AI MCP Server
下载MCP工程后达成JAR,并复制jar的完整路径

观察Springboot AI应用程序将MCP(Stdio)结合Deepseek的过程
单元测试
java
@Test
public void mcp_model_stdio_call_tool_test(){
ServerParameters serverParameters = ServerParameters.builder("java")
.args("-Dspring.ai.mcp.server.stdio=true", "-jar", "/Users/jiangyangang/gitee_project/ai-mcp-server-demo/target/ai-mcp-server-demo-1.0.0.jar")
.env(null)
.build();
StdioClientTransport stdioClientTransport = new StdioClientTransport(serverParameters);
McpSyncClient mcpSyncClient = McpClient.sync(stdioClientTransport).requestTimeout(Duration.ofSeconds(50L)).build();
mcpSyncClient.initialize();
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl("https://api.deepseek.com/")
.completionsPath("v1/chat/completions")
.embeddingsPath("v1/embeddings")
.restClientBuilder(RestClient.builder().requestInterceptor(new RestClientFilter()))
.apiKey("sk-529fb4e1f9e448b6bbe2a252a0f5c796")
.build();
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("deepseek-chat")
.build())
.build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpSyncClient).getToolCallbacks())
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
String system = "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位";
String content = chatClient
.prompt(new SystemPromptTemplate(system).create())
.user("员工蒋颜刚的职位是什么?")
.call().content();
System.out.println(content);
}
第一次对模型提问
提问内容(缩略)
在提问时,spring ai会在promote中加入**tools**字段来告诉模型现在有哪些MCP服务可以调用,
parameters :mcp服务中需要提供哪些参数
name :mcp服务接口名称
description :mcp服务描述
required :哪些字段是必须要传的
json
{
"messages": [
{
"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位",
"role": "system"
},
{
"content": "员工蒋颜刚的职位是什么?",
"role": "user"
}
],
"model": "deepseek-chat",
"stream": false,
"tools": [
{
"type": "function",
"function": {
"description": "获取公司某个员工详细信息的接口",
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"parameters": {
"additionalProperties": false,
"type": "object",
"properties": {
"request": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "员工名字"
}
},
"required": [
"name"
]
}
},
"required": [
"request"
]
}
}
}
]
}
第一次模型返回
tool_calls :AI应用重新应该调用哪些mcp服务
name :mcp服务接口名字
arguments :mcp服务接口字段和对应的参数
tool_call_id : 调用MCP服务的请求流水ID,由大模型生成
json
{
"id": "6e069021-23b8-41d5-a8f7-44d06b20161a",
"object": "chat.completion",
"created": 1757908504,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "我来帮您查询员工蒋颜刚的职位信息。",
"tool_calls": [
{
"index": 0,
"id": "call_00_sVqnvhaz8xn3O8aAq8MM6FsB",
"type": "function",
"function": {
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"
}
}
]
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 225,
"completion_tokens": 38,
"total_tokens": 263,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 225
},
"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
第二次对模型提问
在第二次提问时,AI应用程序会新增一个角色tool 用来告诉模型上一笔调用mcp的结果,大模型根据tool_call_id 就能够进行工具的关联。
json
{
"messages": [
{
"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位",
"role": "system"
},
{
"content": "员工蒋颜刚的职位是什么?",
"role": "user"
},
{
"content": "我来帮您查询员工蒋颜刚的职位信息。",
"role": "assistant",
"tool_calls": [
{
"id": "call_00_sVqnvhaz8xn3O8aAq8MM6FsB",
"type": "function",
"function": {
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"
}
}
]
},
{
"content": "[{\"text\":\"{\\\"address\\\":\\\"深圳\\\",\\\"job\\\":\\\"后端开发工程师\\\"}\"}]",
"role": "tool",
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"tool_call_id": "call_00_sVqnvhaz8xn3O8aAq8MM6FsB"
}
],
"model": "deepseek-chat",
"stream": false,
"tools": [....]
}
第二次模型返回
json
{
"id": "9e376a0e-8cb7-41f2-be98-b973996d418f",
"object": "chat.completion",
"created": 1757908508,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "根据查询结果,员工蒋颜刚的职位是**后端开发工程师**,工作地点在深圳。"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 287,
"completion_tokens": 22,
"total_tokens": 309,
"prompt_tokens_details": {
"cached_tokens": 256
},
"prompt_cache_hit_tokens": 256,
"prompt_cache_miss_tokens": 31
},
"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
观察Springboot AI应用程序将MCP(sse)结合Deepseek的过程
修改并启动MCP服务
在ai-mcp-server-employee服务中加入webflux依赖
xml
<!-- sse 模式需要配置 webflux -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
之后启动MCP服务
单元测试
java
@Test
public void mcp_model_sse_call_tool_test(){
HttpClientSseClientTransport sseClientTransport = HttpClientSseClientTransport
.builder("http://127.0.0.1:9102/")
.sseEndpoint("sse")
.build();
McpSyncClient mcpSyncClient = McpClient.sync(sseClientTransport).requestTimeout(Duration.ofSeconds(5L)).build();
mcpSyncClient.initialize();
//
McpSchema.ListToolsResult listToolsResult = mcpSyncClient.listTools(null);
System.out.println(JSONObject.toJSONString(listToolsResult));
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl("https://api.deepseek.com/")
.completionsPath("v1/chat/completions")
.embeddingsPath("v1/embeddings")
.restClientBuilder(RestClient.builder().requestInterceptor(new RestClientFilter()))
.apiKey("sk-xxxx")
.build();
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("deepseek-chat")
.build())
.build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpSyncClient).getToolCallbacks())
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
String system = "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位";
String content = chatClient
.prompt(new SystemPromptTemplate(system).create())
.user("员工蒋颜刚的职位是什么?")
.call().content();
System.out.println(content);
}
第一次对模型提问
提问内容(缩略)
在提问时,spring ai会在promote中加入**tools**字段来告诉模型现在有哪些MCP服务可以调用,
parameters :mcp服务中需要提供哪些参数
name :mcp服务接口名称
description :mcp服务描述
required :哪些字段是必须要传的
json
{
"messages": [
{
"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位",
"role": "system"
},
{
"content": "员工蒋颜刚的职位是什么?",
"role": "user"
}
],
"model": "deepseek-chat",
"stream": false,
**"tools": [
{
"type": "function",
"function": {
"description": "获取公司某个员工详细信息的接口",
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"parameters": {
"additionalProperties": false,
"type": "object",
"properties": {
"request": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "员工名字"
}
},
"required": [
"name"
]
}
},
"required": [
"request"
]
}
}
}
]**
}
第一次模型返回
tool_calls :AI应用重新应该调用哪些mcp服务
name :mcp服务接口名字
arguments :mcp服务接口字段和对应的参数
tool_call_id : 调用MCP服务的请求流水ID,由大模型生成
json
{
"id": "475fe411-4ac4-44d3-bc09-71a3ac895a73",
"object": "chat.completion",
"created": 1757683715,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "我来帮您查询蒋颜刚的职位信息。",
**"tool_calls": [
{
"index": 0,
"id": "call_00_5JnhTbbmeICprFEMbzgnNkxJ",
"type": "function",
"function": {
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"
}
}
]**
},
"logprobs": null,
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 225,
"completion_tokens": 37,
"total_tokens": 262,
"prompt_tokens_details": {
"cached_tokens": 192
},
"prompt_cache_hit_tokens": 192,
"prompt_cache_miss_tokens": 33
},
"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
第二次对模型提问
在第二次提问时,AI应用程序会新增一个角色**tool** 用来告诉模型上一笔调用mcp的结果,大模型根据**tool_call_id** 就能够进行工具的关联。
json
{
"messages": [
{
"content": "你是一个查询员工信息的小助手,你可以通过我给你的MCP接口【getSalaryDetailInfo】来查询员工的信息,包括地址和职位",
"role": "system"
},
{
"content": "员工蒋颜刚的职位是什么?",
"role": "user"
},
{
"content": "我来帮您查询蒋颜刚的职位信息。",
"role": "assistant",
"tool_calls": [
{
"id": "call_00_frX3BDPuCRge9oBnwKWgBiCY",
"type": "function",
"function": {
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"arguments": "{\"request\": {\"name\": \"蒋颜刚\"}}"
}
}
]
},
{
"content": "[{\"text\":\"{\\\"address\\\":\\\"深圳\\\",\\\"job\\\":\\\"后端开发工程师\\\"}\"}]",
"role": "tool",
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"tool_call_id": "call_00_frX3BDPuCRge9oBnwKWgBiCY"
}
],
"model": "deepseek-chat",
"stream": false,
"tools": [
{
"type": "function",
"function": {
"description": "获取公司某个员工详细信息的接口",
"name": "JavaSDKMCPClient_getSalaryDetailInfo",
"parameters": {
"additionalProperties": false,
"type": "object",
"properties": {
"request": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "员工名字"
}
},
"required": [
"name"
]
}
},
"required": [
"request"
]
}
}
}
]
}
第二次模型响应
json
{
"id": "f7d8c859-1858-4421-9ea3-361e754ed4ee",
"object": "chat.completion",
"created": 1757684702,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "根据查询结果,员工蒋颜刚的职位是**后端开发工程师**,工作地点在深圳。"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 286,
"completion_tokens": 22,
"total_tokens": 308,
"prompt_tokens_details": {
"cached_tokens": 256
},
"prompt_cache_hit_tokens": 256,
"prompt_cache_miss_tokens": 30
},
"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}
调用时序图

附录
POM依赖
xml
<!-- spring ai 1.0.0 https://central.sonatype.com/artifact/org.springframework.ai/spring-ai-bom -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</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-vector-store-pgvector</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.28</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>