From 026ac0733fab45176c395979397d7aeb862f24c5 Mon Sep 17 00:00:00 2001 From: Roberto Luna Rojas Date: Wed, 31 Jul 2024 23:38:21 +0000 Subject: [PATCH 1/3] Amazon ElastiCache | Build a generative AI Virtual Assistant with Amazon Bedrock --- webinars/genai-chatbot/.gitignore | 1 + webinars/genai-chatbot/chatbot_app.py | 87 +++++++++++---------- webinars/genai-chatbot/chatbot_lib.py | 97 +++++++++++++++++------- webinars/genai-chatbot/requirements.lock | 81 ++++++++++++++++++++ webinars/genai-chatbot/requirements.txt | 93 ++--------------------- 5 files changed, 202 insertions(+), 157 deletions(-) create mode 100644 webinars/genai-chatbot/requirements.lock diff --git a/webinars/genai-chatbot/.gitignore b/webinars/genai-chatbot/.gitignore index b06f302..1f94200 100644 --- a/webinars/genai-chatbot/.gitignore +++ b/webinars/genai-chatbot/.gitignore @@ -1,3 +1,4 @@ .venv/* .env.sh __pycache__/* +.env \ No newline at end of file diff --git a/webinars/genai-chatbot/chatbot_app.py b/webinars/genai-chatbot/chatbot_app.py index 937c6b6..c74c90c 100644 --- a/webinars/genai-chatbot/chatbot_app.py +++ b/webinars/genai-chatbot/chatbot_app.py @@ -1,52 +1,55 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 +import logging # Import the logging module for logging purposes +import streamlit as st # Import the Streamlit library for building interactive web apps +import chatbot_lib as glib # Import a custom module named 'chatbot_lib' +from dotenv import load_dotenv # Import the load_dotenv function from the 'dotenv' module -import os -import streamlit as st #all streamlit commands will be available through the "st" alias -import chatbot_lib as glib #reference to local lib script +# Set up the logging configuration with the INFO level and a specific format +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -st.set_page_config(page_title="Chatbot") #HTML title -st.title("Chatbot") #page title -# Add a login mechanism for the user +# Load environment variables from a '.env' file +load_dotenv() -username = st.text_input("Enter your username:") -session_id = username -print("username being sent to session", session_id) -redis_url=os.environ.get("ELASTICACHE_ENDPOINT_URL") -key_prefix="chat_history:" +st.set_page_config(page_title="Chatbot") # Set the page title for the Streamlit app +st.title("Chatbot") # Display the title "Chatbot" on the Streamlit app + +username = st.text_input("Enter your username:") # Get the username from the user input +session_id = username # Set the session_id to the username +redis_url = os.environ.get("ELASTICACHE_ENDPOINT_URL") # Get the Redis URL from the environment variables +key_prefix = "chat_history:" # Set a prefix for the chat history key if username: st.session_state.username = username # Store the username in the session state - st.info(f"Logged in as: {username}") - -if 'memory' not in st.session_state: #see if the memory hasn't been created yet - print(f"DEBUG: session_id: {session_id}") - st.session_state.memory = glib.get_memory(session_id=session_id, url=redis_url, key_prefix=key_prefix) #initialize the memory - -if 'chat_history' not in st.session_state: #see if the chat history hasn't been created yet - st.session_state.chat_history = [] #initialize the chat history - -print(f"DEBUG: session_id2: {session_id}") -st.session_state.memory = glib.get_memory(session_id=session_id, url=redis_url, key_prefix=key_prefix) #initialize the memory - -#Re-render the chat history (Streamlit re-runs this script, so need this to preserve previous chat messages) -for message in st.session_state.chat_history: #loop through the chat history - with st.chat_message(message["role"]): #renders a chat line for the given role, containing everything in the with block - st.markdown(message["text"]) #display the chat content - -input_text = st.chat_input("Chat with your bot here") #display a chat input box - -if input_text: #run the code in this if block after the user submits a chat message - with st.chat_message("user"): #display a user chat message - st.markdown(input_text) #renders the user's latest message - #Debugging : Pring the input_text to the model - print("Input text to the model:", input_text) - st.session_state.chat_history.append({"role":"user", "text":input_text}) #append the user's latest message to the chat history - #Debugging: Print the memory being sent to the model - print("Memory to the Model:", st.session_state.memory) - chat_response = glib.get_chat_response(input_text=input_text, memory=st.session_state.memory) #call the model through the supporting library - with st.chat_message("assistant"): #display a bot chat message - st.markdown(chat_response) #display bot's latest response - st.session_state.chat_history.append({"role":"assistant", "text":chat_response}) #append the bot's latest message to the chat history + st.info(f"Logged in as: {username}") # Display a message with the logged-in username + +# If 'memory' is not in the session state, initialize it by calling the 'get_memory' function from 'chatbot_lib' +if 'memory' not in st.session_state: + st.session_state.memory = glib.get_memory(session_id=session_id, url=redis_url, key_prefix=key_prefix) + +# If 'chat_history' is not in the session state, initialize it as an empty list +if 'chat_history' not in st.session_state: + st.session_state.chat_history = [] + +# Update the 'memory' in the session state by calling the 'get_memory' function from 'chatbot_lib' +st.session_state.memory = glib.get_memory(session_id=session_id, url=redis_url, key_prefix=key_prefix) + +# Display the chat history by iterating over the messages and rendering them using Streamlit's chat_message +for message in st.session_state.chat_history: + with st.chat_message(message["role"]): + st.markdown(message["text"]) + +input_text = st.chat_input("Chat with your bot here") # Get the user's input text from the chat input + +if input_text: + with st.chat_message("user"): + st.markdown(input_text) # Display the user's input text in the chat + logging.info(f"Input text to the model: {input_text}") # Log the user's input text + st.session_state.chat_history.append({"role": "user", "text": input_text}) # Add the user's input to the chat history + logging.info(f"Memory to the Model: {st.session_state.memory}") # Log the memory passed to the model + chat_response = glib.get_chat_response(input_text=input_text, memory=st.session_state.memory) # Get the chat response from the 'chatbot_lib' module + with st.chat_message("assistant"): + st.markdown(chat_response) # Display the chat response in the chat + st.session_state.chat_history.append({"role": "assistant", "text": chat_response}) # Add the chat response to the chat history diff --git a/webinars/genai-chatbot/chatbot_lib.py b/webinars/genai-chatbot/chatbot_lib.py index fc6b3f3..1117e6b 100644 --- a/webinars/genai-chatbot/chatbot_lib.py +++ b/webinars/genai-chatbot/chatbot_lib.py @@ -1,62 +1,101 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 - import os +import logging +from dotenv import load_dotenv from langchain.memory import ConversationSummaryBufferMemory, RedisChatMessageHistory from langchain.llms.bedrock import Bedrock from langchain.chains import ConversationChain from langchain.prompts.prompt import PromptTemplate +# Load environment variables from .env file +load_dotenv() + +# Set up logging +logging.basicConfig(level=logging.INFO) -redis_url=os.environ.get("ELASTICACHE_ENDPOINT_URL") +# Get Redis URL from environment variable +redis_url = os.environ.get("ELASTICACHE_ENDPOINT_URL") def get_llm(): - model_kwargs = { + """ + Returns an instance of the Bedrock LLM with the specified model and configuration. + Amazon Bedrock endpoints and quotas => https://docs.aws.amazon.com/general/latest/gr/bedrock.html + + Returns: + Bedrock: An instance of the Bedrock LLM. + """ + model_kwargs = { "max_tokens_to_sample": 8000, - "temperature": 0, - "top_k": 50, - "top_p": 1, - "stop_sequences": ["\n\nHuman:"] + "temperature": 0.5, + "top_k": 50, + "top_p": 1, + "stop_sequences": ["\n\nHuman:"] } - # Amazon Bedrock endpoints and quotas => https://docs.aws.amazon.com/general/latest/gr/bedrock.html llm = Bedrock( - credentials_profile_name=os.environ.get("BWB_PROFILE_NAME"), #sets the profile name to use for AWS credentials (if not the default) - region_name=os.environ.get("BWB_REGION_NAME"), #sets the region name (if not the default) - endpoint_url=os.environ.get("BWB_ENDPOINT_URL"), #sets the endpoint URL (if necessary) - model_id="anthropic.claude-v2", #use the Anthropic Claude model - model_kwargs=model_kwargs) #configure the properties for Claude + credentials_profile_name=os.getenv("BWB_PROFILE_NAME"), + region_name=os.getenv("BWB_REGION_NAME"), + endpoint_url=os.getenv("BWB_ENDPOINT_URL"), + model_id="anthropic.claude-v2", + model_kwargs=model_kwargs) return llm - - + + def get_chat_history(): - chat_history=RedisChatMessageHistory(session_id='username', url=redis_url, key_prefix="chat_history:") + """ + Returns an instance of RedisChatMessageHistory for storing chat history. + + Returns: + RedisChatMessageHistory: An instance of RedisChatMessageHistory. + """ + chat_history = RedisChatMessageHistory(session_id='username', url=redis_url, key_prefix="chat_history:") return chat_history -def get_memory(session_id, url, key_prefix): # create memory for this chat session +def get_memory(session_id, url, key_prefix): + """ + Creates and returns a ConversationSummaryBufferMemory instance for maintaining conversation history. + + Args: + session_id (str): The session ID for the chat session. + url (str): The URL of the Redis instance. + key_prefix (str): The key prefix for storing chat history in Redis. + + Returns: + ConversationSummaryBufferMemory: An instance of ConversationSummaryBufferMemory. + """ # ConversationSummaryBufferMemory requires an LLM for summarizing older messages - # this allows us to maintain the "big picture" of a long-running conversation + # This allows us to maintain the "big picture" of a long-running conversation llm = get_llm() - chat_history=RedisChatMessageHistory(session_id=session_id, url=url, key_prefix=key_prefix) - memory = ConversationSummaryBufferMemory(ai_prefix="AI Assistant",llm=llm, max_token_limit=1024, chat_memory=chat_history) #Maintains a summary of previous messages - # memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=1024) # Maintains a summary of previous messages + chat_history = RedisChatMessageHistory(session_id=session_id, url=url, key_prefix=key_prefix) + memory = ConversationSummaryBufferMemory(ai_prefix="AI Assistant", llm=llm, max_token_limit=1024, chat_memory=chat_history) return memory -def get_chat_response(input_text, memory): #chat client function +def get_chat_response(input_text, memory): + """ + Generates a chat response based on the input text and conversation history. + + Args: + input_text (str): The input text from the user. + memory (ConversationSummaryBufferMemory): The conversation history and summary. + + Returns: + str: The chat response generated by the LLM. + """ llm = get_llm() - template = """The following is a friendly conversation between a human and an AI. + template = """The following is a friendly conversation between a human and an AI. AI provide very concise responses. If the AI does not know the answer to a question, it truthfully says it does not know. Current conversation:{history}. Human: {input} AI Assistant:""" PROMPT = PromptTemplate(input_variables=["history", "input"], template=template) - conversation_with_summary = ConversationChain( #create a chat client - llm = llm, #using the Bedrock LLM - memory = memory, #with the summarization memory + conversation_with_summary = ConversationChain( + llm=llm, # using the Bedrock LLM + memory=memory, # with the summarization memory prompt=PROMPT, - verbose = True #print out some of the internal states of the chain while running + verbose=False # disable printing internal states ) - chat_response = conversation_with_summary.predict(input=input_text) #pass the user message and summary to the model + chat_response = conversation_with_summary.predict(input=input_text) # pass the user message and summary to the model + logging.debug(f"Chat response: {chat_response}") return chat_response - diff --git a/webinars/genai-chatbot/requirements.lock b/webinars/genai-chatbot/requirements.lock new file mode 100644 index 0000000..7936ba4 --- /dev/null +++ b/webinars/genai-chatbot/requirements.lock @@ -0,0 +1,81 @@ +aiohappyeyeballs==2.3.4 +aiohttp==3.10.0 +aiosignal==1.3.1 +altair==5.3.0 +annotated-types==0.7.0 +anthropic==0.32.0 +anyio==3.7.1 +attrs==23.2.0 +blinker==1.8.2 +boto3==1.34.151 +botocore==1.34.151 +cachetools==5.4.0 +certifi==2024.7.4 +charset-normalizer==3.3.2 +click==8.1.7 +dataclasses-json==0.6.7 +distro==1.9.0 +filelock==3.15.4 +frozenlist==1.4.1 +fsspec==2024.6.1 +gitdb==4.0.11 +GitPython==3.1.43 +greenlet==3.0.3 +h11==0.14.0 +hiredis==3.0.0 +httpcore==1.0.5 +httpx==0.27.0 +huggingface-hub==0.24.5 +idna==3.7 +Jinja2==3.1.4 +jiter==0.5.0 +jmespath==1.0.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.23.0 +jsonschema-specifications==2023.12.1 +langchain==0.0.317 +langsmith==0.0.51 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +marshmallow==3.21.3 +mdurl==0.1.2 +multidict==6.0.5 +mypy-extensions==1.0.0 +numpy==1.26.4 +packaging==24.1 +pandas==2.2.2 +pillow==10.4.0 +protobuf==5.27.3 +pyarrow==17.0.0 +pydantic==2.8.2 +pydantic_core==2.20.1 +pydeck==0.9.1 +Pygments==2.18.0 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +pytz==2024.1 +PyYAML==6.0.1 +redis==5.0.8 +referencing==0.35.1 +requests==2.32.3 +rich==13.7.1 +rpds-py==0.19.1 +s3transfer==0.10.2 +six==1.16.0 +smmap==5.0.1 +sniffio==1.3.1 +SQLAlchemy==2.0.31 +streamlit==1.37.0 +tenacity==8.5.0 +tokenizers==0.19.1 +toml==0.10.2 +toolz==0.12.1 +tornado==6.4.1 +tqdm==4.66.4 +typing-inspect==0.9.0 +typing_extensions==4.12.2 +tzdata==2024.1 +urllib3==2.2.2 +watchdog==4.0.1 +yarl==1.9.4 diff --git a/webinars/genai-chatbot/requirements.txt b/webinars/genai-chatbot/requirements.txt index c88e908..d8ef10a 100644 --- a/webinars/genai-chatbot/requirements.txt +++ b/webinars/genai-chatbot/requirements.txt @@ -1,87 +1,8 @@ -aiohttp==3.9.4 -aiosignal==1.3.1 -altair==5.1.2 -annotated-types==0.6.0 -anyio==3.7.1 -async-timeout==4.0.3 -attrs==23.1.0 -awscli==1.29.70 -blinker==1.6.3 -boto3==1.28.70 -botocore==1.31.70 -cachetools==5.3.2 -certifi==2024.7.4 -charset-normalizer==3.3.1 -click==8.1.7 -colorama==0.4.4 -dataclasses-json==0.6.1 -docutils==0.16 -faiss-cpu==1.7.4 -filelock==3.12.4 -frozenlist==1.4.0 -fsspec==2023.10.0 -gitdb==4.0.11 -GitPython==3.1.41 -greenlet==3.0.1 -huggingface-hub==0.17.3 -idna==3.7 -importlib-metadata==6.8.0 -Jinja2==3.1.4 -jmespath==1.0.1 -jq==1.6.0 -jsonpatch==1.33 -jsonpointer==2.4 -jsonschema==4.19.1 -jsonschema-specifications==2023.7.1 -langchain==0.0.325 +anthropic==0.32.0 +boto3==1.34.151 +python-dotenv==1.0.1 +langchain==0.0.317 langsmith==0.0.51 -markdown-it-py==3.0.0 -MarkupSafe==2.1.3 -marshmallow==3.20.1 -mdurl==0.1.2 -multidict==6.0.4 -mypy-extensions==1.0.0 -numpy==1.26.1 -packaging==23.2 -pandas==2.1.1 -Pillow==10.2.0 -protobuf==4.24.4 -pyarrow==14.0.1 -pyasn1==0.5.0 -pydantic==2.4.2 -pydantic_core==2.10.1 -pydeck==0.8.1b0 -Pygments==2.16.1 -pypdf==3.17.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -PyYAML==6.0.1 -referencing==0.30.2 -regex==2023.10.3 -requests==2.32.2 -rich==13.6.0 -rpds-py==0.10.6 -rsa==4.7.2 -s3transfer==0.7.0 -safetensors==0.4.0 -six==1.16.0 -smmap==5.0.1 -sniffio==1.3.0 -SQLAlchemy==2.0.22 -streamlit==1.30.0 -tenacity==8.2.3 -tokenizers==0.14.1 -toml==0.10.2 -toolz==0.12.0 -tornado==6.4.1 -tqdm==4.66.3 -transformers==4.36.0 -typing-inspect==0.9.0 -typing_extensions==4.8.0 -tzdata==2023.3 -tzlocal==5.2 -urllib3==2.2.2 -validators==0.22.0 -watchdog==3.0.0 -yarl==1.9.2 -zipp==3.19.1 +streamlit==1.37.0 +hiredis==3.0.0 +redis==5.0.8 \ No newline at end of file From ecf53bca6854b43961e57f571c6f293e24691ddc Mon Sep 17 00:00:00 2001 From: Roberto Luna Rojas Date: Wed, 31 Jul 2024 23:44:21 +0000 Subject: [PATCH 2/3] Amazon ElastiCache | Build a generative AI Virtual Assistant with Amazon Bedrock | dotenv file --- webinars/genai-chatbot/.env.example | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 webinars/genai-chatbot/.env.example diff --git a/webinars/genai-chatbot/.env.example b/webinars/genai-chatbot/.env.example new file mode 100644 index 0000000..ac10f5d --- /dev/null +++ b/webinars/genai-chatbot/.env.example @@ -0,0 +1,4 @@ +BWB_ENDPOINT_URL=https://bedrock-runtime.us-east-1.amazonaws.com +ELASTICACHE_ENDPOINT_URL=rediss://elaticache.serverless.use1.cache.amazonaws.com:6379 +BWB_PROFILE_NAME=IF_YOU_NEED_TO_USE_AN_AWS_CLI_PROFILE_IT_GOES_HERE +BWB_REGION_NAME=REGION_NAME_GOES_HERE_IF_YOU_NEED_TO_OVERRIDE_THE_DEFAULT_REGION \ No newline at end of file From 77d3ce48c3dfb2c8f7e257263ea0efe7035e9dd4 Mon Sep 17 00:00:00 2001 From: Roberto Luna Rojas Date: Wed, 31 Jul 2024 23:48:46 +0000 Subject: [PATCH 3/3] Amazon ElastiCache | Build a generative AI Virtual Assistant with Amazon Bedrock | dotenv file --- webinars/genai-chatbot/chatbot_app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webinars/genai-chatbot/chatbot_app.py b/webinars/genai-chatbot/chatbot_app.py index c74c90c..72af9a2 100644 --- a/webinars/genai-chatbot/chatbot_app.py +++ b/webinars/genai-chatbot/chatbot_app.py @@ -1,6 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 +import os import logging # Import the logging module for logging purposes import streamlit as st # Import the Streamlit library for building interactive web apps import chatbot_lib as glib # Import a custom module named 'chatbot_lib'