Skip to content

Bedrock Agents

Create Amazon Bedrock Agents and focus on building your agent's logic without worrying about parsing and routing requests.

flowchart LR
    Bedrock[LLM] <-- uses --> Agent
    You[User input] --> Agent
    Agent[Bedrock Agent] <-- tool use --> Lambda

    subgraph Agent[Bedrock Agent]
        ToolDescriptions[Tool Definitions]
    end

    subgraph Lambda[Lambda Function]
        direction TB
        Parsing[Parameter Parsing] --> Routing
        Routing --> Code[Your code]
        Code --> ResponseBuilding[Response Building]
    end

    style You stroke:#0F0,stroke-width:2px

Key Features

  • Easily expose tools for your Large Language Model (LLM) agents
  • Automatic routing based on tool name and function details
  • Graceful error handling and response formatting

Terminology

Event handler is a Powertools for AWS feature that processes an event, runs data parsing and validation, routes the request to a specific function, and returns a response to the caller in the proper format.

Function details consist of a list of parameters, defined by their name, data type, and whether or not they are required. The agent uses these configurations to determine what information it needs to elicit from the user.

Action group is a collection of two resources where you define the actions that the agent should carry out: an OpenAPI schema to define the APIs that the agent can invoke to carry out its tasks, and a Lambda function to execute those actions.

Large Language Models (LLM) are very large deep learning models that are pre-trained on vast amounts of data, capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases within that text.

Amazon Bedrock Agent is an Amazon Bedrock feature to build and deploy conversational agents that can interact with your customers using Large Language Models (LLM) and AWS Lambda functions.

Getting Started

1
npm i @aws-lambda-powertools/event-handler

Required resources

You must create an Amazon Bedrock Agent with at least one action group. Each action group can contain up to 5 tools, which in turn need to match the ones defined in your Lambda function. Bedrock must have permission to invoke your Lambda function.

Click to see example IaC templates
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Timeout: 30
    MemorySize: 256
    Runtime: nodejs22.x

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      CodeUri: hello_world

  AirlineAgentRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${AWS::StackName}-AirlineAgentRole'
      Description: 'Role for Bedrock Airline agent'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: bedrock
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 'bedrock:*'
                Resource:
                  - !Sub 'arn:aws:bedrock:us-*::foundation-model/*'
                  - !Sub 'arn:aws:bedrock:us-*:*:inference-profile/*'

  BedrockAgentInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref HelloWorldFunction
      Action: lambda:InvokeFunction
      Principal: bedrock.amazonaws.com
      SourceAccount: !Ref 'AWS::AccountId'
      SourceArn: !Sub 'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${AirlineAgent}'

  # Bedrock Agent
  AirlineAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: AirlineAgent
      Description: 'A simple Airline agent'
      FoundationModel: !Sub 'arn:aws:bedrock:us-west-2:${AWS::AccountId}:inference-profile/us.amazon.nova-pro-v1:0'
      Instruction: |
        You are an airport traffic control agent. You will be given a city name and you will return the airport code for that city.
      AgentResourceRoleArn: !GetAtt AirlineAgentRole.Arn
      AutoPrepare: true
      ActionGroups:
        - ActionGroupName: AirlineActionGroup
          ActionGroupExecutor:
            Lambda: !GetAtt AirlineAgentFunction.Arn
          FunctionSchema:
            Functions:
              - Name: getAirportCodeForCity
                Description: 'Get the airport code for a given city'
                Parameters:
                  city:
                    Type: string
                    Description: 'The name of the city to get the airport code for'
                    Required: true
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { Arn, RemovalPolicy, Stack, type StackProps } from 'aws-cdk-lib';
import { CfnAgent } from 'aws-cdk-lib/aws-bedrock';
import {
  PolicyDocument,
  PolicyStatement,
  Role,
  ServicePrincipal,
} from 'aws-cdk-lib/aws-iam';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction, OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs';
import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
import type { Construct } from 'constructs';

