文档管理
项目目标
本项目旨在构建一个功能完备的 RAG(Retrieval-Augmented Generation)系统,主要目标包括:
- 知识库管理:支持创建、更新和删除知识库,便于用户高效维护内容。
- 文档处理:包括文档的拆分、片段的向量化处理,以提升检索效率和准确性。
- 问答系统:提供高效的向量检索和实时生成回答的能力,支持复杂汇总类问题的处理。
- 系统优化:通过统计分析和推理问答调试,不断优化系统性能和用户体验。
系统核心概念
在 RAG 系统中,以下是几个核心概念:
- 应用:知识库的集合。每个应用可以自定义提示词,以满足不同的个性化需求。
- 知识库:由多个文档组成,便于用户对内容进行分类和管理。
- 文档:系统中对应的真实文档内容。
- 片段:文档经过拆分后的最小内容单元,用于更高效的处理和检索。
功能实现步骤
- 数据库设计 查看 01.md 
 设计并实现项目所需的数据表结构与数据库方案,为后续的数据操作打下坚实基础。
- 用户登录 查看 02.md 
 实现了安全可靠的用户认证系统,保护用户数据并限制未经授权的访问。
- 模型管理 查看 03.md 
 支持针对不同平台的模型(如 OpenAI、Google Gemini、Claude)进行管理与配置。
- 知识库管理 查看 04.md 
 提供创建、更新及删除知识库的功能,方便用户维护与管理文档内容。
- 文档拆分 查看 05.md 
 可将文档拆分为多个片段,便于后续向量化和检索操作。
- 片段向量 查看 06.md 
 将文本片段进行向量化处理,以便进行语义相似度计算及高效检索。
- 命中率测试 查看 07.md 
 通过语义相似度和 Top-N 算法,检索并返回与用户问题最相关的文档片段,用于评估检索的准确性。
- 文档管理 查看 08.md 
 提供上传和管理文档的功能,上传后可自动拆分为片段便于进一步处理。
- 片段管理 查看 09.md 
 允许对已拆分的片段进行增、删、改、查等操作,确保内容更新灵活可控。
- 问题管理 查看 10.md 
 为片段指定相关问题,以提升检索时的准确性与关联度。
- 应用管理 查看 11.md 
 提供创建和配置应用(智能体)的功能,并可关联指定模型和知识库。
- 向量检索 查看 12.md 
 基于语义相似度,在知识库中高效检索与用户问题最匹配的片段。
- 推理问答调试 查看 13.md 
 提供检索与问答性能的评估工具,帮助开发者进行系统优化与调试。
- 对话问答 查看 14.md 
 为用户提供友好的人机交互界面,结合检索到的片段与用户问题实时生成回答。
- 统计分析 查看 15.md 
 对用户的提问与系统回答进行数据化分析,并以可视化图表的形式呈现系统使用情况。
- 用户管理 查看 16.md 
 提供多用户管理功能,包括用户的增删改查及权限控制。
- API 管理 查看 17.md 
 对外提供标准化 API,便于外部系统集成和调用本系统的功能。
- 存储文件到 S3 查看 18.md 
 将用户上传的文件存储至 S3 等对象存储平台,提升文件管理的灵活性与可扩展性。
- 文档解析优化 查看 19.md 
 介绍与对比常见的文档解析方案,并提供提升文档解析速度和准确性的优化建议。
- 片段汇总 查看 20.md 
 对片段内容进行汇总,以提升总结类问题的查询与回答效率。
- 文档多分块与检索 查看 21.md 
 将片段进一步拆分为句子并进行向量检索,提升检索的准确度与灵活度。
- 多文档支持 查看 22.md 
 兼容多种文档格式,包括- .doc,- .docx,- .xls,- .xlsx,- .ppt,- .pptx等。
- 对话日志 查看 23.md 
 记录并展示对话日志,用于后续分析和问题回溯。
- 检索性能优化 查看 24.md 
 提供整库扫描和分区检索等多种方式,进一步提高检索速度和效率。
- Milvus 查看 25.md 
 将向量数据库切换至 Milvus,以在大规模向量检索场景中获得更佳的性能与可扩展性。
- 文档解析方案和费用对比 查看 26.md 
 对比不同文档解析方案在成本、速度、稳定性等方面的差异,为用户提供更加经济高效的选择。
