The Bedrock Agent Function Resolver is a utility for AWS Lambda that simplifies building serverless applications working with Amazon Bedrock Agents. This library eliminates boilerplate code typically required when implementing Lambda functions that serve as action groups for Bedrock Agents.
Amazon Bedrock Agents can invoke functions to perform tasks based on user input. This library provides an elegant way to register, manage, and execute these functions with minimal code, handling all the parameter extraction and response formatting automatically.
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
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 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 on it.
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.
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.
AWSTemplateFormatVersion:'2010-09-09'Transform:AWS::Serverless-2016-10-31Globals:Function:Timeout:30MemorySize:256Runtime:dotnet8Resources:HelloWorldFunction:Type:AWS::Serverless::FunctionProperties:Handler:FunctionHandlerCodeUri:hello_worldAirlineAgentRole:Type:AWS::IAM::RoleProperties:RoleName:!Sub'${AWS::StackName}-AirlineAgentRole'Description:'RoleforBedrockAirlineagent'AssumeRolePolicyDocument:Version:'2012-10-17'Statement:-Effect:AllowPrincipal:Service:bedrock.amazonaws.comAction:sts:AssumeRolePolicies:-PolicyName:bedrockPolicyDocument:Version:'2012-10-17'Statement:-Effect:AllowAction:'bedrock:*'Resource:-!Sub'arn:aws:bedrock:us-*::foundation-model/*'-!Sub'arn:aws:bedrock:us-*:*:inference-profile/*'BedrockAgentInvokePermission:Type:AWS::Lambda::PermissionProperties:FunctionName:!RefHelloWorldFunctionAction:lambda:InvokeFunctionPrincipal:bedrock.amazonaws.comSourceAccount:!Ref'AWS::AccountId'SourceArn:!Sub'arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${AirlineAgent}'# Bedrock AgentAirlineAgent:Type:AWS::Bedrock::AgentProperties:AgentName:AirlineAgentDescription:'AsimpleAirlineagent'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:!GetAttAirlineAgentRole.ArnAutoPrepare:trueActionGroups:-ActionGroupName:AirlineActionGroupActionGroupExecutor:Lambda:!GetAttAirlineAgentFunction.ArnFunctionSchema:Functions:-Name:getAirportCodeForCityDescription:'Gettheairportcodeforagivencity'Parameters:city:Type:stringDescription:'Thenameofthecitytogettheairportcodefor'Required:true
To create an agent, use the BedrockAgentFunctionResolver to register your tools and handle the requests. 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.
1 2 3 4 5 6 7 8 9101112131415161718192021222324
usingAmazon.Lambda.Core;usingAmazon.Lambda.RuntimeSupport;usingAWS.Lambda.Powertools.EventHandler.Resolvers;usingAWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models;varresolver=newBedrockAgentFunctionResolver();resolver.Tool("GetWeather",(stringcity)=>$"The weather in {city} is sunny").Tool("CalculateSum",(inta,intb)=>$"The sum of {a} and {b} is {a + b}").Tool("GetCurrentTime",()=>$"The current time is {DateTime.Now}");// The function handler that will be called for each Lambda eventvarhandler=async(BedrockFunctionRequestinput,ILambdaContextcontext)=>{returnawaitresolver.ResolveAsync(input,context);};// Build the Lambda runtime client passing in the handler to call for each// event and the JSON serializer to use for translating Lambda JSON documents// to .NET types.awaitLambdaBootstrapBuilder.Create(handler,newDefaultLambdaJsonSerializer()).Build().RunAsync();
usingAWS.Lambda.Powertools.EventHandler.Resolvers;usingAWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models;usingAmazon.Lambda.Core;[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]namespaceMyLambdaFunction{publicclassFunction{privatereadonlyBedrockAgentFunctionResolver_resolver;publicFunction(){_resolver=newBedrockAgentFunctionResolver();// Register simple tool functions_resolver.Tool("GetWeather",(stringcity)=>$"The weather in {city} is sunny").Tool("CalculateSum",(inta,intb)=>$"The sum of {a} and {b} is {a + b}").Tool("GetCurrentTime",()=>$"The current time is {DateTime.Now}");}// Lambda handler functionpublicBedrockFunctionResponseFunctionHandler(BedrockFunctionRequestinput,ILambdaContextcontext){return_resolver.Resolve(input,context);}}}
When the Bedrock Agent invokes your Lambda function with a request to use the "GetWeather" tool and a parameter for "city", the resolver automatically extracts the parameter, passes it to your function, and formats the response.
You can return any type from your tool function, the library will automatically format the response in a way that Bedrock Agents expect.
The response will include:
The action group name
The function name
The function response body, which can be a text response or other structured data in string format
Any session attributes that were passed in the request or modified during the function execution
The response body will always be a string.
If you want to return an object the best practice is to override the ToString() method of your return type to provide a custom string representation, or if you don't override, create an anonymous object return new {} and pass your object, or simply return a string directly.
1 2 3 4 5 6 7 8 9101112131415161718192021222324
publicclassAirportInfo{publicstringCity{get;set;}=string.Empty;publicstringCode{get;set;}=string.Empty;publicstringName{get;set;}=string.Empty;publicoverridestringToString(){return$"{Name} ({Code}) in {City}";}}resolver.Tool("getAirportCodeForCity","Get airport code and full name for a specific city",(stringcity,ILambdaContextcontext)=>{varairportService=newAirportService();varairportInfo=airportService.GetAirportInfoForCity(city);// Note: Best approach is to override the ToString method in the AirportInfo classreturnairportInfo;});//Alternatively, you can return an anonymous object if you dont override ToString()// return new {// airportInfo// };
You can have your own custom types as arguments to the tool function. The library will automatically handle serialization and deserialization of these types. In this case, you need to ensure that your custom type is serializable to JSON, if serialization fails, the object will be null.
123456789
resolver.Tool(name:"PriceCalculator",description:"Calculate total price with tax",handler:(MyCustomTypemyCustomType)=>{varwithTax=myCustomType.Price*1.2m;return$"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}";});
For native AOT compilation, you can use JsonSerializerContext and pass it to BedrockAgentFunctionResolver. This allows the library to generate the necessary serialization code at compile time, ensuring compatibility with AOT.
1 2 3 4 5 6 7 8 9101112131415
varresolver=newBedrockAgentFunctionResolver(MycustomSerializationContext.Default);resolver.Tool(name:"PriceCalculator",description:"Calculate total price with tax",handler:(MyCustomTypemyCustomType)=>{varwithTax=myCustomType.Price*1.2m;return$"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}";});[JsonSerializable(typeof(MyCustomType))]publicpartialclassMycustomSerializationContext:JsonSerializerContext{}
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.
1 2 3 4 5 6 7 8 91011121314151617181920212223
resolver.Tool("CustomFailure",()=>{// Return a custom FAILURE responsereturnnewBedrockFunctionResponse{Response=newResponse{ActionGroup="TestGroup",Function="CustomFailure",FunctionResponse=newFunctionResponse{ResponseBody=newResponseBody{Text=newTextBody{Body="Critical error occurred: Database unavailable"}},ResponseState=ResponseState.FAILURE// Mark as FAILURE to abort the conversation}}};});
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.
// Create a counter tool that reads and updates session attributesresolver.Tool("CounterTool",(BedrockFunctionRequestrequest)=>{// Read the current count from session attributesintcurrentCount=0;if(request.SessionAttributes!=null&&request.SessionAttributes.TryGetValue("counter",outvarcountStr)&&int.TryParse(countStr,outvarcount)){currentCount=count;}// Increment the countercurrentCount++;// Create a new dictionary with updated countervarupdatedSessionAttributes=newDictionary<string,string>(request.SessionAttributes??newDictionary<string,string>()){["counter"]=currentCount.ToString(),["lastAccessed"]=DateTime.UtcNow.ToString("o")};// Return response with updated session attributesreturnnewBedrockFunctionResponse{Response=newResponse{ActionGroup=request.ActionGroup,Function=request.Function,FunctionResponse=newFunctionResponse{ResponseBody=newResponseBody{Text=newTextBody{Body=$"Current count: {currentCount}"}}}},SessionAttributes=updatedSessionAttributes,PromptSessionAttributes=request.PromptSessionAttributes};});
_resolver.Tool("FetchUserData","Fetches user data from external API",async(stringuserId,ILambdaContextctx)=>{// Log the requestctx.Logger.LogLine($"Fetching data for user {userId}");// Simulate API callawaitTask.Delay(100);// Return user informationreturnnew{Id=userId,Name="John Doe",Status="Active"}.ToString();});
_resolver.Tool("ProcessRawRequest","Processes the raw Bedrock Agent request",(BedrockFunctionRequestinput)=>{varfunctionName=input.Function;varparameterCount=input.Parameters.Count;return$"Received request for {functionName} with {parameterCount} parameters";});
The library supports dependency injection for integrating with services:
1 2 3 4 5 6 7 8 910111213141516171819
usingMicrosoft.Extensions.DependencyInjection;// Set up dependency injectionvarservices=newServiceCollection();services.AddSingleton<IWeatherService,WeatherService>();services.AddBedrockResolver();// Extension method to register the resolvervarserviceProvider=services.BuildServiceProvider();varresolver=serviceProvider.GetRequiredService<BedrockAgentFunctionResolver>();// Register a tool that uses an injected serviceresolver.Tool("GetWeatherForecast","Gets the weather forecast for a location",(stringcity,IWeatherServiceweatherService,ILambdaContextctx)=>{ctx.Logger.LogLine($"Getting weather for {city}");returnweatherService.GetForecast(city);});
You can define Bedrock Agent functions using attributes instead of explicit registration. This approach provides a clean, declarative way to organize your tools into classes:
// Define your tool class with BedrockFunctionType attribute[BedrockFunctionType]publicclassWeatherTools{// Each method marked with BedrockFunctionTool attribute becomes a tool[BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast for a location")]publicstaticstringGetWeather(stringcity,intdays){return$"Weather forecast for {city} for the next {days} days: Sunny";}// Supports dependency injection and Lambda context access[BedrockFunctionTool(Name = "GetDetailedForecast", Description = "Gets detailed weather forecast")]publicstaticstringGetDetailedForecast(stringlocation,IWeatherServiceweatherService,ILambdaContextcontext){context.Logger.LogLine($"Getting forecast for {location}");returnweatherService.GetForecast(location);}}
Using the extension method provided in the library, you can easily register all tools from a class:
1234567
varservices=newServiceCollection();services.AddSingleton<IWeatherService,WeatherService>();services.AddBedrockResolver();// Extension method to register the resolvervarserviceProvider=services.BuildServiceProvider();varresolver=serviceProvider.GetRequiredService<BedrockAgentFunctionResolver>().RegisterTool<WeatherTools>();// Register tools from the class during service registration
usingAmazon.BedrockAgentRuntime.Model;usingAmazon.Lambda.Core;usingAWS.Lambda.Powertools.EventHandler;usingMicrosoft.Extensions.DependencyInjection;[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]namespaceMyBedrockAgent{// Service interfaces and implementationspublicinterfaceIWeatherService{stringGetForecast(stringcity);}publicclassWeatherService:IWeatherService{publicstringGetForecast(stringcity)=>$"Weather forecast for {city}: Sunny, 75°F";}publicinterfaceIProductService{stringCheckInventory(stringproductId);}publicclassProductService:IProductService{publicstringCheckInventory(stringproductId)=>$"Product {productId} has 25 units in stock";}// Main Lambda functionpublicclassFunction{privatereadonlyBedrockAgentFunctionResolver_resolver;publicFunction(){// Set up dependency injectionvarservices=newServiceCollection();services.AddSingleton<IWeatherService,WeatherService>();services.AddSingleton<IProductService,ProductService>();services.AddBedrockResolver();// Extension method to register the resolvervarserviceProvider=services.BuildServiceProvider();_resolver=serviceProvider.GetRequiredService<BedrockAgentFunctionResolver>();// Register tool functions that use injected services_resolver.Tool("GetWeatherForecast","Gets weather forecast for a city",(stringcity,IWeatherServiceweatherService,ILambdaContextctx)=>{ctx.Logger.LogLine($"Weather request for {city}");returnweatherService.GetForecast(city);}).Tool("CheckInventory","Checks inventory for a product",(stringproductId,IProductServiceproductService)=>productService.CheckInventory(productId)).Tool("GetServerTime","Returns the current server time",()=>DateTime.Now.ToString("F"));}publicActionGroupInvocationOutputFunctionHandler(ActionGroupInvocationInputinput,ILambdaContextcontext){return_resolver.Resolve(input,context);}}}