export class BedrockAgentsStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const fnName = 'BedrockAgentsFn';
    const logGroup = new LogGroup(this, 'AirlineAgentLogGroup', {
      logGroupName: `/aws/lambda/${fnName}`,
      removalPolicy: RemovalPolicy.DESTROY,
      retention: RetentionDays.ONE_DAY,
    });
    const fn = new NodejsFunction(this, 'AirlineAgentFunction', {
      functionName: fnName,
      logGroup,
      runtime: Runtime.NODEJS_22_X,
      entry: './src/index.ts',
      handler: 'handler',
      bundling: {
        minify: true,
        mainFields: ['module', 'main'],
        sourceMap: true,
        format: OutputFormat.ESM,
      },
    });

    const agentRole = new Role(this, 'AirlineAgentRole', {
      assumedBy: new ServicePrincipal('bedrock.amazonaws.com'),
      description: 'Role for Bedrock Airline agent',
      inlinePolicies: {
        bedrock: new PolicyDocument({
          statements: [
            new PolicyStatement({
              actions: ['bedrock:*'],
              resources: [
                Arn.format(
                  {
                    service: 'bedrock',
                    resource: 'foundation-model/*',
                    region: 'us-*',
                    account: '',
                  },
                  Stack.of(this)
                ),
                Arn.format(
                  {
                    service: 'bedrock',
                    resource: 'inference-profile/*',
                    region: 'us-*',
                    account: '*',
                  },
                  Stack.of(this)
                ),
              ],
            }),
          ],
        }),
      },
    });

    const agent = new CfnAgent(this, 'AirlineAgent', {
      agentName: 'AirlineAgent',
      actionGroups: [
        {
          actionGroupName: 'AirlineActionGroup',
          actionGroupExecutor: {
            lambda: fn.functionArn,
          },
          functionSchema: {
            functions: [
              {
                name: 'getAirportCodeForCity',
                description: 'Get the airport code for a given city',
                parameters: {
                  city: {
                    type: 'string',
                    description:
                      'The name of the city to get the airport code for',
                    required: true,
                  },
                },
              },
            ],
          },
        },
      ],
      agentResourceRoleArn: agentRole.roleArn,
      autoPrepare: true,
      description: 'A simple Airline agent',
      foundationModel: `arn:aws:bedrock:us-west-2:${Stack.of(this).account}:inference-profile/us.amazon.nova-pro-v1:0`,
      instruction:
        'You are an airport traffic control agent. You will be given a city name and you will return the airport code for that city.',
    });
    fn.addPermission('BedrockAgentInvokePermission', {
      principal: new ServicePrincipal('bedrock.amazonaws.com'),
      action: 'lambda:InvokeFunction',
      sourceAccount: this.account,
      sourceArn: `arn:aws:bedrock:${this.region}:${this.account}:agent/${agent.attrAgentId}`,
    });
  }
}

Usage

Use the BedrockAgentFunctionResolver to register your tools and handle the requests to your Lambda function. The resolver will automatically parse the request, route it to the appropriate function, and return a well-formed response that includes the tool's output and any existing session attributes.

When passing the tool parameters to your handler, we will automatically cast them to the appropriate type based on the type field defined in the action group. This means you can use native JavaScript types like string, number, boolean without worrying about parsing them yourself.

Currently, we don't support parsing array types, so you will receive them as strings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent';
import type { Context } from 'aws-lambda';

const app = new BedrockAgentFunctionResolver();

app.tool<{ city: string }>(
  async ({ city }) => {
    return {
      city,
      airportCode: 'XYZ',
    };
  },
  {
    name: 'getAirportCodeForCity',
    description: 'Get the airport code for a given city', // (1)!
  }
);

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);
  1. The description field is optional, but highly recommended in the action group definition so that the LLM can understand what the tool does and how to use it.

Advanced

Handling errors

By default, we will handle errors gracefully and return a well-formed response to the agent so that it can continue the conversation with the user.

When an error occurs, we send back an error message in the response body that includes the error type and message. The agent will then use this information to let the user know that something went wrong.