- 爬取网页数据 查看 27.md 
 支持从网页中抓取所需内容,后续处理流程与本地文档一致:分段、向量化、存储与检索。
 在本节中,我们将实现 文档管理 功能。此前,我们已完成数据库设计、用户登录、知识库管理、文件拆分、片段向量化功能以及命中率测试。文档管理功能将进一步完善我们的系统,使用户能够高效地管理和检索文档。
功能概述
文档管理 功能包括以下主要操作:
- 获取文档列表:根据数据集 ID 获取分页文档列表。
- 获取单个文档详情:根据数据集 ID 和文档 ID 获取特定文档的详细信息。
- 获取所有文档:根据数据集 ID 获取该数据集下的所有文档。
接口实现
我们提供了三个主要的 API 接口来支持文档管理功能:
- 获取文档列表
- 获取单个文档详情
- 获取所有文档
1. 获取文档列表
请求
GET http://localhost:3000/api/dataset/{datasetId}/document/{pageNo}/{pageSize}
- 路径参数: - datasetId:数据集的唯一标识。
- pageNo:当前页码。
- pageSize:每页显示的文档数量。
 
示例请求
GET http://localhost:3000/api/dataset/443309276048408576/document/1/10
响应
{
  "message": null,
  "data": {
    "size": 10,
    "total": 1,
    "current": 1,
    "records": [
      {
        "tenant_id": "0",
        "directly_return_similarity": 0.9,
        "creator": "",
        "is_active": true,
        "create_time": 1730853299657,
        "dataset_id": "443309276048408576",
        "title": null,
        "type": "pdf",
        "updater": "",
        "update_time": 1730853299657,
        "deleted": 0,
        "user_id": "1",
        "char_length": 22837,
        "meta": null,
        "file_id": "443431990348681216",
        "name": "ICS111_31391_Miller_Syllabus_F24.pdf",
        "paragraph_count": 6,
        "hit_handling_method": "optimization",
        "files": null,
        "id": "443662133182980096",
        "status": "1"
      }
    ]
  },
  "code": 200
}
响应字段说明
- message:响应消息,成功时为- null。
- data:响应数据对象。- size:每页文档数量。
- total:总文档数。
- current:当前页码。
- records:文档列表数组,每个文档包含以下字段:- tenant_id:租户 ID。
- directly_return_similarity:直接返回的相似度评分。
- creator:文档创建者。
- is_active:文档是否激活。
- create_time:文档创建时间(时间戳)。
- dataset_id:所属数据集 ID。
- title:文档标题。
- type:文档类型(如 PDF)。
- updater:文档更新者。
- update_time:文档更新时间(时间戳)。
- deleted:删除标识(0 表示未删除)。
- user_id:用户 ID。
- char_length:文档字符长度。
- meta:元数据。
- file_id:文件 ID。
- name:文档名称。
- paragraph_count:段落数量。
- hit_handling_method:命中处理方法。
- files:关联文件信息。
- id:文档唯一标识。
- status:文档状态。
 
 
- code:响应状态码,- 200表示成功。
2. 获取单个文档详情
请求
GET http://localhost:3000/api/dataset/{datasetId}/document/{documentId}
- 路径参数: - datasetId:数据集的唯一标识。
- documentId:文档的唯一标识。
 
示例请求
GET http://localhost:3000/api/dataset/443309276048408576/document/1
响应
{
  "message": null,
  "data": {
    "tenant_id": "0",
    "creator": "",
    "create_time": 1730769172064,
    "embedding_mode_id": "443263808507674624",
    "remark": null,
    "type": "0",
    "updater": "",
    "update_time": 1730769172064,
    "deleted": 0,
    "user_id": "1",
    "meta": null,
    "name": "ICS 141",
    "id": "443309276048408576",
    "desc": "ICS 141 课程资料"
  },
  "code": 200
}
响应字段说明
- message:响应消息,成功时为- null。
- data:文档详细信息对象,包含以下字段:- tenant_id:租户 ID。
- creator:文档创建者。
- create_time:文档创建时间(时间戳)。
- embedding_mode_id:嵌入模式 ID。
- remark:备注信息。
- type:文档类型。
- updater:文档更新者。
- update_time:文档更新时间(时间戳)。
- deleted:删除标识(0 表示未删除)。
- user_id:用户 ID。
- meta:元数据。
- name:文档名称。
- id:文档唯一标识。
- desc:文档描述。
 
