From 844d16d41752e1727b5853bbf5196c46a90989a8 Mon Sep 17 00:00:00 2001 From: "Shin, Jungseob" Date: Tue, 22 Oct 2024 08:51:06 +0900 Subject: [PATCH] Update prompt and delete unnecessary stacks --- .../generative_ai_rag/lambda/index.py | 103 ------------ .../web-app/pages/rag_integration.py | 158 ++++++++++-------- .../other_stack/bedrock_agent_stack.py | 20 +-- 3 files changed, 92 insertions(+), 189 deletions(-) delete mode 100644 cdk/examples/generative_ai_rag/lambda/index.py diff --git a/cdk/examples/generative_ai_rag/lambda/index.py b/cdk/examples/generative_ai_rag/lambda/index.py deleted file mode 100644 index be42bfdb..00000000 --- a/cdk/examples/generative_ai_rag/lambda/index.py +++ /dev/null @@ -1,103 +0,0 @@ -import json -import boto3 -import os -from botocore.exceptions import ClientError - -dynamodb = boto3.resource('dynamodb') -table = dynamodb.Table(os.environ['TABLE_NAME']) - -def lambda_handler(event, context): - print(f"Received event: {event}") - print(f"Received context: {context}") - - agent = event['agent'] - action_group = event['actionGroup'] - api_path = event['apiPath'] - http_method = event['httpMethod'] - - function = determine_function(api_path, http_method) - message = execute_function(function, event) - - response_body = create_response_body(message) - action_response = create_action_response(action_group, api_path, http_method, response_body) - function_response = create_function_response(action_response, event['messageVersion']) - - print(f"Response: {function_response}") - return function_response - -def determine_function(api_path, http_method): - if api_path == '/bookmark/' and http_method == 'POST': - return 'register-bookmark' - elif api_path == '/bookmark/' and http_method == 'GET': - return 'get-bookmark' - else: - return 'unknown' - -def execute_function(function, event): - if function == 'register-bookmark': - return register_bookmark(event) - elif function == 'get-bookmark': - return get_bookmarks() - else: - return f"Unknown function: {function}" - -def register_bookmark(event): - try: - request_body = event.get('requestBody', {}) - properties = request_body.get('content', {}).get('application/json', {}).get('properties', []) - - params = {prop['name']: prop['value'] for prop in properties if 'name' in prop and 'value' in prop} - - session_code = params.get('sessionCode') - session_title = params.get('sessionTitle') - session_description = params.get('sessionDescription') - - print(f"Received sessionCode: {session_code}") - print(f"Received sessionTitle: {session_title}") - print(f"Received sessionDescription: {session_description}") - - if not session_code: - return "Session code is required." - - table.put_item( - Item={ - 'sessionCode': session_code, - 'sessionTitle': session_title, - 'sessionDescription': session_description - } - ) - return "Bookmark has been successfully registered!" - except ClientError as e: - print(e.response['Error']['Message']) - return "Failed to add bookmark." - -def get_bookmarks(): - try: - response = table.scan() - items = response.get('Items', []) - return json.dumps(items) - except ClientError as e: - print(e.response['Error']['Message']) - return "Failed to retrieve bookmarks." - -def create_response_body(message): - return { - "TEXT": { - "body": message - } - } - -def create_action_response(action_group, api_path, http_method, response_body): - return { - 'actionGroup': action_group, - 'apiPath': api_path, - 'httpMethod': http_method, - 'httpStatusCode': 200, - 'responseBody': response_body - } - -def create_function_response(action_response, message_version): - return { - 'response': action_response, - 'messageVersion': message_version - } diff --git a/cdk/examples/generative_ai_rag/web-app/pages/rag_integration.py b/cdk/examples/generative_ai_rag/web-app/pages/rag_integration.py index 01b3f6e4..00d08a77 100644 --- a/cdk/examples/generative_ai_rag/web-app/pages/rag_integration.py +++ b/cdk/examples/generative_ai_rag/web-app/pages/rag_integration.py @@ -37,80 +37,94 @@ def invoke_bedrock_stream(client: Any, system_prompt: str, user_prompt: str) -> "messages": [{"role": "user", "content": user_prompt}] }) - try: - response = client.invoke_model_with_response_stream( - body=body, - modelId=MODEL_ID, - accept='application/json', - contentType='application/json' - ) - for event in response.get('body'): - chunk = json.loads(event['chunk']['bytes']) - if chunk['type'] == 'content_block_delta': - if chunk['delta']['type'] == 'text_delta': - yield chunk['delta']['text'] - elif chunk['type'] == 'content_block_stop': - break - except Exception as e: - st.error(f"Error invoking Bedrock model: {e}") - yield "" + with st.spinner("Generating response...."): + try: + response = client.invoke_model_with_response_stream( + body=body, + modelId=MODEL_ID, + accept='application/json', + contentType='application/json' + ) + for event in response.get('body'): + chunk = json.loads(event['chunk']['bytes']) + if chunk['type'] == 'content_block_delta': + if chunk['delta']['type'] == 'text_delta': + yield chunk['delta']['text'] + elif chunk['type'] == 'content_block_stop': + break + except Exception as e: + st.error(f"Error invoking Bedrock model: {e}") + yield "" def get_prompt_template(retrieved_passages: List[str]) -> str: """Return the improved prompt template for the AI assistant with response formatting.""" return f""" - You are an AI assistant answering questions about AWS re:Invent 2024 session information and general queries. + You are an AI assistant answering questions about AWS re:Invent 2024 session information and general queries. Your task is to analyze the user's question, categorize it, and provide an appropriate response. + Every session information includes the following fields: + - Title + - Session Code + - Description + - Session Type + - Topic + - Areas of Interest + - Level + - Target Roles + - Venue + - Date and Time + - Prerequisites + - Key Points First, analyze the user's question and categorize it into one of the following: - 1. General question - 2. re:Invent session recommendation question - 3. re:Invent session information question - - Then, based on your analysis, prepare a response following these guidelines: - - 1. For general questions: - - Ignore the content in the retrieved passages. - - Provide a direct answer to the question based on your general knowledge. - - Do not include any uncertain information or speculation. - - If the question is outside your knowledge base, politely state that you don't have that information. - - 2. For re:Invent session recommendation questions: - - Use the information from the retrieved passages to recommend sessions. - - Prioritize recommendations based on: - a) Exact matches in the "Related AWS Services" field (highest priority) - b) Relevance in the "Related AWS Services", "Description" and "Title" fields - c) Relevance to the "Topic" and "Areas of Interest" - - Must to include "Description", "Time" and "Venue" information for recommended sessions. - - 3. For re:Invent session information questions: - - Use the information from the retrieved passages to provide detailed session information. - - Focus on the following aspects: - - Description - - Areas of Interest - - Session Type - - Prerequisites - - Key Points - - Related AWS Services - - Provide accurate Venue and Date/Time information when asked about schedules and locations. - - If the search results lack a clear answer, explicitly state the inability to find exact information. - - General guidelines: - - Provide concise answers with all relevant information. - - Address the core question directly without unnecessary preamble or conclusion. - - Always base your answers on the provided data and refrain from offering uncertain information. - - Verify user assertions against search results; don't assume user statements are factual. + + 1. GENERAL + - This question type is for general questions that are not related to re:Invent sessions. + + 2. REINVENT_INFORMATION + - This question type is for questions requesting information about specific sessions + - This question type is for questions requesting information about sessions held at certain venue and times + + 3. REINVENT_RECOMMENDATION + - This question type is for session recommendation questions for specific topics or interests + + Second, analyze the user's question and provide a response based on the question type. + + 1. GENERAL + - Ignore the content in the retrieved passages. + - Provide a direct answer to the question based on your general knowledge. + - Do not include any uncertain information or speculation. + - If the question is outside your knowledge base, politely state that you don't have that information. + + 2. REINVENT_INFORMATION + - Think step-by-step before providing an answer. + - Use the information from the retrieved passages to answer the question. + - Extract all fields data from each information and use it to generate an answer. + - Prioritize recommendations based on: + a) Relevance in the "Related AWS Services", "Description" and "Title" fields + b) Relevance to the "Topic" and "Areas of Interest" + - When a specific date is mentioned, only recommend sessions occurring on that exact date. + + 3. REINVENT_RECOMMENDATION + - Think step-by-step before providing an answer. + - Use the information from the retrieved passages to answer the question. + - Extract all fields data from each information and use it to generate an answer. + - Analyze each condition of the question one by one, with particular emphasis on matching the exact date and time, and venue. + - When a specific date or venue is mentioned, only recommend sessions occurring on that exact date and venue. Here are the retrieved passages: {retrieved_passages} IMPORTANT: - Your final response should only contain the actual answer to the user's question. - Do not include any explanation of your thought process, categorization, or analysis in the final response. - If retrieved passages are empty and question type is not GENERAL, respond with "Sorry. I couldn't find any related information." + - Always base your answers on the provided data and refrain from offering uncertain information. + - Your final response should only contain the actual answer to the user's question. + - Do not include any explanation of your thought process, categorization, or analysis in the final response. + - If retrieved passages are empty and question type is not GENERAL, respond with "Sorry. I couldn't find any related information." + - Do not modify fields data in the retrieved passages. + - If all conditions are not met, recommend similar sessions and be sure to explain the reason. CRITICAL RESPONSE FORMAT: - You MUST format your entire response EXACTLY as follows, with no exceptions: + - You MUST format your entire response EXACTLY as follows, with no exceptions: [QUESTION_TYPE] GENERAL or REINVENT_RECOMMENDATION or REINVENT_INFORMATION @@ -119,20 +133,28 @@ def get_prompt_template(retrieved_passages: List[str]) -> str: Your complete answer here, with no text outside these tags. [/RESPONSE] - IMPORTANT RULES: + IMPORTANT RESPONSE FORMAT RULES: 1. ALWAYS include both [QUESTION_TYPE] and [RESPONSE] tags. 2. [QUESTION_TYPE] must contain ONLY ONE of the three specified types, nothing else. 3. [RESPONSE] must contain your COMPLETE answer and nothing else. 4. DO NOT include ANY text outside of these tags. - 5. If you cannot provide an answer, still use the tags and put your "Sorry" message inside the [RESPONSE] tags. + 5. If you cannot provide an answer, still use the tags and put your "Sorry. I couldn't answer that question." message inside the [RESPONSE] tags. 6. Never use [GENERAL], [REINVENT_RECOMMENDATION], or [REINVENT_INFORMATION] as standalone tags. EXAMPLE OF CORRECT FORMAT: [QUESTION_TYPE] - GENERAL + REINVENT_RECOMMENDATION [/QUESTION_TYPE] [RESPONSE] - This is where your complete answer would go, with no other text outside these tags. + Based on your question, I recommend the following session: + + 1. Responsible generative AI tabletop: Governance and oversight [REPEAT] + - Session Code: GHJ208-R1 + - Session Type: Gamified learning + - Venue: Mandalay Bay + - Date and Time: Wednesday, Dec 04, 12:00 p.m. + - This session offers a gamified learning experience where participants engage in a tabletop exercise simulating board-level decision-making for generative AI initiatives in a fictional organization. ... (blah, blah) ... + 2. ... (More sessions) ... [/RESPONSE] Failure to follow this format exactly will result in an error. Double-check your response before submitting. @@ -149,7 +171,7 @@ def retrieve_from_knowledge_base(client: Any, knowledge_base_id: str, prompt: st }, retrievalConfiguration={ 'vectorSearchConfiguration': { - 'numberOfResults': 10 + 'numberOfResults': 20 } } ) @@ -185,8 +207,6 @@ def main(): retrieved_passages = [result['content']['text'] for result in retrieved_results] formatted_passages = "\n\n".join(f"Passage {i+1}:\n{passage}" for i, passage in enumerate(retrieved_passages)) - print(formatted_passages) - # Initialize variables to store the full response and the content after the [RESPONSE] tag full_response = "" response_content = "" @@ -224,14 +244,10 @@ def main(): question_type = question_type_match.group(1).strip() if question_type_match else "UNKNOWN" final_response = response_match.group(1).strip() if response_match else "I apologize. There was an issue generating an appropriate response." - print(full_response) - print(question_type) - print(final_response) - message_placeholder.markdown(final_response) st.session_state.messages.append({"role": "assistant", "content": final_response}) - + # Display citations only for non-general questions if question_type not in ["GENERAL", "UNKNOWN"]: with st.expander("Data Sources"): diff --git a/cdk/examples/other_stack/bedrock_agent_stack.py b/cdk/examples/other_stack/bedrock_agent_stack.py index 2a196aa3..67b74946 100644 --- a/cdk/examples/other_stack/bedrock_agent_stack.py +++ b/cdk/examples/other_stack/bedrock_agent_stack.py @@ -20,17 +20,6 @@ def __init__( super().__init__(scope, id, **kwargs) - # create dynamodb table - self.bookmark_table = dynamodb.Table( - self, - "ReinventBookmarkTable", - table_name="reinvent-bookmark", - partition_key=dynamodb.Attribute(name="sessionCode", - type=dynamodb.AttributeType.STRING), - billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST, - removal_policy=RemovalPolicy.DESTROY, - ) - # create S3 bucket self.bucket = s3.Bucket( self, @@ -57,10 +46,11 @@ def __init__( self.knowledge_base_data_source = bedrock.S3DataSource(self, 'KnowledgeBaseDataSource', bucket=self.bucket, knowledge_base=self.knowledge_base, - data_source_name='ReinventSessionInformationText', - chunking_strategy= bedrock.ChunkingStrategy.fixed_size( - max_tokens= 512, - overlap_percentage= 20 + data_source_name='ReinventSessionInformationText', + chunking_strategy= bedrock.ChunkingStrategy.hierarchical( + overlap_tokens=60, + max_parent_token_size=1500, + max_child_token_size=300 ) )