Generative (A)IaC in the IDE with Application Composer
Published: 2024-01-18 19:14:47

_TL;DR: New tutorial dropped using App Composer + gen AI! Get the [tutorial repo](https://github.com/bildungsroman/lex-lambda-bedrock-app-composer) or read on to follow along!_

[AWS Application Composer](https://aws.amazon.com/application-composer/) launched in the AWS Console at re:Invent one year ago, and this re:Invent it expanded to the VS Code IDE as part of the [AWS Toolkit](https://aws.amazon.com/visualstudiocode/) - but that’s not the only exciting part. When using App Composer in the IDE, users also get access to a generative AI partner that will help them write infrastructure as code (IaC) for all 1100+ [AWS CloudFormation](https://aws.amazon.com/cloudformation/) resources that Application Composer now [supports](https://aws.amazon.com/about-aws/whats-new/2023/09/aws-application-composer-1000-cloudformation-resources/).

## Better than docs

Application Composer lets users create IaC templates by dragging and dropping cards on a virtual canvas that represent AWS CloudFormation resources, and wiring them together to create permissions and references. Initially launching with support for 13 core serverless resources such as AWS Lambda, Amazon S3, and DynamoDB, in September our team [launched support](https://twitter.com/annaspies/status/1706790378358374469) for all 1100+ resources that AWS CloudFormation allows, updated regularly. That means that users can now build with everything from Amplify to XRay, and hundreds of resources in between.

However, while the 13 enhanced resources come with defaults based on best practices, standard AWS CloudFormation resources come only with basic configuration - generally required settings only, with a type set by a schema rather than an example. So a user adding an Amplify App resource would be given the following configuration by default:

```
  MyAmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: <String>
```

And they would see this in the console:

![Amplify resource in App Composer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u2bx19n9mwyfcedooxk8.png)

That’s well and good for the AWS CloudFormation expert, but someone new to IaC or even just that resource would then have to go into the CloudFormation docs to find additional properties and determine their types, or scour the web for example configurations that may or may not fit their use case. Luckily, the newest App Composer feature saves builders those steps - and uses generative AI to generate resource-specific configurations with safeguards right in the IDE.

When working on a CloudFormation or SAM template in VS Code, users can sign in with their Builder ID and generate multiple suggested configurations in App Composer - here’s an example for our `AWS::Amplify::App` type:

{% youtube -auVKK8UsHU %}

These suggestions are specific to the resource type, and are safeguarded by a check against the CloudFormation schema to ensure valid values or helpful placeholders. You can then select, use and modify the suggestions to fit your needs.

So we now know how to generate a simple example with one resource, but let’s look at building a full application with the help of AI-generated suggestions.

## Building together with AI

You may be aware of [Serverlessland](https://serverlessland.com/), a treasure trove of developer-centered content and examples of serverless applications. I decided to take one of their more popular (and AI-focused) tutorials, titled “[Use GenAI capabilities to build a chatbot](https://serverlessland.com/repos/lex-lambda-bedrock-cdk-python)”, and recreate it with App Composer and our trusty AI assistant. Here we go!

###  Getting started with the AWS Toolkit in VS Code

First of all, if you don’t yet have the [AWS Toolkit](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-toolkit-vscode) extension, you can find it under the Extensions tab in VS Code. Install or update it so you’re at least on version 2.1.0, and you’ll see a screen like this that mentions Amazon Q and Application Composer:

![AWS Toolkit in VS Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c90lb8unb15ihlpx7sky.png)

Next, to enable our AI assistant, we need to enable CodeWhisperer using our Builder ID. The easiest way is to open Q, and select authenticate - you should be taken to this screen, where you can select the Builder ID option and be taken to the browser to create or sign in with your Builder ID.

![Sign in with Builder ID](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5l9auj1gxc19gg5w7lb.png)

If all goes well, you’ll see your connection in the VS Code toolkit panel:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/825xsqjgf0ve0fdz77z9.png)

Once you’re connected, you’re ready to start building! 

### Composing your architecture

In a workspace, create a new folder and a blank file called `template.yaml`. Open `template.yaml`, and you should see the Application Composer icon in the top right. Click on it to initialize App Composer with a blank canvas. Now we can start building our application.

![Click the App Composer icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ycuhfd963cdko507ycw3.png)

You may have noticed that the tutorial includes an architecture diagram that looks like this:

![Original architecture diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q8txxmnnmochc1gl68qr.png)

First we’re going to add the services in the diagram to sketch out our application architecture - with the added bonus of creating a deployable CloudFormation template.

1. From the `Enhanced components` list, drag in a Lambda function and a Lambda layer.

2. Double-click the Function resource to edit its properties. Rename the Lambda function’s Logical ID to `LexGenAIBotLambda`.
Change the Source path to `src/LexGenAIBotLambda`, and the runtime to Python.

3. Change the handler value to `TextGeneration.lambda_handler`, and click Save.

4. Double-click the Layer resource to edit its properties.

5. Rename the layer `Boto3Layer` and change its build method to Python. Change its Source path to `src/Boto3PillowPyshorteners.zip`, and click Save.

6. Finally, connect the layer to the function to add a reference between them.

You can follow along with the video to see this in action:

{% youtube b6pEnn1ebg0 %}

You’ll see that your `template.yaml` file has now been updated to include those resources (and some auto-generated ones, such as a Lambda log group for your function). If you go into your source directory, you’ll see some generated function files. Don’t worry about those - we’ll replace them with the tutorial function and layers later.

So that was the easy part - you added some resources and generated IaC that includes best-practices defaults. Next we'll explore using standard CloudFormation components.

### With a little help from our (AI) friend

Let’s start by searching for and adding several of the `Standard components` needed for our application. These include the types `AWS::Lambda::Permission`, two roles of type `AWS::IAM::Role`, and one `AWS::IAM::Policy`. Some standard resources will have all the defaults you need. For example, when you add the `AWS::Lambda::Permission` resource, all you have to do is replace the placeholder values.

Other resources, such as the IAM Roles and IAM Policy, have bare-bones config. This is where our AI assistant comes in handy! Choose an IAM Role resource and click `Generate suggestions`.

![Generate suggestions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g6d5l4mi99geqjrgv5c9.png)

Because these suggestions are generated by an LLM, they will likely differ between each generation, but they are tailored to be specific to each resource and to follow valid CloudFormation schema - so go ahead, generate away!

{% youtube rsU7FhAa2gM %}

Generating different configurations will give you an idea of what a resource’s policy _should_ look like, and will often give you keys that you can then fill in with the values you need. Below are the actual settings we’ll be using for each resource, so you can replace the generated values when applicable - be sure to replace each resource’s Logical ID as well!

```yaml
# CfnLexGenAIDemoRole - type AWS::IAM::Role
AssumeRolePolicyDocument:
  Statement:
    - Action: sts:AssumeRole
      Effect: Allow
      Principal:
        Service: lexv2.amazonaws.com
  Version: '2012-10-17'
ManagedPolicyArns:
  - !Join
    - ''
    - - 'arn:'
      - !Ref AWS::Partition
      - ':iam::aws:policy/AWSLambdaExecute'
      
# LexGenAIBotLambdaServiceRole - type AWS::IAM::Role
AssumeRolePolicyDocument:
  Statement:
    - Action: sts:AssumeRole
      Effect: Allow
      Principal:
        Service: lambda.amazonaws.com
  Version: '2012-10-17'
ManagedPolicyArns:
  - !Join
    - ''
    - - 'arn:'
      - !Ref AWS::Partition
      - ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'

# LexGenAIBotLambdaServiceRoleDefaultPolicy - type AWS::IAM::Policy
PolicyDocument:
  Statement:
    - Action:
        - lex:*
        - logs:*
        - s3:DeleteObject
        - s3:GetObject
        - s3:ListBucket
        - s3:PutObject
      Effect: Allow
      Resource: '*'
    - Action: bedrock:InvokeModel
      Effect: Allow
      Resource: !Join
        - ''
        - - 'arn:aws:bedrock:'
          - !Ref AWS::Region
          - '::foundation-model/anthropic.claude-v2'
  Version: '2012-10-17'
PolicyName: LexGenAIBotLambdaServiceRoleDefaultPolicy
Roles:
  - !Ref LexGenAIBotLambdaServiceRole
  
# LexGenAIBotLambdaInvoke - type AWS::Lambda::Permission
Action: lambda:InvokeFunction
FunctionName: !GetAtt LexGenAIBotLambda.Arn
Principal: lexv2.amazonaws.com
```

Finally, let’s add our Lex bot! In the resource picker, search for and add type `AWS::Lex::Bot`. Here’s another chance to see what configuration the AI comes up with!

{% youtube OGME7y0wNt8 %}

Change the Lex bot’s Logical ID to `LexGenAIBot` and update its configuration to the following:

```yaml
DataPrivacy:
  ChildDirected: false
IdleSessionTTLInSeconds: 300
Name: LexGenAIBot
RoleArn: !GetAtt CfnLexGenAIDemoRole.Arn
AutoBuildBotLocales: true
BotLocales:
  - Intents:
      - InitialResponseSetting:
          CodeHook:
            EnableCodeHookInvocation: true
            IsActive: true
            PostCodeHookSpecification: {}
        IntentClosingSetting:
          ClosingResponse:
            MessageGroupsList:
              - Message:
                  PlainTextMessage:
                    Value: Hi there, I'm a GenAI Bot. How can I help you?
        Name: WelcomeIntent
        SampleUtterances:
          - Utterance: Hi
          - Utterance: Hey there
          - Utterance: Hello
          - Utterance: I need some help
          - Utterance: Help needed
          - Utterance: Can I get some help?
      - FulfillmentCodeHook:
          Enabled: true
          IsActive: true
          PostFulfillmentStatusSpecification: {}
        InitialResponseSetting:
          CodeHook:
            EnableCodeHookInvocation: true
            IsActive: true
            PostCodeHookSpecification: {}
        Name: GenerateTextIntent
        SampleUtterances:
          - Utterance: Generate content for
          - Utterance: 'Create text '
          - Utterance: 'Create a response for '
          - Utterance: Text to be generated for
      - FulfillmentCodeHook:
          Enabled: true
          IsActive: true
          PostFulfillmentStatusSpecification: {}
        InitialResponseSetting:
          CodeHook:
            EnableCodeHookInvocation: true
            IsActive: true
            PostCodeHookSpecification: {}
        Name: FallbackIntent
        ParentIntentSignature: AMAZON.FallbackIntent
    LocaleId: en_US
    NluConfidenceThreshold: 0.4
Description: Bot created demonstration of GenAI capabilities.
TestBotAliasSettings:
  BotAliasLocaleSettings:
    - BotAliasLocaleSetting:
        CodeHookSpecification:
          LambdaCodeHook:
            CodeHookInterfaceVersion: '1.0'
            LambdaArn: !GetAtt LexGenAIBotLambda.Arn
        Enabled: true
      LocaleId: en_US
```

Once all of your resources are configured, your application should look like this:

![Final architecture in App Composer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5c96cjzinky4isurnip.png)

In case your template looks different or you want to double-check your configuration, you can copy the template directly from my [Github repository](https://github.com/bildungsroman/lex-lambda-bedrock-app-composer/blob/main/template.yaml). You’ll want to copy the [Lambda layer](https://github.com/bildungsroman/lex-lambda-bedrock-app-composer/blob/main/src/Boto3PillowPyshorteners.zip) directly from the repository, and add it to `./src/Boto3PillowPyshorteners.zip`. Finally, rename the generated `handler.py` to `TextGeneration.py` and replace the placeholder code with the following:

```python
import json
import boto3
import os
import logging
from botocore.exceptions import ClientError

LOG = logging.getLogger()
LOG.setLevel(logging.INFO)

region_name = os.getenv("region", "us-east-1")
s3_bucket = os.getenv("bucket")
model_id = os.getenv("model_id", "anthropic.claude-v2")

# Bedrock client used to interact with APIs around models
bedrock = boto3.client(service_name="bedrock", region_name=region_name)

# Bedrock Runtime client used to invoke and question the models
bedrock_runtime = boto3.client(service_name="bedrock-runtime", region_name=region_name)


def get_session_attributes(intent_request):
    session_state = intent_request["sessionState"]
    if "sessionAttributes" in session_state:
        return session_state["sessionAttributes"]

    return {}


def close(intent_request, session_attributes, fulfillment_state, message):
    intent_request["sessionState"]["intent"]["state"] = fulfillment_state
    return {
        "sessionState": {
            "sessionAttributes": session_attributes,
            "dialogAction": {"type": "Close"},
            "intent": intent_request["sessionState"]["intent"],
        },
        "messages": [message],
        "sessionId": intent_request["sessionId"],
        "requestAttributes": intent_request["requestAttributes"]
        if "requestAttributes" in intent_request
        else None,
    }


def lambda_handler(event, context):
    LOG.info(f"Event is {event}")
    accept = "application/json"
    content_type = "application/json"
    prompt = event["inputTranscript"]

    try:
        request = json.dumps(
            {
                "prompt": "\n\nHuman:" + prompt + "\n\nAssistant:",
                "max_tokens_to_sample": 4096,
                "temperature": 0.5,
                "top_k": 250,
                "top_p": 1,
                "stop_sequences": ["\\n\\nHuman:"],
            }
        )

        response = bedrock_runtime.invoke_model(
            body=request,
            modelId=model_id,
            accept=accept,
            contentType=content_type,
        )

        response_body = json.loads(response.get("body").read())
        LOG.info(f"Response body: {response_body}")
        response_message = {
            "contentType": "PlainText",
            "content": response_body["completion"],
        }
        session_attributes = get_session_attributes(event)
        fulfillment_state = "Fulfilled"

        return close(event, session_attributes, fulfillment_state, response_message)

    except ClientError as e:
        LOG.error(f"Exception raised while execution and the error is {e}")
```

To deploy your infrastructure, go back to the App Composer extension, and click the `Sync` icon. You'll need to have [local IAM credentials](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/prerequisites.html#prerequisites-configure-credentials) and the [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) installed, so grab it if you don't already have it. Follow the guided AWS SAM instructions to initiate and complete your deployment.

![Click Sync to deploy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cbvyacei4esp2jdfpr15.png)

If all goes well, you’ll see the message `SAM Sync succeeded` and can navigate to CloudFormation in the AWS Console to see your newly-created resources.

{% youtube u6R6_IEWdws %}

If you want to continue with building your chatbot, be sure to follow the rest of the original [tutorial](https://github.com/awsarippa/lex-lambda-bedrock-cdk-python#usage) to keep building.

## Go build - with help!

I hope that building with AI-generated CloudFormation will help increase your understanding of the different resource settings that are available and commonly used, and accelerate your time from idea to deployed application. As with everything using generative AI today, be sure to read the [AWS Responsible AI Policy](https://aws.amazon.com/machine-learning/responsible-ai/policy/) before applying these examples yourself. Happy building!

_A [version](https://aws.amazon.com/blogs/compute/using-generative-infrastructure-as-code-with-application-composer/) of this post was originally published over at the [AWS Compute Blog](https://aws.amazon.com/blogs/compute/) - give them a follow to keep up with all things AWS!_