分享一套锋哥原创的基于LangChain4j的RAG医疗健康知识智能问答系统(SpringBoot4+Vue3+Ollama)

大家好,我是java1234_小锋老师,分享一套基于LangChain4j的RAG医疗健康知识智能问答系统(SpringBoot4+Vue3+Ollama)

项目简介

随着人工智能与大语言模型技术的快速发展,智能问答系统在医疗健康领域展现出广阔的应用前景。然而,通用大语言模型在回答医疗健康类问题时,普遍存在知识更新滞后、易产生"幻觉"以及缺乏可信来源等问题,难以直接应用于对准确性要求较高的医疗健康咨询场景。检索增强生成(Retrieval-Augmented Generation,RAG)技术通过将外部知识库检索与大语言模型生成相结合,能够在不重新训练模型的前提下显著提升回答的准确性与可追溯性,为构建可信的医疗健康知识问答系统提供了有效途径。

本文设计并实现了一个基于LangChain4j的RAG医疗健康知识智能问答系统。系统后端采用Spring Boot 4框架,以LangChain4j作为RAG应用编排框架,通过Ollama在本地部署qwen3对话模型与qwen3-embedding嵌入模型,使用Redis向量数据库存储文档向量并完成相似度检索,业务数据持久化采用MySQL与MyBatis-Plus,并通过Spring Security与JWT实现无状态的认证授权。前端采用Vue3、Element Plus与ECharts构建用户问答界面与管理后台。系统实现了知识库文档的上传、解析、分块、向量化入库,以及基于语义检索与提示词工程的智能问答、引用来源展示、问答历史管理、用户管理、分类管理和数据统计可视化等功能。

测试结果表明,本系统能够基于本地医疗健康知识库返回准确、可溯源的答案,有效缓解了大语言模型的幻觉问题,系统运行稳定、交互友好,达到了预期的设计目标,具有一定的实用价值与推广意义。

源码下载

链接: pan.baidu.com/s/185YGwaNf... 提取码: 1234

相关截图

核心代码

java 复制代码
package com.java1234.rag.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.java1234.rag.common.Result;
import com.java1234.rag.security.LoginUserContext;
import com.java1234.rag.service.KnowledgeService;
import com.java1234.rag.vo.KnowledgeDocVO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * 知识库文档控制器
 */
@RestController
@RequiredArgsConstructor
public class KnowledgeController {

    private final KnowledgeService knowledgeService;

    /**
     * 上传知识库文档(管理员)
     */
    @PostMapping("/api/admin/knowledge/upload")
    public Result<KnowledgeDocVO> upload(@RequestParam("file") MultipartFile file,
                                         @RequestParam Long categoryId,
                                         @RequestParam(required = false) String title) {
        Long uploaderId = LoginUserContext.get().getUserId();
        return Result.success(knowledgeService.upload(file, categoryId, title, uploaderId));
    }

    /**
     * 分页查询文档
     */
    @GetMapping("/api/knowledge")
    public Result<Page<KnowledgeDocVO>> page(@RequestParam(defaultValue = "1") int page,
                                             @RequestParam(defaultValue = "10") int size,
                                             @RequestParam(required = false) Long categoryId,
                                             @RequestParam(required = false) String keyword) {
        return Result.success(knowledgeService.pageDocs(page, size, categoryId, keyword));
    }

