此文章是小智医疗个人学习项目的后端实现流程。

学习地址:https://www.bilibili.com/video/BV1cpLTz1EVp/?spm_id_from=333.1391.0.0&p=66

目录

一、项目概述与技术架构

1.1 项目功能特性

1.2 技术架构图

二、开发环境搭建与项目初始化

2.1 环境要求

2.2 阿里云百炼 API Key 获取

2.3 Pinecone 向量数据库配置

三、核心技术详解

3.1 LangChain4J 框架入门

3.2 Spring Boot 3.x 整合 AI 大模型

3.3 流式响应与 SSE 技术

四、项目开发步骤详解

步骤一:项目初始化与依赖配置(pom.xml)

步骤二:配置文件设置(application.properties)

步骤三:实体类与数据传输对象设计

步骤四:数据访问层实现(MyBatis-Plus)

步骤五:MongoDB 对话记忆存储实现

步骤六:AI 大模型配置

步骤七:向量数据库配置(Pinecone)

步骤八:AI Tools 工具类开发

步骤九:AI Agent 接口定义

步骤十:Controller 控制器实现

五、关键技术难点解析

5.1 @AiService 注解深度解析

5.2 ChatMemoryProvider 的工作原理

5.3 RAG 检索增强生成实现

5.4 工具调用(Function Calling)机制

六、项目总结与学习建议

6.1 核心知识点总结

6.2 学习路径建议

6.3 扩展学习方向

一、项目概述与技术架构

小智医疗是一款基于人工智能的智能医疗客服系统,能够为用户提供医疗咨询、科室导诊、预约挂号等服务。

1.1 项目功能特性

  • AI 医疗咨询:基于大语言模型提供专业的医疗建议
  • 智能分导诊:根据患者症状智能推荐合适的科室
  • 预约挂号:支持查询号源、预约、取消预约功能
  • 多轮对话:基于 MongoDB 实现持久化的对话记忆
  • 知识库增强:使用 Pinecone 向量数据库存储医疗知识
  • 流式响应:使用 WebFlux 实现打字机效果的实时回复

1.2 技术架构图

┌─────────────────────────────────────────────────────────────┐
│                      小智医疗系统架构                        │
├─────────────────────────────────────────────────────────────┤
│  表现层  │  Vue3 + Element Plus (前端,本项目不涉及)          │
├──────────┼──────────────────────────────────────────────────┤
│  控制层  │  Spring Boot 3.2.6 + WebFlux (SSE流式响应)        │
├──────────┼──────────────────────────────────────────────────┤
│  AI层    │  LangChain4J 0.36.2 + 通义千问 Qwen               │
│          │  - AiService 接口代理                             │
│          │  - Tools 工具调用                                 │
│          │  - RAG 向量检索                                   │
├──────────┼──────────────────────────────────────────────────┤
│  数据层  │  MySQL (预约信息) + MongoDB (对话历史)            │
│          │  Pinecone (向量数据库) + MyBatis-Plus             │
└──────────┴─────────────────────

: Knife4j─────────────────────────────┘

二、开发环境搭建与项目初始化

2.1 环境要求

  • JDK 17+(Spring Boot 3.x 最低要求)
  • Maven 3.8+
  • MySQL 8.0+
  • MongoDB 5.0+
  • Pinecone 账号(向量数据库)
  • 阿里云百炼 API Key

2.2 阿里云百炼 API Key 获取

1. 访问阿里云百炼控制台:https://bailian.console.aliyun.com

2. 登录阿里云账号

3. 进入「我的应用」页面

4. 创建新的应用或使用默认应用

5. 复制 API Key 供项目使用

2.3 Pinecone 向量数据库配置

Pinecone 是一个托管的向量数据库服务,适合存储和检索高维向量数据(如文本嵌入)。

【重要】Pinecone 提供免费套餐,包含 1 个索引和 10 万个向量,足够开发测试使用。

三、核心技术详解

3.1 LangChain4J 框架入门

LangChain4J 是 LangChain 的 Java 实现,提供了与 LLM(大语言模型)交互的统一抽象。核心概念:

  • ChatLanguageModel:普通对话模型接口,同步返回完整响应
  • StreamingChatLanguageModel:流式对话模型接口,支持逐字返回
  • EmbeddingModel:嵌入模型,将文本转换为向量
  • ChatMemory:对话记忆,保存多轮对话上下文
  • AiService:声明式 AI 服务接口,自动实现代理
  • Tool:工具注解,让 AI 能够调用外部方法

