对话界面 - 使用 Meta Llama2 LLM 的聊天机器人

本笔记本应该可以在 SageMaker Studio 中的 Data Science 3.0 内核上很好地运行

在本笔记本中,我们将使用 Amazon Bedrock 中的基础模型(FMs)构建一个聊天机器人。对于我们的用例,我们使用 Meta Llama 2 作为我们构建聊天机器人的 FM。

概述

对话界面,如聊天机器人和虚拟助手,可用于增强我们客户的用户体验。聊天机器人使用自然语言处理(NLP)和机器学习算法来理解和响应用户查询。聊天机器人可用于各种应用程序,如客户服务、销售和电子商务,以快速高效地回答用户的问题。它们可通过各种渠道访问,如网站、社交媒体平台和消息应用程序。

使用 Amazon Bedrock 的聊天机器人

Amazon Bedrock - 对话界面

用例

  1. 基本聊天机器人 - 使用 FM 模型的零射聊天机器人
  2. 使用提示的聊天机器人 - 模板(Langchain) - 带有一些上下文提供在提示模板中的聊天机器人
  3. 带有角色的聊天机器人 - 具有定义角色的聊天机器人,如职业教练和人类互动
  4. 上下文感知聊天机器人 - 通过生成嵌入来传递外部文件中的上下文

使用 Langchain 框架构建 Amazon Bedrock 的聊天机器人

在对话界面(如聊天机器人)中,高度重要的是记住以前的交互,不仅是短期的,也是长期的。

LangChain 以两种形式提供内存组件。首先,LangChain 提供了管理和操作先前聊天消息的帮助实用程序。这些被设计为模块化的,并且无论如何使用它们都很有用。其次,LangChain 提供了轻松地将这些实用程序纳入链中的方法。 它允许我们轻松定义和交互不同类型的抽象,这使得构建强大的聊天机器人变得容易。

构建带有上下文的聊天机器人 - 关键元素

构建上下文感知聊天机器人的第一个过程是为上下文生成嵌入。通常,我们将有一个摄取过程,该过程将通过我们的嵌入模型运行并生成将存储在某种矢量存储中的嵌入。在这个例子中,我们使用 Titan 嵌入模型来完成这个任务。

嵌入

第二个过程是用户请求编排、交互、调用和返回结果。

聊天机器人

架构[上下文感知聊天机器人]

4

设置

⚠️ ⚠️ ⚠️ 在运行此笔记本之前,请确保我们已经运行了 Bedrock boto3 设置笔记本 。⚠️ ⚠️ ⚠️

%pip install -U --no-cache-dir boto3
%pip install -U --no-cache-dir  \
    "langchain>=0.1.11" \
    sqlalchemy -U \
    "faiss-cpu>=1.7,<2" \
    "pypdf>=3.8,<4" \
    pinecone-client==2.2.4 \
    apache-beam==2.52. \
    tiktoken==0.5.2 \
    "ipywidgets>=7,<8" \
    matplotlib==3.8.2 \
    anthropic==0.9.0
%pip install -U --no-cache-dir transformers
import warnings

import warnings
from io import StringIO
import sys
import textwrap
import os
from typing import Optional

# 外部依赖项:
import boto3

warnings.filterwarnings('ignore')
def print_ww(*args, width: int = 100, **kwargs):
    """像 print()一样,但将输出包装到 `width` 字符(默认 100)"""
    buffer = StringIO()
    try:
        _stdout = sys.stdout
        sys.stdout = buffer
        print(*args, **kwargs)
        output = buffer.getvalue()
    finally:
        sys.stdout = _stdout
    for line in output.splitlines():
        print("\n".join(textwrap.wrap(line, width=width)))
        

warnings.filterwarnings('ignore')
import json
import os
import sys

import boto3
import botocore

boto3_bedrock = boto3.client('bedrock-runtime')

聊天机器人(基本 - 无上下文)

使用 LangChain 的 CoversationChain 启动对话

聊天机器人需要记住之前的交互。对话内存允许我们做到这一点。我们可以以几种方式实现对话内存。在 LangChain 的上下文中,它们都建立在 ConversationChain 之上。

注意:模型输出是非确定性的

from langchain.chains import ConversationChain
from langchain.llms.bedrock import Bedrock
from langchain.memory import ConversationBufferMemory
modelId = "meta.llama2-13b-chat-v1"
llama2_llm = Bedrock(model_id=modelId, client=boto3_bedrock)
llama2_llm.model_kwargs = {"max_gen_len": 500}

memory = ConversationBufferMemory()
memory.human_prefix = "User"
memory.ai_prefix = "Bot"

conversation = ConversationChain(
    llm=llama2_llm, verbose=True, memory=memory
)
conversation.prompt.template = """System: 以下是一个友好的对话,一个知识渊博的有帮助的助手和一个客户之间的对话。助手健谈,并从其上下文中提供大量具体细节。\n\n当前对话:\n{history}\nUser: {input}\nBot:"""

try:
    
    print_ww(conversation.predict(input="嗨,你好!"))

except ValueError as error:
    if  "AccessDeniedException" in str(error):
        print(f"\x1b[41m{error}\
        \n要解决这个问题,请参考以下资源。\
         \nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_access-denied.html\
         \nhttps://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html\x1b[0m\n")      
        class StopExecution(ValueError):
            def _render_traceback_(self):
                pass
        raise StopExecution        
    else:
        raise error

