首页 » General » 正文

如何构建RAG 驱动的聊天机器人

本文将介绍如何向聊天机器人添加 RAG 功能,并使其能够连接到外部文档,将其响应基于这些文档,并在其响应中生成文档引用。

借助 RAG,开发人员可以为企业打造强大的产品体验。 聊天终端与 RAG 功能集成,这将极大地简化开发基于 RAG 的应用程序。

1. 内容简介:

聊天端点可以使用三种 RAG 模式:

  • 文档模式:指定模型在生成响应时使用的文档
  • 连接器模式:将端点与处理所有文档检索逻辑的外部服务连接
  • 查询生成模式:根据用户消息再生成一个查询

在本文中,您将学习如何在文档模式下使用 RAG,这也需要查询生成模式。

在进入分步指南之前,让我们先看一下我们将构建的演示应用程序的高级实施计划。 下面的图表概述了我们将要构建的内容,后面列出了所涉及的关键步骤。

RAG 驱动的聊天机器人的实施计划

设置阶段

  • 步骤 0:摄取文档 – 获取文档、块、嵌入和索引。

每个用户与聊天机器人的交互

  • 步骤 1:获取用户消息
  • 步骤 2:以查询生成模式调用聊天端点
  • 如果至少生成一个查询
    • 步骤 3:检索相关文档并重新排序
    • 步骤 4:在文档模式下调用聊天端点以生成带有引用的接地响应
  • 如果没有生成查询
    • 步骤 4:在正常模式下调用Chat端点以生成响应

整个谈话过程中

  • 将用户与聊天机器人交互附加到对话线程
  • 每次交互都重复

下面是聊天机器人和用户之间的示例对话的屏幕截图:

该聊天机器人充当智能知识助手。 它能够从外部文档中提取相关上下文,并使用它向用户提供有用的响应,并可通过文档引用进行验证。


聊天机器人通过引用提供有用且可验证的回复

通过将 RAG 功能与聊天范例结合起来,我们可以构建上下文感知应用程序,这些应用程序既能够维护对话状态,又能生成有根据的、有引用支持的响应。 这使得能够在企业中构建实际应用程序,例如协助客户支持代理综合来自多个来源的信息并帮助知识工作者完善报告。


聊天终端将 RAG 功能与聊天范例封装在一起

 

2. 分步骤指南

我们将使用 Cohere 的 Python SDK 作为代码示例。 本章附带 Google Colaboratory 笔记本。 此外,API 参考页面包含聊天端点的输入参数和响应对象的详细描述。

如前所述,本指南展示了如何使用聊天端点的文档模式构建 RAG 系统。

该应用程序将使用多个 Cohere API 端点:

  • Chat:用于处理聊天机器人的主要逻辑,包括将用户消息转换为查询、生成响应和生成引用
  • Embed:用于将文本文档转换为嵌入表示,稍后用于检索(我们将使用最新、最先进的 Embed v3 模型)
  • Rerank:根据检索到的文档与查询的相关性对它们进行重新排序


该演示应用程序将使用 Cohere 的聊天、嵌入和重新排名

2.1 设置

首先,让我们导入该项目所需的库。 这包括 cohere、用于矢量库的 hnswlib 以及用于分块文档的非结构化。

pip install cohere hnswlib unstructured -q

然后,除了其他所需的模块之外,还从这些库中导入必要的模块。 我们还创建一个 Cohere 客户端。

import cohere
import os
import hnswlib
import json
import uuid
from typing import List, Dict
from unstructured.partition.html import partition_html
from unstructured.chunking.title import chunk_by_title

co = cohere.Client(os.environ["COHERE_API_KEY"])

我们将构建三个类,它们构成应用程序的关键组件:DocumentsChatbotApp


应用程序的三个组件:文档、聊天机器人和应用程序

 

现在,让我们开始构建第一个组件:文档

2.2 创建文档组件

Documents 类处理文档的摄取,并返回给定查询的相关文档,其中包括检索(使用矢量搜索)和重新排名。


文档组件处理文档摄取和检索

 

我们首先创建类,该类采用代表文档源的字典列表。

class Documents:

    def __init__(self, sources: List[Dict[str, str]]):
        self.sources = sources
        self.docs = []
        self.docs_embs = []
        self.retrieve_top_k = 10
        self.rerank_top_k = 3
        self.load()
        self.embed()
        self.index()

每个词典项目都包含一个包含两个键的 Web URL:“title”和“URL”。 格式如下:

sources = [
    {
        "title": "Text Embeddings", 
        "url": "https://docs.cohere.com/docs/text-embeddings"
     },
    {
        ...
    }  
]

我们还初始化了一些实例属性和方法。 这些属性包括表示原始文档的 self.sources、表示文档的分块版本的 self.docs、表示分块文档的嵌入的 self.docs_embs 以及用于检索和重新排名的几个 top_k 参数。

同时,这些方法包括用于摄取文档的加载、嵌入和索引。 这些方法加载一组原始文档,将它们分成更小的块,为每个块生成嵌入,并将它们存储在索引中。


文档组件的文档摄取部分

2.2.1 加载和分块文档

接下来,我们创建加载方法来加载和分块文档。

在加载过程中,每个 URL 都会被处理并变成更小的块。 信息检索的分块本身就是一个广泛的话题,人工智能社区正在讨论许多策略。 对于我们的示例,我们将使用非结构化库中的partition_html方法。 请阅读其文档,了解有关分块方法的更多信息。

我们将每个块转换为包含三个字段的字典对象:title(网页的标题)、text(块的文本内容)和 url(网页的 URL)。 这些信息最终将传递给聊天机器人生成响应的提示,因此将相关信息填充到这个字典中至关重要。

请注意,我们不限于这三个领域。 聊天端点至少需要文本字段,但除此之外,我们还可以添加自定义字段,以提供有关文档的更多上下文,例如字幕、摘要、标签等。

另一方面,出于提示截断的目的,文本字段是必需的。 端点还有另一个参数,称为prompt_truncation,它接受 AUTO 或 OFF 作为参数。 将 Prompt_truncation 设置为 AUTO 时,对话历史记录和文档中的某些元素将被删除,以尝试构建适合模型上下文长度限制的提示。 发生这种情况时,端点将使用文本字段进行提示截断。

生成的文档存储在 self.docs 属性中。

class Documents:

    ...
    ...

    def load(self) -> None:
    """
    Loads the documents from the sources and chunks the HTML content.
    """
        print("Loading documents...")

        for source in self.sources:
            elements = partition_html(url=source["url"])
            chunks = chunk_by_title(elements)
            for chunk in chunks:
                self.docs.append(
                    {
                        "title": source["title"],
                        "text": str(chunk),
                        "url": source["url"],
                    }
                )

2.2.2 嵌入文档

接下来,我们创建 embed 方法来生成分块文档的嵌入。 我们使用 Embed 端点,利用 Embed v3 模型,该模型根据可信的 MTEB 和 BEIR 基准提供最先进的性能。 我们将使用的模型是 embed-english-v3.0。

对于 Embed v3 模型,我们需要定义一个 input_type,其中根据任务类型有四个选项。 使用这些输入类型可确保相应任务的最高质量。 对于我们的文档(即用于检索的文档),我们使用 search_document 作为 input_type。

class Documents:

    ...
    ...

    def embed(self) -> None:
        """
        Embeds the documents using the Cohere API.
        """
        print("Embedding documents...")

        batch_size = 90
        self.docs_len = len(self.docs)

        for i in range(0, self.docs_len, batch_size):
            batch = self.docs[i : min(i + batch_size, self.docs_len)]
            texts = [item["text"] for item in batch]
            docs_embs_batch = co.embed(
		              texts=texts,										               model="embed-english-v3.0",							               input_type="search_document"
	 		).embeddings
            self.docs_embs.extend(docs_embs_batch)

请注意,我们会将文档批量发送到嵌入端点,因为该端点每次调用的文档数限制为 96 个。

生成的文档嵌入存储在 self.doc_embs 属性中。

2.2.3 索引文件

接下来,我们创建索引方法来索引文档嵌入。

出于多种原因,我们将嵌入存储在索引中。 其中之一是检索效率。 该索引以结构化且有组织的方式存储嵌入。 该组织确保检索期间有效的相似性搜索。

有许多选项可用于构建索引。 对于生产环境,通常需要矢量数据库来处理索引文档和维护索引的连续过程。

然而,在我们的示例中,我们将保持简单并使用矢量库。 我们可以从许多开源项目中进行选择,例如 Faiss、Annoy、ScaNN 和 Hnswlib,这就是我们将使用的项目。 这些库将嵌入存储在内存索引中,并实现近似最近邻 (ANN) 算法,以提高相似性搜索的效率。

生成的文档嵌入存储在 self.index 属性中。

class Documents:

    ...
    ...

    def index(self) -> None:
        """
    Indexes the documents for efficient retrieval.
    """
        print("Indexing documents...")

        self.index = hnswlib.Index(space="ip", dim=1024)
        self.index.init_index(max_elements=self.docs_len, ef_construction=512, M=64)
        self.index.add_items(self.docs_embs, list(range(len(self.docs_embs))))

        print(f"Indexing complete with {self.index.get_current_count()} documents.")

2.2.4 实施检索

接下来,我们创建检索方法来检索给定查询的相关文档。

我们将实现一个语义搜索系统,该系统利用嵌入来检索文档,从而对基本关键字匹配方法进行重大改进。 嵌入可以捕获文档的上下文含义,从而能够检索与给定查询高度相关的结果。

首先,我们需要将查询转换为嵌入。 为此,我们使用 embed-english-v3.0 模型,这次使用 search_query 作为 input_type。

检索是通过 hnswlib 库中的 knn_query 方法执行的。 给定一个查询,它返回与该查询最相似的文档。 我们可以使用属性 self.retrieve_top_k 定义要检索的顶级文档的数量,我们选择 10。

class Documents:

    ...
    ...

    def retrieve(self, query: str) -> List[Dict[str, str]]:
        	  """
        Retrieves documents based on the given query.

        Parameters:
        query (str): The query to retrieve documents for.

        Returns:
        List[Dict[str, str]]: A list of dictionaries representing the retrieved  documents, with 'title', 'snippet', and 'url' keys.
        """
        docs_retrieved = []
        query_emb = co.embed(
                    texts=[query],
                    model="embed-english-v3.0",
                    input_type="search_query"
                    ).embeddings				    

        doc_ids = self.index.knn_query(query_emb, k=self.retrieve_top_k)[0][0]

2.2.5 实施重新排名

接下来,我们在检索方法中实现重新排名步骤。

虽然我们的语义搜索组件已经能够很好地检索相关文档,但重新排名端点可以进一步提高搜索结果的质量,特别是对于复杂的特定领域的查询。 在我们的例子中,它获取搜索结果 10 个文档,并根据它们与查询的相关性对它们进行排序。


文档摄取、检索和重新排序的详细视图

使用 Rerank 端点实现重新排名只需一行代码。 为了调用端点,我们传递查询和要重新排序的文档列表。 我们还使用属性 self.rerank_top_k 定义要检索的最高重新排名文档的数量,我们选择 3。我们使用的模型是 rerank-english-v2.0。

最后,我们将检索到的最多文档存储在 docs_retrieved 属性中,并将这些文档返回给聊天机器人,我们接下来将实现该聊天机器人。

注意:当 prompt_truncation 设置为 AUTO 时,端点将默认处理重新排名。 在这种情况下,我们不必实施自己的重新排名步骤。 但是,如果我们想要更好地控制重新排名过程,例如定义重新排名后要检索的文档数量,我们仍然可以这样做。 另外,在接下来的章节中讨论连接器时,我们将更深入地探讨即时截断的工作原理。

