使用Qdrant Cloud和LangChain构建客户支持聊天机器人

2024年09月04日 由 alex 发表 138 0

简介

想象一下:你在一家公司担任客户支持助理,经常收到有关各种产品的请求和投诉。如果能有一个聊天机器人,让它在成百上千的投诉中快速找到你想要解决的问题,是不是很有帮助?这是看待问题的一种方式。


换个角度想:你是一家公司的数据科学家,你的公司,比方说 “公司王子”,希望建立聊天机器人来帮助客户支持团队快速识别和处理客户询问。你碰巧接到了这项任务。你将如何解决这个问题?


在本文中,我将为你解答。我们将使用 Qdrant、OpenAI 的 GPT-3.5-Turbo 和 LangChain 构建一个系统。


12


安装和设置

开始前,我们先创建一个项目目录,并下载一些将用到的文件。这主要是 CSV 文件。


$ mkdir customer_support_ticket_chatbot
$ cd customer_support_ticket_chatbot
$ mkdir data
$ python3 -m venv venv
$ source venv/bin/activate
$ touch chatbot.ipynb
$ touch .env


有了这个目录结构后,就需要下载要用到的 CSV 文件。


我对数据进行了一些预处理和清理,生成了一个新的 CSV 文件。


下载文件后,将其放入 /data 目录。


完成后,我将在本文中使用 VS Code。如果你想使用其他适合自己的环境,也完全没问题。


打开我们在 chatbot.ipynb 文件上方创建的 Jupyter Notebook。


完成后,就可以运行下面的命令来安装本教程所需的所有软件包。将此命令复制粘贴到 Jupyter Notebook 的某个单元格中。


!pip install pandas langchain-openai qdrant-client langchain langchain-community langchain-qdrant


14


获取 API 密钥

成功安装所有软件包后,我们还需要 Qdrant 和 OpenAI 的 API 密钥。


生成 OpenAI API 密钥后,就可以回到 VS-code 中,打开 .env 文件并添加以下内容:


OPENAI_API_KEY=sk-xxxxxxxxxxxxx


进入 Qdrant,首先需要创建一个 Qdrant 云账户。你可以使用这个链接来创建。一旦你在 Gmail 下创建了账户,我们就可以进入下一阶段了。


创建Qdrant云集群

有了API密钥后,我们就需要继续创建Qdrant云中所谓的集群(Cluster)。登录账户后,你应该会看到这个仪表板。


15


在屏幕最左侧的边栏上,点击 “群集 ”按钮。


点击屏幕右上方的 “创建 ”按钮,填写创建群集的详细信息


16


17


完成后,你就可以在仪表板上看到创建的群集了。


18


单击群集名称,重定向到群集仪表板。


19


在那里,你应该可以看到群集的详细信息。


20


获取Qdrant API密钥

要获取 Qdrant API 密钥,必须点击屏幕最左侧的 “数据访问控制 ”按钮。


21


完成后,点击屏幕右上方的 “创建 ”按钮。填写详细信息并创建 API 密钥。


22


选择要创建 API 密钥的群集,然后单击 “好”。


23


不要点击该模式外的内容,轻轻复制第一个输入框中出现的 API 密钥。


复制完成后,你可以回到 VS 代码中,打开 .env 文件并将其添加到其中


OPENAI_API_KEY=sk-xxxx
QDRANT_API_KEY=xxxxxx


我们还需要刚刚创建的群集的 URL 或 “端点”;点击侧边栏上的 “群集 ”按钮即可获取 URL。


24


复制完成后,按如下方式粘贴到 .env 文件中:


OPENAI_API_KEY=sk-xxxxxxxxxxxxx
QDRANT_URL=https://xxxxxxxxxxxxxxxxxxxxxx
QDRANT_API_KEY=xxxxxxx


OpenAI 嵌入模型

我们将使用 OpenAI 的文本嵌入模型来创建文本文档的矢量表示。为此,我们需要在 .env 文件中设置一些环境变量。