3.2 Spring Boot 3.x 整合 AI 大模型

Spring Boot 3.x 相比 2.x 的主要变化:

  • 最低 Java 版本要求从 8 提升到 17
  • Spring Framework 从 5.x 升级到 6.x
  • 默认使用 Jakarta EE 命名空间(javax.* → jakarta.*)
  • 内嵌服务器升级:Tomcat 10.x、Jetty 11.x
  • 自动配置机制优化

3.3 流式响应与 SSE 技术

SSE(Server-Sent Events)是一种服务器向浏览器推送实时更新的技术。相比 WebSocket,SSE 更轻量,适合单向数据流场景。

// 流式响应返回类型
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestBody ChatForm chatForm) {
    return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage());
}

四、项目开发步骤详解

根据代码分析,项目开发按照以下顺序进行:

步骤一:项目初始化与依赖配置(pom.xml)

这是项目的第一步,需要配置所有必要的依赖。

<!-- 核心依赖配置 -->
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <spring-boot.version>3.2.6</spring-boot.version>
    <langchain4j.version>0.36.2</langchain4j.version>
    <mybatis-plus.version>3.5.11</mybatis-plus.version>
</properties>

<!-- LangChain4J BOM
管理 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>${langchain4j.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

【要点】langchain4j-bom 使用 import 作用域统一管理所有 LangChain4J 相关依赖的版本,避免版本冲突。

步骤二:配置文件设置(application.properties)

# MySQL 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/guiguxiaozhi
spring.datasource.username=root
spring.datasource.password=xxx

# MongoDB
配置(对话历史)
spring.data.mongodb.uri=mongodb://localhost:27017/chat_memory_db

#
阿里云百炼 API 配置
dashscope.api-key=sk-xxxxx
dashscope.chat-model=qwen-max
dashscope.embedding-model=text-embedding-v3

#
向量数据库配置
vectorstore.type=pinecone
pinecone.api-key=pcsk_xxxxx
pinecone.index=xiaozhi-index

步骤三:实体类与数据传输对象设计

3.3.1 Appointment 预约实体类(MySQL)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Appointment {
    @TableId(type = IdType.AUTO)  // MyBatis-Plus
主键自增
    private Long id;
    private String username;
    private String idCard;
    private String department;
    private String date;
    private String time;
    private String doctorName;
}

3.3.2 ChatMessages 对话消息实体类(MongoDB)

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document("chat_messages")  // MongoDB
集合名称
public class ChatMessages {
    @Id
    private ObjectId messageId;  // BSON
格式,自动填充
    private Long memoryId;       // 对话标识
    private String content;      // JSON 格式的消息列表
}

【关键区别】@TableId 用于 MyBatis-Plus(关系型数据库),@Document 和 @Id(org.springframework.data.annotation)用于 MongoDB(文档数据库)

步骤四:数据访问层实现(MyBatis-Plus)

4.4.1 Mapper 接口定义

@Mapper
public interface AppointmentMapper extends BaseMapper<Appointment> {
    //
继承 BaseMapper 获得基础的 CRUD 方法
}

4.4.2 Service 接口与实现

public interface AppointmentService extends IService<Appointment> {
    Appointment getOne(Appointment appointment);
}

@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);
    }
}

LambdaQueryWrapper 优势】使用 Lambda 表达式避免硬编码字段名,编译期类型检查,IDE 自动补全,重构安全。

步骤五:MongoDB 对话记忆存储实现

这是项目的核心难点之一,需要自定义 ChatMemoryStore 实现。

@Component
public class MongoChatMemoryStore implements ChatMemoryStore {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        Criteria criteria = Criteria.where("memoryId").is(memoryId);
        Query query = new Query(criteria);
        ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);

        if (chatMessages == null) {
            return new LinkedList<>();
        }
        //
JSON 反序列化为 ChatMessage 列表
        return ChatMessageDeserializer.messagesFromJson(chatMessages.getContent());
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> list) {
        Criteria criteria = Criteria.where("memoryId").is(memoryId);
        Query query = new Query(criteria);
        Update update = new Update();
        //
将消息列表序列化为 JSON
        update.set("content", ChatMessageSerializer.messagesToJson(list));
        mongoTemplate.upsert(query, update, ChatMessages.class);
    }

    @Override
    public void deleteMessages(Object memoryId) {
        mongoTemplate.remove(
            new Query(Criteria.where("memoryId").is(memoryId)),
            ChatMessages.class
        );
    }
}

