SpringBoot整合MCP,利用国产大模型DeepSeek实现数据库查询(无需ollama)

文章标题:

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:请查询所有叫张三的同学的平均年龄?

示例1图片SpringBoot整合MCP,利用国产大模型DeepSeek实现数据库查询(无需ollama)

" />

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

示例2图片SpringBoot整合MCP,利用国产大模型DeepSeek实现数据库查询(无需ollama)

" />

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

示例3图片SpringBoot整合MCP,利用国产大模型DeepSeek实现数据库查询(无需ollama)

" />

六、技术要点解析

  1. 工具动态注册机制
    通过MethodToolCallbackProvider实现Spring Bean方法的自动发现,在运行时动态注册工具函数

  2. 国产模型适配
    通过修改base-url实现对DeepSeek的适配,保持与OpenAI API的兼容性

  3. 上下文管理
    defaultSystem指令确保大模型始终遵循预设的业务规则

七、源码地址

注:需使用jdk17+ 源码中deepseek的api-key需替换为自己的

git源码链接

github源码链接

相关文章

暂无评论

暂无评论...