If you want to handle errors differently, you can return a BedrockFunctionResponse with a custom body and responseState set to FAILURE. This is useful when you want to abort the conversation.

Tip

You can use the same technique to reprompt the user for missing information or for them to correct their input. Just return a BedrockFunctionResponse with a custom message and responseState set to REPROMPT.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import {
  BedrockAgentFunctionResolver,
  BedrockFunctionResponse,
} from '@aws-lambda-powertools/event-handler/bedrock-agent';
import type { Context } from 'aws-lambda';

const app = new BedrockAgentFunctionResolver();

app.tool<{ city: string }>(
  async ({ city }, { event }) => {
    try {
      throw new Error('Simulated error for demonstration purposes');
    } catch (error) {
      const {
        sessionAttributes,
        promptSessionAttributes,
        knowledgeBasesConfiguration,
      } = event;
      return new BedrockFunctionResponse({
        body: `An error occurred while fetching the airport code for ${city}`,
        responseState: 'FAILURE',
        sessionAttributes,
        promptSessionAttributes,
        knowledgeBasesConfiguration,
      });
    }
  },
  {
    name: 'getAirportCodeForCity',
    description: 'Get the airport code for a given city',
  }
);

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);

Accessing Lambda context and event

You can access to the original Lambda event or context for additional information. These are passed to the handler function as optional arguments.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent';
import type { Context } from 'aws-lambda';

const app = new BedrockAgentFunctionResolver();

app.tool<{ city: string }>(
  async ({ city }, { event, context }) => {
    const { sessionAttributes } = event;
    sessionAttributes.requestId = context.awsRequestId;

    return {
      city,
      airportCode: 'XYZ', // Simulated airport code for the city
    };
  },
  {
    name: 'getAirportCodeForCity',
    description: 'Get the airport code for a given city',
  }
);

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);

Setting session attributes

When Bedrock Agents invoke your Lambda function, it can pass session attributes that you can use to store information across multiple interactions with the user. You can access these attributes in your handler function and modify them as needed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import {
  BedrockAgentFunctionResolver,
  BedrockFunctionResponse,
} from '@aws-lambda-powertools/event-handler/bedrock-agent';
import type { Context } from 'aws-lambda';

const app = new BedrockAgentFunctionResolver();

app.tool<{ city: string }>(
  async ({ city }, { event }) => {
    const {
      sessionAttributes,
      promptSessionAttributes,
      knowledgeBasesConfiguration,
    } = event;

    // your logic to fetch airport code for the city

    return new BedrockFunctionResponse({
      body: JSON.stringify({
        city,
        airportCode: 'XYZ',
      }),
      sessionAttributes: {
        ...sessionAttributes,
        isCommercialAirport: true,
      },
      promptSessionAttributes,
      knowledgeBasesConfiguration,
    });
  },
  {
    name: 'getAirportCodeForCity',
    description: 'Get the airport code for a given city',
  }
);

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);

Logging

By default, the BedrockAgentFunctionResolver uses the global console logger and emits only warnings and errors.

You can change this behavior by passing a custom logger instance to the BedrockAgentFunctionResolver constructor and setting its log level. Alternatively, you can also enable Lambda Advanced Logging Controls and setting the log level to DEBUG.

When debug logging is enabled, the resolver will emit logs that show the underlying handler registration and the routing process. This is useful for understanding how the agent resolves the tools and routes the requests.

For example, when using the Powertools for AWS Lambda logger, you can set the LOG_LEVEL to DEBUG in your environment variables or at the logger level and pass the logger instance to the BedrockAgentFunctionResolver constructor to enable debug logging.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';

const logger = new Logger({
  serviceName: 'serverlessAirline',
  logLevel: 'DEBUG',
});
const app = new BedrockAgentFunctionResolver({ logger });

app.tool<{ city: string }>(
  async ({ city }) => {
    return {
      city,
      airportCode: 'XYZ', // Simulated airport code for the city
    };
  },
  {
    name: 'getAirportCodeForCity',
    description: 'Get the airport code for a given city',
  }
);

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);