LangChain Playbook#

在 LLM 和检索增强生成 (RAG) 工作流程中,嵌入将文本转换为捕捉语义含义的向量。这使得能够根据用户的查询高效搜索上下文相关的文档。然后将这些文档作为额外的上下文提供给 LLM,从而增强其生成准确响应的能力。

本 playbook 介绍了如何将 NeMo Retriever 文本嵌入 NIM (文本嵌入 NIM) 与 LangChain 结合使用,以使用 NVIDIAEmbeddings 类进行 RAG 工作流程。首先,它展示了如何从用户查询生成嵌入。然后,它使用此方法嵌入文档,将嵌入存储在向量数据库中,最后在 LangChain 表达式语言 (LCEL) 链中使用嵌入,以帮助 LLM 回答有关 NVIDIA H200 的问题。

Notebook 要求#

设置#

使用以下 bash 脚本安装此 playbook 所需的软件包和依赖项。

cat > requirements.txt << "EOF" 
faiss_cpu==1.8.0
fastapi==0.115.6
langchain==0.3.13
langchain-community==0.3.12
langchain-core==0.3.27
langchain-nvidia-ai-endpoints==0.3.7
numpy==1.26.4
sentence-transformers==3.3.1
unstructured==0.16.11
EOF

pip install -r requirements.txt

使用 NVIDIA API Catalog 或用于 LLM 的 NVIDIA NIM#

接下来,初始化此 playbook 的 LLM。此 playbook 提供了两个示例。一个使用 NVIDIA API Catalog,另一个使用用于 LLM 的 NVIDIA NIM。您可以使用 langchain-nvidia-ai-endpoints 包中的 ChatNVIDIA 类访问这两种方法的聊天模型,该软件包包含 LangChain 集成,用于构建使用 NVIDIA NIM 上大型语言模型 (LLM) 的应用程序。有关更多信息,请参阅 ChatNVIDIA 文档。

选项 1:NVIDIA API Catalog#

要使用 NVIDIA API Catalog,请将 NVIDIA_API_KEY 设置为环境变量。有关生成和使用 API 密钥的信息,请参阅 NGC 身份验证

import os
from langchain_nvidia_ai_endpoints import ChatNVIDIA

os.environ["NVIDIA_API_KEY"] = "nvapi-***" 
llm = ChatNVIDIA(model="meta/llama-3.1-8b-instruct")

选项 2:用于 LLM 的 NVIDIA NIM#

要使用用于 LLM 的 NVIDIA NIM,请按照 入门指南 中的说明进行操作。在您的基础设施上部署 NIM 后,您可以使用 ChatNVIDIA 类访问它,如以下示例所示。

from langchain_nvidia_ai_endpoints import ChatNVIDIA

# connect to a LLM NIM running at localhost:8000, specifying a specific model
llm = ChatNVIDIA(base_url="http://127.0.0.1:8000/v1", model="meta/llama-3.1-8b-instruct")

在 LLM 准备就绪后,您可以将其与 LangChain 的 ChatPromptTemplate 一起使用,这是一个用于构建多轮对话和格式化语言模型输入的类。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", (
        "You are a helpful and friendly AI!"
        "Your responses should be concise and no longer than two sentences."
        "Say you don't know if you don't have this information."
    )),
    ("user", "{question}")
])

chain = prompt | llm | StrOutputParser()

要在 LangChain 表达式语言 (LCEL) 链中与 LLM 交互,请使用 invoke 方法,如以下示例所示。

print(chain.invoke({"question": "What's the difference between a GPU and a CPU?"}))

此查询应生成类似于以下的输出

GPU 或图形处理单元是一种专门类型的处理器,旨在快速渲染和处理图形和图像。CPU 或中央处理单元是计算机的主要处理组件,它执行计算机内部的大部分处理。虽然 CPU 对于通用处理任务仍然很重要,但 GPU 更适合并行处理,并且通常用于涉及图形、游戏和机器学习的任务。

使用以下示例尝试另一个关于 NVIDIA A100 GPU 的问题

print(chain.invoke({"question": "What does the A in the NVIDIA A100 stand for?"}))

很高兴为您服务!NVIDIA A100 中的 “A” 代表 “Ampere”,它是此 GPU 模型的架构代号。

接下来,询问有关 NVIDIA H200 GPU 的问题。由于许多 LLM 的知识截止日期为 2022 年底或 2023 年初,因此该模型可能无法访问该时间范围之后的任何信息。

print(chain.invoke({"question": "How much memory does the NVIDIA H200 have?"}))

很抱歉,我没有该特定信息。NVIDIA H200 似乎不是 NVIDIA GPU 产品线中公认的产品。

使用文本嵌入 NIM 生成嵌入#

要回答上一个问题,请构建一个简单的检索增强生成 (RAG) 管道

