一、核心目标

基于 Function Calling 实现硅谷小智的挂号预约核心业务:查询号源、预约挂号、取消挂号,对接 MySQL 数据库完成数据持久化,通过 @Tool 注解让大模型自动调用业务工具完成交互。

二、预约业务实现(MySQL + MyBatis-Plus)

2.1 1. 环境准备

1.1 创建数据库表

sql

-- 创建数据库
CREATE DATABASE `guiguxiaozhi`;
USE `guiguxiaozhi`;

-- 预约订单表
CREATE TABLE `appointment` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` VARCHAR(50) NOT NULL COMMENT '用户姓名',
  `id_card` VARCHAR(18) NOT NULL COMMENT '身份证号',
  `department` VARCHAR(50) NOT NULL COMMENT '预约科室',
  `date` VARCHAR(10) NOT NULL COMMENT '预约日期(格式:2025-04-14)',
  `time` VARCHAR(10) NOT NULL COMMENT '预约时间(上午/下午)',
  `doctor_name` VARCHAR(50) DEFAULT NULL COMMENT '预约医生',
  PRIMARY KEY (`id`)
) COMMENT = '协和医院预约挂号表';
1.2 引入依赖(pom.xml)

xml

<!-- MySQL 驱动 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

<!-- MyBatis-Plus (简化CRUD操作) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version> <!-- 适配SpringBoot3版本 -->
</dependency>
1.3 配置数据库连接(application.properties)

properties

# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/guiguxiaozhi?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis-Plus 日志配置(打印SQL,方便调试)
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

2.2 2. 持久层开发(Entity + Mapper + Service)

2.1 实体类(Appointment)

对应数据库表,使用 MyBatis-Plus 注解简化配置:

package com.atguigu.java.ai.langchain4j.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 预约订单实体类
 */
@Data // 自动生成get/set/toString等
@AllArgsConstructor // 全参构造
@NoArgsConstructor  // 无参构造
@TableName("appointment") // 绑定数据库表名
public class Appointment {
    @TableId(type = IdType.AUTO) // 主键自增
    private Long id;
    private String username;    // 姓名
    private String idCard;      // 身份证号(数据库字段id_card,MyBatis-Plus自动驼峰转换)
    private String department;  // 科室
    private String date;        // 预约日期
    private String time;        // 预约时间
    private String doctorName;  // 医生姓名
}
2.2 Mapper 接口

继承 MyBatis-Plus 的 BaseMapper,无需手动编写基础 CRUD SQL:

package com.atguigu.java.ai.langchain4j.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.atguigu.java.ai.langchain4j.entity.Appointment;
import org.apache.ibatis.annotations.Mapper;

/**
 * 预约订单Mapper
 */
@Mapper // 标记为MyBatis Mapper,注入Spring容器
public interface AppointmentMapper extends BaseMapper<Appointment> {
    // 基础CRUD由BaseMapper提供,无需手动编写
}
2.3 Mapper XML(可选)

放置在 resources/mapper/AppointmentMapper.xml,暂无自定义 SQL,仅做占位:

xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.java.ai.langchain4j.mapper.AppointmentMapper">
    <!-- 自定义SQL可在此编写,基础CRUD无需配置 -->
</mapper>
2.4 Service 接口 + 实现类

封装业务逻辑,重点实现 “根据唯一条件查询预约记录”:

// Service接口
package com.atguigu.java.ai.langchain4j.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.atguigu.java.ai.langchain4j.entity.Appointment;

public interface AppointmentService extends IService<Appointment> {
    /**
     * 根据姓名、身份证、科室、日期、时间查询唯一预约记录(防止重复预约)
     * @param appointment 查询条件
     * @return 预约记录(null表示无)
     */
    Appointment getOne(Appointment appointment);
}

// Service实现类
package com.atguigu.java.ai.langchain4j.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.java.ai.langchain4j.entity.Appointment;
import com.atguigu.java.ai.langchain4j.mapper.AppointmentMapper;
import com.atguigu.java.ai.langchain4j.service.AppointmentService;
import org.springframework.stereotype.Service;

@Service
public class AppointmentServiceImpl extends ServiceImpl<AppointmentMapper, Appointment> 
        implements AppointmentService {

    @Override
    public Appointment getOne(Appointment appointment) {
        // 构建查询条件:姓名+身份证+科室+日期+时间 唯一确定一条预约
        LambdaQueryWrapper<Appointment> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Appointment::getUsername, appointment.getUsername())
                   .eq(Appointment::getIdCard, appointment.getIdCard())
                   .eq(Appointment::getDepartment, appointment.getDepartment())
                   .eq(Appointment::getDate, appointment.getDate())
                   .eq(Appointment::getTime, appointment.getTime());
        // 查询唯一记录
        return baseMapper.selectOne(queryWrapper);
    }
}

2.3 3. 测试持久层功能

验证数据库 CRUD 是否正常:

package com.atguigu.java.ai.langchain4j.service;

import com.atguigu.java.ai.langchain4j.entity.Appointment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class AppointmentServiceTest {

    @Autowired
    private AppointmentService appointmentService;

    // 测试查询预约记录
    @Test
    void testGetOne() {
        Appointment appointment = new Appointment();
        appointment.setUsername("张三");
        appointment.setIdCard("123456789012345678");
        appointment.setDepartment("内科");
        appointment.setDate("2025-04-14");
        appointment.setTime("上午");
        Appointment appointmentDB = appointmentService.getOne(appointment);
        System.out.println(appointmentDB); // 输出查询结果(null表示无记录)
    }

    // 测试新增预约
    @Test
    void testSave() {
        Appointment appointment = new Appointment();
        appointment.setUsername("张三");
        appointment.setIdCard("123456789012345678");
        appointment.setDepartment("内科");
        appointment.setDate("2025-04-14");
        appointment.setTime("上午");
        appointment.setDoctorName("张医生");
        appointmentService.save(appointment); // 新增记录
    }

    // 测试删除预约
    @Test
    void testRemoveById() {
        appointmentService.removeById(1L); // 根据ID删除
    }
}

三、集成 Function Calling(预约工具开发)

3.1 1. 创建预约工具类(AppointmentTools)

通过 @Tool 注解标记业务方法,让大模型能调用预约、取消、查询号源功能:

package com.atguigu.java.ai.langchain4j.tools;

import com.atguigu.java.ai.langchain4j.entity.Appointment;
import com.atguigu.java.ai.langchain4j.service.AppointmentService;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 预约挂号工具类(供大模型调用)
 */
@Component // 注入Spring容器
public class AppointmentTools {

    @Autowired
    private AppointmentService appointmentService;

    /**
     * 预约挂号工具
     * @param appointment 预约信息(姓名、身份证、科室、日期、时间、医生)
     * @return 预约结果
     */
    @Tool(
        name = "预约挂号",
        value = "根据用户提供的预约信息,先检查是否已有重复预约,无则创建预约记录,返回预约结果"
    )
    public String bookAppointment(Appointment appointment) {
        // 检查是否已有相同预约
        Appointment appointmentDB = appointmentService.getOne(appointment);
        if (appointmentDB == null) {
            appointment.setId(null); // 防止大模型传递无效ID,确保自增
            // 保存预约记录
            if (appointmentService.save(appointment)) {
                return "🎉 预约成功!\n预约详情:\n姓名:" + appointment.getUsername() +
                        "\n科室:" + appointment.getDepartment() +
                        "\n日期:" + appointment.getDate() + " " + appointment.getTime() +
                        "\n医生:" + (appointment.getDoctorName() == null ? "默认医生" : appointment.getDoctorName());
            } else {
                return "❌ 预约失败,请稍后重试!";
            }
        }
        return "⚠️ 您已在【" + appointment.getDepartment() + "】" + appointment.getDate() + " " + appointment.getTime() + "有预约记录,无法重复预约!";
    }

    /**
     * 取消预约工具
     * @param appointment 预约信息(姓名、身份证、科室、日期、时间)
     * @return 取消结果
     */
    @Tool(
        name = "取消预约挂号",
        value = "根据预约信息查询记录,存在则删除,返回取消结果"
    )
    public String cancelAppointment(Appointment appointment) {
        Appointment appointmentDB = appointmentService.getOne(appointment);
        if (appointmentDB != null) {
            // 删除预约记录
            if (appointmentService.removeById(appointmentDB.getId())) {
                return "✅ 取消预约成功!";
            } else {
                return "❌ 取消预约失败,请稍后重试!";
            }
        }
        return "⚠️ 未查询到您的预约记录,请核对姓名、身份证、科室和时间!";
    }

    /**
     * 查询号源工具
     * @param name 科室名称
     * @param date 预约日期
     * @param time 预约时间(上午/下午)
     * @param doctorName 医生名称(可选)
     * @return 是否有号源(true=有,false=无)
     */
    @Tool(
        name = "查询是否有号源",
        value = "根据科室、日期、时间、医生查询是否有可预约号源,返回布尔值"
    )
    public boolean queryDepartment(
            @P(value = "科室名称", required = true) String name,
            @P(value = "日期", required = true) String date,
            @P(value = "时间,可选值:上午、下午", required = true) String time,
            @P(value = "医生名称", required = false) String doctorName
    ) {
        System.out.println("===== 查询号源 =====");
        System.out.println("科室:" + name);
        System.out.println("日期:" + date);
        System.out.println("时间:" + time);
        System.out.println("医生:" + (doctorName == null ? "无" : doctorName));
        
        // TODO 实际业务中需对接医院排班系统:
        // 1. 无医生名称:查询科室该时段是否有医生排班且有号源
        // 2. 有医生名称:查询该医生该时段是否排班且号源充足
        return true; // 模拟返回“有号源”
    }
}

3.2 2. 配置小智 AI 服务(绑定预约工具)

XiaozhiAgent 中添加 tools 属性,绑定预约工具类:

import dev.langchain4j.service.AiService;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;

@AiService(
    wiringMode = EXPLICIT,
    chatModel = "qwenChatModel",               // 绑定大模型
    chatMemoryProvider = "chatMemoryProviderXiaozhi", // 绑定聊天记忆
    tools = "appointmentTools"                 // 绑定预约工具类(Spring Bean名称)
)
public interface XiaozhiAgent {
    String chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}

3.3 3. 测试预约工具调用

package com.atguigu.java.ai.langchain4j;

import com.atguigu.java.ai.langchain4j.assistant.XiaozhiAgent;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class XiaozhiAgentTest {

    @Autowired
    private XiaozhiAgent xiaozhiAgent;

    // 测试查询号源
    @Test
    void testQueryDepartment() {
        String answer = xiaozhiAgent.chat(1L, "消化内科2025-04-14上午有号吗?想挂张医生的号");
        System.out.println(answer);
        // 预期输出:😜 消化内科2025-04-14上午有张医生的号源哦,可以预约~
    }

    // 测试预约挂号
    @Test
    void testBookAppointment() {
        String answer = xiaozhiAgent.chat(1L, "我要预约消化内科2025-04-14上午的号,姓名张三,身份证123456789012345678,医生张医生");
        System.out.println(answer);
        // 预期输出:🎉 预约成功!预约详情:姓名:张三...
    }

    // 测试取消预约
    @Test
    void testCancelAppointment() {
        String answer = xiaozhiAgent.chat(1L, "取消我张三的预约,身份证123456789012345678,消化内科2025-04-14上午");
        System.out.println(answer);
        // 预期输出:✅ 取消预约成功!
    }
}

四、核心总结

  1. 预约业务落地需先完成MySQL + MyBatis-Plus 持久层开发,核心是通过 LambdaQueryWrapper 实现 “姓名 + 身份证 + 科室 + 日期 + 时间” 的唯一预约校验;
  2. 工具类通过 @Tool 注解标记业务方法,@P 注解描述参数,帮助大模型理解参数含义并正确传参;
  3. 小智 AI 服务通过 tools 属性绑定工具类后,大模型可自动分析用户指令(如 “预约挂号”),调用对应工具方法并返回自然语言结果,实现 “自然语言交互 → 工具调用 → 业务落地” 的闭环。

五、待优化点

  1. 号源查询:需对接医院真实排班系统,而非模拟返回 true,需维护医生排班表、号源余量;
  2. 参数校验:在工具方法中添加身份证格式、日期格式、科室合法性等校验;
  3. 事务控制:预约 / 取消操作添加 @Transactional 注解,保证数据一致性;
  4. 多轮交互:优化提示词,让大模型在用户参数不全时主动询问(如 “请提供您的身份证号以便预约~”)。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