詹学伟
詹学伟
Published on 2025-06-07 / 72 Visits
0
0

AI项目实战:AI智能体实现智能预约挂号

说明:该实战项目是基于前面几篇文章的基础下进行的,一些基础知识不懂可以看看前面几篇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;
    }
}

九、测试只能挂号

第一轮对话:

第二轮对话:

但三轮对话:

第四轮对话:

第五轮对话:

第六轮对话:

十、结果

六轮对话完成后,挂号成功,看数据库:


Comment