以下示例使用 LangChain 通过使用与上一个示例相同的 langchain-nvidia-ai-endpoints 包中的 NVIDIAEmbeddings Python 类与文本嵌入 NIM 交互。在使用此示例之前,请验证 文本嵌入 NIM 是否正在运行。此示例使用 nvidia/llama-3.2-nv-embedqa-1b-v2。如果您使用不同的文本嵌入 NIM,请更新 model

使用以下命令从用户查询生成嵌入

from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings

# Initialize and connect to a NeMo Retriever Text Embedding NIM (nvidia/llama-3.2-nv-embedqa-1b-v2) running at localhost:8001
embedding_model = NVIDIAEmbeddings(model="nvidia/llama-3.2-nv-embedqa-1b-v2",
                                   base_url="http://127.0.0.1:8001/v1")

# Create vector embeddings of the query
embedding_model.embed_query("How much memory does the NVIDIA H200 have?")[:10]

接下来,加载 NVIDIA H200 数据手册的 PDF。此文档成为 LLM 用来检索相关信息以回答问题的知识库。

LangChain 提供了各种文档加载器,这些加载器从许多不同的来源和位置(私有 s3 存储桶、公共网站)加载各种类型的文档(HTML、PDF、代码)。此示例使用 LangChain PyPDFLoader 加载有关 NVIDIA H200 Tensor Core GPU 的数据手册。

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("https://nvdam.widen.net/content/udc6mzrk7a/original/hpc-datasheet-sc23-h200-datasheet-3002446.pdf")

document = loader.load()
document[0]

加载文档后,通常会对其进行转换。一种转换方法称为分块,它将大段文本(例如长文档中的文本)分解为较小的片段。此技术很有价值,因为它有助于优化从向量数据库返回的内容的相关性

LangChain 提供了各种文档转换器,例如文本拆分器。在此示例中,我们使用 RecursiveCharacterTextSplitterRecursiveCharacterTextSplitter 根据指定的块大小将大段文本划分为较小的块。它采用递归作为其拆分文本的核心机制,利用预定义的字符集(例如 “\n\n”、“\n”、“ ” 和 “”)来确定应在何处进行拆分。该过程首先尝试使用集合中的第一个字符拆分文本。如果生成的块仍然大于所需的块大小,则它继续使用集合中的下一个字符并尝试再次拆分。此过程一直持续到所有块都符合指定的最大块大小。

文本拆分存在一些细微的复杂性,因为从理论上讲,语义相关的文本应该放在一起。

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=100,
    separators=["\n\n", "\n", ".", ";", ",", " ", ""],
)

document_chunks = text_splitter.split_documents(document)
print("Number of chunks from the document:", len(document_chunks))

以下代码片段演示了如何为单个文档创建向量嵌入。此步骤对于 RAG 管道不是必需的,但此处包含用于演示目的。该示例使用嵌入模型将文本块转换为向量。它仅显示来自第一个文档块的此向量的前 10 个元素,以了解这些嵌入的外观。

# Extract text (page content) from the document chunks
page_contents = [doc.page_content for doc in document_chunks]

# Create vector embeddings from the document
embedding_model.embed_documents(page_contents)[0][:10]

将文档嵌入存储在向量数据库中#

生成文档嵌入后,它们将存储在向量数据库中。当收到用户查询时,您可以

  1. 嵌入查询
  2. 在向量数据库中执行相似性搜索,以检索最相关的文档嵌入
  3. 使用检索到的文档生成对用户查询的响应

向量数据库负责存储嵌入的数据并执行向量搜索。LangChain 提供对各种向量数据库的支持,在此示例中我们将使用 FAISS。

from langchain_community.vectorstores import FAISS

vector_store = FAISS.from_documents(document_chunks, embedding=embedding_model)

将文本嵌入 NIM 与 LCEL 结合使用#

下一个示例将向量数据库与 LLM 集成。 LangChain 表达式语言 (LCEL) 将这些组件组合在一起。然后,它制定提示占位符(上下文和问题),并将它们管道传输到我们的 LLM 连接器,以使用来自 NVIDIA H200 datasheet 文档的嵌入来回答第一个示例中的原始问题 (NVIDIA H200 有多少内存?)。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", 
        "You are a helpful and friendly AI!"
        "Your responses should be concise and no longer than two sentences."
        "Do not hallucinate. Say you don't know if you don't have this information."
        # "Answer the question using only the context"
        "\n\nQuestion: {question}\n\nContext: {context}"
    ),
    ("user", "{question}")
])

chain = (
    {
        "context": vector_store.as_retriever(),
        "question": RunnablePassthrough()
    }
    | prompt
    | llm
    | StrOutputParser()
)

print(chain.invoke("How much memory does the NVIDIA H200 have?"))

此查询应生成类似于以下的输出

NVIDIA H200 具有 141 千兆字节 (GB) 的 HBM3e 内存。