新问题

模型已经用初始消息进行了响应,让我们问几个问题

print_ww(conversation.predict(input="给我几个如何开始新花园的建议。"))

在问题上建立

让我们问一个不提到花园一词的问题,看看模型是否能理解之前的对话

print_ww(conversation.predict(input="很酷。那对番茄也有用吗?"))

结束这个对话

print_ww(conversation.predict(input="就这些,谢谢!"))

使用提示模板的聊天机器人(Langchain)

PromptTemplate 负责构建这个输入。LangChain 提供了几个类和函数来简化构建和使用提示。我们将在这里使用默认的 PromptTemplate

from langchain.memory import ConversationBufferMemory
from langchain.prompts import PromptTemplate

chat_history = []

memory=ConversationBufferMemory()
memory.human_prefix = "User"
memory.ai_prefix = "Bot"

# 将 verbose 设置为 true 以查看完整的日志和文档
qa= ConversationChain(
    llm=llama2_llm, verbose=False, memory=memory #memory_chain
)
qa.prompt.template = """System: 以下是一个友好的对话,一个知识渊博的有帮助的助手和一个客户之间的对话。助手健谈,并从其上下文中提供大量具体细节。\n\n当前对话:\n{history}\nUser: {input}\nBot:"""

print(f"ChatBot:DEFAULT:PROMPT:TEMPLATE: is ={qa.prompt.template}")
import ipywidgets as ipw
from IPython.display import display, clear_output

class ChatUX:
    """ 使用 IPWidgets 的聊天 UX
    """
    def __init__(self, qa, retrievalChain = False):
        self.qa = qa
        self.name = None
        self.b=None
        self.retrievalChain = retrievalChain
        self.out = ipw.Output()


    def start_chat(self):
        print("Starting chat bot")
        display(self.out)
        self.chat(None)


    def chat(self, _):
        if self.name is None:
            prompt = ""
        else: 
            prompt = self.name.value
        if 'q' == prompt or 'quit' == prompt or 'Q' == prompt:
            print("谢谢,这次聊天很愉快!!")
            return
        elif len(prompt) > 0:
            with self.out:
                thinking = ipw.Label(value="思考中...")
                display(thinking)
                try:
                    if self.retrievalChain:
                        result = self.qa.run({'question': prompt })
                    else:
                        result = self.qa.run({'input': prompt }) #, 'history':chat_history})
                except:
                    result = "没有答案"
                thinking.value=""
                print_ww(f"AI:{result}")
                self.name.disabled = True
                self.b.disabled = True
                self.name = None

        if self.name is None:
            with self.out:
                self.name = ipw.Text(description="你:", placeholder='q 退出')
                self.b = ipw.Button(description="发送")
                self.b.on_click(self.chat)
                display(ipw.Box(children=(self.name, self.b)))

让我们开始聊天

chat = ChatUX(qa)
chat.start_chat()

带有角色的聊天机器人

AI 助手将扮演职业教练的角色。角色扮演对话要求在开始聊天之前设置用户消息。ConversationBufferMemory 用于预填充对话。

memory = ConversationBufferMemory()
memory.chat_memory.add_user_message("你将扮演职业教练的角色。你的目标是为用户提供职业建议")
memory.chat_memory.add_ai_message("我是职业教练,提供职业建议")
llama2_llm = Bedrock(model_id="meta.llama2-13b-chat-v1",client=boto3_bedrock)
conversation = ConversationChain(
     llm=llama2_llm, verbose=True, memory=memory
)

print_ww(conversation.predict(input="AI 领域有哪些职业选择?"))
让我们问一个不是这个角色专长的问题,模型不应该回答那个问题,并给出理由。
conversation.verbose = False
print_ww(conversation.predict(input="如何修理我的汽车?"))

带有上下文的聊天机器人

在这个用例中,我们将要求聊天机器人回答从上下文中传递的问题。我们将获取一个 csv 文件,并使用 Titan 嵌入模型创建向量。该向量存储在 FAISS 中。当聊天机器人被问一个问题时,我们会传递这个向量并检索答案。

Titan 嵌入模型

嵌入是一种将单词、短语或任何其他离散项目表示为连续向量空间中的向量的方法。这允许机器学习模型对这些表示执行数学运算,并捕获它们之间的语义关系。

这将用于 RAG 文档搜索功能

from langchain.embeddings import BedrockEmbeddings
from langchain.vectorstores import FAISS
from langchain.prompts import PromptTemplate

br_embeddings = BedrockEmbeddings(model_id="amazon.titan-embed-text-v1", client=boto3_bedrock)

为文档搜索创建嵌入

FAISS 作为 VectorStore

为了能够使用嵌入进行搜索,我们需要一个可以有效执行向量相似性搜索的存储。在本笔记本中,我们使用 FAISS,这是一个内存存储。要永久存储向量,可以使用 pgVector、Pinecone 或 Chroma。

Langchain VectorStore API 可在此处 获得

要了解更多关于 FAISS 矢量存储的信息,请参考这个文档

from langchain.document_loaders import CSVLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.indexes.vectorstore import VectorStoreIndexWrapper

s3_path = f"s3://jumpstart-cache-prod-us-east-2/training-datasets/Amazon_SageMaker_FAQs/Amazon_SageMaker_FAQs.csv"
!aws s3 cp $s3_path ./rag_data/