class Documents:

    ...
    ...

    def retrieve(self, query: str) -> List[Dict[str, str]]:

        ...
        ...
				

        docs_to_rerank = []
        for doc_id in doc_ids:
            docs_to_rerank.append(self.docs[doc_id]["text"])

        rerank_results = co.rerank(
            query=query,
            documents=docs_to_rerank,
            top_n=self.rerank_top_k,
            model="rerank-english-v2.0",
        )

        doc_ids_reranked = []
        for result in rerank_results:
            doc_ids_reranked.append(doc_ids[result.index])

        for doc_id in doc_ids_reranked:
            docs_retrieved.append(
                {
                    "title": self.docs[doc_id]["title"],
                    "text": self.docs[doc_id]["text"],
                    "url": self.docs[doc_id]["url"],
                }
            )

        return docs_retrieved

2.3 创建聊天机器人组件

聊天机器人类处理聊天机器人的逻辑,包括根据用户消息生成搜索查询、检索文档以及生成对用户的响应。

聊天机器人组件处理聊天逻辑,获取用户消息到生成响应

这是我们实现调用聊天端点的方法的地方。 如前所述,我们将了解如何以两种方式使用端点:生成查询和在文档模式下生成响应。

我们首先创建 Chatbot 类,该类采用 Documents 类的实例。 我们为该实例初始化 self.docs 属性,以及每个对话所需的唯一对话 ID。

class Chatbot:

    def __init__(self, docs: Documents):
        self.docs = docs
        self.conversation_id = str(uuid.uuid4())

2.3.1 生成查询

第一步是决定如何处理用户消息。 对于 RAG 支持的聊天机器人,此时需要做出两个关键决策:

  • 它应该直接响应用户消息还是在响应之前检索外部信息?
  • 如果决定检索信息,则给定用户消息的最佳查询集是什么?

聊天端点的一个特征是底层命令模型已经过训练来处理这些场景。 这意味着我们可以利用开箱即用的功能,而无需任何进一步的微调。

让我们用几个例子来说明这意味着什么。

首先,我们需要以查询生成模式调用聊天端点。 语法很简单:传递用户消息并将 search_queries_only 设置为 True。 至于响应,我们对 cohere.Chat 对象的 search_queries 字段感兴趣。

响应= co.chat(消息=消息,search_queries_only=True)

查询生成模式下可能会发生一些情况:

  • 不需要查询:假设我们有一条用户消息“您好,我正在编写的报告需要帮助。” 这种类型的消息不需要来自外部信息的任何额外上下文,因此不需要检索。 聊天机器人的直接响应就足够了(例如:“当然,我能帮忙吗?”)。 当我们将其发送到聊天端点时,我们会得到一个空的 search_queries 结果,这正是我们所期望的。
  • 生成一个查询:获取此用户消息:“报告对公司第四季度的业绩有何评价?”。这确实需要额外的上下文,因为它引用了一份报告,因此需要检索。根据此消息,聊天端点会返回 search_queries 第四季度公司业绩的结果。在这里,它将用户消息转换为针对搜索优化的查询。另一个重要场景是在对话上下文中生成查询。假设有一个正在进行的对话,其中用户正在从聊天机器人学习深度学习。如果 在某些时候,用户问“为什么它很重要”,那么生成的搜索查询将成为为什么深度学习很重要,为检索过程提供急需的上下文。
  • 生成多个查询:如果用户消息稍微复杂一点怎么办,例如“报告对公司第四季度的业绩及其产品和服务范围有何评论?”。这需要检索多条信息。 收到此消息后,聊天端点会返回两个搜索查询结果:第四季度的公司业绩以及公司的产品和服务范围。

这些场景突出了聊天端点根据用户消息决定下一步操作的适应性。 因此,我们要实现的第一步是查询生成步骤。 这成为我们称为generate_response 的方法的第一部分。

class Chatbot:

	  ...
	  ...

	  def generate_response(self, message: str):
        """
        Generates a response to the user's message.

        Parameters:
        message (str): The user's message.

        Yields:
        Event: A response event generated by the chatbot.

        Returns:
        List[Dict[str, str]]: A list of dictionaries representing the retrieved documents.

        """

        # Generate search queries (if any)
        response = co.chat(message=message, search_queries_only=True)

2.3.2 检索文档并重新排序

