Scale Your Productivity: Leveraging AWS Gen AI to Summarize Meeting Notes in Seconds

Disclaimer: The meeting transcript used in this article is entirely fictional and created for illustrative and educational purposes only. It does not reflect any actual conversations, events, or individuals. Any resemblance to actual persons or events is purely coincidental.
Disclaimer: The project outlined in this article involves using several Amazon Web Services (AWS), specifically Amazon S3, Amazon API Gateway, AWS Lambda, and Amazon Bedrock. While AWS offers a free tier that provides limited access to some of its services, using AWS beyond these limits or using certain services not covered by the free tier will incur fees. It is essential to review the pricing details for Amazon S3, Amazon API Gateway, AWS Lambda, Amazon Bedrock, and any other AWS services you plan to use to understand the costs involved. You are responsible for any charges incurred while following this project. Please ensure you know AWS pricing clearly and monitor your usage to avoid unexpected charges.
Introduction
Have you ever found yourself in what seems to be a redundant or even borderline pointless meeting at your workplace? The answer is undoubtedly yes for most of us in the corporate setting. I find myself in at least one of these meetings every single week. There are some weeks when my calendar is mainly booked, and there is little time to accomplish my tasks. Eventually, I just started working on my tasks while on mute. Occasionally, I would miss something important that pertained to me, so I had to start giving my full attention again. This can be highly frustrating as during an entire hour, there may only be a few seconds of dialogue pertinent to you.
With the emergence of Generative AI applications, I never have to worry about finding myself in these situations again! In this article, I will walk the reader through how they can leverage AWS Bedrock, AWS Lambda, AWS API Gateway, and AWS S3 to build a scalable pipeline utilizing a generative AI offering to summarize meeting transcripts.
Why AWS
Suppose you are familiar with Generative AI applications such as ChatGPT. Why use AWS when you could copy and paste a transcript into ChatGPT and generate a summary in seconds? Also, some applications already do just this, which you can add to your desktop if not included. There are many reasons for this, but I will focus on two of the most important ones: Scalability and security.
Scalability
Being a cloud platform, AWS allows users, businesses, entities, etc., to build applications and store data at scale. For the use case we are discussing in this article, a meeting transcript summary application in AWS could be scaled to have a nearly endless amount of end users within a company, an internal pipeline from wherever a meeting transcript would originate that feeds directly into AWS, and optimize the storage costs and capacity for the transcript and generated summaries. In addition, you can customize your prompts, output tokens, and many other parameters.
Security
Earlier, I mentioned that one could use ChatGPT to generate a summary by copying and pasting the transcript; this would violate many security best practices, and you could even be liable in some cases if critical data were leaked. With AWS, necessary permissions and guardrails can be set in place so one can take full advantage of a generative AI application without worrying about internal company data traversing a public network. Now, without further ado, let's jump straight into this project.
Part 1: Creating a S3 Bucket
We will start by creating our S3 bucket. S3 is an object storage service offered by AWS that allows users to store data quickly and at scale. It can also be integrated with many of AWS's other services and third-party applications.
To navigate to S3, search for it in the search bar, click on the Services menu next to it, or find it in your recently visited section if you have previously used it.

Once you are on the S3 page, click on create bucket.

Give your bucket a name and keep all other options as they are. As you can see in the above screenshot, I named my bucket bedrock-text-summarization. Note that all bucket names must be globally unique, meaning no two buckets can have the same name across any AWS account.
Part 2: Creating a boto3 layer
As of this article's writing, AWS Lambda does not have the latest version of boto3, the AWS SDK for Python. Therefore, we must create a boto3 layer and upload it to AWS. This ensures we have the proper libraries to invoke AWS Bedrock. This process is relatively simple; however, it can take some time if you have never used the Windows command prompt. Note that the commands below will not work for a Linux system.
To begin, open your Windows command prompt and run the below commands individually. If this is your first time doing so, it may take some time to install the boto3 package.
## Make a new directory for your boto3 layer
mkdir boto3_layer
## Set it as the primary directory
cd boto3_layer
## make a directory called "python"
mkdir python
## Create a virtual environment within the python directory
python3 -m venv venv
## Activate the virtual environment
venv/bin/activate
## install boto3 to the environment
pip install boto3 -t ./python
## deactive the virtual environment
deactivate
Once completed, I elected to zip the Python file manually by following the below steps:
- Open File Explorer and navigate to the directory containing the folder you want to zip (in this case, the python folder within the boto3_layer we created).
- Right-click on the python folder you want to zip.
- Select "Send to" from the context menu.
- Click on "Compressed (zipped) folder"
Once you have the zipped folder, navigate to the Lambda homepage and click on Layers in the left-hand side menu.

