项目简介:

小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案,帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践,并应用到自己的日常工作里。

本次介绍的是如何在亚马逊云科技利用PostgreSQL向量数据库和大模型托管服务Amazon Bedrock,开发一个人工智能对话机器人。本架构使用了RAG技术,机器人从大语言AI模型获得的对话内容只基于向量数据库内的文档内容生成,使对话机器人为用户生成可靠的、负责任的的内容。本架构设计全部采用了云原生Serverless架构,提供可扩展和安全的AI解决方案。本方案的解决方案架构图如下:

方案所需基础知识 

什么是 Amazon Bedrock?

Amazon Bedrock 是亚马逊云科技提供的一项服务,旨在帮助开发者轻松构建和扩展生成式 AI 应用。Bedrock 提供了访问多种强大的基础模型(Foundation Models)的能力,支持多种不同大模型厂商的模型,如AI21 Labs, Anthropic, Cohere, Meta, Mistral AI, Stability AI, 和Amazon,用户可以使用这些模型来创建、定制和部署各种生成式 AI 应用程序,而无需从头开始训练模型。Bedrock 支持多个生成式 AI 模型,包括文本生成、图像生成、代码生成等,简化了开发流程,加速了创新。

什么是 Amazon Aurora PostgreSQL 向量数据库?

Amazon Aurora PostgreSQL 是亚马逊云科技提供的一种高性能、可扩展的关系型数据库,结合了 PostgreSQL 的灵活性和 Aurora 的企业级性能。Aurora PostgreSQL 现在支持向量数据库功能,使得用户可以在数据库中高效存储和检索高维向量数据,这对于处理和查询复杂数据类型(如图像、文本嵌入)非常有用。

应用 RAG 到对话机器人中的好处

将检索增强生成(RAG)技术应用到对话机器人中,能够显著提升生成内容的准确性和可靠性。RAG 通过结合实时检索和生成式 AI 模型,确保对话机器人在提供答案时,能够利用最新的、高相关性的知识库进行响应。这样不仅提高了对话的质量,还能有效减少错误信息的生成,增强用户的信任和满意度。

本方案包括的内容

1. 创建Amazon Aurora PostgreSQL向量数据库

2. 利用Amazon Bedrock创建和配置知识库

3. 将S3内的文档向量化,并同步到向量数据库中

4. 对聊天机器人进行测试,提问知识库中包含和不包含的内容

项目搭建具体步骤:

1. 进入亚马逊云科技控制台,创建一台EC2,命名为“DBBastion”,用于PostgreSQL数据库的管理和维护。接下来我们通过SSH连接进入到Linux服务器中。

2.  创建一个SQL脚本文件“vector_config.sql”,复制以下SQL命令。该SQL脚本安装了vector扩展,创建了一个新的Schema和表,并创建了索引。

/*
The below SQL statements check if the PostgreSQL extension named 'vector' is installed, and if not, it installs it. Then, it creates a schema called 'bedr
ock_integration' and a table named 'bedrock_kb' within that schema, which includes columns for storing UUID, vector embeddings, text chunks, JSON metadata
, country, variety, points, and price.

Finally, it creates an index on the 'embedding' column using the HNSW (Hierarchical Navigable Small World) algorithm for efficient vector similarity searc
hes.
*/

SELECT extversion FROM pg_extension WHERE extname='vector';
CREATE EXTENSION IF NOT EXISTS vector;
SELECT extversion FROM pg_extension WHERE extname='vector';
CREATE SCHEMA IF NOT EXISTS bedrock_integration;
CREATE TABLE bedrock_integration.bedrock_kb (id uuid PRIMARY KEY, embedding vector(1024), chunks text, metadata json, country text, variety text, points s
mallint, price numeric);
CREATE INDEX on bedrock_integration.bedrock_kb USING hnsw (embedding vector_cosine_ops);

在命令行运行该脚本,将cluster_endpoint替换为数据库集群URL写入节点。


psql -h <cluster_endpoint> -U postgres -d vector -w -f vector_config.sql

3. 接下来我们创建一个新的S3存储桶“wine-knowledgebase-07a0da60”,用于保存对话机器人知识库的源文档。

4. 接下来我们进入到亚马逊云科技密码管理服务Secret Manager,创建一个新的Secret保存数据库账户和密码,secret类型为“Credentials for Amazon RDS database”。

5. 选择我们将账户密码匹配到对应的数据库。

6. 为Secret起名“vectordb-secret”,点击Next到下一步

 7. 在亚马逊云科技上,我们可以通过以下代码调用Secret Manager API取出账户和密码。

# Use this code snippet in your app.
# If you need more information about configurations
# or implementing the sample code, visit the AWS docs:
# https://aws.amazon.com/developer/language/python/

import boto3
from botocore.exceptions import ClientError


def get_secret():

    secret_name = "vectordb-secret"
    region_name = "us-east-1"

    # Create a Secrets Manager client
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )

    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        # For a list of exceptions thrown, see
        # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
        raise e

    secret = get_secret_value_response['SecretString']

    # Your code goes here.

