说明:该实战项目是基于前面几篇文章的基础下进行的,一些基础知识不懂可以看看前面几篇AI相关的文章。
其中比较核心的,一个是记忆聊天,二个是Aiservice使用,三个是Tool的使用
其中涉及到Tool的使用中,提示模版的章节未补充,后面慢慢完善~~
一、新建数据库和表
CREATE TABLE `appointment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`id_card` varchar(18) NOT NULL,
`department` varchar(50) NOT NULL,
`date` varchar(10) NOT NULL,
`time` varchar(10) NOT NULL,
`doctor_name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
二、添加POM依赖
<mybatis-plus.version>3.5.11</mybatis-plus.version>
<!-- Mysql Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--mybatis-plus 持久层-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
三、配置文件
spring:
data:
mongodb:
uri: mongodb://admin:123456@192.168.10.109:27017/chat_memory_db?authSource=admin
datasource:
url: jdbc:mysql://192.168.10.109:3306/xuewei?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
langchain4j:
open-ai:
chat-model:
# api-key: demo
# base-url: http://langchain4j.dev/demo/openai/v1
# model-name: gpt-4o-mini
# deepseek
#base-url: https://api.deepseek.com
#api-key: sk-851e968414c947c6be86427b1014xxxx
#model-name: deepseek-chat
#model-name: =deepseek-reasoner
# 阿里白练通义千问
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: sk-09c7b571687b46d5a2e25a03fbddxxxx
model-name: deepseek-v3
log-requests: true
log-responses: true
# 阿里白练通义千问
community:
dashscope:
chat-model:
api-key: sk-09c7b571687b46d5a2e25a03fbddxxxx
model-name: qwen-max
logging:
level:
root: info
四、MP代码生成
1.entity
package com.zhan.chat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author zhanxuewei
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Appointment {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String idCard;
private String department;
private String date;
private String time;
private String doctorName;
}
2.mapper
package com.zhan.chat.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhan.chat.entity.Appointment;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AppointmentMapper extends BaseMapper<Appointment> {
}
3.service
package com.zhan.chat.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zhan.chat.entity.Appointment;
/**
* @author zhanxuewei
*/
public interface AppointmentService extends IService<Appointment> {
Appointment getOne(Appointment appointment);
}
package com.zhan.chat.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhan.chat.entity.Appointment;
import com.zhan.chat.mapper.AppointmentMapper;
import com.zhan.chat.service.AppointmentService;
import org.springframework.stereotype.Service;
/**
* @author zhanxuewei
*/
@Service
public class AppointmentServiceImpl extends ServiceImpl<AppointmentMapper, Appointment> implements AppointmentService {
@Override
public Appointment getOne(Appointment appointment) {
return baseMapper.selectOne(new LambdaQueryWrapper<Appointment>()
.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()));
}
}
4.controller
package com.zhan.chat.controller;
import com.zhan.chat.assistant.XueweiAgent;
import com.zhan.chat.bean.ChatForm;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "智能医生助手")
@RestController
@RequestMapping("/doctor/assistant")
public class XueweiController {
@Autowired
private XueweiAgent xueweiAgent;
@Operation(summary = "对话")
@PostMapping("/chat")
public String chat(@RequestBody ChatForm chatForm) {
return xueweiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage());
}
}
五、测试CRUD
package com.zhan.chat;
import com.zhan.chat.entity.Appointment;
import com.zhan.chat.service.AppointmentService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class AppointmentServiceTest {
@Autowired
private AppointmentService appointmentService;
@Test
void testGetOne() {
Appointment appointment = new Appointment();
appointment.setUsername("张三");
appointment.setIdCard("123456789012345678");
appointment.setDepartment("内科");
appointment.setDate("2025-06-07");
appointment.setTime("上午");
Appointment appointmentDB = appointmentService.getOne(appointment);
System.out.println(appointmentDB);
}
@Test
void testSave() {
Appointment appointment = new Appointment();
appointment.setUsername("张三");
appointment.setIdCard("123456789012345678");
appointment.setDepartment("内科");
appointment.setDate("2025-06-07");
appointment.setTime("上午");
appointment.setDoctorName("詹医生");
appointmentService.save(appointment);
}
@Test
void testRemoveById() {
appointmentService.removeById(1L);
}
}
六、编写智能体
package com.zhan.chat.assistant;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
/**
* @author zhanxuewei
*/
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "qwenChatModel",
chatMemory = "chatMemory",
chatMemoryProvider = "chatMemoryProviderXuewei", tools = "appointmentTools")
public interface XueweiAgent {
@SystemMessage(fromResource = "xuewei-prompt-template.txt")
String chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}
消息模版:
七、编写ChatMemoryProvider
package com.zhan.chat.config;
import com.zhan.chat.store.MongoChatMemoryStore;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zhanxuewei
*/
@Configuration
public class XueweiAgentConfig {
@Autowired
private MongoChatMemoryStore mongoChatMemoryStore;
@Bean
ChatMemoryProvider chatMemoryProviderXuewei() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(mongoChatMemoryStore)
.build();
}
}
八、编写AppointmentTools
package com.zhan.chat.tools;
import com.zhan.chat.entity.Appointment;
import com.zhan.chat.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;
/**
* @author zhanxuewei
*/
@Component
public class AppointmentTools {
@Autowired
private AppointmentService appointmentService;
@Tool(name = "预约挂号", value = "根据参数,先执行工具方法queryDepartment查询是否可预约,并直 接给用户回答是否可预约,并让用户确认所有预约信息,用户确认后再进行预约。")
public String bookAppointment(Appointment appointment) {
//查找数据库中是否包含对应的预约记录
Appointment existAppointment = appointmentService.getOne(appointment);
if (existAppointment == null) {
appointment.setId(null);
if (appointmentService.save(appointment)) {
return "预约成功,并返回预约详情";
} else {
return "预约失败";
}
}
return "您在相同的科室和时间已有预约";
}
@Tool(name = "取消预约挂号", value = "根据参数,查询预约是否存在,如果存在则删除预约记录并返回取 消预约成功,否则返回取消预约失败")
public String cancelAppointment(Appointment appointment) {
Appointment one = appointmentService.getOne(appointment);
if (one != null) {
//删除预约记录
if (appointmentService.removeById(one.getId())) {
return "取消预约成功";
} else {
return "取消预约失败";
}
}
//取消失败
return "您没有预约记录,请核对预约科室和时间";
}
@Tool(name = "查询是否有号源", value = "根据科室名称,日期,时间和医生查询是否有号源,并返回给用户")
public boolean queryDepartment(@P(value = "科室名称") String name, @P(value = "日期") String date, @P(value = "时间,可选值:上午、下午") 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);
//TODO 维护医生的排班信息:
//如果没有指定医生名字,则根据其他条件查询是否有可以预约的医生(有返回true,否则返回false;
//如果指定了医生名字,则判断医生是否有排班(没有排版返回false)
//如果有排班,则判断医生排班时间段是否已约满(约满返回false,有空闲时间返回true)
return true;
}
}
九、测试只能挂号
第一轮对话:
第二轮对话:
但三轮对话:
第四轮对话:
第五轮对话:
第六轮对话:
十、结果
六轮对话完成后,挂号成功,看数据库: