Spring Boot 整合 Spring AI 与 MCP 开发智能体工具指南

Spring Boot 整合 Spring AI 与 MCP 开发智能体工具指南

一、引言

随着大语言模型(LLM)的普及,越来越多的开发者希望将其集成到自己的应用中。Spring AI 作为 Spring 生态下的 AI 集成框架,提供了便捷的方式来对接各种大模型。而 MCP(Model Context Protocol) 则是 Spring AI 中用于扩展模型能力的重要机制,允许我们通过自定义工具(Tool)增强模型的功能。

本文将详细介绍如何在 Spring Boot 项目中整合 Spring AI 和 MCP,开发自定义工具,并通过一个完整的示例展示整个流程。

二、环境准备

首先创建一个 Spring Boot 项目,添加以下依赖:

<dependencies><!-- Spring Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring AI --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-core</artifactId><version>1.0.0-M6</version></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai</artifactId><version>1.0.0-M6</version></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-server-webmvc</artifactId><version>1.0.0-M6</version></dependency><!-- 工具库 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>

application.properties 中配置 OpenAI API 密钥:

spring.ai.openai.api-key=your-openai-api-key
spring.ai.openai.model=gpt-3.5-turbo
三、理解工具(Tool)概念

在 Spring AI 中,工具是一个可以被大模型调用的功能单元。工具通常对应一个具体的函数或方法,用于执行特定的任务,如查询数据库、调用外部 API、执行计算等。

工具的核心特点:

  • 有明确的功能和输入输出
  • 可以被大模型理解和调用
  • 可以组合使用,形成复杂的工作流
四、开发自定义工具

下面通过一个完整的例子,展示如何开发自定义工具。我们将创建一个简单的智能助手,支持数学计算、天气查询和翻译功能。
先给出整体流程图:
流程图

