用于互动学习的私人聊天机器人

时间: 120 分钟级别: 高级

通过聊天机器人,公司可以扩展其培训计划以适应大量员工,提供跨部门、地点和时区的一致和标准化的学习体验。此外,已经完成在线培训的公司员工可能希望回顾旧的课程材料。这些信息大部分是公司的专有信息,手动搜索整个材料库需要时间。然而,基于这些知识构建的聊天机器人可以在眨眼之间做出响应。

通过一个简单的RAG管道,您可以构建一个私有的聊天机器人。在本教程中,您将在一个封闭的基础设施内结合开源工具,并使用一个可靠的框架将它们连接在一起。这个自定义解决方案使您能够在没有公共互联网访问的情况下运行聊天机器人。您将能够在不损害隐私的情况下保护敏感数据的安全。

OpenShift 图1: LLM和Qdrant混合云被容器化为独立服务。Haystack将它们组合成一个RAG管道,并通过Hayhooks暴露API。

组件

为了保持数据的完全隔离,我们需要限制自己使用开源工具,并在私有环境中使用它们,例如红帽 OpenShift。管道将在内部运行,并且无法从互联网访问。

  • 数据集: 红帽互动学习门户,一个在线图书馆,包含红帽课程材料。
  • LLM: mistralai/Mistral-7B-Instruct-v0.1, 作为独立服务部署在OpenShift上。
  • 嵌入模型: BAAI/bge-base-en-v1.5,轻量级嵌入模型,通过快速嵌入在Haystack管道内部部署
  • 向量数据库: Qdrant 混合云 运行在 OpenShift 上。
  • 框架: Haystack 2.x 用于连接所有内容,哈约克斯 用于通过HTTP端点提供应用程序服务。

步骤

Haystack 框架利用了两个管道,这些管道按顺序组合我们的组件来处理数据。

  1. The Indexing Pipeline will run offline in batches, when new data is added or updated.
  2. The Search Pipeline will retrieve information from Qdrant and use an LLM to produce an answer.

注意: 我们将在Python中定义管道,然后将其导出为YAML格式,以便海钩可以将它们作为Web服务运行。

先决条件

将LLM部署到OpenShift

按照第6章 服务大型语言模型中的步骤操作。这将从HuggingFace下载LLM,并使用单一模型服务平台将其部署到OpenShift上。

您的LLM服务将有一个URL,您需要将其存储为环境变量。

export INFERENCE_ENDPOINT_URL="http://mistral-service.default.svc.cluster.local"
import os

os.environ["INFERENCE_ENDPOINT_URL"] = "http://mistral-service.default.svc.cluster.local"

启动Qdrant混合云

完成如何在Red Hat OpenShift上设置Qdrant。在混合云环境中,您的Qdrant实例是私有的,并且其节点与您的其他组件运行在相同的OpenShift基础设施上。

获取您的Qdrant URL和API密钥,并将它们存储为环境变量:

export QDRANT_URL="https://qdrant.example.com"
export QDRANT_API_KEY="your-api-key"
os.environ["QDRANT_URL"] = "https://qdrant.example.com"
os.environ["QDRANT_API_KEY"] = "your-api-key"

实现

我们将首先创建一个索引管道,以向系统添加文档。 然后,搜索管道将从我们的文档中检索相关数据。 在管道测试完成后,我们将它们导出到YAML文件中。

索引管道

Haystack 2.x 包含了许多有用的组件,从数据获取、HTML解析到向量存储。在我们开始之前,需要安装一些Python包:

pip install haystack-ai \
    qdrant-client \
    qdrant-haystack \
    fastembed-haystack

我们的环境现在已经准备好了,所以我们可以直接进入代码部分。让我们定义一个空的管道,并逐步向其中添加组件:

from haystack import Pipeline

indexing_pipeline = Pipeline()

数据获取和转换

在这一步中,我们将使用Haystack的LinkContentFetcher从一系列URL下载课程内容,并将其存储在Qdrant中以供检索。 由于我们不希望存储原始的HTML,此工具将从每个网页中提取文本内容。然后,fetcher会将它们分成易于消化的块,因为文档可能相当长。