You must name your layer and upload your zipped folder from this point. Also, make sure you select the proper compatible runtime. In this case, I will choose the latest version of Python; however, if you plan to include libraries/functions/etc. from earlier versions of Python in your script, you must also ensure those are enabled.

Part 3: Request Access for Bedrock
Bedrock is a unique service within AWS. Simply put, it is an API that allows users to connect to many Foundational Models from leading AI companies. To use any of these models, you will need to request access. Navigate to the main Bedrock page and select Model Access in the bottom left-hand corner:

Once on the model access page, you must click on the specific models you want access to. In this project, we are using Claude from Anthropic. Navigate to this section, and beside each offering from Anthropic, you should see a link that says: Available to request. Note that in my screenshot below, I already have access, so your screen may not look like this. Click on this and complete the necessary steps. The actual approval process should only take a few minutes. I recommend reading through the documentation and pricing of any model you use. For our purposes and as of writing this article, the version of Claude we will use costs $0.008 per 1,000 input tokens and $0.024 per 1,000 output tokens. Our entire workflow should only cost a few cents at most for a single use but know that any repetitive use could rack up costs quickly if you are not careful.

Part 4: Creating the Lambda Function
AWS Lambda is a serverless computing service offered by AWS. It is integrated with many of AWS's other services. I see it as AWS's centralized orchestration tool. For example, whenever new data is uploaded to a specific AWS S3 bucket (a cloud storage service), you could configure a lambda function to send a training job to AWS SageMaker (a machine learning training and model hosting service) to kick off a model training job. That is just one of the countless possibilities of AWS Lambda.
For this project, we will use Lambda to send AWS Bedrock a prompt with a meeting summary transcript that will generate a summary of the meeting and follow-up actions assigned to specific stakeholders in the transcript. Lambda will then send this generated summary to the S3 bucket we created earlier. I will post the complete Lambda function code at the end of this section.
To begin, navigate to the Lambda function page and click on Functions. From there, you only need to name your function and select the necessary runtime, which, in our case, will be the latest version of Python.

Your screen should now look like the below image:

Next, we will add our boto3 layer. Navigate to the bottom of the page to see a Layers section. Click on Add a layer. That will take you to the below page:

From here, select Custom layers and select the boto3 layer we uploaded. You will also see another drop-down menu that specifies which version; choose 1, then click the Add button.
Now that we have added our layer, you can navigate to the Code source window to begin coding our function. I'll link a GitHub gist of the full Lambda Function at the end of this article; however, I will break it down piece by piece here.
Ironically, our Lambda Function will combine a series of smaller functions to orchestrate our complete workflow. Our first function will extract plain text from our document. In other words, anything that is readable text will be taken from the document. This is necessary as documents can be filled with noise that we don't necessarily need.
def extract_text_from_multipart(data):
msg = message_from_bytes(data)
text_content = ""
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
text_content += part.get_payload(decode=True).decode('utf-8') + "n"
else:
if msg.get_content_type() == "text/plain":
text_content = msg.get_payload(decode=True).decode('utf-8')
return text_content.strip() if text_content else None
Next, we will create a function to send the text and a prompt to invoke AWS bedrock. For the actual model, we will be using Anthropic's Claude V2. Note you can easily edit the prompt text to fit your specific needs. Take note of the dictionary body. This is where we can specify parameters for the AI model we invoke. Unfamiliar with these parameters? No worries! I will break them down below:
- _max_tokens_tosample: This is the maximum number of tokens the application will sample, or more practically, the maximum number of individual words and/or numbers the model will read.
- _topk: For each token the model generates, it considers a list of the most likely "next" tokens. A lower value will narrow the selection pool of tokens, while a higher one will allow the model to consider a more expansive selection pool.
- _topp: Very similar to top_k; however, it uses a probability distribution logic. For example, if we select 0.1, the model will consider the top 10% of most likely tokens.
- _stopsequences: Essentially a stopping point for the model. If the model encounters one that you specify, it will stop generating after that sequence.
You can also check out the formal documentation here!
def generate_summary_from_bedrock(content:str) ->str:
prompt_text = f"""Human: Summarize the following meeting transcript and list the specific follow up actions along with who should take those actions: {content}
Assistant:"""
body = {
"prompt":prompt_text,
"max_tokens_to_sample":5000,
"top_k":250,
"top_p":0.2,
"stop_sequences": ["nnHuman:"]
}
try:
bedrock = boto3.client("bedrock-runtime",region_name="us-east-1",config = botocore.config.Config(read_timeout=300, retries = {'max_attempts':3}))
response = bedrock.invoke_model(body=json.dumps(body),modelId="anthropic.claude-v2:1")
response_content = response.get('body').read().decode('utf-8')
response_data = json.loads(response_content)
summary = response_data["completion"].strip()
return summary
except Exception as e:
print(f"Error generating the summary: {e}")
return ""
The response from the model we invoke needs a location to send the meeting summary. Therefore, we will build a function that sends it to an S3 bucket.
## Saves the output to a given S3 bucket
def save_summary_to_s3_bucket(summary, s3_bucket, s3_key):
s3 = boto3.client('s3')
try:
s3.put_object(Bucket = s3_bucket, Key = s3_key, Body = summary)
print("Summary saved to s3")
except Exception as e:
print("Error when saving the summary to s3")
Lastly, we will create our Lambda Handler function, which will be the heart of our Lambda function. The Lambda Handler acts as the entry point when the Lambda function is called; therefore, we will utilize all the functions we created within the handler.
def lambda_handler(event,context):
decoded_body = base64.b64decode(event['body'])
text_content = extract_text_from_multipart(decoded_body)
if not text_content:
return {
'statusCode':400,
'body':json.dumps("Failed to extract content")
}
summary = generate_summary_from_bedrock(text_content)
if summary:
current_time = datetime.now().strftime('%H%M%S') #UTC TIME, NOT NECCESSARILY YOUR TIMEZONE
s3_key = f'summary-output/{current_time}.txt'
s3_bucket = 'bedrock-text-summarization'
save_summary_to_s3_bucket(summary, s3_bucket, s3_key)
else:
print("No summary was generated")
return {
'statusCode':200,
'body':json.dumps("Summary generation finished")
}
Our function may be complete; however, we must make some minor configurations. When we invoke our bedrock model, it could take several seconds, possibly even a minute, to generate the summary, depending on the size of the transcript we send it. Therefore, we will set the timeout of our Lambda function to be four minutes at maximum to ensure our function does not timeout. To do so, navigate to the Configuration menu at the bottom, click General Configuration on the left, and click Edit in the window. After that, you should see a screen like the one below. Ensure that the timeout is set at four minutes.

Part 5: Creating the API
As mentioned on the AWS API Gateway homepage, we will build the front door for our meeting transcript summary generator here. Using API Gateway, we will build an API with a POST method, allowing us to send data to our Lambda function.
To begin, navigate to the AWS API Gateway homepage and click on Create API.

Next, click **** Build in the HTTP API window, as this type of API will be optimal for our particular use case.

Give it a name, and click Next.

Continue clicking Next until you are on the Review and Create page. Click Create. You should now be on the Routes page, which should look like the one below. Click Create in the left-hand window.

This is where we will create the POST route, allowing us to use the API to send data to our application. Select POST from the dropdown and give it a name.

You should now be back on the Routes page. Click Deploy in the top right-hand corner. A pop-up window like the one below will appear. Click on create a new stage, which is highlighted in blue.