- code:响应状态码,- 200表示成功。
3. 获取所有文档
请求
GET http://localhost:3000/api/dataset/{datasetId}/document
- 路径参数: - datasetId:数据集的唯一标识。
 
示例请求
GET http://localhost:3000/api/dataset/443309276048408576/document
响应
{
  "message": null,
  "data": [
    {
      "tenant_id": "0",
      "directly_return_similarity": 0.9,
      "creator": "",
      "is_active": true,
      "create_time": 1730853299657,
      "dataset_id": "443309276048408576",
      "title": null,
      "type": "pdf",
      "updater": "",
      "update_time": 1730853299657,
      "deleted": 0,
      "user_id": "1",
      "char_length": 22837,
      "meta": null,
      "file_id": "443431990348681216",
      "name": "ICS111_31391_Miller_Syllabus_F24.pdf",
      "paragraph_count": 6,
      "hit_handling_method": "optimization",
      "files": null,
      "id": "443662133182980096",
      "status": "1"
    }
  ],
  "code": 200
}
响应字段说明
- message:响应消息,成功时为- null。
- data:文档列表数组,每个文档包含以下字段:- tenant_id:租户 ID。
- directly_return_similarity:直接返回的相似度评分。
- creator:文档创建者。
- is_active:文档是否激活。
- create_time:文档创建时间(时间戳)。
- dataset_id:所属数据集 ID。
- title:文档标题。
- type:文档类型(如 PDF)。
- updater:文档更新者。
- update_time:文档更新时间(时间戳)。
- deleted:删除标识(0 表示未删除)。
- user_id:用户 ID。
- char_length:文档字符长度。
- meta:元数据。
- file_id:文件 ID。
- name:文档名称。
- paragraph_count:段落数量。
- hit_handling_method:命中处理方法。
- files:关联文件信息。
- id:文档唯一标识。
- status:文档状态。
 
- code:响应状态码,- 200表示成功。
代码实现
以下是文档管理功能的核心代码实现,包括 API 控制器和服务层。
1. API 控制器
文件:ApiDatasetController.java
import java.util.List;
import com.litongjava.annotation.Get;
import com.litongjava.annotation.RequestPath;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.maxkb.service.MaxKbDocumentService;
import com.litongjava.model.result.ResultVo;
import com.litongjava.tio.boot.http.TioRequestContext;
@RequestPath("/api/dataset")
public class ApiDatasetController {
  /**
   * 获取指定数据集下的文档列表(分页)
   *
   * @param datasetId 数据集 ID
   * @param pageNo    当前页码
   * @param pageSize  每页文档数量
   * @return ResultVo 包含分页文档列表
   */
  @Get("/{datasetId}/document/{pageNo}/{pageSize}")
  public ResultVo pageDocument(Long datasetId, Integer pageNo, Integer pageSize) {
    // 获取当前用户 ID
    Long userId = TioRequestContext.getUserIdLong();
    // 调用文档服务获取分页文档列表
    return Aop.get(MaxKbDocumentService.class).page(userId, datasetId, pageNo, pageSize);
  }
  /**
   * 获取指定数据集下的单个文档详情
   *
   * @param datasetId  数据集 ID
   * @param documentId 文档 ID
   * @return ResultVo 包含文档详细信息
   */
  @Get("/{datasetId}/document/{documentId}")
  public ResultVo getDocument(Long datasetId, Long documentId) {
    // 获取当前用户 ID
    Long userId = TioRequestContext.getUserIdLong();
    // 调用文档服务获取文档详情
    return Aop.get(MaxKbDocumentService.class).get(userId, datasetId, documentId);
  }
  /**
   * 获取指定数据集下的所有文档
   *
   * @param datasetId 数据集 ID
   * @return ResultVo 包含文档列表
   */
  @Get("/{datasetId}/document")
  public ResultVo documentList(Long datasetId) {
    Long userId = TioRequestContext.getUserIdLong();
    return Aop.get(MaxKbDocumentService.class).list(userId, datasetId);
  }
}
代码说明
- 注解 @RequestPath("/api/dataset"):定义控制器的基础路径为/api/dataset。
- 方法 pageDocument:- 注解 @Get("/{datasetId}/document/{pageNo}/{pageSize}"):定义 GET 请求路径。
- 参数: - datasetId:数据集 ID。
- pageNo:当前页码。
- pageSize:每页文档数量。
 