让我们从数据获取和文本转换开始:

from haystack.components.fetchers import LinkContentFetcher
from haystack.components.converters import HTMLToDocument

fetcher = LinkContentFetcher()
converter = HTMLToDocument()

indexing_pipeline.add_component("fetcher", fetcher)
indexing_pipeline.add_component("converter", converter)

我们的管道知道有两个组件,但它们还没有连接。我们需要定义它们之间的流程:

indexing_pipeline.connect("fetcher.streams", "converter.sources")

每个组件都有一组输入和输出,这些输入和输出可以在有向图中组合。输入和输出的定义通常在组件的文档中提供。LinkContentFetcher有以下参数:

Parameters of the LinkContentFetcher

来源: https://docs.haystack.deepset.ai/docs/linkcontentfetcher

分块和创建嵌入

我们使用HTMLToDocument将HTML源转换为Haystack的Document实例,这是一个包含一些待查询数据的基础类。然而,单个文档可能太长,无法由嵌入模型处理,并且它携带的信息也太多,无法使搜索相关。

因此,我们需要将文档分割成较小的部分并将它们转换为嵌入。为此,我们将使用指向我们BAAI/bge-base-en-v1.5模型的DocumentSplitterFastembedDocumentEmbedder

from haystack.components.preprocessors import DocumentSplitter
from haystack_integrations.components.embedders.fastembed import FastembedDocumentEmbedder

splitter = DocumentSplitter(split_by="sentence", split_length=5, split_overlap=2)
embedder = FastembedDocumentEmbedder(model="BAAI/bge-base-en-v1.5")
embedder.warm_up()

indexing_pipeline.add_component("splitter", splitter)
indexing_pipeline.add_component("embedder", embedder)

indexing_pipeline.connect("converter.documents", "splitter.documents")
indexing_pipeline.connect("splitter.documents", "embedder.documents")

将数据写入Qdrant

分割器将生成最大长度为5个句子的块,重叠2个句子。然后,这些较小的部分将被转换为嵌入。

最后,我们需要将我们的嵌入存储在Qdrant中。

from haystack.utils import Secret
from haystack_integrations.document_stores.qdrant import QdrantDocumentStore
from haystack.components.writers import DocumentWriter

document_store = QdrantDocumentStore(
    os.environ["QDRANT_URL"], 
    api_key=Secret.from_env_var("QDRANT_API_KEY"),
    index="red-hat-learning", 
    return_embedding=True, 
    embedding_dim=768,
)
writer = DocumentWriter(document_store=document_store)

indexing_pipeline.add_component("writer", writer)

indexing_pipeline.connect("embedder.documents", "writer.documents")

我们的管道现在已经完成。Haystack 提供了一个方便的管道可视化工具,因此您可以查看并验证组件之间的连接。它在 Jupyter 笔记本中显示,但您也可以将其导出到文件:

indexing_pipeline.draw("indexing_pipeline.png")

Structure of the indexing pipeline

测试整个管道

我们终于可以在一个URL列表上运行它,以在Qdrant中索引内容。我们有一堆指向所有Red Hat OpenShift基础课程课程的URL,所以让我们使用它们:

course_urls = [
    "https://developers.redhat.com/learn/openshift/foundations-openshift",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:openshift-and-developer-sandbox",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:overview-web-console",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:use-terminal-window-within-red-hat-openshift-web-console",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:install-application-source-code-github-repository-using-openshift-web-console",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:install-application-linux-container-image-repository-using-openshift-web-console",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:install-application-linux-container-image-using-oc-cli-tool",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:install-application-source-code-using-oc-cli-tool",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:scale-applications-using-openshift-web-console",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:scale-applications-using-oc-cli-tool",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:work-databases-openshift-using-oc-cli-tool",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:work-databases-openshift-web-console",
    "https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:view-performance-information-using-openshift-web-console",
]