1. 数学计算工具
package com.example.aiagent.tools;import org.springframework.ai.tool.Tool;
import org.springframework.stereotype.Component;@Component
public class CalculatorTool {@Tool(name = "calculator",description = "执行基本的数学计算,支持加(+), 减(-), 乘(*), 除(/)运算",parameters = {@ToolParam(name = "expression", description = "数学表达式,如 '2+3'", type = "string")})public String calculate(String expression) {try {// 简单的表达式解析if (expression.contains("+")) {String[] parts = expression.split("\\+");double a = Double.parseDouble(parts[0].trim());double b = Double.parseDouble(parts[1].trim());return String.valueOf(a + b);} else if (expression.contains("-")) {String[] parts = expression.split("-");double a = Double.parseDouble(parts[0].trim());double b = Double.parseDouble(parts[1].trim());return String.valueOf(a - b);} else if (expression.contains("*")) {String[] parts = expression.split("\\*");double a = Double.parseDouble(parts[0].trim());double b = Double.parseDouble(parts[1].trim());return String.valueOf(a * b);} else if (expression.contains("/")) {String[] parts = expression.split("/");double a = Double.parseDouble(parts[0].trim());double b = Double.parseDouble(parts[1].trim());return String.valueOf(a / b);} else {return "不支持的表达式: " + expression;}} catch (Exception e) {return "计算错误: " + e.getMessage();}}
}
2. 天气查询工具
package com.example.aiagent.tools;import org.springframework.ai.tool.Tool;
import org.springframework.stereotype.Component;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;@Component
public class WeatherTool {private static final String API_KEY = "your-weather-api-key";private static final String API_URL = "https://api.weatherapi.com/v1/current.json";@Tool(name = "weather",description = "查询指定城市的当前天气",parameters = {@ToolParam(name = "city", description = "城市名称,如 '北京'", type = "string")})public String getWeather(String city) {try {URL url = new URL(API_URL + "?key=" + API_KEY + "&q=" + city);HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));StringBuilder response = new StringBuilder();String line;while ((line = reader.readLine()) != null) {response.append(line);}reader.close();return response.toString();} catch (Exception e) {return "获取天气失败: " + e.getMessage();}}
}
3. 翻译工具
package com.example.aiagent.tools;import org.springframework.ai.tool.Tool;
import org.springframework.stereotype.Component;@Component
public class TranslationTool {@Tool(name = "translator",description = "将文本从一种语言翻译成另一种语言",parameters = {@ToolParam(name = "text", description = "需要翻译的文本", type = "string"),@ToolParam(name = "targetLanguage", description = "目标语言代码,如 'en' 表示英语,'zh' 表示中文", type = "string")})public String translate(String text, String targetLanguage) {try {// 这里使用简单的模拟,实际应用中可以调用翻译APIif ("en".equalsIgnoreCase(targetLanguage)) {return "This is a simulated translation result for: " + text;} else if ("zh".equalsIgnoreCase(targetLanguage)) {return "这是一个模拟的翻译结果,原文是: " + text;} else {return "暂不支持的语言: " + targetLanguage;}} catch (Exception e) {return "翻译失败: " + e.getMessage();}}
}
五、自动扫描与注册工具

Spring AI 提供了便捷的方式来自动扫描和注册所有带有 @Tool 注解的工具类,无需手动逐个注册。
注册工具

package com.example.aiagent.config;import org.springframework.ai.mcp.server.tool.MethodToolCallbackProvider;
import org.springframework.ai.mcp.server.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.ai.tool.Tool;@Configuration
// 自动扫描指定包下所有带有 @Tool 注解的类
@ComponentScan(basePackages = "com.example.aiagent.tools",includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Tool.class)
)
public class ToolAutoScanConfig {@Beanpublic ToolCallbackProvider toolCallbackProvider(Object[] toolBeans) {// 自动注册所有扫描到的工具return MethodToolCallbackProvider.builder().toolObjects(toolBeans).build();}
}
六、配置AI客户端与动态工具描述

动态生成系统提示,自动包含所有已注册工具的信息:

package com.example.aiagent.config;import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.mcp.server.tool.ToolCallback;
import org.springframework.ai.mcp.server.tool.ToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class AIClientConfig {@Beanpublic ChatClient chatClient(ChatClient.Builder builder, ToolCallbackProvider toolCallbackProvider) {// 动态生成系统提示,包含所有可用工具信息String systemPrompt = generateSystemPrompt(toolCallbackProvider);return builder.defaultSystem(systemPrompt).defaultTools(toolCallbackProvider).build();}private String generateSystemPrompt(ToolCallbackProvider toolCallbackProvider) {StringBuilder sb = new StringBuilder();sb.append("你是一个智能助手,可以使用以下工具来回答用户问题:\n\n");// 动态添加所有工具描述toolCallbackProvider.getToolCallbacks().forEach(callback -> {sb.append("- ").append(callback.getName()).append(": ").append(callback.getDescription()).append("\n");});sb.append("\n如果你需要使用工具,请按照以下格式返回:\n");sb.append("[TOOL_CALL]\n");sb.append("{\n");sb.append("    \"name\": \"工具名称\",\n");sb.append("    \"parameters\": {\n");sb.append("        \"参数名\": \"参数值\"\n");sb.append("    }\n");sb.append("}\n");sb.append("[/TOOL_CALL]\n\n");sb.append("如果不需要工具,直接回答用户问题。");return sb.toString();}
}
七、智能工具调用处理器

创建一个服务来处理模型生成的工具调用:
工具调用处理器

package com.example.aiagent.service;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.mcp.server.tool.ToolCallback;
import org.springframework.ai.mcp.server.tool.ToolCallbackRegistry;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;@Service
public class ToolInvocationService {private static final Pattern TOOL_CALL_PATTERN = Pattern.compile("\\[TOOL_CALL\\](.*?)\\[/TOOL_CALL\\]", Pattern.DOTALL);private final ToolCallbackRegistry toolCallbackRegistry;private final ObjectMapper objectMapper;public ToolInvocationService(ToolCallbackRegistry toolCallbackRegistry, ObjectMapper objectMapper) {this.toolCallbackRegistry = toolCallbackRegistry;this.objectMapper = objectMapper;}/*** 检查并处理工具调用*/public String processToolCalls(String modelResponse) {Matcher matcher = TOOL_CALL_PATTERN.matcher(modelResponse);while (matcher.find()) {String toolCallJson = matcher.group(1).trim();try {// 解析工具调用JSONMap<String, Object> toolCall = objectMapper.readValue(toolCallJson, HashMap.class);String toolName = (String) toolCall.get("name");Map<String, Object> parameters = (Map<String, Object>) toolCall.get("parameters");// 调用对应的工具ToolCallback callback = toolCallbackRegistry.getToolCallback(toolName);if (callback != null) {Object result = callback.invoke(parameters);// 将工具调用结果替换回响应中modelResponse = modelResponse.replace("[TOOL_CALL]" + toolCallJson + "[/TOOL_CALL]","工具[" + toolName + "]执行结果: " + result);} else {modelResponse = modelResponse.replace("[TOOL_CALL]" + toolCallJson + "[/TOOL_CALL]","错误: 未找到工具[" + toolName + "]");}} catch (Exception e) {modelResponse = modelResponse.replace("[TOOL_CALL]" + toolCallJson + "[/TOOL_CALL]","工具调用错误: " + e.getMessage());}// 重新匹配,因为内容已被替换matcher = TOOL_CALL_PATTERN.matcher(modelResponse);}return modelResponse;}
}
八、创建控制器处理用户请求
package com.example.aiagent.controller;import com.example.aiagent.model.ChatRequest;
import com.example.aiagent.model.ChatResponse;
import com.example.aiagent.service.ToolInvocationService;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.prompt.Prompt;
import org.springframework.ai.prompt.PromptTemplate;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/api/chat")
public class ChatController {private final ChatClient chatClient;private final ToolInvocationService toolInvocationService;public ChatController(ChatClient chatClient, ToolInvocationService toolInvocationService) {this.chatClient = chatClient;this.toolInvocationService = toolInvocationService;}@PostMappingpublic ChatResponse chat(@RequestBody ChatRequest request) {// 创建用户提示Map<String, Object> modelParams = new HashMap<>();modelParams.put("userInput", request.getMessage());PromptTemplate promptTemplate = new PromptTemplate("{userInput}", modelParams);Prompt prompt = new Prompt(promptTemplate.create());// 调用AI模型ChatResponse response = chatClient.generate(prompt);String content = response.getGeneration().getContent();// 处理工具调用content = toolInvocationService.processToolCalls(content);return new ChatResponse(content);}
}
九、模型类
package com.example.aiagent.model;import lombok.Data;@Data
public class ChatRequest {private String message;
}@Data
public class ChatResponse {private String content;public ChatResponse(String content) {this.content = content;}
}
十、测试工具功能

完成上述代码后,你可以通过发送 POST 请求到 /api/chat 测试工具功能。以下是几个测试示例:

1. 数学计算测试
{"message": "3乘以5等于多少?"
}

预期模型可能会生成工具调用:

[TOOL_CALL]
{"name": "calculator","parameters": {"expression": "3*5"}
}
[/TOOL_CALL]
2. 天气查询测试
{"message": "今天北京的天气如何?"
}

预期模型可能会生成工具调用:

[TOOL_CALL]
{"name": "weather","parameters": {"city": "北京"}
}
[/TOOL_CALL]
3. 翻译测试
{"message": "请将'Hello World'翻译成中文"
}

预期模型可能会生成工具调用:

[TOOL_CALL]
{"name": "translator","parameters": {"text": "Hello World","targetLanguage": "zh"}
}
[/TOOL_CALL]
十一、总结

通过以上步骤,我们完成了一个完整的 Spring Boot 项目,整合了 Spring AI 和 MCP 框架,并开发了自定义工具。主要关键点包括:

  1. 工具开发:使用 @Tool 注解标记工具方法,明确工具名称、描述和参数
  2. 自动扫描:通过 @ComponentScanMethodToolCallbackProvider 自动注册所有工具
  3. 动态系统提示:根据已注册工具动态生成系统提示,无需手动维护
  4. 工具调用处理:统一处理模型生成的工具调用请求,执行对应工具并返回结果

这里的实现具有以下优势:

  • 简化配置:无需手动注册每个工具类,系统自动发现并注册
  • 可扩展性:随时添加新工具,只需在工具包下创建带 @Tool 注解的类
  • 维护方便:工具描述与工具实现保持在一起,易于理解和修改
  • 灵活性:工具调用处理逻辑集中管理,便于扩展更复杂的工具调用链

希望这篇教程能帮助你入门 Spring Boot 与 Spring AI 开发,让你能够轻松开发自己的智能体工具!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/85443.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/85443.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源项目】GraphRAG Agent:可解释、可推理的下一代智能问答系统

GraphRAG Agent&#xff1a;可解释、可推理的下一代智能问答系统 ​​引言​​ 传统 RAG&#xff08;检索增强生成&#xff09;系统常因“黑盒推理”和上下文断裂被诟病。微软开源的 GraphRAG 框架尝试用知识图谱解决这一问题&#xff0c;而​​Graph RAG Agent​​&#xff0…

【论文笔记】【强化微调】AgentThink:思维链推理 + 工具调用

AgentThink: A Unified Framework for Tool-Augmented Chain-of-Thought Reasoning in Vision-Language Models for Autonomous Driving 1. 引述 这是一篇自动驾驶领域的论文。我对这篇论文主要感兴趣的点在于其对于工具调用&#xff08;Tool Call&#xff09;的设计。这一点同…

前端页面Javascript进阶DOM与BOM

一、DOM基础概念 DOM 是文档对象模型&#xff0c;提供编程接口用于操作 HTML 和 XML 文档。它将文档表示为节点树&#xff0c;每个节点代表文档的一部分&#xff0c;如元素、文本、属性等。通过 DOM&#xff0c;开发者可以访问和修改文档的结构、样式与内容。 文档节点类型 …

AWS CloudFormation深度解析:构建现代云原生应用基础设施

在现代云原生应用开发中,基础设施即代码(Infrastructure as Code, IaC)已成为标准实践。本文将深入解析一个完整的AWS CloudFormation模板,该模板为GlowChat Connector应用构建了生产级的基础设施。 模板概述 这个CloudFormation模板是一个两部分部署架构中的第一部分,专…

Oracle 查看所有表的字段名、数据类型及长度

1.只查看某个特定表的字段名 SELECT column_name, data_type, data_length FROM user_tab_columns WHERE table_name 你的表名 -- 注意大写 ORDER BY column_id;2.查看当前用户下所有表的字段名 SELECT table_name, column_name, data_type, data_length FROM user_tab_colu…

创客匠人分享知识付费监管升级下的行业价值重构:从合规挑战到发展机遇的实践路径

引言&#xff1a;监管政策背后的行业逻辑转向 知识付费领域的监管体系升级&#xff0c;本质上是对行业发展路径的重新校准。随着 "内容产品需具备知识沉淀载体" 等新规落地&#xff0c;行业正在经历从 "流量驱动型增长" 到 "价值驱动型发展" 的…

边缘计算的认识和应用

边缘计算 边缘计算是一种分布式计算范式&#xff0c;它将计算能力和数据存储放置在离数据源更近的位置&#xff0c;而不是依赖于集中式的数据中心。通过在“边缘”进行数据处理&#xff0c;边缘计算可以减少延迟、提高响应速度、节省带宽&#xff0c;并增强数据隐私和安全性。…

Arduino R4 WIFI横向滚动显示16×16LED屏

实现一个从左向右横向滚动的"吉祥如意"显示效果。 arduino r4 WiFi滚动显示16*16led #include <SPI.h>// 引脚定义 const int RowA 2, RowB 3, RowC 4, RowD 5; const int OE 6; const int LATCH 10;// 字模数据 (吉祥如意) const PROGMEM byte characte…

html css js网页制作成品——HTML+CSS+js力学光学天文网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

嵌入式开发之freeRTOS移植

FreeRTOS 是一款广泛应用于嵌入式系统的开源实时操作系统&#xff08;RTOS&#xff09;&#xff0c;其移植过程需要结合具体硬件平台和编译器进行适配。以下是 FreeRTOS 移植的详细步骤和关键注意事项&#xff1a; 一、移植前的准备工作 1. 硬件平台确认 处理器架构&#xf…

【算法 day07】LeetCode 344.反转字符串 | 541. 反转字符串II | 卡码网:54.替换数字

344.反转字符串 题目链接 | 文档讲解 |视频讲解 : 链接 1.思路&#xff1a; 采用双指针&#xff0c;left从0开始移动,right从尾元素进行移动 循环判断条件&#xff1a;left< right,边界值使用举例法&#xff0c;eg: [ h ,e ,l,o ]偶数个不会相遇, [h ,e ,l ,l ,o ]奇数个&…

从检索到生成:RAG 如何重构大模型的知识边界?

引言&#xff1a;知识边界的突破与重构 在人工智能技术快速发展的今天&#xff0c;大型语言模型&#xff08;LLMs&#xff09;已经展现出强大的文本生成和理解能力。然而&#xff0c;这些模型在实际应用中仍面临着知识时效性、事实准确性和可溯源性等核心挑战。检索增强生成&a…

前端基础知识CSS系列 - 05(BFC的理解)

一、是什么 我们在页面布局的时候&#xff0c;经常出现以下情况&#xff1a; 这个元素高度怎么没了&#xff1f;这两栏布局怎么没法自适应&#xff1f;这两个元素的间距怎么有点奇怪的样子&#xff1f;...... 原因是元素之间相互的影响&#xff0c;导致了意料之外的情况&…

Prompt Engineering 学习指南:从入门到精通的最佳路径与资源

本 Prompt Engineering 技术报告,旨在提供一个从入门到精通的清晰学习路径、核心方案,并附上最关键的 GitHub 仓库资源。您可以将此报告作为快速提升 Prompt 能力的“速查手册”和“成长地图”。 Prompt Engineering 学习指南:从入门到精通的最佳路径与资源 技术报告摘要 (…

fastmcp MCPConfig多服务器使用案例;sse、stdio、streamable-http使用

1、sse、stdio、streamable-http使用 参考&#xff1a;https://gofastmcp.com/deployment/running-server#the-run-method stdio本地使用&#xff1b;sse、streamable-http远程调用&#xff08; Streamable HTTP—New in version: 2.3.0&#xff09; 调用&#xff1a; stdio、…

网站服务器被DDOS攻击打不开,是要换高防服务器还是加CDN能防护住?

高防云服务器、高防 IP 和高防 CDN 作为常见应对网络攻击的重要利器&#xff0c;它们各自有着独特的特点和应用场景&#xff0c;从技术架构看&#xff0c;高防云服务器是资源型防护&#xff0c;深度整合计算与防御资源&#xff1b;高防IP是流量型防护&#xff0c;以代理模式实现…

深入解析原型模式:从理论到实践的全方位指南

深入解析原型模式&#xff1a;从理论到实践的全方位指南 引言&#xff1a;为什么需要原型模式&#xff1f; 在软件开发过程中&#xff0c;对象创建是一个频繁且关键的操作。传统方式&#xff08;如直接使用new关键字&#xff09;在某些场景下会显得效率低下且不够灵活。想象这…

HuggingFace镜像配置失效问题深度解析:Python模块导入机制的陷阱

前言 在使用HuggingFace的transformers和datasets库时&#xff0c;国内用户经常会遇到网络连接问题。虽然设置了镜像源环境变量&#xff0c;但仍然报错无法连接到huggingface.co。本文将深入分析这个问题的根因&#xff0c;并从Python模块导入机制的角度解释为什么环境变量设置…

leetcode146-LRU缓存

leetcode 146 思路 什么是LRU缓存&#xff1f; LRU&#xff08;Least Recently Used&#xff09;缓存是一种常见的缓存淘汰策略&#xff0c;核心思想是&#xff1a;当缓存容量满时&#xff0c;优先淘汰最久未使用的数据。LeetCode 146 题要求实现一个支持get和put操作的 LR…

MQTT:构建高效物联网通信的轻量级协议

MQTT – 轻量级物联网消息推送协议 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是机器对机器(M2M)/物联网(IoT)连接协议。它被设计为一个极其轻量级的发布/订阅消息传输协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用&#xf…