Skip to content

如何构建聊天机器人#

LlamaIndex 充当您的数据与大型语言模型(LLMs)之间的桥梁,提供一套工具包,使您能够围绕数据建立查询接口,以执行各种任务,例如问答和摘要生成。

在本教程中,我们将引导您使用数据代理构建一个上下文增强的聊天机器人。该代理由 LLM 驱动,能够智能地执行数据相关任务。最终成果是一个配备 LlamaIndex 提供的数据接口工具的聊天机器人代理,用于回答关于您数据的查询。

注意:本教程基于最初关于在 SEC 10-K 文件上创建查询接口的工作 - 点击此处查看

背景#

在本指南中,我们将构建一个"10-K 聊天机器人",使用来自 Dropbox 的原始 UBER 10-K HTML 文件。用户可以与聊天机器人交互,询问与 10-K 文件相关的问题。

准备工作#

import os
import openai

os.environ["OPENAI_API_KEY"] = "sk-..."
openai.api_key = os.environ["OPENAI_API_KEY"]

import nest_asyncio

nest_asyncio.apply()

数据摄取#

首先下载 2019-2022 年的原始 10-k 文件。

# 注意:代码示例假设您在 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

为了将 HTML 文件解析为格式化文本,我们使用 Unstructured 库。借助 LlamaHub,我们可以直接与 Unstructured 集成,允许将任何文本转换为 LlamaIndex 可以摄取的 Document 格式。

首先安装必要的包:

!pip install llama-hub unstructured

然后我们可以使用 UnstructuredReader 将 HTML 文件解析为 Document 对象列表。

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)

为每年设置向量索引#

我们首先为每年设置一个向量索引。每个向量索引允许我们询问关于特定年份 10-K 文件的问题。

我们构建每个索引并将其保存到磁盘。

# 初始化简单向量索引
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.core import Settings

Settings.chunk_size = 512
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 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_index

设置子问题查询引擎以综合跨 10-K 文件的答案#

由于我们可以访问 4 年的文档,我们可能不仅想询问特定年份 10-K 文档的问题,还想询问需要分析所有 10-K 文件的问题。

为了解决这个问题,我们可以使用子问题查询引擎。它将查询分解为子查询,每个子查询由单独的向量索引回答,然后综合结果以回答整体查询。

LlamaIndex 提供了一些围绕索引(和查询引擎)的包装器,以便它们可以被查询引擎和代理使用。首先我们为每个向量索引定义一个 QueryEngineTool。 每个工具都有一个名称和描述;这些是 LLM 代理用来决定选择哪个工具的信息。

from llama_index.core.tools import QueryEngineTool, ToolMetadata

individual_query_engine_tools = [
    QueryEngineTool(
        query_engine=index_set[year].as_query_engine(),
        metadata=ToolMetadata(
            name=f"vector_index_{year}",
            description=f"当您想回答关于 Uber {year} 年 SEC 10-K 的查询时非常有用",
        ),
    )
    for year in years
]

现在我们可以创建子问题查询引擎,它将允许我们综合跨 10-K 文件的答案。我们传入上面定义的 individual_query_engine_tools,以及一个用于运行子查询的 llm

from llama_index.llms.openai import OpenAI
from llama_index.core.query_engine import SubQuestionQueryEngine

query_engine = SubQuestionQueryEngine.from_defaults(
    query_engine_tools=individual_query_engine_tools,
    llm=OpenAI(model="gpt-3.5-turbo"),
)

设置聊天机器人代理#

我们使用 LlamaIndex 数据代理来设置外部聊天机器人代理,该代理可以访问一组工具。具体来说,我们将使用 OpenAIAgent,它利用了 OpenAI API 的函数调用功能。我们希望使用之前为每个索引(对应特定年份)定义的单独工具,以及为我们上面定义的子问题查询引擎的工具。

首先我们为子问题查询引擎定义一个 QueryEngineTool

query_engine_tool = QueryEngineTool(
    query_engine=query_engine,
    metadata=ToolMetadata(
        name="sub_question_query_engine",
        description="当您想回答需要分析 Uber 多个 SEC 10-K 文档的查询时非常有用",
    ),
)