OPENAI_API_KEY=sk-xxxxxxxxxxxxx
QDRANT_URL=https://xxxxxxxxxxxxxxxxxxxxxx
QDRANT_API_KEY=xxxxxxx
QDRANT_VECTOR_DIMENSION=1536
EMBEDDING_MODEL="text-embedding-3-small"


读取 CSV 文件

我的 GitHub 仓库中有多个 CSV 文件。我们要使用的文件是 ./data/product_ticket_decription.csv 文件。


首先,我们来读取该文件。为此,我们将使用 Pandas


import pandas as pd
import ast


df = pd.read_csv("./data/product_ticket_description.csv")
df.head()


25


从 CSV 文件创建 LangChain 文档

使用 Pandas 读取 CSV 文件后,我们将把每一行数据转换成 LangChain 文档。


from langchain_core.documents import Document


为了简单和快速执行,我只处理前 250 行数据。如果你愿意,可以使用全部 8000 多条记录。


documents = []
for index, row in df[:250].iterrows():
    document = Document(
        page_content=row["ticket_description"],
        metadata={"product_name": row["product_purchased"]}
    )
    documents.append(document)


26


print(documents[0].page_content)
len(documents)


27


文件 UUID

上面创建的文档列表中的每个文档都将有一个唯一标识。让我们继续创建它们:


from uuid import uuid4
uuids = [str(uuid4()) for _ in range(len(documents))]


28


连接到 Qdrant 云

现在,我们已经准备好了文档,我们需要将它们嵌入并存储到我们的矢量数据库中,在本例中就是Qdrant云。为此,我们首先需要连接到刚刚创建的Qdrant云实例。


为此,我们需要以下代码块


from qdrant_client import QdrantClient


qdrant_client = QdrantClient(
    api_key=config("QDRANT_API_KEY"),
    url=config("QDRANT_URL")
)


29


我们可以使用下面的代码检查我们是否有任何现有的集合:


qdrant_client.get_collections()


30


从上图可以看出,我们目前没有任何收藏集;让我们继续创建一个。


创建集合

要创建一个集合,我们需要以下代码块:


from qdrant_client.http.models import Distance, VectorParams


COLLECTION_NAME ="customer_support_tickets"


31


qdrant_client.create_collection(
    collection_name=COLLECTION_NAME,
    vectors_config=VectorParams(
        size=os.getenv("QDRANT_VECTOR_DIMENSION", 1536), 
        distance=Distance.COSINE),
)


32


现在,我们已经创建了收藏集,你可以回到 Qdrant Cloud 面板,查看已创建的 “收藏集”。点击屏幕右上方的 “打开控制面板 ”按钮。


33


这将把你重定向到一个页面,并要求你提供认证详细信息;输入详细信息后,你应该会看到这样的视图:


34


连接到矢量商店

现在让我们继续连接到这个集合。为此,我们将使用以下代码块。


首先,我们需要创建 OpenAI 嵌入模型实例。我们将使用 text-embedding-3-small 模型。


