衡阳外贸网站设计,通辽网站seo,网站推广途径,西安官方网站建设在之前#xff0c;我们主要完成了数据检索阶段#xff0c; 但是完整的RAG流程还需要有emedding阶段#xff0c; 即#xff1a;提取#xff08;读取#xff09;、转换#xff08;分隔#xff09;和加载#xff08;写入#xff09;Document LoadersDocument Loaders 文…在之前我们主要完成了数据检索阶段 但是完整的RAG流程还需要有emedding阶段 即提取读取、转换分隔和加载写入Document LoadersDocument Loaders 文档读取器springai提供了以下文档阅读器JSON文本HTMLJSoupMarkdownPDF页面PDF段落TikaDOCX、PPTX、HTML……alibaba ai也提供了很多阅读器https://github.com/alibaba/spring-ai-alibaba/tree/main/community/document-parsersdocument-parser-apache-pdfbox用于解析 PDF 格式文档。document-parser-bshtml用于解析基于 BSHTML 格式的文档。document-parser-pdf-tables专门用于从 PDF 文档中提取表格数据。document-parser-bibtex用于解析 BibTeX 格式的参考文献数据。document-parser-markdown用于解析 Markdown 格式的文档。document-parser-tika一个多功能文档解析器支持多种文档格式。以及网络来源文档读取器https://github.com/alibaba/spring-ai-alibaba/tree/main/community/document-readers读取TextTestpublic void testReaderText(Value(classpath:rag/terms-of-service.txt) Resource resource) {TextReader textReader new TextReader(resource);ListDocument documents textReader.read();for (Document document : documents) {System.out.println(document.getText());}}读取markdowndependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-markdown-document-reader/artifactId/dependencyTestpublic void testReaderMD(Value(classpath:rag/9_横店影视股份有限公司_0.md) Resource resource) {MarkdownDocumentReaderConfig config MarkdownDocumentReaderConfig.builder().withHorizontalRuleCreateDocument(true) // 分割线创建新document.withIncludeCodeBlock(false) // 代码创建新document false 会创建.withIncludeBlockquote(false) // 引用创建新document false 会创建.withAdditionalMetadata(filename, resource.getFilename()) // 每个document添加的元数据.build();MarkdownDocumentReader markdownDocumentReader new MarkdownDocumentReader(resource, config);ListDocument documents markdownDocumentReader.read();for (Document document : documents) {System.out.println(document.getText());}}pdf● PagePdfDocumentReader一页1个document● ParagraphPdfDocumentReader 按pdf目录分成一个个documentdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-markdown-document-reader/artifactId/dependencyTestpublic void testReaderPdf(Value(classpath:rag/平安银行2023年半年度报告摘要.pdf) Resource resource) {PagePdfDocumentReader pdfReader new PagePdfDocumentReader(resource,PdfDocumentReaderConfig.builder().withPageTopMargin(0).withPageExtractedTextFormatter(ExtractedTextFormatter.builder().withNumberOfTopTextLinesToDelete(0).build()).withPagesPerDocument(1).build());ListDocument documents pdfReader.read();for (Document document : documents) {System.out.println(document.getText());}}// 必需要带目录 按pdf的目录分documentTestpublic void testReaderParagraphPdf(Value(classpath:rag/平安银行2023年半年度报告.pdf) Resource resource) {ParagraphPdfDocumentReader pdfReader new ParagraphPdfDocumentReader(resource,PdfDocumentReaderConfig.builder()// 不同的PDF生成工具可能使用不同的坐标系 如果内容识别有问题 可以设置该属性为true.withReversedParagraphPosition(true).withPageTopMargin(0) // 上边距.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()// 从页面文本中删除前 N 行.withNumberOfTopTextLinesToDelete(0).build()).build());ListDocument documents pdfReader.read();for (Document document : documents) {System.out.println(document.getText());}}B站dependencygroupIdcom.alibaba.cloud.ai/groupIdartifactIdspring-ai-alibaba-starter-document-reader-bilibili/artifactId/dependencyTestvoid bilibiliDocumentReaderTest() {BilibiliDocumentReader bilibiliDocumentReader new BilibiliDocumentReader(https://www.bilibili.com/video/BV1C5UxYuEc2/?spm_id_from333.1387.upload.video_card.clickvd_sourcefa810d8b8d6765676cb343ada918d6eb);ListDocument documents bilibiliDocumentReader.get();System.out.println(documents);}DocumentSplitterDocumentSplitter文档拆分器转换器由于文本读取过来后 还需要分成一段一段的片段(分块chunk) 分块是为了更好地拆分语义单元这样在后面可以更精确地进行语义相似性检索也可以避免LLM的Token限制。SpringAi就提供了一个文档拆分器TextSplitter 抽象类TokenTextSplitter 按token分隔TokenTextSplitterchunkSize (默认值: 800) 100○ 每个文本块的目标大小以token为单位minChunkSizeChars (默认值: 350) 建议小一点○ 如果块超过最小块字符数( 按照块的最后. ! ? \n 符号截取)○ 如果块没超过最小块字符数 不会按照符号截取(保留原块。本服务条款适用于您对图灵航空 的体验。预订航班即表示您同意这些条款。1. 预订航班- 通过我们的网站或移动应用程序预订。- 预订时需要全额付款。 \n- 确保个人信息姓名、ID 等的准确性因为更正可能会产生 25minChunkLengthToEmbed (默认值: 5) 5○ 丢弃短于此长度的文本块(如果去掉\r\n 只剩5个有效文本 那就丢掉本服务条maxNumChunks(默认值: 10000)○ 最多能分多少个块 超过了就不管了keepSeparator(默认值: true)○ 是否在块中保留分隔符、换行符 \r\nTestpublic void testTokenTextSplitter(Value(classpath:rag/terms-of-service.txt) Resource resource) {TextReader textReader new TextReader(resource);textReader.getCustomMetadata().put(filename, resource.getFilename());ListDocument documents textReader.read();TokenTextSplitter splitter new TokenTextSplitter(1000, 400, 10, 5000, true);ListDocument apply splitter.apply(documents);apply.forEach(System.out::println);}整个流程如下自定分割器支持中英文同时支持中文和英文标点符号package com.xushu.springai.rag.ELT;public class ChineseTokenTextSplitter extends TextSplitter {private static final int DEFAULT_CHUNK_SIZE 800;private static final int MIN_CHUNK_SIZE_CHARS 350;private static final int MIN_CHUNK_LENGTH_TO_EMBED 5;private static final int MAX_NUM_CHUNKS 10000;private static final boolean KEEP_SEPARATOR true;private final EncodingRegistry registry Encodings.newLazyEncodingRegistry();private final Encoding encoding this.registry.getEncoding(EncodingType.CL100K_BASE);// The target size of each text chunk in tokensprivate final int chunkSize;// The minimum size of each text chunk in charactersprivate final int minChunkSizeChars;// Discard chunks shorter than thisprivate final int minChunkLengthToEmbed;// The maximum number of chunks to generate from a textprivate final int maxNumChunks;private final boolean keepSeparator;public ChineseTokenTextSplitter() {this(DEFAULT_CHUNK_SIZE, MIN_CHUNK_SIZE_CHARS, MIN_CHUNK_LENGTH_TO_EMBED, MAX_NUM_CHUNKS, KEEP_SEPARATOR);}public ChineseTokenTextSplitter(boolean keepSeparator) {this(DEFAULT_CHUNK_SIZE, MIN_CHUNK_SIZE_CHARS, MIN_CHUNK_LENGTH_TO_EMBED, MAX_NUM_CHUNKS, keepSeparator);}public ChineseTokenTextSplitter(int chunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks,boolean keepSeparator) {this.chunkSize chunkSize;this.minChunkSizeChars minChunkSizeChars;this.minChunkLengthToEmbed minChunkLengthToEmbed;this.maxNumChunks maxNumChunks;this.keepSeparator keepSeparator;}public static Builder builder() {return new Builder();}Overrideprotected ListString splitText(String text) {return doSplit(text, this.chunkSize);}protected ListString doSplit(String text, int chunkSize) {if (text null || text.trim().isEmpty()) {return new ArrayList();}ListInteger tokens getEncodedTokens(text);ListString chunks new ArrayList();int num_chunks 0;// maxNumChunks多能分多少个块 超过了就不管了while (!tokens.isEmpty() num_chunks this.maxNumChunks) {// 按照chunkSize进行分隔ListInteger chunk tokens.subList(0, Math.min(chunkSize, tokens.size()));String chunkText decodeTokens(chunk);// Skip the chunk if it is empty or whitespaceif (chunkText.trim().isEmpty()) {tokens tokens.subList(chunk.size(), tokens.size());continue;}// Find the last period or punctuation mark in the chunkint lastPunctuation Math.max(chunkText.lastIndexOf(.),Math.max(chunkText.lastIndexOf(?),Math.max(chunkText.lastIndexOf(!),Math.max(chunkText.lastIndexOf(\n),// 添加上我们中文的分割符号Math.max(chunkText.lastIndexOf(。),Math.max(chunkText.lastIndexOf(),chunkText.lastIndexOf()))))));// 按照句子截取之后长度 minChunkSizeCharsif (lastPunctuation ! -1 lastPunctuation this.minChunkSizeChars) {// 保留按照句子截取之后的内容chunkText chunkText.substring(0, lastPunctuation 1);}// 按照句子截取之后长度 minChunkSizeChars 保留原块// keepSeparatortrue 替换/r/n false不管String chunkTextToAppend (this.keepSeparator) ? chunkText.trim(): chunkText.replace(System.lineSeparator(), ).trim();// 替换/r/n之后的内容是不是this.minChunkLengthToEmbed 忽略if (chunkTextToAppend.length() this.minChunkLengthToEmbed) {chunks.add(chunkTextToAppend);}// Remove the tokens corresponding to the chunk text from the remaining tokenstokens tokens.subList(getEncodedTokens(chunkText).size(), tokens.size());num_chunks;}// Handle the remaining tokensif (!tokens.isEmpty()) {String remaining_text decodeTokens(tokens).replace(System.lineSeparator(), ).trim();if (remaining_text.length() this.minChunkLengthToEmbed) {chunks.add(remaining_text);}}return chunks;}private ListInteger getEncodedTokens(String text) {Assert.notNull(text, Text must not be null);return this.encoding.encode(text).boxed();}private String decodeTokens(ListInteger tokens) {Assert.notNull(tokens, Tokens must not be null);var tokensIntArray new IntArrayList(tokens.size());tokens.forEach(tokensIntArray::add);return this.encoding.decode(tokensIntArray);}public static final class Builder {private int chunkSize DEFAULT_CHUNK_SIZE;private int minChunkSizeChars MIN_CHUNK_SIZE_CHARS;private int minChunkLengthToEmbed MIN_CHUNK_LENGTH_TO_EMBED;private int maxNumChunks MAX_NUM_CHUNKS;private boolean keepSeparator KEEP_SEPARATOR;private Builder() {}public Builder withChunkSize(int chunkSize) {this.chunkSize chunkSize;return this;}public Builder withMinChunkSizeChars(int minChunkSizeChars) {this.minChunkSizeChars minChunkSizeChars;return this;}public Builder withMinChunkLengthToEmbed(int minChunkLengthToEmbed) {this.minChunkLengthToEmbed minChunkLengthToEmbed;return this;}public Builder withMaxNumChunks(int maxNumChunks) {this.maxNumChunks maxNumChunks;return this;}public Builder withKeepSeparator(boolean keepSeparator) {this.keepSeparator keepSeparator;return this;}public ChineseTokenTextSplitter build() {return new ChineseTokenTextSplitter(this.chunkSize, this.minChunkSizeChars, this.minChunkLengthToEmbed,this.maxNumChunks, this.keepSeparator);}}}分隔经验过细分块的潜在问题语义割裂 破坏上下文连贯性影响模型理解。计算成本增加分块过细会导致向量嵌入和检索次数增多增加时间和算力开销。信息冗余与干扰碎片化的文本块可能引入无关内容干扰检索结果的质量降低生成答案的准确性。分块过大的弊端信息丢失风险过大的文本块可能超出嵌入模型的输入限制导致关键信息未被有效编码。检索精度下降大块内容可能包含多主题混合与用户查询的相关性降低影响模型反馈效果。场景 分块策略 参数参考微博/短文本 句子级分块保留完整语义 每块100-200字符学术论文 段落级分块叠加10%重叠 每块300-500字符法律合同 条款级分块严格按条款分隔 每块200-400字符长篇小说 章节级分块过长段落递归拆分为段落 每块500-1000字符不要过分指望按照文本主题进行分隔 因为实战中的资料太多而且没有规律 根本没办法保证每个chunk是一个完整的主题内容 哪怕人为干预也很难。 所以实战中往往需要结合资料来决定分割器大多数情况就是按token数分 因为没有完美的 还可以加入人工干预,或者大模型分隔。分块五种策略以下是 RAG 的五种分块策略1固定大小分块生成块的最直观和直接的方法是根据预定义的字符、单词或标记数量将文本分成统一的段。由于直接分割会破坏语义流因此建议在两个连续的块之间保持一些重叠上图蓝色部分。这很容易实现。而且由于所有块的大小相同它简化了批处理。但有一个大问题。这通常会打断句子或想法。因此重要的信息很可能会分散到不同的块之间。2语义分块这个想法很简单。根据句子、段落或主题部分等有意义的单位对文档进行细分。接下来为每个片段创建嵌入。