然后,我们将上面定义的工具合并为代理的单个工具列表:

tools = individual_query_engine_tools + [query_engine_tool]

最后,我们调用 OpenAIAgent.from_tools 创建代理,传入上面定义的工具列表。

from llama_index.agent.openai import OpenAIAgent

agent = OpenAIAgent.from_tools(tools, verbose=True)

测试代理#

我们现在可以用各种查询测试代理。

如果我们用一个简单的"hello"查询测试它,代理不会使用任何工具。

response = agent.chat("hi, i am bob")
print(str(response))
你好 Bob!今天我能帮您什么吗?

如果我们用一个关于特定年份 10-k 的查询测试它,代理将使用相关的向量索引工具。

response = agent.chat(
    "2020 年 Uber 面临的最大风险因素有哪些?"
)
print(str(response))
=== 调用函数 ===
调用函数:vector_index_2020 参数:{
  "input": "biggest risk factors"
}
获得输出:上下文中提到的最大风险因素包括:
1. COVID-19 大流行及其缓解措施对业务的负面影响。
2. 司机可能被重新分类为雇员、工人或准雇员而非独立承包商。
3. 移动、配送和物流行业的激烈竞争,存在低成本替代方案和资金充足的竞争对手。
4. 需要降低票价或服务费并提供司机激励和消费者折扣以保持竞争力。
5. 遭受重大损失和实现盈利的不确定性。
6. 无法吸引或维持关键数量的平台用户的风险。
7. 与工作场所文化和前瞻性方法相关的运营、合规和文化挑战。
8. 国际投资的潜在负面影响和在外国开展业务的挑战。
9. 与运营和合规挑战、本地化、法律法规、竞争、社会接受度、技术兼容性、不当商业行为、责任不确定性、管理国际运营、货币波动、现金交易、税务后果和支付欺诈相关的风险。
========================
2020 年 Uber 面临的一些最大风险因素包括:

1. COVID-19 大流行及其缓解措施对业务的负面影响。
2. 司机可能被重新分类为雇员、工人或准雇员而非独立承包商。
3. 移动、配送和物流行业的激烈竞争,存在低成本替代方案和资金充足的竞争对手。
4. 需要降低票价或服务费并提供司机激励和消费者折扣以保持竞争力。
5. 遭受重大损失和实现盈利的不确定性。
6. 无法吸引或维持关键数量的平台用户的风险。
7. 与工作场所文化和前瞻性方法相关的运营、合规和文化挑战。
8. 国际投资的潜在负面影响和在外国开展业务的挑战。
9. 与运营和合规挑战、本地化、法律法规、竞争、社会接受度、技术兼容性、不当商业行为、责任不确定性、管理国际运营、货币波动、现金交易、税务后果和支付欺诈相关的风险。

这些风险因素突出了 Uber 在 2020 年面临的挑战和不确定性。

最后,如果我们用一个查询来比较/对比不同年份的风险因素,代理将使用子问题查询引擎工具。

cross_query_str = "比较/对比 Uber 10-K 中描述的不同年份的风险因素。用项目符号回答。"

