Spring AI Alibaba框架整合百炼大模型平台
文章目录
版本springboot 3.5.x, jdk17, springaiAlibaba1.1.2.2
MCP服务
MCP (Model Context Protocol) 被称为"AI 的 USB-C 接口",它标准化了 AI 模型与外部数据源(数据库、API、文件系统)的连接方式。
1.核心概念与架构
- MCP Server (服务端):负责"暴露能力"。它将本地的工具(如查询天气、读取 SQL)封装成标准接口。
- MCP Client (客户端) :负责"使用能力"。通常集成在 AI 应用(如 Spring AI 的
ChatClient)中,它发现 Server 提供的工具,并在大模型需要时自动调用。 - 通信协议 :支持
stdio(本地进程间通信) 和SSE/HTTP(远程网络通信)。
2.构建 MCP Server
将"天气查询"功能发布为 MCP 服务。
1.服务端依赖
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
2.服务端yml配置
- SSE模式:通过URL连接的sse端点连接,有中断风险。同时你还需要检查服务端是否在线、端点是否正确等。
- STDIO模式 :客户端启动时会自动拉起你的
WeatherServicejar包作为子进程。服务端的log.info输出直接可见,连接几乎零中断,调试时所有请求/响应都清晰可控。
1.sse模式
通过客户端url调用服务
yml
spring:
ai:
mcp:
server:
enabled: true
name: weather-mcp-server
version: 0.0.1
type: SYNC
protocol: SSE
# sse
stdio: false
2.stdio模式
通过打成jar包执行
yml
spring:
ai:
mcp:
server:
enabled: true
name: weather-mcp-server
version: 0.0.1
type: SYNC
stdio: true
# stdio
main:
web-application-type: none # 关键禁用内嵌Web服务器,不再需要网络端口
3.编写查询服务
spring-ai 1.1.2版本使用@McpTool和@McpArg注解,也可以使用之前的@Tool注解
java
@Slf4j
@Service
public class WeatherService {
/**
* 使用 @McpTool 注解定义工具方法,AI 模型会自动发现并调用。
* 根据城市名称获取天气信息
* @param city 城市名称,如:北京
*/
@McpTool(description = "根据城市名称获取天气信息")
public String getWeatherByCity(@McpArg(description = "城市名称") String city) {
log.info("✅ MCP 工具被调用,城市: {}", city);
return city + " 今天天气晴朗,气温 25℃,适宜出行。";
}
}
1.0.0版本需要如下注册,1.1.2版本不用,会自动扫McpTool注解
java
@Configuration
public class McpServerConfig {
/**
* 将 WeatherService 注册为 MCP 工具提供者
*/
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
}
4.启动或打包
3.MCP Client调用
1.客户端依赖
xml
<!-- Spring AI MCP Client -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
2.yml配置
1.sse方式
yml
spring:
ai:
mcp:
client:
enabled: true
# 配置远程 SSE 连接
sse:
connections:
weather-mcp: #自定义服务名
url: http://localhost:8137
sse-endpoint: /sse # SSE 端点(可选,默认 /sse)
2.stdio方式
yml和配置文件可以同时使用,可以内部应用采用yml方式,外部用 servers-configuration文件
yml
spring:
ai:
mcp:
client:
enabled: true
stdio:
servers-configuration: classpath:mcp-test-servers.json
# 保留YAML (connections):用于管理你在开发或紧密维护的内部MCP服务,这些服务的配置可能随项目迭代而频繁修改,放在主配置文件中更方便。
# 使用外部JSON (servers-configuration):用于引入相对固定或第三方的公开MCP服务,如百度地图、高德等。将它们的配置隔离在独立的JSON中,层次分明,便于维护。
# 如果YAML的connections和外部JSON的servers-configuration中定义了同名的连接,它两不会相互覆盖。系统会将它们视为两个独立的连接定义,
# 很可能会在启动时因为发现名称冲突而报错。因此,使用混合配置的关键是确保所有服务器连接拥有唯一的名称。
connections:
#自定义mcp查询服务
springboot-ai-alibaba-mcp-server:
command: java
args:
- "-Dspring.ai.mcp.server.stdio=true"
- "-Dspring.main.web-application-type=none"
- "-Dlogging.pattern.console="
- "-jar"
- "springboot-ai-alibaba-mcp-server/target/springboot-ai-alibaba-mcp-server-0.0.1-SNAPSHOT.jar"
env: {}
# 新增的高德地图工具
amap-maps:
command: npx.cmd # Mac/Linux 改成 npx
args:
- "-y"
- "@amap/amap-maps-mcp-server"
env:
AMAP_MAPS_API_KEY: ${AMAP_MAPS_API_KEY}
3.stdio方式配置文件
json
{
"mcpServers": {
"amap-maps": {
"command": "npx.cmd",
"args": [
"-y",
"@amap/amap-maps-mcp-server"
],
"env": {
"AMAP_MAPS_API_KEY": "你的 API Key"
}
},
"springboot-ai-alibaba-mcp-server": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"springboot-ai-alibaba-mcp-server/target/springboot-ai-alibaba-mcp-server-0.0.1-SNAPSHOT.jar"
],
"env": {}
}
}
}
4.调用测试
java
@RestController
@RequestMapping("/learn/mcp")
public class LearningMcpController {
private final ChatClient.Builder clientBuilder;
@Resource
private ToolCallbackProvider toolCallbackProvider;
public LearningMcpController(ChatClient.Builder clientBuilder) {
this.clientBuilder = clientBuilder;
}
@GetMapping("/ask")
public Map<String, String> ask(@RequestParam String question) {
System.out.println("🔧 当前可用MCP工具数量: " + toolCallbackProvider.getToolCallbacks().length);
String answer = clientBuilder.build().prompt()
.system("你是 MCP 工具调度助手,优先通过可用工具获取数据后再回答。")
.user(question)
.toolCallbacks(toolCallbackProvider)
.call()
.content();
return Map.of("question", question, "answer", answer);
}
}