indexing_pipeline.run(data={
    "fetcher": {
        "urls": course_urls,
    }
})

执行可能需要一些时间,因为模型需要处理所有文档。处理完成后,我们应该将所有文档存储在Qdrant中,准备进行搜索。您应该会看到一个处理文档的简短摘要:

{'writer': {'documents_written': 381}}

搜索管道

我们的文档现在已经索引完毕,可以开始搜索了。下一个管道稍微简单一些,但我们仍然需要定义一些组件。让我们再次从一个空的管道开始:

search_pipeline = Pipeline()

我们的第二个流程接收用户输入,将其转换为嵌入向量,然后使用查询嵌入向量搜索最相关的文档。这看起来可能很熟悉,但我们不再使用Document实例,因为查询只接受原始文本。因此,一些组件将会有所不同,特别是嵌入器,因为它必须接受单个字符串作为输入并生成单个嵌入向量作为输出:

from haystack_integrations.components.embedders.fastembed import FastembedTextEmbedder
from haystack_integrations.components.retrievers.qdrant import QdrantEmbeddingRetriever

query_embedder = FastembedTextEmbedder(model="BAAI/bge-base-en-v1.5")
query_embedder.warm_up()

retriever = QdrantEmbeddingRetriever(
    document_store=document_store,  # The same document store as the one used for indexing
    top_k=3,  # Number of documents to return
)

search_pipeline.add_component("query_embedder", query_embedder)
search_pipeline.add_component("retriever", retriever)

search_pipeline.connect("query_embedder.embedding", "retriever.query_embedding")

运行测试查询

如果我们的目标只是检索相关文档,我们可以在这里停止。让我们在一个简单的查询上尝试当前的管道:

query = "How to install an application using the OpenShift web console?"

search_pipeline.run(data={
    "query_embedder": {
        "text": query
    }
})

我们将top_k参数设置为3,因此检索器应返回三个最相关的文档。您的输出应如下所示:

{
    'retriever': {
        'documents': [
            Document(id=867b4aa4c37a91e72dc7ff452c47972c1a46a279a7531cd6af14169bcef1441b, content: 'Install a Node.js application from GitHub using the web console The following describes the steps r...', meta: {'content_type': 'text/html', 'source_id': 'f56e8f827dda86abe67c0ba3b4b11331d896e2d4f7b2b43c74d3ce973d07be0c', 'url': 'https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:work-databases-openshift-web-console'}, score: 0.9209432),
            Document(id=0c74381c178597dd91335ebfde790d13bf5989b682d73bf5573c7734e6765af7, content: 'How to remove an application from OpenShift using the web console. In addition to providing the cap...', meta: {'content_type': 'text/html', 'source_id': '2a0759f3ce4a37d9f5c2af9c0ffcc80879077c102fb8e41e576e04833c9d24ce', 'url': 'https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:install-application-linux-container-image-repository-using-openshift-web-console'}, score: 0.9132109500000001),
            Document(id=3e5f8923a34ab05611ef20783211e5543e880c709fd6534d9c1f63576edc4061, content: 'Path resource: Install an application from source code in a GitHub repository using the OpenShift w...', meta: {'content_type': 'text/html', 'source_id': 'a4c4cd62d07c0d9d240e3289d2a1cc0a3d1127ae70704529967f715601559089', 'url': 'https://developers.redhat.com/learning/learn:openshift:foundations-openshift/resource/resources:install-application-source-code-github-repository-using-openshift-web-console'}, score: 0.912748935)
        ]
    }
}

生成答案

检索不仅仅应该服务于文档。因此,我们需要使用LLM来生成我们问题的确切答案。 这是我们第二个管道的最后一个组件。

Haystack 将创建一个提示,将您的文档添加到模型的上下文中。

from haystack.components.builders.prompt_builder import PromptBuilder
from haystack.components.generators import HuggingFaceTGIGenerator