response = agent.chat(cross_query_str)
print(str(response))
=== 调用函数 ===
调用函数:sub_question_query_engine 参数:{
  "input": "Compare/contrast the risk factors described in the Uber 10-K across years"
}
生成了 4 个子问题。
[vector_index_2022] Q: 2022 年 Uber SEC 10-K 中描述的风险因素有哪些?
[vector_index_2021] Q: 2021 年 Uber SEC 10-K 中描述的风险因素有哪些?
[vector_index_2020] Q: 2020 年 Uber SEC 10-K 中描述的风险因素有哪些?
[vector_index_2019] Q: 2019 年 Uber SEC 10-K 中描述的风险因素有哪些?
[vector_index_2021] A: 2021 年 Uber SEC 10-K 中描述的风险因素包括 COVID-19 大流行对其业务的负面影响、司机可能被重新分类为雇员而非独立承包商、移动、配送和物流行业的激烈竞争、需要降低票价和提供激励以保持竞争力、公司遭受的重大损失、吸引和维持关键数量平台用户的重要性,以及关于司机分类的持续法律挑战。
[vector_index_2020] A: 2020 年 Uber SEC 10-K 中描述的风险因素包括 COVID-19 大流行对其业务的负面影响、司机可能被重新分类为雇员、移动、配送和物流行业的激烈竞争、需要降低票价或服务费以保持竞争力、重大损失和实现盈利的不确定性、吸引和保留关键数量的司机和用户的重要性,以及与其工作场所文化和运营合规相关的挑战。
[vector_index_2022] A: 2022 年 Uber SEC 10-K 中描述的风险因素包括如果司机被分类为雇员而非独立承包商可能对其业务产生的负面影响、移动、配送和物流行业的高度竞争性、需要降低票价或服务费以在某些市场保持竞争力、公司历史上的重大损失和未来运营费用增加的预期,以及如果他们无法吸引或维持关键数量的司机、消费者、商家、托运人和承运人对其平台的潜在影响。
[vector_index_2019] A: 2019 年 Uber SEC 10-K 中描述的风险因素包括在伦敦失去运营许可证、由于监管不确定性导致的业务和运营模式的复杂性、Other Bets 部门其他产品可能面临的额外法规、关于自动驾驶汽车开发和部署的法律法规的演变,以及全球范围内越来越多的数据保护和隐私法律。此外,Uber 还涉及法律程序、诉讼、索赔和政府调查,这些可能给管理层和员工带来负担,并伴随辩护成本或不利裁决。
获得输出:Uber 10-K 报告中描述的风险因素包括司机可能被重新分类为雇员而非独立承包商、移动、配送和物流行业的激烈竞争、需要降低票价和提供激励以保持竞争力、公司遭受的重大损失、吸引和维持关键数量平台用户的重要性,以及关于司机分类的持续法律挑战。此外,每年的报告中还提到了一些特定的风险因素,例如 2020 年和 2021 年 COVID-19 大流行的负面影响、2019 年在伦敦失去运营许可证,以及 2019 年关于自动驾驶汽车法律法规的演变。总体而言,虽然提到的风险因素有一些相似之处,但也有因年份而异的具体因素。
========================
=== 调用函数 ===
调用函数:vector_index_2022 参数:{
  "input": "risk factors"
}
获得输出:上下文中提到的一些风险因素包括如果司机被分类为雇员而非独立承包商可能对业务产生的负面影响、移动、配送和物流行业的高度竞争性、需要降低票价或服务费以保持竞争力、公司历史上的重大损失和运营费用增加的预期、未来大流行或疾病爆发对业务和财务结果的影响,以及经济状况及其对消费者可自由支配支出的影响可能对业务造成的损害。
========================
=== 调用函数 ===
调用函数:vector_index_2021 参数:{
  "input": "risk factors"
}
获得输出:COVID-19 大流行和缓解大流行的行动已经并可能继续对我们部分业务产生负面影响。如果司机被分类为雇员、工人或准雇员而非独立承包商,我们的业务将受到负面影响。移动、配送和物流行业竞争激烈,存在几十年来可用的成熟且低成本的替代方案、低进入门槛、低转换成本,以及几乎每个主要地理区域都有资金充足的竞争对手。为了在某些市场保持竞争力,我们过去曾降低并可能继续降低票价或服务费,并且我们过去曾提供并可能继续提供大量司机激励和消费者折扣与促销。自成立以来,我们已遭受重大损失,包括在美国和其他主要市场。我们预计在可预见的未来我们的运营费用将显著增加,我们可能无法实现或保持盈利。如果我们无法吸引或维持关键数量的司机、消费者、商家、托运人和承运人,无论是由于竞争还是其他因素,我们的平台对平台用户的吸引力将降低。
========================
=== 调用函数 ===
调用函数:vector_index_2020 参数:{
  "input": "risk factors"
}
获得输出:上下文中提到的风险因素包括 COVID-19 大流行对业务的负面影响、司机可能被重新分类为雇员、移动、配送和物流行业的高度竞争性、需要降低票价或服务费以保持竞争力、公司历史上的重大损失和潜在未来费用、吸引和维持关键数量平台用户的重要性,以及公司面临的运营和文化挑战。
========================
=== 调用函数 ===
调用函数:vector_index_2019 参数:{
  "input": "risk factors"
}
获得输出:上下文中提到的风险因素包括与当地公司的竞争、社会接受度的不同水平、技术兼容性问题、暴露于不当商业行为、法律不确定性、管理国际运营的困难、货币汇率波动、管理当地货币的法规、税务后果、财务会计负担、实施财务系统的困难、进出口限制、政治和经济不稳定、公共卫生问题、知识产权保护减少、对少数股权附属公司的影响力有限,以及监管复杂性。这些风险因素可能对公司的国际运营、业务、财务状况和运营结果产生不利影响。
========================
以下是 Uber 10-K 报告中描述的不同年份风险因素的比较:

2022 年风险因素:
- 如果司机被分类为雇员而非独立承包商可能产生的负面影响。
- 移动、配送和物流行业的高度竞争性。
- 需要降低票价或服务费以保持竞争力。
- 历史上的重大损失和运营费用增加的预期。
- 未来大流行或疾病爆发对业务和财务结果的影响。
- 经济状况及其对消费者可自由支配支出的影响可能对业务造成的损害。

2021 年风险因素:
- COVID-19 大流行及其缓解措施对业务的负面影响。
- 司机可能被重新分类为雇员而非独立承包商。
- 移动、配送和物流行业的高度竞争性。
- 需要降低票价或服务费并提供激励以保持竞争力。
- 历史上的重大损失和实现盈利的不确定性。
- 吸引和维持关键数量平台用户的重要性。

2020 年风险因素:
- COVID-19 大流行对业务的负面影响。
- 司机可能被重新分类为雇员。
- 移动、配送和物流行业的高度竞争性。
- 需要降低票价或服务费以保持竞争力。
- 历史上的重大损失和潜在未来费用。
- 吸引和维持关键数量平台用户的重要性。
- 公司面临的运营和文化挑战。

2019 年风险因素:
- 与当地公司的竞争。
- 社会接受度的不同水平。
- 技术兼容性问题。
- 暴露于不当商业行为。
- 法律不确定性。
- 管理国际运营的困难。
- 货币汇率波动。
- 管理当地货币的法规。
- 税务后果。
- 财务会计负担。
- 实施财务系统的困难。
- 进出口限制。
- 政治和经济不稳定。
- 公共卫生问题。
- 知识产权保护减少。
- 对少数股权附属公司的影响力有限。
- 监管复杂性。

这些比较突出了 Uber 在不同年份面临的共同和独特的风险因素。

设置聊天机器人循环#

现在我们已经设置好了聊天机器人,只需几个步骤就可以设置一个基本的交互循环,与我们的 SEC 增强聊天机器人聊天!

agent = OpenAIAgent.from_tools(tools)  # 默认 verbose=False

while True:
    text_input = input("用户: ")
    if text_input == "exit":
        break
    response = agent.chat(text_input)
    print(f"代理: {response}")

以下是循环运行的示例:

``` 用户: 2022 年针对 Uber 的法律诉讼有哪些? 代理: 2022 年,Uber 面临了几项法律诉讼。一些值得注意的包括:

  1. 针对第 22 号提案的请愿:在加利福尼亚州提交了一份请愿书,声称将基于应用程序的司机分类为独立承包商的第 22 号提案违宪。

  2. 马萨诸塞州总检察长的诉讼:马萨诸塞州总检察长对 Uber 提起诉讼,声称司机应被分类为雇员并有权获得工资和劳动法保护。

  3. 纽约总检察长的指控:纽约总检察长对 Uber 提出指控,涉及司机的错误分类和相关就业违规行为。

  4. 瑞士社会保障裁决:瑞士社会保障裁决将 Uber 司机分类为雇员,这可能对 Uber 在瑞士的运营产生影响。

  5. 澳大利亚的集体诉讼:Uber 在澳大利亚面临集体诉讼,指控该公司共谋损害出租车、租赁汽车和豪华轿车行业的参与者。

需要注意的是,