【难点解析】1. ChatMessageSerializer/Deserializer 是 LangChain4J 提供的工具类,用于消息的序列化与反序列化;2. upsert 方法表示"存在则更新,不存在则插入";3. memoryId 用于区分不同用户的不同对话会话。

步骤六:AI 大模型配置

@Configuration
public class DashScopeConfig {

    @Value("${dashscope.api-key:}")
    private String apiKey;

    @Bean("qwenStreamingChatModel")
    public StreamingChatLanguageModel streamingChatLanguageModel() {
        String key = apiKey.isEmpty() ? System.getenv("DASHSCOPE_API_KEY") : apiKey;
        return QwenStreamingChatModel.builder()
                .apiKey(key)
                .modelName("qwen-max")  //
通义千问最大模型
                .build();
    }

    @Bean
    public EmbeddingModel embeddingModel() {
        return QwenEmbeddingModel.builder()
                .apiKey(apiKey)
                .modelName("text-embedding-v3")  //
嵌入模型
                .build();
    }
}

步骤七:向量数据库配置(Pinecone)

@Configuration
public class EmbeddingStoreConfig {

    @Value("${vectorstore.type:memory}")
    private String vectorStoreType;

    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        if ("pinecone".equalsIgnoreCase(vectorStoreType)
                && pineconeApiKey != null && !pineconeApiKey.isEmpty()) {
            return createPineconeEmbeddingStore();
        }
        return new InMemoryEmbeddingStore<>();  //
降级方案
    }

    private EmbeddingStore<TextSegment> createPineconeEmbeddingStore() {
        return PineconeEmbeddingStore.builder()
                .apiKey(apiKey)
                .index("xiaozhi-index")
                .nameSpace("xiaozhi-namespace")
                .createIndex(PineconeServerlessIndexConfig.builder()
                        .cloud("AWS")
                        .region("us-east-1")
                        .dimension(embeddingModel.dimension())  //
向量维度
                        .build())
                .build();
    }
}

步骤八:AI Tools 工具类开发

@Component
public class AppointmentTools {

    @Autowired
    private AppointmentService appointmentService;

