利用检索增强生成(RAG)技术快速开发解决方案,助力问答 LLM 工作流程,由此催生出新型系统架构。NVIDIA 将 AI 用于内部运营,并在系统功能与用户期望之间取得了多项重要发现。
我们发现,无论预期的范围或用例如何,用户通常都希望能够执行非 RAG 任务,例如执行文档翻译、编辑电子邮件甚至编写代码。一个普通的 RAG 应用程序可能会这样实现:对每个消息执行检索管道,从而导致令牌的过度使用,并由于包含不相关的结果而导致不必要的延迟。
我们还发现,即使应用程序专为访问内部私有数据而设计,用户也非常喜欢访问 Web 搜索和摘要功能。我们使用 Perplexity 的搜索 API 来满足这一需求。
在本文中,我们分享了用于解决这些问题的基本架构,即使用路由和多源 RAG 生成能够回答各种问题的聊天应用程序。这是我们已用于 NVIDIA 生产的应用程序的精简版本,用于支持销售业务。有关更多信息,请参阅 NVIDIA/GenerativeAIExamples GitHub 存储库。
我们特别重点介绍了如何使用 LlamaIndex、NVIDIA NIM 微服务和 Chainlit 快速部署此应用。您可以将此项目用作 NVIDIA 和 LlamaIndex 开发者竞赛 的灵感,展示这些技术在真实应用中的创新用途,并有机会赢取丰厚的奖品。
我们发现这些技术之间存在巨大的协同效应。NVIDIA NIM 微服务及其 LlamaIndex 连接器使开发具有自我管理或托管 LLM 的 LLM 应用变得轻而易举。Chainlit 和 LlamaIndex Workflow
事件因其共享的事件驱动架构而完美契合,因此可以轻松为用户界面提供有关 LLM 响应的完整追踪信息。我们将在本文中详细介绍系统详情。
用于 LLM 部署的 NIM 微服务
我们的项目围绕 NVIDIA NIM 微服务构建,适用于以下模型:
尽管我们的团队中没有任何机器学习工程师或 LLM 推理专家,但我们在短短几个小时内就申请并部署了自己的 llama-3.1-70b-instruct 实例,使用的是在配备 NVIDIA A100 的节点(8 个 GPU)上运行的 NIM 容器。这帮助我们避免了一些企业 LLM API 的可用性和延迟问题。
要试用 NIM API,请在 build.nvidia.com 上注册帐户并获取 API 密钥。要在此项目中使用 API 密钥,请确保在位于项目目录的 .env
文件中提供该 API 密钥。 Python 包 llama-index-llms-nvidia
中提供了用于 NVIDIA 模型和 API 的 LlamaIndex 连接器。有关基于 NIM 的 LLM 部署的性能优势的更多信息,请参阅《 使用 NVIDIA NIM 微服务优化大规模 LLM 的推理效率 》。
LlamaIndex Workflow
活动
此应用程序的第一版基于 LlamaIndex 的 ChatEngine 类构建,后者为部署由向量数据库支持的对话式 AI 助手提供了一站式解决方案。虽然效果不错,但我们发现我们希望注入更多步骤来增强上下文,并以需要更高可扩展性的方式切换功能。
幸运的是,LlamaIndex Workflow
events 通过其事件驱动、基于步骤的方法来控制应用程序的执行流程,准确地提供了我们所需的解决方案。我们发现,将应用程序扩展为 Workflow
events 要容易得多,速度也更快,同时仍保留关键的 LlamaIndex 功能,例如必要时的向量存储和检索器。
图 2 显示了我们的 Workflow
事件,我们稍后会在本文中详细说明。
Chainlit
对于我们基于 LLM 的聊天应用来说, Chainlit 是一个不错的解决方案。它使用 chainlit.Step
装饰器支持进度指标和步骤摘要,而 LlamaIndexCallbackHandler
支持自动追踪。我们为每个 LlamaIndex Workflow
事件使用了 Step
装饰器,以在不会让用户不堪重负的情况下展示应用的内部运作。
Chainlit 对企业身份验证和 PostgreSQL 数据层的支持对于生产也至关重要。
设置项目环境、依赖项和安装
要部署此项目,请克隆位于 /NVIDIA/GenerativeAIExamples 的存储库,并创建一个虚拟 Python 环境,运行以下命令以创建和激活环境,然后再安装依赖项:
mkdir .venv
pip -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
配置
安装依赖项后,请确保项目的顶层目录中有一个 .env
文件,其中的值对应以下内容:
NVIDIA_API_KEY
:必填项。您可以从 build.nvidia.com 获取 NVIDIA 服务的 API 密钥。PERPLEXITY_API_KEY
. 可选。如果未提供,则应用程序在运行时不使用 Perplexity 的搜索 API。要获取 Perplexity 的 API 密钥,请 按照说明操作 。
项目结构
我们将项目代码整理成单独的文件:
- LlamaIndex
Workflow
(workflow.py
):路由查询并聚合来自多个来源的响应。 - 文档提取 (
ingest.py
):将文档加载到 Milvus Lite 数据库中,这是一种在没有容器的情况下从 Milvus 开始的简单方法。Milvus Lite 的主要限制是低效的向量查找,当文档集合增长时,可以考虑切换到专用集群。提取模块使用 LlamaIndex 的SimpleDirectoryReader
来解析和加载 PDF。 - Chainlit 应用 (
chainlit_app.py
):Chainlit 应用包含由事件触发的函数,主函数 (on_message
) 在用户发送消息时激活。 - 配置 (
config.py
):若要使用不同的模型类型,请编辑默认值。在此,您可以选择不同的模型用于路由和聊天完成,以及每次完成时从聊天历史记录中使用的过去消息的数量,以及 Perplexity 用于网络搜索和总结的模型类型。
您还可以调整 prompts.py
中列出的提示,以适应您的用例。
构建核心功能
此应用通过 Chainlit 集成了 LlamaIndex 和 NIM 微服务。为了展示如何实现这种逻辑,我们讨论以下步骤:
- 创建用户界面
- 实施
Workflow
事件 - 集成 NIM 微服务
创建用户界面
以下是从 chainlit_app.py
中的 Chainlit 应用开始,介绍此项目的实施方式。在 set_starter
函数中创建 Starter
对象列表,以将初始问题预填充为可单击按钮。这些内容有助于指导用户了解可能的操作或问题,并将其引导至特定功能。
主聊天功能在 main 函数中进行管理,使用 cl.user_session
变量处理消息历史记录。 Chainlit 不需要这一点来显示对话历史记录,但它使我们能够在客户端而不是 LlamaIndex 对象中保持状态。
这种方法使原型设计更加直接,并促进了向传统用户前端后端应用程序的过渡,而有状态的 LlamaIndex ChatEngine
则使 REST API 部署变得复杂化。
使用 workflow.run
调用 Workflow 时,会通过 Workflow 触发一系列异步函数调用,这只需要当前用户查询和过去的聊天消息作为输入。生成流式响应时,请使用 Chainlit 的 Message
类上的 stream_token
方法在用户界面中显示该响应。我们还添加了少量带有样式的 HTML,以显示令牌数量和所用时间。
实施工作流事件
RAG 逻辑包含在 workflow.py
中的 QueryFlow
类中,由定义为 QueryFlow
方法的多个步骤组成。每个方法都在其签名中的事件发生时触发。使用 nodes 属性在步骤之间传递节点列表是构建工作流的一种简单方法。一个节点代表 LlamaIndex 中的离散信息单元。
工作流步骤如下:
workflow_start
:将用户查询和聊天记录添加到工作流的上下文(ctx.data
)中,并使用LLMTextCompletionProgram
在 RAG 和非 RAG 查询之间路由。根据结果,它会生成RawQueryEvent
(触发 RAG 逻辑)或ShortcutEvent
(触发即时响应合成)。rewrite_query
:通过删除可能会阻碍文档查找的指令关键字,如“email”和“table”,转换用户的查询,以获得更好的搜索结果。它会触发 TransformedQueryEvent
,用于 Milvus 检索和 Perplexity 搜索步骤。embed_query
:为转换后的查询产生向量嵌入。milvus_retrieve
:使用向量嵌入进行向量搜索。pplx_retrieve
:使用用于 Perplexity 搜索 API 的 LlamaIndex 连接器获取 Web 搜索结果,并汇总为单个节点。collect_nodes
:结合 Milvus 和 Perplexity 检索的结果。完成两个检索事件后,此步骤便会触发。在此处添加重新排序器可以优先考虑高值节点。
ready = ctx.collect_events(
qe,
expected=[
MilvusQueryEvent,
PerplexityQueryEvent,
],
)
if ready is None:
logger.info("Still waiting for all input events!")
return None
response_synthesis
:使用过去的聊天记录上下文和检索到的文档构建提示字符串。我们手动形成此字符串,虽然也可以使用 LlamaIndex 模板。此步骤触发StopEvent
,结束Workflow
事件,并通过为 LLM 生成的每个令牌生成CompletionResponse
对象来返回对 Chainlit 应用的响应。
总的来说,用户的查询首先经过一个路由步骤,其中 LLM 决定是否值得使用检索来查找文档来回答查询。如果不值得,则使用后续完成调用来生成答案。
当用户希望使用 LLM 执行不需要检索的任务(例如编辑电子邮件或总结现有文本的段落)时,系统会触发此分支。如果选择检索,则用户的查询将转换为更适合搜索的形式。然后,使用 NVIDIA 嵌入模型以及 Milvus 向量存储,这可用于对提取的文档进行向量查找。
然后,使用 Perplexity 的 API 搜索结果来增强从这些步骤返回的文本,该 API 可以从 Web 查找数据以形成答案。最后,这些结果用于响应合成。图 2 显示了使用 llama-index-utils-workflow
生成的图表。
集成 NIM 微服务
得益于 llama-index-llms-nvidia
和 llama-index-embeddings-nvidia
软件包中提供的连接器,可快速将 NVIDIA NIM 微服务用于 LLM 和嵌入功能。
由于 build.nvidia.com 提供了一系列模型,我们可以选择小型、快速执行模型 Meta 的 meta/llama-3.1-8b-instruct 来执行路由查询,同时还可以使用更大的模型 Mistral 的 mistralai/mistral-large-2-instruct 来生成具有出色推理能力的最终响应。
对于高性能的大型模型,另一个不错的选择是 Meta 的 Meta/LLAMA-3.1-405B-INSTRUCT。
使用 NIM 微服务的一大优势是,如果您想迁移到本地或自我管理的 LLM 推理部署,除了为创建 LLM 设置 base_url 参数外,无需更改代码。否则,完全相同!
对于我们自己的内部开发,我们通常会在 NVIDIA 推理的公共 API(位于 build.nvidia.com )与我们自己的自我管理的 Llama 3.1 部署之间进行切换。这为我们提供了很大的灵活性,可以在决定要自行管理和部署的 NIM 微服务类型之前,尝试多个模型进行原型设计。
其他功能
虽然其中一些功能超出了本文的讨论范围,但以下是一些易于添加的功能,可提升价值:
- 通过使用视觉语言模型(VLM)读取表格、执行光学字符识别和描述图像来实现 多模态摄入 。您可以在 Vision Language Models 中找到许多此类模型。
- 使用 Chainlit 的 Postgres 连接器的 用户聊天记录 。要保持用户对话持续进行,您可以使用
chainlit.data_layer
的功能向 Chainlit 提供 PostgreSQL 连接详细信息。 - RAG 使用基于 NVIDIA Mistral 的重新排名器进行排名。
- 提示 LLM 使用 HTML 样式显示带有答案的超链接引文,从而 添加引文
- 通过错误处理和超时管理 来增强可靠性。虽然像 Perplexity 这样的 API 对于回答各种查询非常强大,但由于所涉及的底层组件的复杂性,它们的执行时间可能会非常可变。设置合理的超时并在此类答案不可用时迅速恢复是实现生产就绪型应用的重要一步。
参加 NVIDIA 和 LlamaIndex 开发者竞赛,探索高级聊天功能改进后的译文:参加 NVIDIA 和 LlamaIndex 开发者竞赛,探索高级聊天功能
我们希望这篇博文对您有用,因为您可以详细了解生成式 AI,以及如何将 NIM 微服务和 LlamaIndex Workflow
事件结合使用,以快速开发高级聊天功能。
如果您受到此项目的启发,可以考虑参加 NVIDIA 和 LlamaIndex 开发者竞赛 ,构建自己的 AI 解决方案,即有机会赢取现金奖励、NVIDIA GeForce RTX 4080 SUPER GPU、DLI 积分等。
如果您有兴趣了解更多信息或探索类似的应用,可以考虑深入了解此处的代码,或尝试使用 GitHub 上的 GenerativeAI Examples 库 中的其他类似参考应用。