如果查询生成模式下的聊天机器人响应包含至少一个搜索查询,则下一步是检索与查询相关的文档。 为此,我们创建了retrieve_docs方法来通过我们之前创建的Documents类来检索和重新排列文档。

然后,检索到的文档将存储在retrieved_docs 属性中。

class Chatbot:

        ...
        ...

        def generate_response(self, message: str):

            ...
        	...

            # If there are search queries, retrieve documents and respond
            if response.search_queries:
                print("Retrieving information...")

                documents = self.retrieve_docs(response)

                ...
                ...

        def retrieve_docs(self, response) -> List[Dict[str, str]]:
            """
            Retrieves documents based on the search queries in the response.

            Parameters:
            response: The response object containing search queries.

            Returns:
            List[Dict[str, str]]: A list of dictionaries representing the retrieved documents.

            """
            # Get the query(s)
            queries = []
            for search_query in response.search_queries:
                queries.append(search_query["text"])

            # Retrieve documents for each query
            retrieved_docs = []
            for query in queries:
                retrieved_docs.extend(self.docs.retrieve(query))

            return retrieved_docs

2.3.3 生成响应

现在我们已经检索到了相关文档,我们可以将它们传递到聊天端点以生成响应。 为此,我们通过向调用添加文档参数并传递我们之前检索到的文档,以文档模式调用聊天端点。 由于由端点处理,因此不需要即时工程。

同时,如果查询生成模式下的聊天机器人响应不包含任何搜索查询,则不需要信息检索。 为了生成响应,我们再次调用聊天端点,传递用户消息,而不需要向调用添加任何文档。

无论哪种情况,我们都会传递 conversation_id 参数,该参数将用户和聊天机器人之间的交互保留在同一对话线程中。 我们还启用了流参数,以便我们可以将聊天机器人响应流式传输到应用程序。

class Chatbot:

        ...
        ...

        def generate_response(self, message: str):

        ...
        ...

        # If there are search queries, retrieve documents and respond
        if response.search_queries:
            print("Retrieving information...")

            documents = self.retrieve_docs(response)

            response = co.chat(
                message=message,
                documents=documents,
                conversation_id=self.conversation_id,
                stream=True,
            )
            for event in response:
                yield event

        # If there is no search query, directly respond
        else:
            response = co.chat(
                message=message, 
                conversation_id=self.conversation_id, 
                stream=True
            )
            for event in response:
                yield event

2.4 创建应用程序组件

App 类处理用户和聊天机器人之间的交互。 在我们的例子中,我们在 Jupyter 笔记本中创建一个简单的文本界面。


应用程序组件处理用户和聊天机器人之间的交互

我们首先创建 App 类,它采用 Chatbot 类的实例。

class App:
    def __init__(self, chatbot: Chatbot):
        """
        Initializes an instance of the App class.

        Parameters:
        chatbot (Chatbot): An instance of the Chatbot class.

        """
        self.chatbot = chatbot

2.4.1 获取用户消息

接下来,我们创建一个 run 方法并实现获取用户消息的逻辑,并为用户提供结束对话的方法。

class App:

    ...
    ...

    def run(self):
    """
    Runs the chatbot application.
    """
    while True:
        # Get the user message
        message = input("User: ")

        # Typing "quit" ends the conversation
        if message.lower() == "quit":
            print("Ending chat.")
            break
        else:
            print(f"User: {message}")

2.4.2 显示引用的回复

接下来,我们将用户消息传递给我们之前在 Chatbot 类中创建的generate_response 方法,该方法会执行生成查询、检索相关信息和生成响应的步骤。

为了显示响应,我们从响应流中打印文本生成事件。

除了生成响应之外,聊天端点还提供引用来指示响应所依据的检索文档的范围。 这是一个例子:

[{'start': 59, 'end': 73, 'text': 'large datasets', 'document_ids': ['doc_0', 'doc_1']}]

每个引文的格式为:

  • start:引用一个或多个文档的跨度的起点
  • end:引用一个或多个文档的范围的结束点
  • text:表示该跨度的文本
  • document_ids:所引用文档的 ID(doc_0 是传递给端点调用中的 documentscreating 参数的第一个文档的 ID,依此类推)
