Spring Boot 整合 Spring AI 与 MCP 开发智能体工具指南
一、引言
随着大语言模型(LLM)的普及,越来越多的开发者希望将其集成到自己的应用中。Spring AI 作为 Spring 生态下的 AI 集成框架,提供了便捷的方式来对接各种大模型。而 MCP(Model Context Protocol) 则是 Spring AI 中用于扩展模型能力的重要机制,允许我们通过自定义工具(Tool)增强模型的功能。
本文将详细介绍如何在 Spring Boot 项目中整合 Spring AI 和 MCP,开发自定义工具,并通过一个完整的示例展示整个流程。
二、环境准备
首先创建一个 Spring Boot 项目,添加以下依赖:
xml
<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 密钥:
properties
spring.ai.openai.api-key=your-openai-api-key
spring.ai.openai.model=gpt-3.5-turbo
三、理解工具(Tool)概念
在 Spring AI 中,工具是一个可以被大模型调用的功能单元。工具通常对应一个具体的函数或方法,用于执行特定的任务,如查询数据库、调用外部 API、执行计算等。
工具的核心特点:
- 有明确的功能和输入输出
- 可以被大模型理解和调用
- 可以组合使用,形成复杂的工作流
四、开发自定义工具
下面通过一个完整的例子,展示如何开发自定义工具。我们将创建一个简单的智能助手,支持数学计算、天气查询和翻译功能。
先给出整体流程图:
1. 数学计算工具
java
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. 天气查询工具
java
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. 翻译工具
java
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 {
// 这里使用简单的模拟,实际应用中可以调用翻译API
if ("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
注解的工具类,无需手动逐个注册。
java
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 {
@Bean
public ToolCallbackProvider toolCallbackProvider(Object[] toolBeans) {
// 自动注册所有扫描到的工具
return MethodToolCallbackProvider.builder()
.toolObjects(toolBeans)
.build();
}
}
六、配置AI客户端与动态工具描述
动态生成系统提示,自动包含所有已注册工具的信息:
java
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 {
@Bean
public 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();
}
}
七、智能工具调用处理器
创建一个服务来处理模型生成的工具调用:
java
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 {
// 解析工具调用JSON
Map<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;
}
}
八、创建控制器处理用户请求
java
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;
}
@PostMapping
public 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);
}
}
九、模型类
java
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. 数学计算测试
json
{
"message": "3乘以5等于多少?"
}
预期模型可能会生成工具调用:
[TOOL_CALL]
{
"name": "calculator",
"parameters": {
"expression": "3*5"
}
}
[/TOOL_CALL]
2. 天气查询测试
json
{
"message": "今天北京的天气如何?"
}
预期模型可能会生成工具调用:
[TOOL_CALL]
{
"name": "weather",
"parameters": {
"city": "北京"
}
}
[/TOOL_CALL]
3. 翻译测试
json
{
"message": "请将'Hello World'翻译成中文"
}
预期模型可能会生成工具调用:
[TOOL_CALL]
{
"name": "translator",
"parameters": {
"text": "Hello World",
"targetLanguage": "zh"
}
}
[/TOOL_CALL]
十一、总结
通过以上步骤,我们完成了一个完整的 Spring Boot 项目,整合了 Spring AI 和 MCP 框架,并开发了自定义工具。主要关键点包括:
- 工具开发 :使用
@Tool
注解标记工具方法,明确工具名称、描述和参数 - 自动扫描 :通过
@ComponentScan
和MethodToolCallbackProvider
自动注册所有工具 - 动态系统提示:根据已注册工具动态生成系统提示,无需手动维护
- 工具调用处理:统一处理模型生成的工具调用请求,执行对应工具并返回结果
这里的实现具有以下优势:
- 简化配置:无需手动注册每个工具类,系统自动发现并注册
- 可扩展性 :随时添加新工具,只需在工具包下创建带
@Tool
注解的类 - 维护方便:工具描述与工具实现保持在一起,易于理解和修改
- 灵活性:工具调用处理逻辑集中管理,便于扩展更复杂的工具调用链
希望这篇教程能帮助你入门 Spring Boot 与 Spring AI 开发,让你能够轻松开发自己的智能体工具!