Advanced RAG实战-金融助手
通过摘要索引进行查询优化# ------------------------ 第三阶段:内容摘要生成 ------------------------# 定义摘要生成提示模板prompt_text = """您是一个专业的内容摘要助手,请对以下表格或文本块进行简洁的总结:# 初始化大模型(此处使用阿里云通义千问)# 构建摘要生成链# 批量生成表格摘要。
1. 项目背景
-
在金融领域,开发一个能够仿效专家解读上市公司年报的智能对话系统,一直是人工智能技术进步的关键目标。尽管目前的人工智能系统在文本对话领域已展现出显著的进展,但在更为精细、更具挑战性的金融领域交互方面,其性能尚需进一步提升。因此,我们致力于在现有大型模型的基础上,通过精细化调整、大型与小型模型的协同工作以及利用向量数据库等尖端技术,旨在进一步增强人工智能模型的性能。
问题描述:
之前我们讲过我们半结构化数据对于传统 RAG 来说可能具有挑战性,文本拆分可能会分解表,从而损坏检索中的数据;嵌入表可能会给语义相似性搜索带来挑战。对于这个问题可以通过构建摘要索引解决这个问题:分别为每个文本和表格数据创建摘要,将其嵌入文档。
-
首先用
Unstructured来提取文档 (PDF) 中的文本和表格,并进行分块 -
然后用
llm分别对每个文本和表格创建摘要,将其嵌入向量数据库 -
最后通过摘要使用
MultiVectorRetriever过滤出相关文档,喂给llm当作上下文
2. 环境配置
pip install langchain langchain-chroma "unstructured[all-docs]" pydantic lxml langchainhub pi_heif
Unstructured(非结构化数据) 使用的 PDF 分区将使用:
-
tesseract:用于光学字符识别 (OCR) -
poppler:用于 PDF 渲染和处理
# mac下载方式 brew install tesseract brew install poppler
Windows下载
-
poppler
-
下载地址:https://github.com/oschwartz10612/poppler-windows/releases
-
导入方式:解压缩后将bin文件夹路径导入windows系统‘Path’变量,可在命令行验证是否成功
-
验证方式:终端命令
pdfinfo -v
-
-
Tesseract
-
导入方式:下载后直接安装即可,安装完成后查看是否安装成功,若未识别,手动添加安装根目录至系统‘Path’变量
-
验证方式:终端命令
tesseract -v
3. 完整代码
from langchain_text_splitters import RecursiveCharacterTextSplitter
from unstructured.partition.pdf import partition_pdf
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
import uuid
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_core.documents import Document
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.runnables import RunnablePassthrough
import os
from dotenv import load_dotenv
load_dotenv()
embedding_model_path = r'D:\LLM\Local_model\BAAI\bge-large-zh-v1___5'
# 初始化嵌入模型(用于文本向量化)
embeddings_model = HuggingFaceEmbeddings(
model_name=embedding_model_path
)
# 定义文件路径(需根据自己的路径修改)准备科学上网
path = r"D:\python_project\AI_object\RAG备课\day03\2020-03-17__厦门灿坤实业股份有限公司__200512__闽灿坤__2019年__年度报告.pdf"
# ------------------------ 第一阶段:PDF解析处理 ------------------------
# 使用unstructured库解析PDF文档
raw_pdf_elements = partition_pdf(
filename=path,
extract_images_in_pdf=False, # 不提取PDF中的图片
infer_table_structure=True, # 启用表格结构识别
max_characters=4000, # 每个文本块最大字符数
new_after_n_chars=3800, # 达到3800个字符后分新块
combine_text_under_n_chars=2000, # 合并小于2000个字符的碎片文本
strategy='hi_res',
)
# 统计各类元素数量
category_counts = {}
for element in raw_pdf_elements:
category = str(type(element))
category_counts[category] = category_counts.get(category, 0) + 1
print("元素类型统计:", category_counts)
# ------------------------ 第二阶段:元素分类处理 ------------------------
# 分类处理PDF元素 把文本和表格元素分类存在列表
table_elements = []
text_elements = []
for element in raw_pdf_elements:
if "unstructured.documents.elements.Table" in str(type(element)):
table_elements.append(str(element))
elif "unstructured.documents.elements.Text" in str(type(element)):
text_elements.append(str(element))
elif "unstructured.documents.elements.NarrativeText" in str(type(element)):
text_elements.append(str(element))
# 手动将文本内容分块
chuck_text_elements = RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=200).split_text(''.join(text_elements))
print(f'文本块内容:{chuck_text_elements}')
print(f"识别到表格数量: {len(table_elements)}, 文本块数量: {len(chuck_text_elements)}")
print("表格示例:", table_elements[0:10])
# ------------------------ 第三阶段:内容摘要生成 ------------------------
# 定义摘要生成提示模板
prompt_text = """您是一个专业的内容摘要助手,请对以下表格或文本块进行简洁的总结:
{element}"""
prompt = ChatPromptTemplate.from_template(prompt_text)
# 初始化大模型(此处使用阿里云通义千问)
model = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("api_key"),
base_url=os.getenv("base_url")
)
# 构建摘要生成链
summarize_chain = {"element": lambda x: x} | prompt | model | StrOutputParser()
# 批量生成表格摘要
table_summaries = summarize_chain.batch(table_elements, {"max_concurrency": 5}) # 并发处理
print("表格摘要示例:", table_summaries[0:10])
# 批量生成文本摘要
text_summaries = summarize_chain.batch(chuck_text_elements, {"max_concurrency": 5})
print("文本摘要示例:", text_summaries[0:10])
# ------------------------ 第四阶段:构建多向量检索器 ------------------------
# 创建向量数据库(用于存储摘要)
vectorstore = Chroma(
collection_name="summaries",
embedding_function=embeddings_model
)
# 创建内存存储(用于存储原始内容)
store = InMemoryStore()
id_key = "doc_id" # 文档标识键
# 初始化多向量检索器
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key=id_key,
)
# 添加文本数据到检索器
text_ids = [str(uuid.uuid4()) for _ in chuck_text_elements]
summary_texts = [
Document(page_content=s, metadata={id_key: text_ids[i]})
for i, s in enumerate(text_summaries)
]
retriever.vectorstore.add_documents(summary_texts)
retriever.docstore.mset(list(zip(text_ids, chuck_text_elements)))
# 添加表格数据到检索器
table_ids = [str(uuid.uuid4()) for _ in table_elements]
summary_tables = [
Document(page_content=s, metadata={id_key: table_ids[i]})
for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)
retriever.docstore.mset(list(zip(table_ids, table_elements)))
# ------------------------ 第五阶段:构建问答链 ------------------------
# 定义问答提示模板
template = """请仅根据以下上下文(包含文本和表格)回答问题:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 构建问答链
chain = (
{"context": lambda x: retriever.invoke(input=x["question"]), "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# 示例问答测试
question = "营业收入构有哪些?可以往哪里发展?"
print("回答:", chain.invoke({"question": question}))
print("检索结果:", retriever.invoke(question))
4. PDF解析处理
可以使用 Unstructured chunking
-
尝试识别文档部分(例如,Introduction等)
-
然后,构建维护部分的文本块,同时也遵循用户定义的块大小
这里我们对某公司的年报进行分析,原文档有151页,考虑到大多数人的电脑性能,删减到47页
embedding_model_path = r'D:\LLM\Local_model\BAAI\bge-large-zh-v1___5'
# 初始化嵌入模型(用于文本向量化)
embeddings_model = HuggingFaceEmbeddings(
model_name=embedding_model_path
)
# 定义文件路径(需根据自己的路径修改)准备科学上网
path = r"D:\python_project\AI_object\RAG备课\day03\2020-03-17__厦门灿坤实业股份有限公司__200512__闽灿坤__2019年__年度报告.pdf"
# ------------------------ 第一阶段:PDF解析处理 ------------------------
# 使用unstructured库解析PDF文档
raw_pdf_elements = partition_pdf(
filename=path,
extract_images_in_pdf=False, # 不提取PDF中的图片
infer_table_structure=True, # 启用表格结构识别
max_characters=4000, # 每个文本块最大字符数
new_after_n_chars=3800, # 达到3800个字符后分新块
combine_text_under_n_chars=2000, # 合并小于2000个字符的碎片文本
strategy='hi_res',
)
# 统计各类元素数量
category_counts = {}
for element in raw_pdf_elements:
category = str(type(element))
category_counts[category] = category_counts.get(category, 0) + 1
print("元素类型统计:", category_counts)
unstructured 文档元素类型全表:
| 类名(Class Name) | 中文名称 | 说明 | 典型应用场景 |
|---|---|---|---|
CompositeElement |
复合元素 | 包含混合内容(文本+简单表格/图片) | 无法明确分类的混合内容区域 |
Table |
表格 | 结构化表格数据,可能包含合并单元格 | 财务报表、数据报表 |
Title |
标题 | 章节标题(通过字体大小和位置识别) | 文档目录、章节标题 |
NarrativeText |
叙述文本 | 段落正文(完整语义段落) | 报告正文、论文段落 |
Image |
图片 | 提取的图片元素(需启用 extract_images_in_pdf=True) |
扫描件、示意图 |
CheckBox |
复选框 | 表单中的勾选框标记 | 调查问卷、申请表 |
PageBreak |
分页符 | 分页符标记 | 跨页内容处理 |
Header |
页眉 | 页面顶部重复出现的标题信息 | 合同、正式文件 |
Footer |
页脚 | 页面底部信息(页码、版权声明) | 技术手册、法律文件 |
Formula |
公式 | 数学公式(LaTeX格式) | 学术论文、数学教材 |
ListItem |
列表项 | 带编号或符号的列表项 | 项目清单、步骤说明 |
CodeSnippet |
代码片段 | 程序代码块(保留缩进和格式) | 技术文档、API参考 |
EmailAddress |
电子邮件地址 | 识别出的电子邮件地址 | 联系信息提取 |
PhoneNumber |
电话号码 | 识别出的电话号码 | 客户信息提取 |
BulletedText |
项目符号文本 | 带项目符号的无序列表 | PPT内容、产品特性列表 |
FigureCaption |
图注 | 图片/表格的说明文字(如 "图1-1") | 学术论文、技术文档 |
Footnote |
脚注 | 页面底部的注释引用 | 学术文献、法律条款 |
Quote |
引用 | 引用文本(通常有缩进或特殊格式) | 书籍、新闻报道 |
Metadata |
元数据 | 文件元信息(作者、创建时间等) | 文档属性分析 |
UncategorizedText |
未分类文本 | 无法归类到任何已知类型的文本 | 边缘情况处理 |
5. 数据分类
-
我们这里只处理了表格数据和文本数据
# ------------------------ 第二阶段:元素分类处理 ------------------------
# 分类处理PDF元素 把文本和表格元素分类存在列表
table_elements = []
text_elements = []
for element in raw_pdf_elements:
if "unstructured.documents.elements.Table" in str(type(element)):
table_elements.append(str(element))
elif "unstructured.documents.elements.Text" in str(type(element)):
text_elements.append(str(element))
elif "unstructured.documents.elements.NarrativeText" in str(type(element)):
text_elements.append(str(element))
# 手动将文本内容分块
chuck_text_elements = RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=200).split_text(''.join(text_elements))
print(f'文本块内容:{chuck_text_elements}')
print(f"识别到表格数量: {len(table_elements)}, 文本块数量: {len(chuck_text_elements)}")
print("表格示例:", table_elements[0:10])
6. 生成摘要
-
通过摘要索引进行查询优化
# ------------------------ 第三阶段:内容摘要生成 ------------------------
# 定义摘要生成提示模板
prompt_text = """您是一个专业的内容摘要助手,请对以下表格或文本块进行简洁的总结:
{element}"""
prompt = ChatPromptTemplate.from_template(prompt_text)
# 初始化大模型(此处使用阿里云通义千问)
model = ChatOpenAI(
model="qwen-plus",
api_key=os.getenv("api_key"),
base_url=os.getenv("base_url")
)
# 构建摘要生成链
summarize_chain = {"element": lambda x: x} | prompt | model | StrOutputParser()
# 批量生成表格摘要
table_summaries = summarize_chain.batch(table_elements, {"max_concurrency": 5}) # 并发处理
print("表格摘要示例:", table_summaries[0:10])
# 批量生成文本摘要
text_summaries = summarize_chain.batch(chuck_text_elements, {"max_concurrency": 5})
print("文本摘要示例:", text_summaries[0:10])
7. 构建多向量检索器
# ------------------------ 第四阶段:构建多向量检索器 ------------------------
# 创建向量数据库(用于存储摘要)
vectorstore = Chroma(
collection_name="summaries",
embedding_function=embeddings_model
)
# 创建内存存储(用于存储原始内容)
store = InMemoryStore()
id_key = "doc_id" # 文档标识键
# 初始化多向量检索器
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key=id_key,
)
# 添加文本数据到检索器
text_ids = [str(uuid.uuid4()) for _ in chuck_text_elements]
summary_texts = [
Document(page_content=s, metadata={id_key: text_ids[i]})
for i, s in enumerate(text_summaries)
]
retriever.vectorstore.add_documents(summary_texts)
retriever.docstore.mset(list(zip(text_ids, chuck_text_elements)))
# 添加表格数据到检索器
table_ids = [str(uuid.uuid4()) for _ in table_elements]
summary_tables = [
Document(page_content=s, metadata={id_key: table_ids[i]})
for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)
retriever.docstore.mset(list(zip(table_ids, table_elements)))
8. 构建问答链
-
可以发现模型的回答和文档是一样的
# ------------------------ 第五阶段:构建问答链 ------------------------
# 定义问答提示模板
template = """请仅根据以下上下文(包含文本和表格)回答问题:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 构建问答链
chain = (
{"context": lambda x: retriever.invoke(input=x["question"]), "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# 示例问答测试
question = "营业收入构有哪些?可以往哪里发展?"
print("回答:", chain.invoke({"question": question}))
print("检索结果:", retriever.invoke(question))
更多推荐

所有评论(0)