- 功能:获取指定数据集下的分页文档列表。
 
- 注解 
- 方法 getDocument:- 注解 @Get("/{datasetId}/document/{documentId}"):定义 GET 请求路径。
- 参数: - datasetId:数据集 ID。
- documentId:文档 ID。
 
- 功能:获取指定数据集下的单个文档详情。
 
- 注解 
- 方法 documentList:- 注解 @Get("/{datasetId}/document"):定义 GET 请求路径。
- 参数: - datasetId:数据集 ID。
 
- 功能:获取指定数据集下的所有文档列表。
 
- 注解 
- 依赖注入:通过 Aop.get(MaxKbDocumentService.class)获取MaxKbDocumentService服务实例。
2. 服务层
文件:MaxKbDocumentService.java
package com.litongjava.maxkb.service;
import java.util.List;
import com.jfinal.kit.Kv;
import com.litongjava.db.TableInput;
import com.litongjava.db.TableResult;
import com.litongjava.db.activerecord.Row;
import com.litongjava.kit.RecordUtils;
import com.litongjava.maxkb.constant.TableNames;
import com.litongjava.maxkb.model.ResultPage;
import com.litongjava.model.page.Page;
import com.litongjava.model.result.ResultVo;
import com.litongjava.table.services.ApiTable;
public class MaxKbDocumentService {
  /**
   * 分页获取文档列表
   *
   * @param userId    当前用户 ID
   * @param datasetId 数据集 ID
   * @param pageNo    当前页码
   * @param pageSize  每页文档数量
   * @return ResultVo 包含分页文档列表
   */
  public ResultVo page(Long userId, Long datasetId, Integer pageNo, Integer pageSize) {
    TableInput tableInput = new TableInput();
    if (userId != 1) { // 假设 userId 为 1 的用户为管理员
      tableInput.set("user_id", userId);
    }
    tableInput.set("dataset_id", datasetId)
              .setPageNo(pageNo)
              .setPageSize(pageSize);
    TableResult<Page<Row>> tableResult = ApiTable.page(TableNames.max_kb_document, tableInput);
    Page<Row> page = tableResult.getData();
    int totalRow = page.getTotalRow();
    List<Row> list = page.getList();
    List<Kv> kvs = RecordUtils.recordsToKv(list, false);
    ResultPage<Kv> resultPage = new ResultPage<>(pageNo, pageSize, totalRow, kvs);
    return ResultVo.ok(resultPage);
  }
  /**
   * 获取所有文档列表
   *
   * @param userId    当前用户 ID
   * @param datasetId 数据集 ID
   * @return ResultVo 包含文档列表
   */
  public ResultVo list(Long userId, Long datasetId) {
    TableInput tableInput = new TableInput();
    if (userId != 1) { // 假设 userId 为 1 的用户为管理员
      tableInput.set("user_id", userId);
    }
    tableInput.set("dataset_id", datasetId);
    TableResult<List<Row>> tableResult = ApiTable.list(TableNames.max_kb_document, tableInput);
    List<Row> records = tableResult.getData();
    List<Kv> kvs = RecordUtils.recordsToKv(records, false);
    return ResultVo.ok(kvs);
  }
  /**
   * 获取单个文档详情
   *
   * @param userId     当前用户 ID
   * @param datasetId  数据集 ID
   * @param documentId 文档 ID
   * @return ResultVo 包含文档详细信息
   */
  public ResultVo get(Long userId, Long datasetId, Long documentId) {
    TableInput tableInput = TableInput.by("id", documentId);
    if (userId != 1) { // 非管理员用户需额外过滤
      tableInput.set("user_id", userId);
    }
    tableInput.set("dataset_id", datasetId);
    Row data = ApiTable.get(TableNames.max_kb_document, tableInput).getData();
    if (data == null) {
      return ResultVo.error("文档未找到");
    }
    return ResultVo.ok(data.toKv());
  }
}
代码说明
- 方法 page:- 参数: - userId:当前用户的 ID,用于权限控制。
- datasetId:数据集 ID,用于过滤文档。
- pageNo:当前页码。
- pageSize:每页文档数量。
 
