微信扫码
与创始人交个朋友
我要投稿
LLM(大型语言模型)在生成自然语言文本方面展现出强大的能力,但它们的训练数据通常来自于公开的、广泛的数据源,这意味着它们在面对特定企业的私有数据时,可能无法准确地生成相关的答案或信息。一种可行的思路是将这些专业数据用于微调大型语言模型(LLM),但这通常对技术和成本的要求都非常高。相比之下,RAG系统提供了一个更加经济和高效的解决方案。它通过在用户的原始问题后附加相关的私域数据,将其与通用LLM结合进行分析和总结。这样,RAG系统通过检索增强的方式为LLM提供更精准的信息,大大提升了最终的回答效果,如下图所示:
企业的私有数据通常以文档形式存储,包括技术手册、政策文件、客户档案、研究报告等。这些文档承载了企业的核心知识,是企业运营的基础。通过将这些文档转换为可检索的结构化数据,RAG系统能够在需要时快速访问和利用这些数据,从而为LLM提供即时的参考。这一过程可以:
提高回答的专业性:确保回答内容符合企业的专业要求和标准。
增强数据安全性:通过私有数据的封闭检索,避免敏感信息泄露。
企业私有数据存储介质的多样性
企业的私有数据通常以多种介质形式存储,这些介质承载了公司内部的关键知识和信息。常见的文档介质包括:
文本文档(如TXT、Markdown)
办公文档(如DOC、DOCX、PPT、XLSX)
PDF文件
数据库(如SQL、NoSQL)
多媒体文件(如图像、音频、视频)
专有格式文件(如设计图纸、技术说明书等)
这些文档介质中的数据往往是结构化、半结构化或非结构化的,因此解析和读取这些数据对实现企业知识的自动化检索与生成至关重要。
基于DOC、DOCX文档的解析
DOC与DOCX是微软Word的文件格式,广泛用于企业的各类文档编写和存储。解析这些文档类型的过程相对复杂,因为它们不仅包含文本,还可能嵌入各种复杂的元素,如表格、图像、超链接、页眉页脚、注释、修订历史等。这些元素的解析和读取涉及多个层次的技术挑战。
段落和标题:DOCX文件内部结构通常是通过XML来表示的,各个段落、标题有明确的标签。解析时需要识别这些标签,以提取不同层级的文本结构信息。
from docx import Document
def parse_docx_text(file_path):
doc = Document(file_path)
for para in doc.paragraphs:
print(f"段落内容: {para.text}")
表格结构:表格在企业文档中非常常见,如财务报表、技术规范表等。解析表格时,需要精确识别每个单元格的数据,并保持其在表格中的位置。
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])
图像:DOCX文档中可能包含的图像可以通过解析文档内嵌的<w:drawing>
标签来提取。
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}")
docProps
来提取。import os
from 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
def __call__(self, filename, binary=None, from_page=0, to_page=100000):
self.doc = Document(
filename) if not binary else Document(BytesIO(binary))
pn = 0
lines = []
last_image = None
for p in self.doc.paragraphs:
if pn > to_page:
break
if from_page <= pn < to_page:
if p.text.strip():
if p.style and p.style.name == 'Caption':
former_image = None
if lines and lines[-1][1] and lines[-1][2] != 'Caption':
former_image = lines[-1][1].pop()
elif last_image:
former_image = last_image
last_image = None
lines.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 = None
lines.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 run in p.runs:
if 'lastRenderedPageBreak' in run._element.xml:
pn += 1
continue
if 'w:br' in run._element.xml and 'type="page"' in run._element.xml:
pn += 1
new_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 = 0
while i < len(r.cells):
span = 1
c = r.cells[i]
for j in range(i+1, len(r.cells)):
if c.text == r.cells[j].text:
span += 1
i = j
i += 1
html += 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 = 0
lines = []
last_image = None
函数入口:__call__
是一个魔术方法,使得类实例可以像函数一样被调用。它接受文件名、二进制数据、起始页和结束页作为输入参数,并将文档加载到self.doc
中。如果提供了二进制数据,则使用BytesIO
进行处理。
from_page
和to_page
参数允许我们控制从文档的哪个页开始解析,到哪个页结束。这对于处理大型文档时非常有用,可以避免一次性加载整个文档导致内存过载。for p in self.doc.paragraphs:
if pn > to_page:
break
if from_page <= pn < to_page:
if p.text.strip():
if p.style and p.style.name == 'Caption':
former_image = None
if lines and lines[-1][1] and lines[-1][2] != 'Caption':
former_image = lines[-1][1].pop()
elif last_image:
former_image = last_image
last_image = None
lines.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 = None
lines.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
记录当前页码,控制页码范围内的解析。文本和图片处理:
段落文本:如果段落有文本内容且属于指定页码范围,则提取文本并进行清理(self.__clean
)。
lines
列表中。对于普通段落,提取图片后也将其加入lines
列表。for run in p.runs:
if 'lastRenderedPageBreak' in run._element.xml:
pn += 1
continue
if 'w:br' in run._element.xml and 'type="page"' in run._element.xml:
pn += 1
分页符处理:for run in p.runs
遍历段落中的每一个运行块(run),检测是否存在分页符或手动分页符,并据此增加页码计数pn
。
new_line = [(line[0], reduce(concat_img, line[1]) if line[1] else None) for line in lines]
数据整合:将解析的行数据(文本+图片)进行合并处理。reduce(concat_img, line[1])
会将同一行中的多张图片合并。
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
表格解析:遍历文档中的每一个表格,将表格内容提取并转换为HTML格式。对于单元格内容相同且连续的情况,使用colspan
合并单元格。
结果返回:最终函数返回提取的文本和图片组合(new_line
),以及转换为HTML格式的表格(tbls
)。
在RAG系统中,这样的解析步骤可以帮助企业将非结构化文档转化为结构化数据,并通过进一步的语义切分和向量化处理,为后续的检索和生成提供高质量的输入。这段代码不仅展示了文档内容提取的技术实现,还表明了在复杂文档环境下,精确的文本与图像关联处理对RAG系统的重要性。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-07-18
2024-05-05
2024-07-09
2024-05-19
2024-07-09
2024-06-20
2024-07-07
2024-07-07
2024-07-08
2024-07-09
2024-11-25
2024-11-06
2024-11-06
2024-11-05
2024-11-04
2024-10-27
2024-10-25
2024-10-21