This is where we will create our dev stage. Note that we do not necessarily need to do this step; this is just a practical project. However, it is good practice to have different stages for application deployments in the development lifecycle. These stages act as a checks and balances system before an application is customer-facing. Name this stage dev (or whatever you prefer) and ensure that auto-deploy is not enabled.

We are not done yet after creating the development stage. You should be on the Stages page. Before clicking deploy, we still need to integrate our Lambda function. Click on Integrations in the left-hand window.

Click on POST, then click on Create and attach an integration.

Select Lambda Function under integration type and select our Lambda function in the next drop-down menu. Be sure the AWS Region is the same as your Lambda Function, or else you will encounter an error when calling the API.

Now, click Deploy in the top right-hand, and select dev in the pop-up window. At this point, the API has officially been deployed with a URL that we can call. Click on the link in the left-hand menu just above the development section, and it will navigate you to the page where we can find our Invoke URL. Note that these URLs are live, so anyone can use them if they have the URL, and we did not add any permissions/tokens that would need to be attached (therefore, I am deleting my URL before posting this article). That said, I urge caution since it invokes services that cost money.

Part 6: Putting it all together!
Now for the easy and exciting part! It's time to put our project into action. We are just one API call away from a meeting summary. To invoke our application, we will utilize Postman. Note that there are many techniques we could use to call our API, but I find Postman to be the most practical for our purposes. This would probably be completed in the real world through a front-end application that seamlessly integrates with your company's meeting software.
On the Postman homepage, click on Workspaces and create a new workspace. Select HTTP when you see the below drop-down window.

You should now see a window that looks like the below image.

We are sending data with our API; hence, we must select the dropdown next to GET and change it to POST. To get our URL for the API, navigate back to the page we landed on after we created a dev-stage API. Copy and paste the Invoke URL for our dev stage and paste it into our Postman workspace next to POST. Screenshot below for reference on where to get our url:

In addition to copying and pasting our API, we also need to add our meeting-summary route we created earlier. Add /meeting-summary to the end of the URL in our Postman workspace. Screenshot below for reference:

Next, click on Body, then select form-data. Underneath Key, type document, and in the dropdown menu to the right of where you typed document, select file. This is where you would then upload your meeting transcript. For this project, I decided to have some fun and had Chat GPT generate a fake meeting transcript about an AI company whose Gen AI application became self-aware. Screenshots of what our Postman workspace should look like before hitting send and a brief preview of the transcript we are sending below!


Now it is time to click Send! If our application was successful, we should see the following response in Postman below:

A note on debugging our Lambda Function
When I initially built this application, my first run was unsuccessful. Yours will likely fail on the first try, so no worries if this is the case. I've yet to meet a developer or data scientist who built a successful model, product, application, etc., on the first test run.
If there is an issue, it will likely be revealed within our Lambda function. To debug this, navigate to AWS Cloudwatch, click Log groups, and click on our Lambda function. Navigate to the Log streams section, and it will show the response of any run of our Lambda function. This is where error handling comes in handy. Some issues I encountered on my first run included minor typos in my code and mismatched regions between the API, Lambda, and S3. If you have any issues, they may vary from mine; however, those may be good starting points to look out for. Screenshot below for reference:

Once you successfully run the application, you should have a txt file within your S3 bucket. Navigate to S3, select the bucket we initially created, and click through to the newly generated file. Select download and check out the output! Here is what mine generated for reference:

Impressive! Don't you think? In seconds, the application generated a summary and specific follow-up actions with key stakeholder assignments. I realize this was a fake meeting transcript, but imagine how much time could be saved at scale with an application such as this one.
Conclusion
I must give a massive shoutout to Patrik Szepesi and his Udemy course on generative AI in AWS, which inspired me to pursue this project and write this article. It has also inspired me to start working on some personal use cases, and I hope this article can encourage the reader to do the same! I hope you found this content valuable in your AI engineering journey. Please feel free to leave a comment if you have any questions or would like to send me feedback!
Full Lambda Function
If you enjoyed this story, please follow me here for more stories on Data Science, Python, Machine learning, Finance, A/B Testing & more!