prompt_builder = PromptBuilder("""
Given the following information, answer the question.

Context: 
{% for document in documents %}
    {{ document.content }}
{% endfor %}

Question: {{ query }}
""")
llm = HuggingFaceTGIGenerator(
    model="mistralai/Mistral-7B-Instruct-v0.1",
    url=os.environ["INFERENCE_ENDPOINT_URL"],
    generation_kwargs={
        "max_new_tokens": 1000,  # Allow longer responses
    },
)

search_pipeline.add_component("prompt_builder", prompt_builder)
search_pipeline.add_component("llm", llm)

search_pipeline.connect("retriever.documents", "prompt_builder.documents")
search_pipeline.connect("prompt_builder.prompt", "llm.prompt")

PromptBuilder 是一个 Jinja2 模板,它将用文档和查询填充。HuggingFaceTGIGenerator 连接到 LLM 服务并生成答案。让我们再次运行管道:

query = "How to install an application using the OpenShift web console?"

response = search_pipeline.run(data={
    "query_embedder": {
        "text": query
    },
    "prompt_builder": {
        "query": query
    },
})

LLM 可能会提供多个回复,如果被要求这样做,所以让我们遍历并打印它们:

for reply in response["llm"]["replies"]:
    print(reply.strip())

在我们的案例中,有一个单一的响应,这应该是问题的答案:

Answer: To install an application using the OpenShift web console, follow these steps:

1. Select +Add on the left side of the web console.
2. Identify the container image to install.
3. Using your web browser, navigate to the Developer Sandbox for Red Hat OpenShift and select Start your Sandbox for free.
4. Install an application from source code stored in a GitHub repository using the OpenShift web console.

我们的最终搜索管道也可以被可视化,这样我们可以看到组件是如何连接在一起的:

search_pipeline.draw("search_pipeline.png")

Structure of the search pipeline

部署

管道现在已经准备就绪,我们可以将它们导出为YAML。Hayhooks将使用这些文件来将管道作为HTTP端点运行。为此,请指定文件路径和您的环境变量。

注意:索引管道可能在您的ETL工具内部运行,但搜索应明确作为HTTP端点暴露。

让我们在本地机器上运行它:

pip install hayhooks

首先,我们需要将管道保存到YAML文件中:

with open("search-pipeline.yaml", "w") as fp:
    search_pipeline.dump(fp)

现在我们可以运行Hayhooks服务了:

hayhooks run

该命令应在默认端口上启动服务,因此您可以在http://localhost:1416访问它。管道尚未部署,但我们只需再执行一个命令即可完成部署:

hayhooks deploy search-pipeline.yaml

完成后,您应该能够在http://localhost:1416/docs看到OpenAPI文档,并测试新创建的端点。

Search pipeline in the OpenAPI documentation

我们的搜索现在可以通过HTTP端点访问,因此我们可以将其与任何其他服务集成。我们甚至可以控制其他参数,例如返回的文档数量:

curl -X 'POST' \
  'http://localhost:1416/search-pipeline' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "llm": {
  },
  "prompt_builder": {
    "query": "How can I remove an application?"
  },
  "query_embedder": {
    "text": "How can I remove an application?"
  },
  "retriever": {
    "top_k": 5
  }
}'

响应应该类似于我们在Python中得到的响应:

{
  "llm": {
    "replies": [
      "\n\nAnswer: You can remove an application running in OpenShift by right-clicking on the circular graphic representing the application in Topology view and selecting the Delete Application text from the dialog that appears when you click the graphic’s outer ring. Alternatively, you can use the oc CLI tool to delete an installed application using the oc delete all command."
    ],
    "meta": [
      {
        "model": "mistralai/Mistral-7B-Instruct-v0.1",
        "index": 0,
        "finish_reason": "eos_token",
        "usage": {
          "completion_tokens": 75,
          "prompt_tokens": 642,
          "total_tokens": 717
        }
      }
    ]
  }
}

下一步

这个页面有用吗?

感谢您的反馈!🙏

我们很抱歉听到这个消息。😔 你可以在GitHub上编辑这个页面,或者创建一个GitHub问题。