class App:

    ...
	...

	def run(self):

        while True:
		
           ...
           ...
		
            # Get the chatbot response
            response = self.chatbot.generate_response(message)

            # Print the chatbot response
            print("Chatbot:")
            flag = False
            for event in response:
                # Text
                if event.event_type == "text-generation":
                    print(event.text, end="")

                # Citations
                if event.event_type == "citation-generation":
                    if not flag:
                        print("\\n\\nCITATIONS:")
                        flag = True
                    print(event.citations)

2.5 运行应用程序

我们现在已经完成了三个组件的创建:文档聊天机器人应用程序,这意味着我们现在已经准备好运行我们的聊天机器人应用程序了!

2.5.1 定义文档

首先,我们定义要摄取并可供检索的文档列表。 例如,我们将使用法学硕士大学第一个模块的内容:什么是大型语言模型? 它有四章,我们在来源属性中定义了它们的网址。

sources = [
    {
        "title": "Text Embeddings", 
        "url": "https://docs.cohere.com/docs/text-embeddings"},
    {
        "title": "Similarity Between Words and Sentences", 
        "url": "https://docs.cohere.com/docs/similarity-between-words-and-sentences"},
    {
        "title": "The Attention Mechanism", 
        "url": "https://docs.cohere.com/docs/the-attention-mechanism"},
    {
        "title": "Transformer Models", 
        "url": "https://docs.cohere.com/docs/transformer-models"}   
]

2.5.2 流程文件

接下来,我们通过创建文档实例来处理这些文档。 在我们的例子中,我们总共获得了 136 个文档,从四个 Web URL 中分块。

documents = Documents(sources)
Loading documents...
Embedding documents...
Indexing documents...
Indexing complete with 136 documents.

2.5.3 运行聊天机器人

我们现在可以运行聊天机器人应用程序。 为此,我们创建聊天机器人和应用程序的实例。 最后,我们通过调用 run 方法来运行聊天机器人。

chatbot = Chatbot(documents)

app = App(chatbot)

app.run()

下面是一个发生了7轮的对话的例子:

User: hello
Chatbot:
Hi! How can I help you today?
-------------------------------------------------------------------------------------------

User: what is the difference between word and sentence embeddings
Chatbot:
Retrieving information...
Word embeddings associate words with lists of numbers (vectors). Similar words are associated with numbers that are close by and dissimilar words with numbers that are far away. A sentence embedding does the same thing but associates a vector with every sentence. Similar sentences are assigned to similar vectors, while different sentences are assigned to different vectors.

CITATIONS:
[{'start': 0, 'end': 63, 'text': 'Word embeddings associate words with lists of numbers (vectors)', 'document_ids': ['doc_0']}]
[{'start': 65, 'end': 177, 'text': 'Similar words are associated with numbers that are close by and dissimilar words with numbers that are far away.', 'document_ids': ['doc_0']}]
[{'start': 178, 'end': 263, 'text': 'A sentence embedding does the same thing but associates a vector with every sentence.', 'document_ids': ['doc_0', 'doc_2']}]
[{'start': 264, 'end': 375, 'text': 'Similar sentences are assigned to similar vectors, while different sentences are assigned to different vectors.', 'document_ids': ['doc_2']}]

-------------------------------------------------------------------------------------------

User: what kind of technology that makes all this possible
Chatbot:
Retrieving information...
Transformer models utilize a lot of data (entire internet, large datasets), post-training, large datasets of conversations and a bias towards the last things it has learned. It relies on the principle of teaching a machine to perform tasks, similar to teaching a person.