8. 接下来我们进入到AI大模型托管服务Amazon Bedrock托管服务中,确保“Titan Text G1 - Premier”和“Titan Text Embeddings V2”模型是开启状态。

9. 接下来我们在Amazon Bedrock主页左边栏点击“Knowledge base”并点击创建一个新的Knowledge base知识库。

10. 为知识库起名“wine-kb”,并创建一个新的IAM role权限,用于授权Bedrock访问其他服务,如读取S3存储桶中的文档内容。

11. 选择S3为知识库数据源

12. 接下来我们配置S3数据源,为数据源命名为“wine-kb-data-source”,并选择我们保存文件的S3存储桶。

13. 接下来我们选择知识库向量模型为“Titan Text Embeddings v2”,这里的向量维度Vector dimensions我们选择为1024。

14. 将我们创建的Amazon Aurora添加为向量数据库

15. 添加保存向量的向量数据库具体参数,如ARN ID、数据库名、表名、保存数据库账户密码的Secret ARN ID等。

16. 接下来配置索引信息,指明在数据库表中存储向量、文本、元数据的索引字段和主键,最后点击创建。

17. 接下来我们点击我们刚刚创建的S3数据源,并点击SYNC将S3中的文件进行向量化,同步到知识库的向量数据库中。

18. 接下来我们回到EC2服务器中,创建一个Python代码依赖信息txt文件,复制以下内容。

aiohttp==3.8.4
aiosignal==1.3.1
altair==5.0.1
async-timeout==4.0.2
attrs==23.1.0
blinker==1.6.2
boto3==1.34.120
botocore==1.34.120
cachetools==5.3.1
certifi==2023.5.7
charset-normalizer==3.1.0
click==8.1.3
dataclasses-json==0.5.7
decorator==5.1.1
frozenlist==1.3.3
gitdb==4.0.10
GitPython==3.1.31
idna==3.4
importlib-metadata==6.6.0
Jinja2==3.1.2
jmespath==1.0.1
jsonschema==4.17.3
langchain==0.0.195
langchainplus-sdk==0.0.8
markdown-it-py==2.2.0
MarkupSafe==2.1.3
marshmallow==3.19.0
marshmallow-enum==1.5.1
mdurl==0.1.2
multidict==6.0.4
mypy-extensions==1.0.0
numexpr==2.8.4
numpy==1.24.3
openapi-schema-pydantic==1.2.4
packaging==23.1
pandas==2.0.2
Pillow==9.5.0
protobuf==4.23.2
pyarrow==12.0.0
pydantic==1.10.9
pydeck==0.8.1b0
Pygments==2.15.1
Pympler==1.0.1
pyrsistent==0.19.3
python-dateutil==2.8.2
pytz==2023.3
pytz-deprecation-shim==0.1.0.post0
PyYAML==6.0
requests==2.31.0
rich==12.6.0
rich-cli==1.8.0
s3transfer==0.10.0
six==1.16.0
smmap==5.0.0
SQLAlchemy==2.0.15
streamlit==1.35.0
streamlit-chat==0.0.2.2
tenacity==8.2.2
toml==0.10.2
toolz==0.12.0
tornado==6.3.2
typing-inspect==0.9.0
typing_extensions==4.6.3
tzdata==2023.3
tzlocal==4.3
urllib3==1.26.16
validators==0.20.0
yarl==1.9.2
zipp==3.15.0
unstructured==0.8.1
transformers~=4.30.2

19. 再创建一个Python文件“winebot-kb.py”,复制以下代码。该Python文件中,我们使用Streamlit 构建了一个 Web 应用程序,基于知识库内的葡萄酒相关的文档,调用Amazon Bedrock上的大语言,在对话机器人中为用户回答葡萄酒相关问题。

import streamlit as st
import boto3
import json

# Initialize AWS clients
aws_region = 'us-east-1'

client = boto3.client("bedrock-runtime", region_name=aws_region,
)
kb_client = boto3.client('bedrock-agent-runtime', region_name=aws_region,
)


def get_kb_id():
    bedrock_agent_client = boto3.client("bedrock-agent","us-east-1",
    )

    response = bedrock_agent_client.list_knowledge_bases(maxResults=1)
    if len(response["knowledgeBaseSummaries"]) == 1:
        return response["knowledgeBaseSummaries"][0]["knowledgeBaseId"]
    else:
        return "None"


# Streamlit page configuration
st.set_page_config(page_title="A Sommelier LLM chatbot with KB", page_icon=":robot:")

# Display the wine image
st.image("winebot.png", use_column_width=True)

# Application title
st.write("""
# Sammy the Sommelier

An LLM based wine expert!
""")

kb_on = st.radio("**Enable Knowledge Base:**", ["No", "Yes"])
if kb_on == "Yes":
    kb_id = get_kb_id()
    country_in = st.selectbox("**Choose a Country:**", ["India", "Georgia", "Romania", "Uruguay", "Brazil"])
    points_in = st.slider("**Choose a minimum wine point score:**", min_value=0, max_value=100, step=5, value=50)
    price_in = st.slider("**Choose a maximum price:**", min_value=0, max_value=1000, step=10, value=1000)
