在此演示中,我们将通过一个用例来展示我们的“RecursiveRetriever”模块在分层数据上的应用。
递归检索的概念在于,我们不仅探索直接最相关的节点,还探索节点与额外检索器/查询引擎的关系并执行它们。例如,一个节点可以代表结构化表格的简洁摘要,并链接到该结构化表格上的 SQL/Pandas 查询引擎。然后,如果该节点被检索到,我们也会查询底层查询引擎以获取答案。
这对于具有分层关系的文档尤其有用。在此示例中,我们将浏览一篇关于亿万富翁的维基百科文章(PDF 格式),该文章包含文本和各种嵌入式结构化表格。我们首先在每个表格上创建一个 Pandas 查询引擎,同时也通过 IndexNode(存储查询引擎的链接)表示每个表格;该节点与其他节点一起存储在向量存储中。
在查询时,如果获取到 IndexNode,则将查询底层的查询引擎/检索器。
关于设置的注意事项
我们使用 camelot
从 PDF 中提取基于文本的表格。
In [ ]
%pip install llama-index-embeddings-openai
%pip install llama-index-readers-file pymupdf
%pip install llama-index-llms-openai
%pip install llama-index-experimental
import camelot
# https://en.wikipedia.org/wiki/The_World%27s_Billionaires
from llama_index.core import VectorStoreIndex
from llama_index.experimental.query_engine import PandasQueryEngine
from llama_index.core.schema import IndexNode
from llama_index.llms.openai import OpenAI
from llama_index.readers.file import PyMuPDFReader
from typing import List
import os os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
import os
os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from llama_index.core import Settings
Settings.llm = OpenAI(model="gpt-3.5-turbo")
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
我们使用 PyMuPDFReader
读取文档的主体文本。
我们还使用 camelot
从文档中提取一些结构化表格
file_path = "billionaires_page.pdf"
file_path = "billionaires_page.pdf"
# initialize PDF reader
reader = PyMuPDFReader()
docs = reader.load(file_path)
# use camelot to parse tables
def get_tables(path: str, pages: List[int]):
table_dfs = []
for page in pages:
table_list = camelot.read_pdf(path, pages=str(page))
table_df = table_list[0].df
table_df = (
table_df.rename(columns=table_df.iloc[0])
.drop(table_df.index[0])
.reset_index(drop=True)
)
table_dfs.append(table_df)
return table_dfs
table_dfs = get_tables(file_path, pages=[3, 25])
# shows list of top billionaires in 2023
table_dfs[0]
姓名 | 净资产\n(美元) | 年龄 | 国籍 | 主要财富来源 | Bernard Arnault 和\n家族 | |
---|---|---|---|---|---|---|
0 | 1 | 2110 亿美元 | 法国 | 74 | LVMH | Elon Musk |
1 | 2 | 1800 亿美元 | 1800亿美元 | 51 | 美国 | 特斯拉, SpaceX, X Corp. |
2 | 3 | 杰夫·贝佐斯 | 1140亿美元 | 59 | 美国 | 亚马逊 |
3 | 4 | 拉里·埃里森 | 1070亿美元 | 78 | 美国 | 甲骨文公司 |
4 | 5 | 沃伦·巴菲特 | 1060亿美元 | 92 | 美国 | 伯克希尔·哈撒韦 |
5 | 6 | 比尔·盖茨 | 1040亿美元 | 67 | 美国 | 微软 |
6 | 7 | 迈克尔·布隆伯格 | 945亿美元 | 81 | 美国 | 彭博社 |
7 | 8 | 卡洛斯·斯利姆及其家族 | 930亿美元 | 83 | 墨西哥 | Telmex, América Móvil, Grupo\nCarso |
8 | 9 | 穆克什·安巴尼 | 834亿美元 | 65 | 印度 | 信实工业 |
9 | 10 | 史蒂夫·鲍尔默 | 807亿美元 | 67 | 美国 | 微软 |
# shows list of top billionaires
table_dfs[1]
年份 | 亿万富翁数量 | 该群体的总净资产 | |
---|---|---|---|
0 | 2023[2] | 2,640 | 12.2万亿美元 |
1 | 2022[6] | 2,668 | 12.7万亿美元 |
2 | 2021[11] | 2,755 | 13.1万亿美元 |
3 | 2020 | 2,095 | 8.0万亿美元 |
4 | 2019 | 2,153 | 8.7万亿美元 |
5 | 2018 | 2,208 | 9.1万亿美元 |
6 | 2017 | 2,043 | 7.7万亿美元 |
7 | 2016 | 1,810 | 6.5万亿美元 |
8 | 2015[18] | 1,826 | 7.1万亿美元 |
9 | 2014[67] | 1,645 | 6.4万亿美元 |
10 | 2013[68] | 1,426 | 5.4万亿美元 |
11 | 2012 | 1,226 | 4.6万亿美元 |
12 | 2011 | 1,210 | 4.5万亿美元 |
13 | 2010 | 1,011 | 3.6万亿美元 |
14 | 2009 | 793 | 2.4万亿美元 |
15 | 2008 | 1,125 | 4.4万亿美元 |
16 | 2007 | 946 | 3.5万亿美元 |
17 | 2006 | 793 | 2.6万亿美元 |
18 | 2005 | 691 | 2.2万亿美元 |
19 | 2004 | 587 | 1.9万亿美元 |
20 | 2003 | 476 | 1.4万亿美元 |
21 | 2002 | 497 | 1.5万亿美元 |
22 | 2001 | 538 | 1.8万亿美元 |
23 | 2000 | 470 | 8980亿美元 |
24 | 来源:福布斯。[18][67][66][68] |
创建 Pandas 查询引擎¶
我们为每个结构化表格创建了一个 pandas 查询引擎。
这些可以独立执行,以回答关于每个表格的查询。
警告: 此工具为 LLM 提供了对 eval
函数的访问权限。在运行此工具的机器上可能会发生任意代码执行。虽然对代码进行了一定程度的过滤,但除非进行严格的沙盒或使用虚拟机,否则不建议在生产环境中使用此工具。
# define query engines over these tables
llm = OpenAI(model="gpt-4")
df_query_engines = [
PandasQueryEngine(table_df, llm=llm) for table_df in table_dfs
]
response = df_query_engines[0].query(
"What's the net worth of the second richest billionaire in 2023?"
)
print(str(response))
$180 billion
response = df_query_engines[1].query(
"How many billionaires were there in 2009?"
)
print(str(response))
793
构建向量索引¶
在分块文档以及链接到表格的额外 IndexNode
对象上构建向量索引。
from llama_index.core import Settings
doc_nodes = Settings.node_parser.get_nodes_from_documents(docs)
# define index nodes
summaries = [
(
"This node provides information about the world's richest billionaires"
" in 2023"
),
(
"This node provides information on the number of billionaires and"
" their combined net worth from 2000 to 2023."
),
]
df_nodes = [
IndexNode(text=summary, index_id=f"pandas{idx}")
for idx, summary in enumerate(summaries)
]
df_id_query_engine_mapping = {
f"pandas{idx}": df_query_engine
for idx, df_query_engine in enumerate(df_query_engines)
}
# construct top-level vector index + query engine
vector_index = VectorStoreIndex(doc_nodes + df_nodes)
vector_retriever = vector_index.as_retriever(similarity_top_k=1)
在我们的 RetrieverQueryEngine
中使用 RecursiveRetriever
¶
我们定义一个 RecursiveRetriever
对象来递归地检索/查询节点。然后我们将它与 ResponseSynthesizer
一起放入我们的 RetrieverQueryEngine
中来合成响应。
我们传入从 id 到检索器以及从 id 到查询引擎的映射。然后我们传入一个代表我们首先查询的检索器的根 id。
# baseline vector index (that doesn't include the extra df nodes).
# used to benchmark
vector_index0 = VectorStoreIndex(doc_nodes)
vector_query_engine0 = vector_index0.as_query_engine()
from llama_index.core.retrievers import RecursiveRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core import get_response_synthesizer
recursive_retriever = RecursiveRetriever(
"vector",
retriever_dict={"vector": vector_retriever},
query_engine_dict=df_id_query_engine_mapping,
verbose=True,
)
response_synthesizer = get_response_synthesizer(response_mode="compact")
query_engine = RetrieverQueryEngine.from_args(
recursive_retriever, response_synthesizer=response_synthesizer
)
response = query_engine.query(
"What's the net worth of the second richest billionaire in 2023?"
)
Retrieving with query id None: What's the net worth of the second richest billionaire in 2023? Retrieved node with id, entering: pandas0 Retrieving with query id pandas0: What's the net worth of the second richest billionaire in 2023? Got response: $180 billion
response.source_nodes[0].node.get_content()
"Query: What's the net worth of the second richest billionaire in 2023?\nResponse: $180\xa0billion"
str(response)
'$180 billion.'
response = query_engine.query("How many billionaires were there in 2009?")
Retrieving with query id None: How many billionaires were there in 2009? Retrieved node with id, entering: pandas1 Retrieving with query id pandas1: How many billionaires were there in 2009? Got response: 793
str(response)
'793'
response = vector_query_engine0.query(
"How many billionaires were there in 2009?"
)
print(response.source_nodes[0].node.get_content())
print(str(response))
Based on the context information, it is not possible to determine the exact number of billionaires in 2009. The provided information only mentions the number of billionaires in 2013 and 2014.
response.source_nodes[0].node.get_content()
response = query_engine.query(
"Which billionaires are excluded from this list?"
)
print(str(response))
Royal families and dictators whose wealth is contingent on a position are excluded from this list.