    @Tool(name = "
预约挂号",
          value = "
根据参数,先执行工具方法queryDepartment查询是否可预约...")
    public String bookAppointment(Appointment appointment) {
        //
查找是否已有相同预约
        Appointment appointmentDB = appointmentService.getOne(appointment);

        if (appointmentDB == null) {
            appointment.setId(null);  //
防止大模型幻觉设置了id
            if (appointmentService.save(appointment)) {
                return "
预约成功";
            }
            return "
预约失败";
        }
        return "
您在相同的科室和时间已有预约";
    }

    @Tool(name = "
查询是否有号源",
          value = "
根据科室名称,时间和医生查询是否有号源")
    public Boolean queryAppointment(
            @P(value = "
科室名称") String name,
            @P(value = "
日期") String date,
            @P(value = "
时间,可选值:上午、下午") String time,
            @P(value = "
医生名称", required = false) String doctorName) {
        return true;
    }
}

@Tool @P 注解】@Tool 的 value 属性是对 AI 的描述,帮助 AI 理解何时调用此工具;@P 注解为参数添加描述,帮助 AI 正确提取参数值。

步骤九:AI Agent 接口定义

@AiService(
        wiringMode = EXPLICIT,                    //
显式指定依赖 Bean
        streamingChatModel = "qwenStreamingChatModel",  //
流式模型
        chatMemoryProvider = "chatMemoryProviderXiaoZhi", // 记忆提供者
        tools = "appointmentTools",               // 工具类 Bean
        contentRetriever = "contentRetrieverXiaozhiPinecone"  // RAG
检索
)
public interface XiaozhiAgent {

    @SystemMessage(fromResource = "XiaoZhi-prompt-template.txt")
    Flux<String> chat(@MemoryId Long memoryId, @UserMessage String userMessage);
}

@MemoryId 关键作用】@MemoryId 用于标识不同的对话会话,相同的 memoryId 会共享同一份对话历史,实现多轮对话上下文理解。

步骤十:Controller 控制器实现

@Tag(name = "硅谷小智")
@RestController
@RequestMapping("/xiaozhi")
public class XiaozhiController {

    @Autowired
    private XiaozhiAgent xiaozhiAgent;

    @Operation(summary = "
对话")
    @PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(@RequestBody ChatForm chatForm) {
        return xiaozhiAgent.chat(
            chatForm.getMemoryId(),
            chatForm.getMessage()
        );
    }
}

五、关键技术难点解析

5.1 @AiService 注解深度解析

@AiService 是 LangChain4J Spring Boot 集成的核心注解,它会在运行时自动生成接口的实现类。

// wiringMode 的两种模式
EXPLICIT  // 显式模式:通过 Bean 名称精确指定依赖
AUTOMATIC // 自动模式:自动从 Spring 容器中查找匹配的 Bean

配置项详解:

  • streamingChatModel:流式对话模型 Bean 名称,用于流式响应
  • chatModel:普通对话模型 Bean 名称,用于同步响应
  • chatMemoryProvider:对话记忆提供者,实现多轮对话
  • tools:工具类 Bean 名称数组,AI 可调用的方法
  • contentRetriever:内容检索器,RAG 实现的关键组件

5.2 ChatMemoryProvider 的工作原理

ChatMemoryProvider 是一个函数式接口,用于为每个对话会话创建独立的 ChatMemory 实例。

@Bean
public ChatMemoryProvider chatMemoryProvider() {
    return memoryId -> MessageWindowChatMemory.builder()
            .id(memoryId)                           //
对话唯一标识
            .maxMessages(10)                        // 最大保留消息数
            .chatMemoryStore(mongoChatMemoryStore)  // 持久化存储
            .build();
}

工作流程:

  1. 用户发起对话,携带 memoryId(如用户ID)
  2. LangChain4J 调用 ChatMemoryProvider.apply(memoryId)
  3. 从 MongoDB 加载该 memoryId 对应的历史消息
  4. 将历史消息与新消息一起发送给大模型
  5. 收到响应后,更新 MongoDB 中的对话记录

5.3 RAG 检索增强生成实现

RAG(Retrieval-Augmented Generation)是一种将外部知识库与 LLM 结合的技术。

@Bean
public ContentRetriever contentRetrieverXiaozhi() {
    return EmbeddingStoreContentRetriever.builder()
            .embeddingModel(embeddingModel)    //
嵌入模型
            .embeddingStore(embeddingStore)    // 向量存储
            .maxResults(1)                     // 最多返回1
            .minScore(0.8)                     // 最小相似度阈值
            .build();
}

RAG 流程】1. 用户提问 → 2. 使用 EmbeddingModel 将问题转为向量 → 3. 在向量数据库中检索相似文档 → 4. 将检索结果作为上下文与问题一起发给 LLM → 5. LLM 基于上下文生成回答

5.4 工具调用(Function Calling)机制

Function Calling 允许 LLM 根据对话上下文决定何时调用外部 API 或方法。

// 工具调用流程(以预约挂号为例)
用户:"我想预约明天上午的神经内科"
   ↓
LLM
分析:需要调用 "预约挂号" 工具
   ↓
LLM
提取参数:
  - date: "2025-04-22"
  - time: "
上午"
  - department: "
神经内科"
   ↓

调用 bookAppointment(appointment)
   ↓

返回结果给 LLM
   ↓
LLM
生成友好回复:"预约成功!您已成功预约..."

六、项目总结与学习建议

6.1 核心知识点总结

LangChain4J 框架:掌握 AiService、ChatMemory、Tool 等核心概念

Spring Boot 3.x理解自动配置、Bean 管理、条件装配

向量数据库:理解 Embedding 原理、向量相似度计算

流式响应:掌握 WebFlux、Flux、SSE 技术

多数据源:MySQL + MongoDB 在 Spring Boot 中的整合

API 设计:RESTful 接口设计、Swagger 文档生成

6.2 学习路径建议

  • 阶段一:夯实基础 - Spring Boot、MyBatis-Plus、MySQL、MongoDB
  • 阶段二:AI 入门 - 了解 LLM、Prompt Engineering、Function Calling
  • 阶段三:框架学习 - LangChain4J 官方文档、示例项目
  • 阶段四:项目实战 - 从简单对话到复杂多轮对话、工具调用
  • 阶段五:进阶优化 - RAG 优化、性能调优、生产部署

6.3 扩展学习方向

  • 其他 LLM 接入:OpenAI、Claude、文心一言、讯飞星火
  • 更复杂的 RAG:文档分块策略、重排序、多路召回
  • Agent 进阶:ReAct、Plan-and-Solve、Multi-Agent
  • 性能优化:缓存、限流、异步处理、连接池
  • 生产部署:Docker、K8s、监控、日志、链路追踪



—— 学习笔记完 ——
 

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