|
LLM(大型语言模型)在生成自然语言文本方面展现出强大的能力,但它们的训练数据通常来自于公开的、广泛的数据源,这意味着它们在面对特定企业的私有数据时,可能无法准确地生成相关的答案或信息。一种可行的思路是将这些专业数据用于微调大型语言模型(LLM),但这通常对技术和成本的要求都非常高。相比之下,RAG系统提供了一个更加经济和高效的解决方案。它通过在用户的原始问题后附加相关的私域数据,将其与通用LLM结合进行分析和总结。这样,RAG系统通过检索增强的方式为LLM提供更精准的信息,大大提升了最终的回答效果,如下图所示: 
企业私有数据形成知识的重要性企业的私有数据通常以文档形式存储,包括技术手册、政策文件、客户档案、研究报告等。这些文档承载了企业的核心知识,是企业运营的基础。通过将这些文档转换为可检索的结构化数据,RAG系统能够在需要时快速访问和利用这些数据,从而为LLM提供即时的参考。这一过程可以: 企业私有数据存储介质的多样性
企业的私有数据通常以多种介质形式存储,这些介质承载了公司内部的关键知识和信息。常见的文档介质包括: 文本文档(如TXT、Markdown) 办公文档(如DOC、DOCX、PPT、XLSX) PDF文件 数据库(如SQL、NoSQL) 多媒体文件(如图像、音频、视频) 专有格式文件(如设计图纸、技术说明书等)
这些文档介质中的数据往往是结构化、半结构化或非结构化的,因此解析和读取这些数据对实现企业知识的自动化检索与生成至关重要。 基于DOC、DOCX文档的解析 DOC与DOCX是微软Word的文件格式,广泛用于企业的各类文档编写和存储。解析这些文档类型的过程相对复杂,因为它们不仅包含文本,还可能嵌入各种复杂的元素,如表格、图像、超链接、页眉页脚、注释、修订历史等。这些元素的解析和读取涉及多个层次的技术挑战。 1. 文本内容的解析from docx import Document
def parse_docx_text(file_path):doc = Document(file_path)for para in doc.paragraphs:print(f"段落内容: {para.text}")
2. 表格内容的解析def parse_docx_tables(file_path):doc = Document(file_path)for table in doc.tables:for row in table.rows:print([cell.text for cell in row.cells])
3. 图像和其他嵌入对象的解析def extract_images_from_docx(docx_path):doc = Document(docx_path)for rel in doc.part.rels.values():if "image" in rel.target_ref:print(f"提取图像: {rel.target_ref}")
4. 元数据的提取- 作者、创建日期、修订历史等元数据包含在文档的属性中,对于文档的版本控制和溯源管理非常重要。这些信息可以通过访问DOCX文件的
docProps来提取。
5. 处理批量文档- 在企业中,批量处理大量DOCX文档是常见的需求。此时需要考虑并行处理技术,以提升解析效率,并可以考虑对解析后的内容进行语义切分,以优化后续的RAG应用。
import osfrom concurrent.futures import ProcessPoolExecutor
def process_docx_files(directory):docx_files = [f for f in os.listdir(directory) if f.endswith('.docx')]with ProcessPoolExecutor() as executor:for _ in executor.map(parse_docx_text, docx_files):pass
解析和处理企业的DOC、DOCX文档是RAG系统的重要步骤,通过精确提取文档中的结构化和非结构化信息,可以更好地实现语义切分和知识库的构建。这些处理步骤不仅确保了企业私有数据的有效利用,还极大提升了RAG系统的生成质量和检索精准度。下面我们详细解析一段Python代码编写的DocxParser类的Docx解析方法。def __call__(self, filename, binary=None, from_page=0, to_page=100000):self.doc = Document(filename) if not binary else Document(BytesIO(binary))pn = 0lines = []last_image = Nonefor p in self.doc.paragraphs:if pn > to_page:breakif from_page <= pn < to_page:if p.text.strip():if p.style and p.style.name == 'Caption':former_image = Noneif lines and lines[-1][1] and lines[-1][2] != 'Caption':former_image = lines[-1][1].pop()elif last_image:former_image = last_imagelast_image = Nonelines.append((self.__clean(p.text), [former_image], p.style.name))else:current_image = self.get_picture(self.doc, p)image_list = [current_image]if last_image:image_list.insert(0, last_image)last_image = Nonelines.append((self.__clean(p.text), image_list, p.style.name))else:if current_image := self.get_picture(self.doc, p):if lines:lines[-1][1].append(current_image)else:last_image = current_imagefor run in p.runs:if 'lastRenderedPageBreak' in run._element.xml:pn += 1continueif 'w:br' in run._element.xml and 'type="page"' in run._element.xml:pn += 1new_line = [(line[0], reduce(concat_img, line[1]) if line[1] else None) for line in lines]
tbls = []for tb in self.doc.tables:html= "<table>"for r in tb.rows:html += "<tr>"i = 0while i < len(r.cells):span = 1c = r.cells[i]for j in range(i+1, len(r.cells)):if c.text == r.cells[j].text:span += 1i = ji += 1html += f"<td>{c.text}</td>" if span == 1 else f"<td colspan='{span}'>{c.text}</td>"html += "</tr>"html += "</table>"tbls.append(((None, html), ""))return new_line, tbls
def __call__(self, filename, binary=None, from_page=0, to_page=100000):self.doc = Document(filename) if not binary else Document(BytesIO(binary))pn = 0lines = []last_image = None
for p in self.doc.paragraphs:if pn > to_page:breakif from_page <= pn < to_page:if p.text.strip():if p.style and p.style.name == 'Caption':former_image = Noneif lines and lines[-1][1] and lines[-1][2] != 'Caption':former_image = lines[-1][1].pop()elif last_image:former_image = last_imagelast_image = Nonelines.append((self.__clean(p.text), [former_image], p.style.name))else:current_image = self.get_picture(self.doc, p)image_list = [current_image]if last_image:image_list.insert(0, last_image)last_image = Nonelines.append((self.__clean(p.text), image_list, p.style.name))else:if current_image := self.get_picture(self.doc, p):if lines:lines[-1][1].append(current_image)else:last_image = current_image
- 段落遍历:
for p in self.doc.paragraphs遍历文档的所有段落。pn记录当前页码,控制页码范围内的解析。 文本和图片处理:
for run in p.runs:if 'lastRenderedPageBreak' in run._element.xml:pn += 1continueif 'w:br' in run._element.xml and 'type="page"' in run._element.xml:pn += 1
new_line=[(line[0],reduce(concat_img,line[1])ifline[1]elseNone)forlineinlines] tbls=[]fortbinself.doc.tables:html="<table>"forrintb.rows:html+="<tr>"i=0whilei<len(r.cells):span=1c=r.cells[i]forjinrange(i+1,len(r.cells)):ifc.text==r.cells[j].text:span+=1i=ji+=1html+=f"<td>{c.text}</td>"ifspan==1elsef"<tdcolspan='{span}'>{c.text}</td>"html+="</tr>"html+="</table>"tbls.append(((None,html),""))returnnew_line,tbls
这段代码展示了如何精确地提取文档的核心信息,包括段落、图片和表格。通过控制解析的页码范围,可以高效处理大型文档,而对于图文并茂的文档,这段代码尤其有价值。在RAG系统中,这样的解析步骤可以帮助企业将非结构化文档转化为结构化数据,并通过进一步的语义切分和向量化处理,为后续的检索和生成提供高质量的输入。这段代码不仅展示了文档内容提取的技术实现,还表明了在复杂文档环境下,精确的文本与图像关联处理对RAG系统的重要性。 说明:企业文档解析的内容较多,所以分成多篇来阐述,重点是Pdf的文档的解析,敬请期待!
|