diff --git a/ActionLambda.py b/ActionLambda.py index fe94de4..04cec76 100644 --- a/ActionLambda.py +++ b/ActionLambda.py @@ -1,8 +1,9 @@ import json +import boto3 +from botocore.exceptions import ClientError def lambda_handler(event, context): print(event) - # Mock data for demonstration purposes company_data = [ #Technology Industry @@ -12,11 +13,11 @@ def lambda_handler(event, context): {"companyId": 4, "companyName": "DigitalMyricalDreams Gaming", "industrySector": "Technology", "revenue": 40000, "expenses": 6000, "profit": 34000, "employees": 10}, {"companyId": 5, "companyName": "NanoMedNoLand Pharmaceuticals", "industrySector": "Technology", "revenue": 50000, "expenses": 7000, "profit": 43000, "employees": 10}, {"companyId": 6, "companyName": "RoboSuperBombTech Industries", "industrySector": "Technology", "revenue": 60000, "expenses": 8000, "profit": 52000, "employees": 12}, - {"companyId": 7, "companyName": "FuturePastNet Solutions", "industrySector": "Technology", "revenue": 60000, "expenses": 9000, "profit": 51000, "employees": 10}, + {"companyId": 7, "companyName": "FuturePastNet Solutions", "industrySector": "Technology", "revenue": 60000, "expenses": 9000, "profit": 51000, "employees": 10}, {"companyId": 8, "companyName": "InnovativeCreativeAI Corp", "industrySector": "Technology", "revenue": 65000, "expenses": 10000, "profit": 55000, "employees": 15}, {"companyId": 9, "companyName": "EcoLeekoTech Energy", "industrySector": "Technology", "revenue": 70000, "expenses": 11000, "profit": 59000, "employees": 10}, {"companyId": 10, "companyName": "TechyWealthHealth Systems", "industrySector": "Technology", "revenue": 80000, "expenses": 12000, "profit": 68000, "employees": 10}, - + #Real Estate Industry {"companyId": 11, "companyName": "LuxuryToNiceLiving Real Estate", "industrySector": "Real Estate", "revenue": 90000, "expenses": 13000, "profit": 77000, "employees": 10}, {"companyId": 12, "companyName": "UrbanTurbanDevelopers Inc.", "industrySector": "Real Estate", "revenue": 100000, "expenses": 14000, "profit": 86000, "employees": 10}, @@ -29,62 +30,103 @@ def lambda_handler(event, context): {"companyId": 19, "companyName": "GlobalRegional Properties Alliance", "industrySector": "Real Estate", "revenue": 170000, "expenses": 21000, "profit": 149000, "employees": 11}, {"companyId": 20, "companyName": "NextGenPast Residences", "industrySector": "Real Estate", "revenue": 180000, "expenses": 22000, "profit": 158000, "employees": 260} ] - - + def get_named_parameter(event, name): + '''Function searches through the parameters list in the event dictionary and returns the value of the parameter whose name matches the provided name string''' return next(item for item in event['parameters'] if item['name'] == name)['value'] - - def get_named_property(event, name): - return next(item for item in event['requestBody']['content']['application/json']['properties'] if item['name'] == name)['value'] - def companyResearch(event): + '''Searches for a company based on the 'name' parameter in the lambda event and returns its information if found.''' companyName = get_named_parameter(event, 'name').lower() print("NAME PRINTED: ", companyName) - for company_info in company_data: if company_info["companyName"].lower() == companyName: return company_info - return None - + def createPortfolio(event, company_data): + '''Creates a portfolio of top companies based on the 'numCompanies' and 'industry' parameters in the lambda event.''' numCompanies = int(get_named_parameter(event, 'numCompanies')) industry = get_named_parameter(event, 'industry').lower() - - industry_filtered_companies = [company for company in company_data - if company['industrySector'].lower() == industry] - + industry_filtered_companies = [company for company in company_data if company['industrySector'].lower() == industry] sorted_companies = sorted(industry_filtered_companies, key=lambda x: x['profit'], reverse=True) - top_companies = sorted_companies[:numCompanies] return top_companies - def sendEmail(event, company_data): + '''Sends an email using Amazon SES with a summary report and portfolio details''' + # Get parameters from event emailAddress = get_named_parameter(event, 'emailAddress') fomcSummary = get_named_parameter(event, 'fomcSummary') - - # Retrieve the portfolio data as a string portfolioDataString = get_named_parameter(event, 'portfolio') - + + # Create the email content + SENDER = emailAddress # Replace with your email. Must be verified in SES + RECIPIENT = emailAddress + SUBJECT = "Company Portfolio and Search Results Summary Report" + + # Create the email body + BODY_TEXT = (f"Search Summary Report:\n\n{fomcSummary}\n\n" + f"Portfolio Details:\n{portfolioDataString}") + + # HTML version of the email + BODY_HTML = f""" + +
+ +{fomcSummary}
+{portfolioDataString}
+
+
+ """
+
+ # The character encoding for the email
+ CHARSET = "UTF-8"
+
+ try:
+ # Create a new SES client
+ ses_client = boto3.client('ses')
+
+ # Send the email
+ response = ses_client.send_email(
+ Destination={
+ 'ToAddresses': [
+ RECIPIENT,
+ ],
+ },
+ Message={
+ 'Body': {
+ 'Html': {
+ 'Charset': CHARSET,
+ 'Data': BODY_HTML,
+ },
+ 'Text': {
+ 'Charset': CHARSET,
+ 'Data': BODY_TEXT,
+ },
+ },
+ 'Subject': {
+ 'Charset': CHARSET,
+ 'Data': SUBJECT,
+ },
+ },
+ Source=SENDER
+ )
+
+ except ClientError as e:
+ print(e.response['Error']['Message'])
+ return f"Error sending email: {str(e)}"
+ else:
+ return f"Email sent successfully to {emailAddress}! MessageId: {response['MessageId']}"
- # Prepare the email content
- email_subject = "Portfolio Creation Summary and FOMC Search Results"
- #email_body = f"FOMC Search Summary:\n{fomcSummary}\n\nPortfolio Details:\n{json.dumps(portfolioData, indent=4)}"
-
- # Email sending code here (commented out for now)
-
- return "Email sent successfully to {}".format(emailAddress)
-
-
result = ''
response_code = 200
action_group = event['actionGroup']
api_path = event['apiPath']
-
- print("api_path: ", api_path )
-
+ print("api_path: ", api_path)
+
if api_path == '/companyResearch':
result = companyResearch(event)
elif api_path == '/createPortfolio':
@@ -94,13 +136,13 @@ def sendEmail(event, company_data):
else:
response_code = 404
result = f"Unrecognized api path: {action_group}::{api_path}"
-
+
response_body = {
'application/json': {
'body': result
}
}
-
+
action_response = {
'actionGroup': event['actionGroup'],
'apiPath': event['apiPath'],
diff --git a/cfn/2-bedrock-agent-lambda-template.yaml b/cfn/2-bedrock-agent-lambda-template.yaml
index 321f8a6..000185b 100644
--- a/cfn/2-bedrock-agent-lambda-template.yaml
+++ b/cfn/2-bedrock-agent-lambda-template.yaml
@@ -39,7 +39,11 @@ Resources:
Action:
- 'sqs:SendMessage'
Resource: !GetAtt ActionCallDLQ.Arn
-
+ - Effect: Allow
+ Action:
+ - 'ses:SendEmail'
+ - 'ses:SendRawEmail'
+ Resource: '*'
# IAM Managed Policy for Lambda Invocation
LambdaInvokePolicy:
Type: 'AWS::IAM::ManagedPolicy'
@@ -88,10 +92,11 @@ Resources:
Code:
ZipFile: |
import json
-
+ import boto3
+ from botocore.exceptions import ClientError
+
def lambda_handler(event, context):
print(event)
-
# Mock data for demonstration purposes
company_data = [
#Technology Industry
@@ -101,11 +106,11 @@ Resources:
{"companyId": 4, "companyName": "DigitalMyricalDreams Gaming", "industrySector": "Technology", "revenue": 40000, "expenses": 6000, "profit": 34000, "employees": 10},
{"companyId": 5, "companyName": "NanoMedNoLand Pharmaceuticals", "industrySector": "Technology", "revenue": 50000, "expenses": 7000, "profit": 43000, "employees": 10},
{"companyId": 6, "companyName": "RoboSuperBombTech Industries", "industrySector": "Technology", "revenue": 60000, "expenses": 8000, "profit": 52000, "employees": 12},
- {"companyId": 7, "companyName": "FuturePastNet Solutions", "industrySector": "Technology", "revenue": 60000, "expenses": 9000, "profit": 51000, "employees": 10},
+ {"companyId": 7, "companyName": "FuturePastNet Solutions", "industrySector": "Technology", "revenue": 60000, "expenses": 9000, "profit": 51000, "employees": 10},
{"companyId": 8, "companyName": "InnovativeCreativeAI Corp", "industrySector": "Technology", "revenue": 65000, "expenses": 10000, "profit": 55000, "employees": 15},
{"companyId": 9, "companyName": "EcoLeekoTech Energy", "industrySector": "Technology", "revenue": 70000, "expenses": 11000, "profit": 59000, "employees": 10},
{"companyId": 10, "companyName": "TechyWealthHealth Systems", "industrySector": "Technology", "revenue": 80000, "expenses": 12000, "profit": 68000, "employees": 10},
-
+
#Real Estate Industry
{"companyId": 11, "companyName": "LuxuryToNiceLiving Real Estate", "industrySector": "Real Estate", "revenue": 90000, "expenses": 13000, "profit": 77000, "employees": 10},
{"companyId": 12, "companyName": "UrbanTurbanDevelopers Inc.", "industrySector": "Real Estate", "revenue": 100000, "expenses": 14000, "profit": 86000, "employees": 10},
@@ -118,58 +123,103 @@ Resources:
{"companyId": 19, "companyName": "GlobalRegional Properties Alliance", "industrySector": "Real Estate", "revenue": 170000, "expenses": 21000, "profit": 149000, "employees": 11},
{"companyId": 20, "companyName": "NextGenPast Residences", "industrySector": "Real Estate", "revenue": 180000, "expenses": 22000, "profit": 158000, "employees": 260}
]
-
-
+
def get_named_parameter(event, name):
+ '''Function searches through the parameters list in the event dictionary and returns the value of the parameter whose name matches the provided name string'''
return next(item for item in event['parameters'] if item['name'] == name)['value']
-
def companyResearch(event):
+ '''Searches for a company based on the 'name' parameter in the lambda event and returns its information if found.'''
companyName = get_named_parameter(event, 'name').lower()
print("NAME PRINTED: ", companyName)
-
for company_info in company_data:
if company_info["companyName"].lower() == companyName:
return company_info
return None
-
+
def createPortfolio(event, company_data):
+ '''Creates a portfolio of top companies based on the 'numCompanies' and 'industry' parameters in the lambda event.'''
numCompanies = int(get_named_parameter(event, 'numCompanies'))
industry = get_named_parameter(event, 'industry').lower()
-
- industry_filtered_companies = [company for company in company_data
- if company['industrySector'].lower() == industry]
-
+ industry_filtered_companies = [company for company in company_data if company['industrySector'].lower() == industry]
sorted_companies = sorted(industry_filtered_companies, key=lambda x: x['profit'], reverse=True)
-
top_companies = sorted_companies[:numCompanies]
return top_companies
-
def sendEmail(event, company_data):
+ '''Sends an email using Amazon SES with a summary report and portfolio details'''
+ # Get parameters from event
emailAddress = get_named_parameter(event, 'emailAddress')
fomcSummary = get_named_parameter(event, 'fomcSummary')
-
- # Retrieve the portfolio data as a string
portfolioDataString = get_named_parameter(event, 'portfolio')
-
-
- # Prepare the email content
- email_subject = "Portfolio Creation Summary and FOMC Search Results"
- #email_body = f"FOMC Search Summary:\n{fomcSummary}\n\nPortfolio Details:\n{json.dumps(portfolioData, indent=4)}"
-
- # Email sending code here (commented out for now)
-
- return "Email sent successfully to {}".format(emailAddress)
-
-
+
+ # Create the email content
+ SENDER = emailAddress # Replace with your email. Must be verified in SES
+ RECIPIENT = emailAddress
+ SUBJECT = "Company Portfolio and Search Results Summary Report"
+
+ # Create the email body
+ BODY_TEXT = (f"Search Summary Report:\n\n{fomcSummary}\n\n"
+ f"Portfolio Details:\n{portfolioDataString}")
+
+ # HTML version of the email
+ BODY_HTML = f"""
+
+
+
+ {fomcSummary}
+{portfolioDataString}
+
+
+ """
+
+ # The character encoding for the email
+ CHARSET = "UTF-8"
+
+ try:
+ # Create a new SES client
+ ses_client = boto3.client('ses')
+
+ # Send the email
+ response = ses_client.send_email(
+ Destination={
+ 'ToAddresses': [
+ RECIPIENT,
+ ],
+ },
+ Message={
+ 'Body': {
+ 'Html': {
+ 'Charset': CHARSET,
+ 'Data': BODY_HTML,
+ },
+ 'Text': {
+ 'Charset': CHARSET,
+ 'Data': BODY_TEXT,
+ },
+ },
+ 'Subject': {
+ 'Charset': CHARSET,
+ 'Data': SUBJECT,
+ },
+ },
+ Source=SENDER
+ )
+
+ except ClientError as e:
+ print(e.response['Error']['Message'])
+ return f"Error sending email: {str(e)}"
+ else:
+ return f"Email sent successfully to {emailAddress}! MessageId: {response['MessageId']}"
+
result = ''
response_code = 200
action_group = event['actionGroup']
api_path = event['apiPath']
-
- print("api_path: ", api_path )
-
+ print("api_path: ", api_path)
+
if api_path == '/companyResearch':
result = companyResearch(event)
elif api_path == '/createPortfolio':
@@ -179,13 +229,13 @@ Resources:
else:
response_code = 404
result = f"Unrecognized api path: {action_group}::{api_path}"
-
+
response_body = {
'application/json': {
'body': result
}
}
-
+
action_response = {
'actionGroup': event['actionGroup'],
'apiPath': event['apiPath'],
@@ -193,9 +243,10 @@ Resources:
'httpStatusCode': response_code,
'responseBody': response_body
}
-
+
api_response = {'messageVersion': '1.0', 'response': action_response}
return api_response
+
# Lambda Permission for Bedrock to Invoke Lambda
LambdaInvokePermission:
diff --git a/cfn/3-ec2-streamlit-template.yaml b/cfn/3-ec2-streamlit-template.yaml
index 0e49d04..7a66fee 100644
--- a/cfn/3-ec2-streamlit-template.yaml
+++ b/cfn/3-ec2-streamlit-template.yaml
@@ -197,7 +197,7 @@ Resources:
apt-get update -y
apt-get upgrade -y
apt-get install -y python3-pip git ec2-instance-connect
- git clone https://github.com/build-on-aws/bedrock-agents-streamlit.git /home/ubuntu/app
+ git clone https://github.com/jossai87/bedrock-agents-streamlit-1.git /home/ubuntu/app
pip3 install -r /home/ubuntu/app/streamlit_app/requirements.txt
cd /home/ubuntu/app/streamlit_app
diff --git a/streamlit_app/app.py b/streamlit_app/app.py
index fab84fe..582c537 100644
--- a/streamlit_app/app.py
+++ b/streamlit_app/app.py
@@ -70,12 +70,26 @@ def format_response(response_body):
response_data = None
try:
- # Extract the response and trace data
- all_data = format_response(response_data['response'])
- the_response = response_data['trace_data']
- except:
+ # Check if response_data is None or contains error
+ if response_data is None:
+ all_data = "No response data"
+ the_response = "Failed to get response from agent"
+ elif 'error' in response_data:
+ all_data = "Error occurred"
+ the_response = f"Agent error: {response_data['error']}"
+ elif 'response' in response_data and 'trace_data' in response_data:
+ # Extract the response and trace data
+ all_data = format_response(response_data['response'])
+ the_response = response_data['trace_data']
+ else:
+ all_data = f"Unexpected response format: {list(response_data.keys()) if response_data else 'None'}"
+ the_response = f"Response data: {response_data}"
+ except Exception as e:
+ print(f"Error extracting response data: {e}")
+ print(f"Response data: {response_data}")
+ print(f"Full response: {response}")
all_data = "..."
- the_response = "Apologies, but an error occurred. Please rerun the application"
+ the_response = f"Error occurred: {str(e)}"
# Use trace_data and formatted_response as needed
st.sidebar.text_area("", value=all_data, height=300)
diff --git a/streamlit_app/invoke_agent.py b/streamlit_app/invoke_agent.py
index 7003336..069058a 100644
--- a/streamlit_app/invoke_agent.py
+++ b/streamlit_app/invoke_agent.py
@@ -11,7 +11,11 @@
from botocore.credentials import Credentials
from requests import request
-ssm = boto3.client('ssm')
+# Region configuration moved here to be available for all clients
+theRegion = "us-west-2"
+os.environ["AWS_REGION"] = theRegion
+
+ssm = boto3.client('ssm', region_name=theRegion)
# ---------------------------------------------------------------------
# Replace with your actual Agent ID and Alias ID below:
@@ -25,11 +29,7 @@
#agentAliasId = ssm.get_parameter(Name='/alias-id', WithDecryption=True)['Parameter']['Value'] #valid if CFN infrastructure templates were ran
-# ---------------------------------------------------------------------
-# REGION CONFIGURATION:
-# ---------------------------------------------------------------------
-theRegion = "us-west-2"
-os.environ["AWS_REGION"] = theRegion
+# Region configuration moved to top of file
# ---------------------------------------------------------------------
# HELPER FUNCTION TO GET AWS CREDENTIALS SAFELY
@@ -157,27 +157,64 @@ def decode_response(response):
final_response = ""
for idx in range(len(split_response)):
if "bytes" in split_response[idx]:
- encoded_last_response = split_response[idx].split("\"")[3]
- decoded = base64.b64decode(encoded_last_response)
- final_response_chunk = decoded.decode('utf-8')
- print(final_response_chunk)
+ try:
+ # More robust extraction of base64 content
+ segment = split_response[idx]
+ # Find the bytes field and extract the base64 string
+ bytes_start = segment.find('"bytes":"') + len('"bytes":"')
+ bytes_end = segment.find('"', bytes_start)
+ if bytes_start > len('"bytes":"') - 1 and bytes_end > bytes_start:
+ encoded_last_response = segment[bytes_start:bytes_end]
+ # Add padding if needed for base64 decoding
+ missing_padding = len(encoded_last_response) % 4
+ if missing_padding:
+ encoded_last_response += '=' * (4 - missing_padding)
+ decoded = base64.b64decode(encoded_last_response)
+ final_response_chunk = decoded.decode('utf-8')
+ print(final_response_chunk)
+ final_response += final_response_chunk
+ else:
+ print(f"Could not extract base64 from: {segment}")
+ except (base64.binascii.Error, UnicodeDecodeError) as e:
+ print(f"Error decoding base64 at index {idx}: {e}")
+ print(f"Raw data: {split_response[idx]}")
else:
print(f"No bytes at index {idx}")
print(split_response[idx])
- # Attempt to parse the last part for finalResponse
- last_response = split_response[-1]
- print(f"Last Response: {last_response}")
- if "bytes" in last_response:
- print("Bytes in last response")
- encoded_last_response = last_response.split("\"")[3]
- decoded = base64.b64decode(encoded_last_response)
- final_response = decoded.decode('utf-8')
- else:
- print("No bytes in last response")
- part1 = string[string.find('finalResponse')+len('finalResponse":'):]
- part2 = part1[:part1.find('"}')+2]
- final_response = json.loads(part2)['text']
+ # If we didn't get a response from the loop above, try the final response parsing
+ if not final_response:
+ last_response = split_response[-1]
+ print(f"Last Response: {last_response}")
+ if "bytes" in last_response:
+ print("Bytes in last response")
+ try:
+ # More robust extraction of base64 content
+ bytes_start = last_response.find('"bytes":"') + len('"bytes":"')
+ bytes_end = last_response.find('"', bytes_start)
+ if bytes_start > len('"bytes":"') - 1 and bytes_end > bytes_start:
+ encoded_last_response = last_response[bytes_start:bytes_end]
+ # Add padding if needed for base64 decoding
+ missing_padding = len(encoded_last_response) % 4
+ if missing_padding:
+ encoded_last_response += '=' * (4 - missing_padding)
+ decoded = base64.b64decode(encoded_last_response)
+ final_response = decoded.decode('utf-8')
+ else:
+ print(f"Could not extract base64 from last response: {last_response}")
+ final_response = "Could not parse final response"
+ except (base64.binascii.Error, UnicodeDecodeError) as e:
+ print(f"Error decoding final response: {e}")
+ final_response = f"Error decoding response: {e}"
+ else:
+ print("No bytes in last response")
+ try:
+ part1 = string[string.find('finalResponse')+len('finalResponse":'):]
+ part2 = part1[:part1.find('"}')+2]
+ final_response = json.loads(part2)['text']
+ except (json.JSONDecodeError, KeyError) as e:
+ print(f"Error parsing final response JSON: {e}")
+ final_response = f"Error parsing response: {e}"
# Cleanup the final response
final_response = final_response.replace("\"", "")