from langchain_qdrant import QdrantVectorStore
from langchain_openai import OpenAIEmbeddings
embedding_model = OpenAIEmbeddings(
    model=os.getenv("EMBEDDING_MODEL", 
                    default="text-embedding-3-small"
                )


from langchain_qdrant import RetrievalMode


QdrantVectorStore 支持 3 种相似性检索模式。可以在设置类时使用 retrieval_mode 参数对它们进行配置。
密集矢量搜索(默认)
稀疏矢量搜索
混合搜索


vector_store = QdrantVectorStore(
    client=qdrant_client,
    collection_name=COLLECTION_NAME,
    embedding=embedding_model,
    # retrieval_mode=RetrievalMode.DENSE
)


如果希望使用稀疏或混合搜索,则需要使用 SparseEmbeddings。


稀疏向量搜索
仅使用稀疏向量搜索、
应将 Retrieval_mode 参数设置为 RetrievalMode.SPARSE。
Sparse_embedding 参数的值必须是使用任何稀疏嵌入式提供程序的 SparseEmbeddings 接口的实现。


在本例中,我们将使用默认的 DENSE 检索模式。


35


插入记录/文档

现在,我们准备将最初创建的文档插入 Qdrant 云计算集群集合。这个过程称为 “Upsert”。


vector_store.add_documents(documents=documents, ids=uuids)


add_documents 方法将使用我们在创建向量存储集合时指定的 OpenAI 模型创建嵌入。它还将使用我们最初创建的 uuids 唯一标识每个文档。


36


查询矢量数据库

现在,我们已经在 Qdrant 云集合中嵌入了数据并建立了索引,我们现在可以继续对其执行一些查询,提出一些问题,看看会得到什么数据。


query = "What are the common complaints regarding the GoPro He


results = vector_store.similarity_search(
    query, k=2
)


在这里,我指定要执行相似性搜索,并返回与我的查询最接近的 k 个文档。k 是一个变量,可以是任何值。请记住,这个值必须合理,不要指定一个像 1000 这样的荒唐值。


默认情况下,你的文档将存储在以下有效载荷结构中,例如


{
"page_content": "I'm having an issue with the GoPro Hero. Please assist. I'm experiencing this issue on multiple devices of the same model, so it seems to be a widespread problem.",
"metadata": {
"product_name": "GoPro Hero",
"_id": "d36c329f-761a-4ce9-afc6–114cc7662f2e",
"_collection_name": "customer_support_tickets"
}
}


for res in results:
    print(f"* {res.page_content} [{res.metadata}]")


37


相似性搜索

如前所述,Qdrant 等矢量数据库会进行相似性搜索,以找到与给定用户查询最相似的文档。这是通过余弦相似性搜索等多种技术实现的。


相似度得分高的文档将被返回。让我们来看看这些分数是什么样的。通常情况下,这些分数的值介于 0 和 1 之间。


results = vector_store.similarity_search_with_score(
    query=query, k=2
)


for doc, score in results:
    print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")


38


元数据过滤

Qdrant 还为我们提供了基于元数据执行搜索的功能;这通常被称为 “元数据过滤”。


请记住,当我们创建LangChain文档类型并将其插入Qdrant云上的集合时,我们指定了一些元数据。还记得这段代码吗?你不必运行它:


for index, row in df[:250].iterrows():
    document = Document(
        page_content=row["ticket_description"],
        metadata={"product_name": row["product_purchased"]}
    )
    documents.append(document)


这就是我们使用的元数据:


metadata={"product_name": row["product_purchased"]}


我们可以据此进行筛选,稍后在构建聊天机器人时会看到更多。


现在,让我们根据元数据进行筛选。


from qdrant_client.http import models


results = vector_store.similarity_search(
    query=query,
    k=2,
    filter=models.Filter(
        should=[
            models.FieldCondition(
                key="metadata.product_name",
                match=models.MatchValue(
                    value="Microsoft Office"
                ),
            ),
        ]
    ),
)


for doc in results:
    print(f"* {doc.page_content} [{doc.metadata}]")


39


上面的代码只是根据产品名称元数据进行过滤;我们希望得到的响应只是 “Microsoft Office”。


在 Qdrant 中执行元数据搜索就是这么简单。


通过前面的操作,你应该对如何使用 Qdrant 有了基本的了解。让我们进一步利用这些新发现的智慧来构建一个聊天机器人。


构建聊天机器人

最后,我们可以进入构建聊天机器人的激动人心的部分了。有几件事我想说一下。


1. 识别用户查询中提到的产品。某种命名实体识别(NER)。


40


2. 根据用户查询和确定的产品名称执行元数据搜索。


3. 将检索到的信息传回大语言模型,以生成响应。


41


命名实体识别

命名实体识别或 NER 将帮助我们挑选出用户查询中出现的产品名称。然后将其传递给信息检索功能,使用识别出的产品名称执行元数据搜索。


from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import List


llm = ChatOpenAI(api_key=config("OPENAI_API_KEY"))


class Entities(BaseModel):
    """Identifying information about Products."""
    names: List[str] = Field(
        ...,
        description="All product names that appear in the text"
    )


product_extraction_prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are extracting product names from the text.",
        ),
        (
            "human",
            "Use the given format to extract information from the following "
            "input: {question}",
        ),
    ]
)
product_extraction_chain = product_extraction_prompt | llm.with_structured_output(Entities)


