LangChain4j 项目实战--2:硅谷小智(预约业务优化)
·
一、核心目标
基于 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);
// 预期输出:✅ 取消预约成功!
}
}
四、核心总结
- 预约业务落地需先完成MySQL + MyBatis-Plus 持久层开发,核心是通过
LambdaQueryWrapper实现 “姓名 + 身份证 + 科室 + 日期 + 时间” 的唯一预约校验; - 工具类通过
@Tool注解标记业务方法,@P注解描述参数,帮助大模型理解参数含义并正确传参; - 小智 AI 服务通过
tools属性绑定工具类后,大模型可自动分析用户指令(如 “预约挂号”),调用对应工具方法并返回自然语言结果,实现 “自然语言交互 → 工具调用 → 业务落地” 的闭环。
五、待优化点
- 号源查询:需对接医院真实排班系统,而非模拟返回
true,需维护医生排班表、号源余量; - 参数校验:在工具方法中添加身份证格式、日期格式、科室合法性等校验;
- 事务控制:预约 / 取消操作添加
@Transactional注解,保证数据一致性; - 多轮交互:优化提示词,让大模型在用户参数不全时主动询问(如 “请提供您的身份证号以便预约~”)。
更多推荐

所有评论(0)