AWS Logo
Menu
Agentic Code Interpreter with AWS CDK

Agentic Code Interpreter with AWS CDK

In this article, we'll walk you through building a skeleton CDK L2 construct for Amazon Bedrock Agent for its code interpreter support.

Pahud Hsieh
Amazon Employee
Published Sep 4, 2024
Last Modified Sep 11, 2024
One of the most exciting news last month was this tweet from Adam Seligman:
And this inspiring video from Mike Chambers:
It was really great! But unfortunately it could not be deployed with AWS CDK and that really caught my interest. Why not build a CDK construct for the whole amazing stuff?

What customers want

Customers want something that when they do this, it just works! Well, probably with very minimal required properties but yes, the most important user experience is it just works!
This means:
When I cdk deploy I know it just works! Hassle-free!
If the Agent needs any IAM role and policies, I don't really care about it, just generate a new role with very scoped policies with least privileges.
Set a default instruction text for the Agent unless I provide specific instructions.
If the Agent needs to enable some "public preview" or new features which is not available in CloudFormation yet, well, I don't care about it. Just enable it with SDK call from a custom resource. I don't care about how to write a custom resource. It should just work. I don't care too much details behind it.
If it requires a Vpc, it should create a well-architected one for me unless I specify an existing one.
If it requires subnet IDs, it should select the private subnets across multi-AZ for me with each AZ has at least one subnet.
It should expose everything I need such as agentId , agentArn or even agentStatus.
I can new Agent() as many as I can and they won't conflict.
It should come with some helpful static methods like addXxx() or grantXxx(). They are not mandatory but would be very helpful.
When I cdk destroy, all created Agents and everything from there should be just eliminated. Nothing would incur on my AWS billing after that.
I can contribute it to the public AWS CDK library or Construct Hub as a public library.
Now, after watching this video, I figured out the general process to enable Amazon Bedrock Agent with CodeInterpreter support in this order as below:
  1. Create an Agent - no problem, we have bedrock.CfnAgent L1 construct.
  2. Create an IAM role with scoped policies and the least required privileges. This would take a while because I need to read the document. But hopefully I should just have to do it once.
  3. Create an ActionGroup that supports CodeInterpreter capabilities, which is currently in public preview. This would be a little challenging because CFN has not supported it yet. The ParentActionGroupSignature only supports AMAZON.UserInput at this moment. But that's fine, we have AwsCustomResource to the rescue.
  4. We need to wait for the Agent until it enters the NOT_PREPARED state before we can continue the next step as described in the video here. Well, this is a little bit challenging but let's see what's happening then.
  5. We have to "prepare" the Agent. Obviously, CloudFormation does not support that at this moment. The only option is AwsCustomResource. No big deal.
  6. Last but not least, we need to create an Alias for that Agent and we can retrieve the agentAliasId from there. The good news is that we have bedrock.CfnAgent L1 construct.
OK. So here is my framework off the top of my head. I literally told Amazon Q Developer my intention and it just generated the framework for me:
  • IAgent - This represents the "Agent". It could be a new one created with new Agent(); or an imported one by Agent.fromAgentAttributes(). We need to define what attributes or properties it should have. I checked the return values of AWS::Bedrock::Agent from Cloudformation document. I believe agentId, agentArn and agentVersion are mandatory and I decided to add agentStatus as optional.
  • AgentProps - This is the props when you new Agent(scope, id, props); We just need to figure out what are props are required from CFN's doc. Looking at all the properties from here, the good news is only AgentName is required. But we still can make it optional in CDK and generate a name when undefined so users can literally pass no props when they new Agent();. This would be amazing! I ended up with this interface that has an optional role only. If you pass an existing iam.IRole in as the ServiceRole, I would just pick up from there; otherwise, generate a scoped one for you.
  • Agent - Now we need to implement the constructor of this class. My preferred pattern is like - if you pass some prop, I just use it, otherwise I generate one for you with a private construct method. This makes the main logic very clean. It looks like:
If you need something to pass to L1, check if that's available from the props and use it if defined otherwise use the private construct method to create a default one for you. This pattern makes your constructor very clean and easy to read.
After you create the resource using the L1 construct, you need to assign the attributes that are defined in IAgent interface so we can reference the attributes like Arn or resource ID from there. In CDK , we use getResourceArnAttribute() to get the resource ARN. I ended up with all the assignments as below:
  • this.createServiceRole() - This should be simply new iam.Role() with bedrock as the service principal and simply addToPolicy() with relevant actions and resources. I ended up with this:
  • this.createCustomResourceRole() - This role is for AwsCustomResource that would create the action group and prepare agent behind the scene. This is not perfect and well-scoped because we use * in the resources. We could optimize it later.
  • this.loadInstruction() - We need a method to read in the instructions from an external file like instructions.txt. This method allows us to pass it to the L1 with instruction: this.loadInstruction() Amazon Q literally generated this function for me. Looks awesome!
  • this.createCodeInterpreterActionGroup() - As mentioned, cloudformation does not support codeinterpreter at this moment. This method essentially invokes a SDK call to create an ActionGroup wtih AMAZON.CodeInterpreter parentActionGroupSignature for us. Easy peasy!
Please note the addDependency() line, this custom resource could be provisioned before the default policy of the role is created and that could cause an error. The addDependency() ensures the custom resource would only be created after the default policy of the role is ready. This trick is very important.
  • this.prepareAgent() - Prepare the agent. Yet another custom resource. No fancy.
  • this.addAlias() - Last but not least, I decided to define a public addAlias method for it so the expected user experience would be like:
The method looks like:
That's it! Now my final code looks like this. Please note I have also modeled the ActionGroup with its interface IActionGroup but turned out not necessary in this use case. You can just ignore them for now.
OK. Now it's time to verify the "it-just-works" developer experience.
Let's check what would be created using cdk diff
$ npx cdk diff
This is great we are creating two custom resources but only one Lambda Singeleton Function and a shared IAM role for them. Awesome!
Let's deploy it!
$ npx cdk deploy
It deployed with all my required info in the Outputs. Awesome!
I will share more about how I validate it using a Dunkin Donuts real-life use case in the next blog post but I'd like to wrap it up with some key takeaways:

Key Take-aways

  1. Build your "It just works" developer experience with a skeleton L2 construct.
  2. A skeleton L2 construct is a MVP(Minimum Viable Product) of a CDK L2 construct that only supports mandatory props with very clear and clean definition that could be extended in future development iteration. It should be built with AI like Amazon Q Developer in your IDE and typically could be drafted within a couple of minutes.
  3. Define your private and public construct helper methods to separate them from your constructor code logic. This makes your code clean and easy to read.
  4. Use the props.foo ?? this.createFoo() pattern.
  5. Don't hesitate to use AwsCustomResource when necessary.
I hope you find this blog post useful and enjoy the journey building your own skeleton L2 constructs to help you onboard any new AWS services or features like this.
Enjoy building GenAI with CDK on AWS!
 

Further Reading:

 

Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.

Comments