product_extraction_chain.invoke("I like my GoPro Hero 10 camera.")


42


信息检索

现在让我们根据命名实体识别部分识别出的产品名称,编写一个使用元数据过滤检索信息的函数。


def information_retriever(question: str) -> str:
    """
    Search and retrieve information from the Qdrant vector store.
    """
    
    # Get product name from the question
    product_name = product_extraction_chain.invoke(question)
    
    # Filter the search results based on the product name
    unstructured_data_results = vector_store.similarity_search(
        query=query,
        k=2,
        filter=models.Filter(
            should=[
                models.FieldCondition(
                    key="metadata.product_name",
                    match=models.MatchValue(
                        value=product_name.names[0]
                    ),
                ),
            ]
        ),
    )
    
    return unstructured_data_results


实际聊天机器人

一旦我们能够执行命名实体识别,结果就会传递给信息检索函数。这将向我们返回从 Qdrant 向量数据库中检索到的信息。


有了这些信息,我们就需要创建一个功能,将检索到的信息传递给聊天机器人,让它以检索到的信息为上下文,以正确的方式回答我们的问题。


为此,我们将使用 LangChain 表达式语言来构建 LLM 链。


from langchain_core.runnables import (
    RunnableParallel,
    RunnablePassthrough,
)


bot_chat_template = """Answer the question based only on the following context:
            {context}
            Question: {question}
            Use natural language and be concise.
            Answer:
            Context Used:
            You are also required to return the context used to answer the question. The context should be the whole context provided 
            to you, do not modify it. If no context was provided do not generate any context, just say no context was provided:
        """      


class ResponseFormat(BaseModel):
    """Identifying information about Products."""
    answer: str = Field(
        ...,
        description="Your response to the user query."
    )
    sources: List[str] = Field(
        ...,
        description="The sources, contexts used to generate the response."
    )


bot_chat_prompt = ChatPromptTemplate.from_template(bot_chat_template)
chat_chain = (
    RunnableParallel(
        {
            "context": information_retriever,"context": information_retriever,
            "question": RunnablePassthrough(),
        }
    )
    | bot_chat_prompt
    | llm.with_structured_output(ResponseFormat)
)  


现在,让我们向机器人提问:


response = chat_chain.invoke("What are the common complaints regarding the GoPro Hero?")"What are the common complaints regarding the GoPro Hero?")


response


机器人的回复:


ResponseFormat(answer='The common complaints regarding the GoPro Hero include widespread issues experienced on multiple devices of the same model and intermittent performance problems.', sources=['Document(metadata={'product_name\': \GoPro Hero\', \'_id\': \’d36c329f-761a-4ce9-afc6–114cc7662f2e\’, \’_collection_name\’: \customer_support_tickets\'}, page_content="我在使用 GoPro Hero 时遇到了问题。请帮助我。我在同一型号的多台设备上都遇到了这个问题,所以这个问题似乎很普遍。"), Document(metadata={\‘product_name\’: \GoPro Hero\', _id\': \’ac33a33b-7ac8–4487-a4c2–81c8e4536ce1\’, \’_collection_name\’: \customer_support_tickets\'}, page_content="我在使用 GoPro Hero 时遇到了问题。请提供帮助。我们与此无关。请尝试与卖家就此问题进行知情讨论。感谢你的宝贵时间!3/28 我遇到的问题是间歇性的。有时工作正常,但有时会出现意外情况。") '])


结论

在这里,我们探讨了构建数据提取管道、Qdrant 向量数据库的基础知识、使用 OpenAI 进行嵌入、从 Qdrant 向量存储中检索信息、元数据过滤等问题。我们结合所有这些知识构建了一个自定义聊天机器人,帮助回答有关客户支持单据的各种问题。



文章来源:https://medium.com/ai-advances/building-a-smart-chatbot-using-qdrant-cloud-and-langchain-for-customer-support-tickets-504a3e17ac83
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消