观察Springboot AI-Function Tools 执行过程

观察Springboot AI-Function Tools 执行过程

Springboot AI-Function Tools 执行流程

构造一个Spring AI MCP Server

一个简易的查询公司员工的MCP Server工程Demo

下载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>
相关推荐
kyle-fang1 小时前
pytorch-张量转换
人工智能·pytorch·python
小蜜蜂dry2 小时前
从零到一,记录docker运行一个web端项目
后端
甄心爱学习2 小时前
计算机视觉11-相机模型与多视几何
人工智能·数码相机·计算机视觉
qunshankeji2 小时前
草莓病害智能识别与分类_Cascade-RCNN_HRNetV2p-W18-20e_COCO实现
人工智能·数据挖掘
聆风吟º2 小时前
【Spring Boot 报错已解决】Spring Boot开发避坑指南:Hibernate实体类主键配置详解与异常修复
android·spring boot·hibernate
CloudWeGo2 小时前
用 Eino ADK 构建你的第一个 AI 智能体:从 Excel Agent 实战开始
人工智能·开源·github
Blossom.1182 小时前
AI Agent记忆系统深度实现:从短期记忆到长期人格的演进
人工智能·python·深度学习·算法·决策树·机器学习·copilot
Felix_XXXXL2 小时前
MySQL----case的用法
java·后端