文章标题:
SpringBoot整合MCP:利用国产大模型DeepSeek实现数据库查询(无需ollama)
文章内容:
本文讲解了怎样通过SpringBoot整合MCP(Model Context Protocol)框架来对接国产大模型DeepSeek,并且实现工具函数的扩展功能。通过完整的案例来展示人工智能能力与业务系统的深度集成。最终达成自然语言提问后查询数据库,再以自然语言输出的流程。
MCP概述
我们知道,人工智能模型要连接外部应用来扩展其功能。然而,每个外部应用的接口各不相同,若要接入十个应用,就得编写十种接入代码,十分繁琐。而且,一旦更换模型,可能所有的接入代码都得重新编写。
基于这样的情况,Anthropic公司在2024年11月提出了MCP协议。外部应用只需支持该协议,提供一个MCP接口(也就是MCP服务器),那么人工智能模型就能以统一的格式进行接入,无需了解外部应用的接入细节。所以,MCP可被视为人工智能与外部应用之间的适配层。对于人工智能来说,只要安装了某个应用的MCP服务器,就能接入该应用,仅需少量配置项,无需编写其他代码。由于MCP解决了人工智能应用接入的痛点,诞生至今仅半年就变得极为流行,就连Anthropic的竞争对手OpenAI公司也公开表示支持,网上开源的MCP服务器项目已有上万个。
一、环境准备
1.1 依赖配置
<!-- 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-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
1.2 DeepSeek核心配置,需使用deepseek-chat模型,其他模型暂不支持工具调用
spring.ai.openai.enabled=true
spring.ai.openai.base-url=https://api.deepseek.com
spring.ai.openai.api-key=your-api-key
spring.ai.openai.chat.options.model=deepseek-chat
二、MCP Server配置
2.1 工具回调注册
@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider studentToolCallbackProvider(StudentService studentService) {
// 打印传入的 studentService 实例
System.out.println("studentService 实例: " + studentService.getClass().getName());
MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
.toolObjects(studentService)
.build();
// 通过反射获取工具名称
Arrays.stream(provider.getToolCallbacks())
.map(ToolCallback::getName)
.forEach(name -> System.out.println("注册的工具Registered Tool: " + name));
return provider;
}
}
2.2 工具函数实现
@Slf4j
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
public StudentServiceImpl() {
System.out.println("StudentServiceImpl 实例已创建");
}
@Override
@Tool(name = "queryListByName", description = "根据学生姓名模糊查询学生信息")
public List<StudentVO> queryListByName(@ToolParam(description = "学生姓名") String name){
//TODO 处理查询条件
LambdaQueryWrapper<Student> wrapper = Wrappers.lambdaQuery();
wrapper.eq(Student::getName,name);
List<Student> list=studentMapper.selectList(wrapper);
List<StudentVO> dataVOList = new ArrayList();
if (CollectionUtils.isNotEmpty(list)) {
list.forEach(p -> dataVOList.add(BeanUtil.toBean(p, StudentVO.class)));
}
return dataVOList;
}
@Override
@Tool(name = "pageInfo", description = "根据条件分页查询学生信息")
public IPage<StudentVO> pageInfo(@ToolParam(description = "学生分页信息") StudentPageDTO pageDTO){
IPage<Student> page = new Page<>();
page.setCurrent(pageDTO.getCurrent());
page.setSize(pageDTO.getSize());
//TODO 处理查询条件
LambdaQueryWrapper<Student> wrapper = Wrappers.lambdaQuery();
wrapper.eq(Student::getSex,pageDTO.getSex());
wrapper.like(Student::getName,pageDTO.getName());
wrapper.like(Student::getClassRoom,pageDTO.getClassRoom());
wrapper.like(Student::getAddress,pageDTO.getAddress());
wrapper.orderByDesc(Student::getId);
IPage<Student> pageList = studentMapper.selectPage(page, wrapper);
List<StudentVO> voList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(pageList.getRecords())) {
pageList.getRecords().forEach(v -> {
voList.add(BeanUtil.toBean(v, StudentVO.class));
});
}
Page<StudentVO> result = new Page<>();
BeanUtils.copyProperties(page,result);
result.setRecords(voList);
return result;
}
}
三、ChatClient配置
3.1 客户端构建
@Configuration
public class ChatClientConfig {
/**
* 配置ChatClient,注册系统指令和工具函数
*/
@Bean
public ChatClient chatClient(ChatClient.Builder builder,ToolCallbackProvider toolCallbackProvider) {
return builder
.defaultSystem("你是一个学生信息管理助手,可以帮助用户查询学生信息。" +
"你可以根据学生姓名模糊查询学生信息、根据条件分页查询学生信息。" +
"回复时,请使用简洁友好的语言,并将学生信息整理为易读的格式。")
// 注册工具方法
.defaultTools(toolCallbackProvider)
.build();
}
}
四、API接口实现
4.1 控制器层
@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Autowired
private ChatClient chatClient;
@PostMapping
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
try {
// 创建用户消息
String userMessage = request.getMessage();
// 使用流式API调用聊天
String content = chatClient.prompt()
.user(userMessage)
.call()
.content();
return ResponseEntity.ok(new ChatResponse(content));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.ok(new ChatResponse("处理请求时出错: " + e.getMessage()));
}
}
}
五、功能验证
5.1 测试案例
测试使用的数据
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS "public"."student";
CREATE TABLE "public"."student" (
"id" int4 NOT NULL,
"name" varchar(255) COLLATE "pg_catalog"."default",
"age" int4,
"sex" varchar(255) COLLATE "pg_catalog"."default",
"class_room" varchar(255) COLLATE "pg_catalog"."default",
"address" varchar(255) COLLATE "pg_catalog"."default"
)
;
COMMENT ON COLUMN "public"."student"."id" IS '主键';
COMMENT ON COLUMN "public"."student"."name" IS '姓名';
COMMENT ON COLUMN "public"."student"."age" IS '年龄';
COMMENT ON COLUMN "public"."student"."sex" IS '性别';
COMMENT ON COLUMN "public"."student"."class_room" IS '班级';
COMMENT ON COLUMN "public"."student"."address" IS '家庭住址';
COMMENT ON TABLE "public"."student" IS '学生表';
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO "public"."student" VALUES (1, '张三', 18, '男', '三年一班', '山东省济南市高新区');
INSERT INTO "public"."student" VALUES (2, '李四', 20, '女', '三年一班', '山东省济南市高新区');
INSERT INTO "public"."student" VALUES (3, '王五', 18, '男', '三年二班', '山东省济南市高新区');
INSERT INTO "public"."student" VALUES (4, '张三', 17, '女', '三年三班', '山东省济南市高新区');
INSERT INTO "public"."student" VALUES (5, '钱六', 15, '男', '三年三班', '山东省济南市高新区');
-- ----------------------------
-- Primary Key structure for table student
-- ----------------------------
ALTER TABLE "public"."student" ADD CONSTRAINT "student_pkey" PRIMARY KEY ("id");
示例1:请查询所有叫张三的同学的平均年龄?

示例2:三年一班有几名女生?

示例3:请查询第一页男同学的信息,每页2条

六、技术要点解析
-
工具动态注册机制
通过MethodToolCallbackProvider
实现Spring Bean方法的自动发现,在运行时动态注册工具函数 -
国产模型适配
通过修改base-url实现对DeepSeek的适配,保持与OpenAI API的兼容性 -
上下文管理
defaultSystem
指令确保大模型始终遵循预设的业务规则
七、源码地址
注:需使用jdk17+ 源码中deepseek的api-key需替换为自己的
转载请注明:
SpringBoot整合MCP,利用国产大模型DeepSeek实现数据库查询(无需ollama)
| 胖虎的工具箱-编程导航