    /**
     * 删除文档(管理员)
     */
    @DeleteMapping("/api/admin/knowledge/{id}")
    public Result<Void> delete(@PathVariable Long id) {
        knowledgeService.deleteDoc(id);
        return Result.success();
    }
}
js 复制代码
<template>
  <div class="page-card">
    <div class="search-bar">
      <el-input v-model="keyword" placeholder="搜索分类名称" clearable style="width:240px" @keyup.enter="loadData" />
      <el-button type="primary" @click="loadData">搜索</el-button>
      <el-button type="success" @click="openDialog()">新增分类</el-button>
    </div>
    <el-table :data="tableData" stripe border>
      <el-table-column prop="id" label="ID" min-width="80" />
      <el-table-column prop="name" label="分类名称" min-width="140" />
      <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
      <el-table-column prop="sort" label="排序" min-width="80" />
      <el-table-column prop="status" label="状态" min-width="100">
        <template #default="{ row }">
          <el-tag :type="row.status === 1 ? 'success' : 'info'">{{ row.status === 1 ? '启用' : '禁用' }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="创建时间" min-width="180">
        <template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
      </el-table-column>
      <el-table-column label="操作" min-width="160" fixed="right">
        <template #default="{ row }">
          <el-button type="primary" link @click="openDialog(row)">编辑</el-button>
          <el-popconfirm title="确定删除该分类?" @confirm="handleDelete(row.id)">
            <template #reference><el-button type="danger" link>删除</el-button></template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination class="pagination" v-model:current-page="page" v-model:page-size="size" :total="total" layout="total, prev, pager, next" @change="loadData" />

    <el-dialog v-model="dialogVisible" :title="form.id ? '编辑分类' : '新增分类'" width="480px">
      <el-form :model="form" label-width="80px">
        <el-form-item label="名称"><el-input v-model="form.name" /></el-form-item>
        <el-form-item label="描述"><el-input v-model="form.description" type="textarea" :rows="3" /></el-form-item>
        <el-form-item label="排序"><el-input-number v-model="form.sort" :min="0" /></el-form-item>
        <el-form-item label="状态">
          <el-select v-model="form.status"><el-option label="启用" :value="1" /><el-option label="禁用" :value="0" /></el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSave">保存</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { pageCategories, addCategory, updateCategory, deleteCategory } from '@/api/category'
import { formatDateTime } from '@/utils/format'

const keyword = ref('')
const page = ref(1)
const size = ref(10)
const total = ref(0)
const tableData = ref([])
const dialogVisible = ref(false)
const form = reactive({ id: null, name: '', description: '', sort: 0, status: 1 })

const loadData = async () => {
  const res = await pageCategories({ page: page.value, size: size.value, keyword: keyword.value })
  tableData.value = res.data.records
  total.value = res.data.total
}

const openDialog = (row) => {
  if (row) Object.assign(form, row)
  else Object.assign(form, { id: null, name: '', description: '', sort: 0, status: 1 })
  dialogVisible.value = true
}

const handleSave = async () => {
  if (form.id) await updateCategory(form)
  else await addCategory(form)
  ElMessage.success('保存成功')
  dialogVisible.value = false
  loadData()
}

const handleDelete = async (id) => {
  await deleteCategory(id)
  ElMessage.success('删除成功')
  loadData()
}

onMounted(loadData)
</script>

<style scoped>
.pagination { margin-top: 16px; justify-content: flex-end; }
</style>
相关推荐
陈天伟教授1 小时前
图解人工智能(52)人工智能应用-GPT 机器作家
人工智能
程序员晨曦2 小时前
Java 并发修仙传:ThreadLocal 从“闭关修炼”到“走火入魔”的救赎之路
java·开发语言
AIGS0012 小时前
探索向量空间JBoltAI:工业企业数智化升级的基础设施
java·人工智能·人工智能ai大模型应用
qq_527887872 小时前
机器学习训练中Epoch、Batch、Bath_size、Data_size的区别
人工智能·机器学习·batch
林间码客2 小时前
《人工智能概论》实验6 知识点复习提纲
人工智能
林间码客2 小时前
《人工智能概论》实验3 知识点复习提纲
人工智能
科技圈快迅2 小时前
商业旅拍后期修图软件实测:像素蛋糕功能与应用分析
人工智能
无忧智库2 小时前
某矿山井下人员精准定位与AI行为安全识别管控系统建设方案(WORD)
人工智能·安全
zhangjw342 小时前
第18篇:Java网络编程零基础详解,IP、端口、TCP、UDP、Socket通信、实战文件传输
java·网络·tcp/ip