CITATIONS:
[{'start': 36, 'end': 40, 'text': 'data', 'document_ids': ['doc_2']}]
[{'start': 41, 'end': 57, 'text': '(entire internet', 'document_ids': ['doc_0', 'doc_2']}]
[{'start': 59, 'end': 73, 'text': 'large datasets', 'document_ids': ['doc_0', 'doc_1']}]
[{'start': 76, 'end': 89, 'text': 'post-training', 'document_ids': ['doc_0', 'doc_1']}]
[{'start': 91, 'end': 122, 'text': 'large datasets of conversations', 'document_ids': ['doc_1']}]
[{'start': 129, 'end': 157, 'text': 'bias towards the last things', 'document_ids': ['doc_0']}]
[{'start': 204, 'end': 222, 'text': 'teaching a machine', 'document_ids': ['doc_0']}]
[{'start': 234, 'end': 239, 'text': 'tasks', 'document_ids': ['doc_0', 'doc_1']}]
[{'start': 252, 'end': 270, 'text': 'teaching a person.', 'document_ids': ['doc_0']}]

-------------------------------------------------------------------------------------------

User: How does the model work
Chatbot:
Retrieving information...
Transformer models utilize the attention step, which helps language models understand the context. It is a powerful multi-head attention technique that has helped language models reach much higher levels of efficacy. Consider the following two sentences:
Sentence 1: The bank of the river.
Sentence 2: Money in the bank.

CITATIONS:
[{'start': 31, 'end': 45, 'text': 'attention step', 'document_ids': ['doc_0']}]
[{'start': 90, 'end': 98, 'text': 'context.', 'document_ids': ['doc_0']}]
[{'start': 116, 'end': 136, 'text': 'multi-head attention', 'document_ids': ['doc_1']}]
[{'start': 255, 'end': 289, 'text': 'Sentence 1: The bank of the river.', 'document_ids': ['doc_0']}]
[{'start': 290, 'end': 320, 'text': 'Sentence 2: Money in the bank.', 'document_ids': ['doc_0']}]

-------------------------------------------------------------------------------------------

User: continue
Chatbot:
These two sentences have completely different meanings. However, fascinatingly, by just looking at the last words, the model can tell whether the sentence is about a river or money. This is done by calculating attention weights by measuring the attraction of two given phrases. The model then decides whether the sentences have similar or different meanings based on these attention weights.
-------------------------------------------------------------------------------------------

User: How accurate can this be
Chatbot:
Retrieving information...
I'm sorry, I did not find any information in my search, but i'll reply to you anyway. It really depends on the quality of the model. Cohere’s model is much better than word embeddings because it can capture many more features of the words.

CITATIONS:
[{'start': 199, 'end': 239, 'text': 'capture many more features of the words.', 'document_ids': ['doc_1']}]

-------------------------------------------------------------------------------------------

User: what do you mean by the quality of the model
Chatbot:
Retrieving information...
I couldn't find any precise sources to support the reply I'm about to write when I searched the databases I have access to -- instead, I'm basing it on my general knowledge. The quality of the model refers to how good it is at performing the task it was designed to do. For example, if you want to build a model that can understand and generate language, you would want to use a language model with high quality.
-------------------------------------------------------------------------------------------

User: quit
Ending chat.

在上面的对话中,请注意一些反映我们构建的不同组件的观察结果:

  • 直接回复:对于不需要检索的用户消息(例如“hello”),聊天机器人会直接回复,无需检索。
  • 引文生成:对于确实需要检索的回复,端点会连同引文一起返回响应。
  • 状态管理:端点通过 conversation_id 参数维护对话的状态,例如,能够正确响应模糊的用户消息“继续”。
  • 响应综合:端点可以决定是否没有检索到的文档提供回答用户消息所需的必要信息。 例如,当被问到“模型质量是什么意思”时,它会回答“我找不到任何精确的来源来支持我要写的回复……”

3. 结论

本文介绍了如何使用聊天端点构建由 RAG 驱动的聊天机器人。 通过访问文档集合,聊天机器人能够对用户请求提供上下文相关的响应以及可验证的引用。

在文档模式下使用了聊天端点,这种模式突出了端点的模块化,使开发人员能够灵活地定制系统的每个组件。

另一种选择是连接器模式。 它抽象了我们在文档模式中看到的一些步骤,这使得构建应用程序变得更简单。 它还可以轻松连接到企业数据源进行大规模操作。