# Initialize chat history
if 'chat_history' not in st.session_state:
    st.session_state.chat_history = []

# Display chat history
for message in st.session_state.chat_history:
    with st.chat_message(message['role']):
        st.markdown(message['text'])


def invoke_llm_with_history(user_query, chat_history, kb_flag):
    body = {}

    parameters = {
        "maxTokenCount": 1000,
        "stopSequences": [],
        "temperature": 0,
        "topP": 1
    }
    conversation = "\n".join([f"{msg['role']}: {msg['text']}" for msg in chat_history])

    if kb_flag == "Yes":
        print("KbYes")
        kb_response, kb_source = invoke_kb(user_query, country_in, points_in, price_in)
        if kb_response:
            st.session_state.chat_history.append({"role": 'user', "text": user_query})
            st.session_state.chat_history.append({"role": 'kb', "text": kb_response})

        prompt = f"""
        You are a wine expert. You enjoy discussing wine in general as well as giving detailed wine recommendations .

        You are given the following query from the user:
        {user_query}

        You also have access to previous conversation history:
        {conversation}

        Take into account the following information retrieved from a knowledge base when making your recommendation:
        {kb_response}
        {price_in}
        {points_in}
        {country_in}

        End your response with, The source for my recommendation comes from:
        {kb_source}
        """

        body = json.dumps({
            "inputText": prompt,
            "textGenerationConfig": parameters
        }).encode()

        try:
            response = client.invoke_model(
                body=body,
                modelId="amazon.titan-text-premier-v1:0",
                accept="application/json",
                contentType="application/json",
            )
            return json.loads(response["body"].read())["results"][0]["outputText"]
        except Exception as e:
            st.error(f"Error invoking LLM: {e}")
            return ""
    else:
        print("KbNo")
        prompt = f"""
        You are a wine expert. You enjoy discussing wine in general as well as giving detailed wine recommendations.


        Your previous conversation history with the human is:
        {conversation}


        Based on the question or statement below you need to recommend a wine including a brief description and price:
        {user_query}

        """

        body = json.dumps({
            "inputText": prompt,
            "textGenerationConfig": parameters
        }).encode()

        try:
            response = client.invoke_model(
                body=body,
                modelId="amazon.titan-text-premier-v1:0",
                accept="application/json",
                contentType="application/json",
            )
            return json.loads(response["body"].read())["results"][0]["outputText"]
        except Exception as e:
            st.error(f"Error invoking LLM: {e}")
            return ""


def invoke_kb(query, country, points, price):
    try:
        response = kb_client.retrieve(
            knowledgeBaseId=kb_id,
            retrievalQuery={
                'text': query
            },
            retrievalConfiguration={
                "vectorSearchConfiguration": {
                    "numberOfResults": 2,
                    "filter": {
                        "andAll": [
                            {
                                "equals": {"key": "country", "value": country}
                            },
                            {
                                "greaterThan": {"key": "points", "value": points}
                            },
                            {
                                "lessThan": {"key": "price", "value": price}
                            }

                        ]
                    }
                }
            }
        )

        contents = response["retrievalResults"]
        print(contents)

        wine_recommendations = ""
        source_docs = ""
        for content in contents:
            print(content['content']['text'])
            print("-------------------------------------------------------------------------------")
            print(content['location']['s3Location']['uri'])
            wine_recommendations = wine_recommendations + content['content']['text'] + "\n"
            source_docs = source_docs + content['location']['s3Location']['uri'] + "\n"
        return wine_recommendations, source_docs

    except Exception as e:
        st.error(f"Error retrieving from knowledge base: {e}")
        return ""


def main():
    user_query = st.chat_input("What kind of wine are you looking for?")
    if user_query:
        with st.chat_message('user'):
            st.markdown(user_query)

        response = invoke_llm_with_history(user_query, st.session_state.chat_history, kb_on)
        if response:
            with st.chat_message('assistant'):
                st.markdown(response)
            st.session_state.chat_history.append({"role": 'assistant', "text": response})


if __name__ == "__main__":
    main()

20. 接下来在Linux命令行中运行以下命令,启动streamlit Web服务器。

python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
streamlit run winebot-kb.py

21. 启动服务器后命令行会返回一个网页URL:“External URL”,我们在浏览器中打开该网页。

22. 我们登入到葡萄酒对话机器人网页中,配置葡萄酒原产地区,设置葡萄酒评分和价格区间,让大模型根据我们的设置参数进行更精准的回复。我们在问题框中提问:我想要一个轻柔并且有果香的葡萄酒。

23. 最终我们可以得到葡萄酒对话机器人基于知识库中的葡萄酒文档得出的最终回复。

以上就是在亚马逊云科技上利用Amazon Bedrock上的AI大模型和Amazon Aurora PostgreSQL向量数据库,开发生成可靠、负责任内容的聊天机器人服务的全部步骤。欢迎大家未来与我一起,未来获取更多国际前沿的生成式AI开发方案。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