修复RAG文档分割错误 根据错误日志问题出现在文档分割阶段。当尝试恢复名为毕业论文的文档时由于文档内容非常短只有1个字符...LangChain4J的递归文档分割器无法将其分割成小于300字符的片段因此抛出了异常。解决方案1.修复RAGKnowledgeBaseService.java中的recoverSingleDocument方法在recoverSingleDocument方法中添加内容长度检查避免处理过短的内容private boolean recoverSingleDocument(String documentName) { Path tempFile null; try { // 1. 从Redis获取文档元数据 MapString, String metadata ragDocumentRedisService.getDocumentMetadata(documentName); String cloudUrl metadata.get(url); if (cloudUrl null) { log.warn(文档 {} 的URL不存在跳过恢复, documentName); return false; } // 2. 从OBS下载文件到临时目录 String objectKey extractObjectKeyFromUrl(cloudUrl); byte[] fileData downloadFromCloudStorage(objectKey); if (fileData null) { log.warn(从OBS下载文档失败: {}, documentName); return false; } // 3. 创建临时文件 tempFile createTempFile(documentName, fileData); // 4. 提取内容并添加到知识库 String content extractContentFromBytes(fileData, getFileExtension(documentName)); // 新增检查内容长度避免过短内容导致分割错误 if (content null || content.trim().isEmpty() || content.length() 10) { log.warn(文档 {} 内容过短或为空跳过恢复。内容长度: {}, documentName, content ! null ? content.length() : 0); return false; } // 新增记录内容摘要用于调试 String contentPreview content.length() 100 ? content.substring(0, 100) ... : content; log.debug(恢复文档 {}内容预览: {}, documentName, contentPreview); Document document Document.from(content); var ingestor EmbeddingStoreIngestor.builder() .documentSplitter(DocumentSplitters.recursive(300, 50)) .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .build(); ingestor.ingest(document); log.info(文档恢复成功: {}, documentName); return true; } catch (Exception e) { log.error(恢复文档失败: {}, documentName, e); return false; } finally { // 5. 无论如何都删除临时文件 if (tempFile ! null) { deleteTempFile(tempFile); } } }2.修复RAGKnowledgeBaseService.java中的extractContentFromBytes方法改进内容提取方法增加错误处理和内容验证/** * 从字节数组提取内容 */ private String extractContentFromBytes(byte[] fileData, String fileExtension) { try { if (fileData null || fileData.length 0) { log.warn(文件数据为空); return ; } // 这里可以根据文件扩展名进行不同的处理 // 目前简单返回字符串内容 String content new String(fileData, UTF-8); // 检查内容是否有效 content content.trim(); // 如果内容是...或其他无意义字符返回空字符串 if (content.matches(^\\.$) || content.length() 10) { log.warn(提取的内容过短或无意义: {}..., content.length() 50 ? content.substring(0, 50) : content); return ; } return content; } catch (Exception e) { log.error(从字节数组提取内容失败, e); throw new RuntimeException(内容提取失败: e.getMessage()); } }3.添加文档内容验证工具方法在RAGKnowledgeBaseService类中添加以下方法/** * 验证文档内容是否有效 */ private boolean isValidDocumentContent(String content) { if (content null || content.trim().isEmpty()) { return false; } // 移除空白字符后检查长度 String trimmedContent content.trim(); // 检查是否只包含点号或其他无意义字符 if (trimmedContent.matches(^[\\s\\.,;:!?]$)) { return false; } // 检查是否有实际内容至少包含一些中文字符或单词 boolean hasChinese trimmedContent.matches(.*[\\u4e00-\\u9fa5].*); boolean hasWords trimmedContent.matches(.*[a-zA-Z]{3,}.*); return trimmedContent.length() 50 (hasChinese || hasWords); } /** * 检查文档是否为有效格式 */ private boolean isValidDocumentFormat(String fileName) { if (fileName null || !fileName.contains(.)) { return false; } String extension getFileExtension(fileName).toLowerCase(); // 只支持常见的文本和文档格式 return extension.matches(txt|md|pdf|doc|docx|ppt|pptx|xls|xlsx); }4.在恢复过程中使用验证更新recoverSingleDocument方法使用验证方法private boolean recoverSingleDocument(String documentName) { Path tempFile null; try { // 1. 从Redis获取文档元数据 MapString, String metadata ragDocumentRedisService.getDocumentMetadata(documentName); String cloudUrl metadata.get(url); if (cloudUrl null) { log.warn(文档 {} 的URL不存在跳过恢复, documentName); return false; } // 新增检查文档格式 if (!isValidDocumentFormat(documentName)) { log.warn(文档 {} 格式不支持跳过恢复, documentName); return false; } // 2. 从OBS下载文件到临时目录 String objectKey extractObjectKeyFromUrl(cloudUrl); byte[] fileData downloadFromCloudStorage(objectKey); if (fileData null || fileData.length 0) { log.warn(从OBS下载文档失败或文件为空: {}, documentName); return false; } // 3. 创建临时文件 tempFile createTempFile(documentName, fileData); // 4. 提取内容并添加到知识库 String content extractContentFromBytes(fileData, getFileExtension(documentName)); // 新增验证内容是否有效 if (!isValidDocumentContent(content)) { log.warn(文档 {} 内容无效跳过恢复。内容长度: {}, documentName, content ! null ? content.length() : 0); return false; } Document document Document.from(content); var ingestor EmbeddingStoreIngestor.builder() .documentSplitter(DocumentSplitters.recursive(300, 50)) .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .build(); ingestor.ingest(document); log.info(文档恢复成功: {}内容长度: {}, documentName, content.length()); return true; } catch (Exception e) { log.error(恢复文档失败: {}, documentName, e); return false; } finally { // 5. 无论如何都删除临时文件 if (tempFile ! null) { deleteTempFile(tempFile); } } }5.在上传时也添加验证可选在addDocumentToKnowledgeBaseWithBackup方法中也可以添加类似的验证/** * 增强版添加文档到知识库支持云存储备份和Redis记录 */ public void addDocumentToKnowledgeBaseWithBackup(String content, String documentName, MultipartFile originalFile) { if (!ragEnabled || embeddingModel null) { log.warn(RAG功能未启用或嵌入模型不可用无法添加文档); return; } // 新增验证文档内容 if (!isValidDocumentContent(content)) { log.error(文档内容无效拒绝添加到知识库: {}, documentName); throw new RuntimeException(文档内容无效请确保文档包含足够的信息内容); } Path tempFile null; try { // ... 原有代码 ... } catch (Exception e) { log.error(添加文档到知识库失败: {}, documentName, e); throw new RuntimeException(文档处理失败: e.getMessage()); } finally { if (tempFile ! null) { deleteTempFile(tempFile); } } }6.在配置中调整文档分割参数在RAGConfig.java中调整内容检索器的配置Bean public EmbeddingStoreContentRetriever contentRetriever(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { if (embeddingModel null) { log.warn(嵌入模型不可用RAG功能将受限); return null; } return EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(3) // 减少结果数量提高性能 .minScore(0.6) // 降低最小分数阈值 .build(); }总结修复的核心思路增加内容验证在恢复文档前检查内容是否有效避免处理无效内容跳过过短、为空或只包含无意义字符的文档更好的错误处理提供更详细的日志信息便于调试格式验证只处理支持的文档格式这些修改可以防止系统在处理损坏或不完整的文档时崩溃同时保持RAG系统的健壮性。建议在应用修复后重新启动服务并检查日志以确保知识库能够正常初始化。我们遇到了一个错误在恢复文档时由于文档内容太短只有一个字符...而导致无法分割。错误信息显示The text ... (1 characters long) doesnt fit into the maximum segment size (300 characters), and there is no subSplitter defined to split it further.问题出现在RAGKnowledgeBaseService的recoverSingleDocument方法中。我们使用DocumentSplitters.recursive(300, 50)来分割文档但是当文档内容只有一个字符时无法分割因为300字符的最小分段大小比1个字符大。但是请注意我们上传的文档可能不是只有一个字符。可能是从OBS下载的文件内容有问题或者我们在提取内容时出了问题。让我们检查一下恢复文档的步骤从Redis获取元数据得到云存储URL。从云存储下载文件数据byte[]。提取内容extractContentFromBytes方法。将内容创建为Document然后进行分割和嵌入。错误发生在第4步说明我们提取的内容只有一个字符...。这显然是不对的。可能的原因下载的文件数据本身就是...但更可能是提取内容的方法有问题。或者文件扩展名不正确导致我们用了错误的方法提取内容。我们注意到在extractContentFromBytes方法中我们只是简单地将字节数组转换为字符串UTF-8。如果文件是二进制格式比如Word、PDF等那么这样转换就会得到乱码可能只有几个字符。但是我们之前上传的文件可能是文本文件但也不排除用户上传了其他格式的文件。然而我们的系统目前只支持文本文件吗从代码上看我们并没有对文件格式做过多处理。另外我们注意到在恢复文档时我们使用了一个getFileExtension方法来获取文件扩展名但是在extractContentFromBytes方法中并没有根据扩展名进行不同的处理。因此我们需要做以下改进在extractContentFromBytes方法中根据文件扩展名使用不同的解析器。例如如果是.txt则直接转换为字符串如果是.docx、.pdf等则需要使用相应的解析器。如果无法解析则应该跳过该文档并记录错误。但是由于时间关系我们可能暂时只支持文本文件。对于非文本文件我们需要在上传时进行限制或者在恢复时跳过。另外我们也可以考虑在恢复文档时如果内容太短比如小于10个字符则记录警告并跳过而不是抛出异常。但是从错误堆栈来看异常是在分割时抛出的因此我们可以在分割前检查文档内容的长度。如果内容长度小于最小分段大小我们可以跳过分割直接嵌入整个文档尽管这样可能不符合分段要求但至少不会报错。然而我们使用的是递归分割器它要求文档内容至少大于最小分段大小300字符。所以对于很短的文档我们需要特殊处理。我们可以修改recoverSingleDocument方法在分割前检查文档内容的长度。如果内容长度小于300我们可以不使用分割器直接将整个文档作为一个片段。但是EmbeddingStoreIngestor默认使用分割器我们无法直接控制。我们可以考虑在恢复文档时如果内容太短我们将其长度调整为至少大于300字符比如在后面填充空格但这样会影响嵌入效果。另一种方法是我们可以在提取内容后如果内容太短则直接跳过该文档并记录日志。根据业务需求我们可能不需要保存内容太短的文档因为它们可能没有有效信息。因此我建议在恢复单个文档时先检查内容长度如果小于某个阈值比如10个字符则记录并跳过。但是错误中显示的内容是...这很可能是一个错误的内容所以我们更应该跳过。让我们修改recoverSingleDocument方法在提取内容后检查长度如果小于10个字符则记录并返回false。同时我们也应该检查提取的内容是否为空。修改步骤在recoverSingleDocument方法中提取内容后String content extractContentFromBytes(fileData, getFileExtension(documentName));if (content null || content.trim().length() 10) {log.warn(文档内容太短或为空跳过恢复: {}, documentName);return false;}这样就可以避免因为内容太短而无法分割的问题。但是这只是一个临时解决方案。长远来看我们需要支持多种文件格式并且确保上传的文件是有效的。另外我们还需要检查上传文件的类型并在上传时进行验证。由于时间有限我们先应用这个临时修复。让我们修改代码。