- 功能: - 创建 TableInput对象,用于构建查询参数。
- 根据用户 ID 进行权限控制,如果用户不是管理员(假设 userId != 1),则仅查询该用户创建的文档。
- 设置数据集 ID、页码和页大小。
- 调用 ApiTable.page方法执行分页查询,获取TableResult。
- 从 TableResult中提取Page<Row>对象,获取总行数和记录列表。
- 将记录列表转换为键值对列表 (List<Kv>)。
- 构建 ResultPage<Kv>对象,包含分页信息和文档列表。
- 返回成功的 ResultVo对象,包含ResultPage数据。
 
- 创建 
 
- 参数: 
- 方法 list:- 参数: - userId:当前用户的 ID,用于权限控制。
- datasetId:数据集 ID,用于过滤文档。
 
- 功能: - 创建 TableInput对象,用于构建查询参数。
- 根据用户 ID 进行权限控制,如果用户不是管理员,则仅查询该用户创建的文档。
- 设置数据集 ID。
- 调用 ApiTable.list方法获取文档列表。
- 将记录列表转换为键值对列表 (List<Kv>)。
- 返回成功的 ResultVo对象,包含文档列表数据。
 
- 创建 
 
- 参数: 
- 方法 get:- 参数: - userId:当前用户的 ID,用于权限控制。
- datasetId:数据集 ID,用于过滤文档。
- documentId:文档 ID,用于查询特定文档。
 
- 功能: - 创建 TableInput对象,并设置文档 ID 作为查询条件。
- 根据用户 ID 进行权限控制: - 如果用户是管理员 (userId == 1),则仅根据数据集 ID 过滤。
- 否则,增加用户 ID 过滤,确保用户只能访问自己的文档。
 
- 如果用户是管理员 (
- 调用 ApiTable.get方法获取单条记录 (Row)。
- 检查记录是否存在,若不存在则返回错误信息。
- 将 Row转换为键值对 (Kv)。
- 返回成功的 ResultVo对象,包含文档详细信息。
 
- 创建 
 
- 参数: 
- 依赖组件: - ApiTable:用于执行数据库表的分页查询和单条记录查询。
- RecordUtils:用于将数据库记录转换为键值对格式。
- ResultVo和- ResultPage:用于标准化 API 响应结构。
 
权限控制
在上述实现中,我们假设 userId 为 1 的用户为管理员,具有查看所有数据集和文档的权限。非管理员用户只能查看自己创建的数据集和文档。这种权限控制确保了数据的安全性和隐私性。
异常处理
当前实现未详细展示异常处理逻辑。建议在实际开发中加入以下内容:
- 参数校验:确保请求参数的有效性,如 datasetId和documentId的存在性、pageNo和pageSize的合理性。
- 权限验证:在服务层进一步验证用户是否有权访问指定的数据集和文档。
- 错误响应:在发生异常时,返回适当的错误消息和状态码,如 400 Bad Request、401 Unauthorized、404 Not Found等。
测试
为了确保文档管理功能的正确性和稳定性,建议编写以下测试用例:
- 获取文档列表: - 查询存在的数据集,验证返回的文档列表是否正确。
- 查询不存在的数据集,验证返回的错误信息。
- 测试不同的 pageNo和pageSize参数,确保分页逻辑正确。
 
- 获取单个文档详情: - 查询存在的文档,验证返回的文档详细信息是否正确。
- 查询不存在的文档,验证返回的错误信息。
- 验证权限控制,确保用户无法访问未授权的文档。
 
- 获取所有文档: - 查询存在的数据集,验证返回的所有文档列表是否正确。
- 查询不存在的数据集,验证返回的错误信息。
- 验证权限控制,确保用户只能获取自己有权限查看的文档。
 
总结
通过上述接口和代码实现,我们成功地为系统添加了文档管理功能。该功能允许用户根据数据集管理和检索文档,支持分页查询、详细信息查看以及获取所有文档。未来,可以进一步扩展此功能,如添加文档上传、编辑、删除等操作,以满足更全面的文档管理需求。
