LlamaIndex作为数据和语言学习模型(LLMs)之间的桥梁,提供了一套工具包,使您能够围绕数据建立查询接口,用于各种任务,如问答和摘要。
1. 案例目标
本教程将指导您使用数据代理(Data Agent)构建一个上下文增强的聊天机器人。这个由LLMs驱动的代理能够智能地执行关于您的数据的任务。最终结果是一个聊天机器人代理,配备了LlamaIndex提供的强大数据接口工具,可以回答关于您数据的问题。
我们将构建一个"10-K聊天机器人",使用来自Dropbox的原始UBER 10-K HTML文件。用户可以与聊天机器人交互,询问与10-K文件相关的问题。
2. 技术栈与核心依赖
- LlamaIndex- 作为数据和LLMs之间的桥梁
- OpenAI API- 提供语言模型和嵌入模型
- Unstructured- 用于解析HTML文件
- VectorStoreIndex- 用于构建向量索引
- SubQuestionQueryEngine- 用于综合多个10-K文件的答案
- FunctionAgent- 用于构建聊天机器人代理
核心依赖包
%pip install llama-index-readers-file %pip install llama-index-embeddings-openai %pip install llama-index-agent-openai %pip install llama-index-llms-openai %pip install llama-index-question-gen-openai %pip install unstructured3. 环境配置
首先需要设置OpenAI API密钥:
import os os.environ["OPENAI_API_KEY"] = "sk-..."然后配置LlamaIndex的全局默认设置:
from llama_index.core import Settings from llama_index.llms.openai import OpenAI from llama_index.embeddings.openai import OpenAIEmbedding # 全局默认设置 Settings.llm = OpenAI(model="gpt-4o-mini") Settings.embed_model = OpenAIEmbedding(model_name="text-embedding-3-large") Settings.chunk_size = 512 Settings.chunk_overlap = 644. 案例实现
4.1 数据摄取
首先下载原始的10-K文件(2019-2022年):
# 注意:代码示例假设您在Jupyter notebook中操作 # 下载文件 !mkdir data !wget "https://www.dropbox.com/s/948jr9cfs7fgj99/UBER.zip?dl=1" -O data/UBER.zip !unzip data/UBER.zip -d data使用Unstructured库将HTML文件解析为格式化文本:
from llama_index.readers.file import UnstructuredReader from pathlib import Path years = [2022, 2021, 2020, 2019] loader = UnstructuredReader() doc_set = {} all_docs = [] for year in years: year_docs = loader.load_data( file=Path(f"./data/UBER/UBER_{year}.html"), split_documents=False ) # 将年份元数据插入到每一年 for d in year_docs: d.metadata = {"year": year} doc_set[year] = year_docs all_docs.extend(year_docs)4.2 为每年设置向量索引
为每年设置一个向量索引。每个向量索引允许我们询问关于特定年份10-K文件的问题:
# 初始化简单向量索引 # 注意:如果索引已经加载,不要运行此单元格! from llama_index.core import VectorStoreIndex, StorageContext index_set = {} for year in years: storage_context = StorageContext.from_defaults() cur_index = VectorStoreIndex.from_documents( doc_set[year], storage_context=storage_context, ) index_set[year] = cur_index storage_context.persist(persist_dir=f"./storage/{year}")要从磁盘加载索引:
# 从磁盘加载索引 from llama_index.core import StorageContext, load_index_from_storage index_set = {} for year in years: storage_context = StorageContext.from_defaults( persist_dir=f"./storage/{year}" ) cur_index = load_index_from_storage( storage_context, ) index_set[year] = cur_index4.3 设置子问题查询引擎以综合跨10-K文件的答案
由于我们有4年的文档访问权限,我们可能不仅想询问关于特定年份10-K文档的问题,还想询问需要分析所有10-K文件的问题。
为此,我们可以使用子问题查询引擎。它将查询分解为子查询,每个子查询由单个向量索引回答,并综合结果以回答整体查询。
首先为每个向量索引定义一个QueryEngineTool:
from llama_index.core.tools import QueryEngineTool individual_query_engine_tools = [ QueryEngineTool.from_defaults( query_engine=index_set[year].as_query_engine(), name=f"vector_index_{year}", description=( "useful for when you want to answer queries about the" f" {year} SEC 10-K for Uber" ), ) for year in years ]创建子问题查询引擎:
from llama_index.core.query_engine import SubQuestionQueryEngine query_engine = SubQuestionQueryEngine.from_defaults( query_engine_tools=individual_query_engine_tools, )4.4 设置聊天机器人代理
我们使用LlamaIndex数据代理来设置外部聊天机器人代理,它可以访问一组工具。具体来说,我们将使用FunctionAgent,利用OpenAI API函数调用。
首先为子问题查询引擎定义QueryEngineTool:
query_engine_tool = QueryEngineTool.from_defaults( query_engine=query_engine, name="sub_question_query_engine", description=( "useful for when you want to answer queries that require analyzing" " multiple SEC 10-K documents for Uber" ), )将工具组合成一个单一列表:
tools = individual_query_engine_tools + [query_engine_tool]创建代理:
from llama_index.core.agent.workflow import FunctionAgent from llama_index.llms.openai import OpenAI agent = FunctionAgent(tools=tools, llm=OpenAI(model="gpt-4o"))4.5 设置聊天循环
agent = FunctionAgent(tools=tools, llm=OpenAI(model="gpt-4o")) ctx = Context(agent) while True: text_input = input("User: ") if text_input == "exit": break response = await agent.run(text_input, ctx=ctx) print(f"Agent: {response}")5. 案例效果
5.1 简单问候
当测试简单的"hello"查询时,代理不使用任何工具:
from llama_index.core.workflow import Context # 设置此特定交互的上下文 ctx = Context(agent) response = await agent.run("hi, i am bob", ctx=ctx) print(str(response)) # 输出: Hello Bob! How can I assist you today?5.2 特定年份查询
当测试关于特定年份10-K的查询时,代理将使用相关的向量索引工具:
response = await agent.run( "What were some of the biggest risk factors in 2020 for Uber?", ctx=ctx ) print(str(response)) # 输出: In 2020, some of the biggest risk factors for Uber included: # 1. Legal and Regulatory Risks... # 2. Data Privacy and Security Risks... # ... (详细列出2020年的风险因素)5.3 跨年份比较查询
当测试比较/对比跨年份风险因素的查询时,代理将使用子问题查询引擎工具:
cross_query_str = ( "Compare/contrast the risk factors described in the Uber 10-K across" " years. Give answer in bullet points." ) response = await agent.run(cross_query_str, ctx=ctx) print(str(response)) # 输出: Here's a comparison of the risk factors for Uber across the years 2020, 2021, and 2022: # - COVID-19 Impact: # - 2020: The pandemic significantly affected business operations... # - 2021: Continued impact of the pandemic was a concern... # - 2022: The pandemic's impact was less emphasized... # ... (详细比较各年份的风险因素)6. 案例实现思路
6.1 数据处理与索引构建
案例首先通过Unstructured库解析HTML格式的SEC 10-K文件,将非结构化数据转换为LlamaIndex可以处理的Document对象。然后为每年的文档创建单独的向量索引,这样可以对特定年份的文档进行高效查询。
6.2 多文档查询处理
为了处理跨多个文档的查询,案例采用了子问题查询引擎(SubQuestionQueryEngine)。这个引擎能够将复杂查询分解为多个子查询,每个子查询针对特定的向量索引执行,然后将结果综合起来形成最终答案。
6.3 代理架构设计
聊天机器人基于FunctionAgent构建,它能够根据查询内容智能选择合适的工具。代理可以访问两种类型的工具:
- 针对特定年份的向量索引工具
- 子问题查询引擎工具(用于跨年份查询)
这种设计使代理能够根据查询的复杂性自动选择最合适的处理方式。
6.4 上下文管理
案例使用Context对象来管理对话状态,使代理能够保持对话上下文,提供更连贯的交互体验。
7. 扩展建议
7.1 扩展数据源
- 添加更多公司的SEC文件,构建跨公司比较分析功能
- 集成其他类型的财务文档,如10-Q(季度报告)或8-K(当前报告)
- 添加实时新闻源,提供最新的公司动态信息
7.2 增强查询能力
- 实现更复杂的查询类型,如趋势分析、预测模型
- 添加可视化功能,如图表生成,展示财务数据趋势
- 集成专门的财务分析工具,提供比率计算、行业比较等功能
7.3 优化性能与用户体验
- 实现查询缓存,减少重复查询的响应时间
- 添加查询历史和收藏功能,方便用户回顾重要信息
- 实现查询建议和自动完成功能,提升用户交互体验
7.4 高级功能
- 集成多模态能力,处理图表、表格等非文本信息
- 实现自定义警报功能,当检测到特定财务指标变化时通知用户
- 添加协作功能,允许多用户共享查询结果和注释
8. 总结
本案例展示了如何使用LlamaIndex构建一个基于SEC 10-K文件的智能聊天机器人。通过结合向量索引、子问题查询引擎和FunctionAgent,我们创建了一个能够理解复杂查询并提供准确答案的系统。
该案例的核心价值在于:
- 数据整合:将非结构化的财务文档转换为可查询的结构化知识
- 智能路由:代理能够根据查询类型自动选择最合适的处理工具
- 跨文档分析:能够综合多个文档的信息,提供全面的答案
- 可扩展架构:系统设计允许轻松添加新的数据源和查询工具
这个案例为构建领域特定的问答系统提供了一个很好的模板,可以扩展到金融、法律、医疗等需要处理大量专业文档的领域。