-
Notifications
You must be signed in to change notification settings - Fork 33
Description
Use case
Bedrock Agents allows you to define action groups for your agents in two ways: OpenAPI schemas, and direct function integration. This issue focuses on the latter.
As a customer I can create Bedrock Agents that have tools at their disposal. These tools, or functions, can be defined as AWS Lambda functions. One Lambda function can hold one or more tools and when looked at together, they are what makes an action group.
When I build a Lambda function with multiple tools in it, I am responsible for parsing the payload sent by Bedrock and, based on certain fields, call the corresponding tool in my code (aka the tool use). The response of this tool use is then returned by my Lambda function handler according to a specific format that Bedrock expects.
This can result in some degree of boilerplate code that I have to repeat for each action group, specifically:
parsing/validating the incoming Bedrock Agent request payload
handling the event using the correct tool/function
building the response according to the response Bedrock Agent payload schema
More details aws-powertools/powertools-lambda-typescript#3710
Solution/User Experience
When paired with a Lambda function via action group, Bedrock sends and expects payloads of known shapes.
Payload Example Documentation
{
"messageVersion": "1.0",
"agent": {
"alias": "PROD",
"name": "hr-assistant-function-def",
"version": "1",
"id": "1234abcd-56ef-78gh-90ij-klmn12345678"
},
"sessionId": "87654321-abcd-efgh-ijkl-mnop12345678",
"sessionAttributes": {
"employeeId": "EMP123",
"department": "Engineering"
},
"promptSessionAttributes": {
"lastInteraction": "2024-02-01T15:30:00Z",
"requestType": "vacation"
},
"inputText": "I want to request vacation from March 15 to March 20",
"actionGroup": "VacationsActionGroup",
"function": "submitVacationRequest",
"parameters": [{
"employeeId": "EMP123",
"startDate": "2024-03-15",
"endDate": "2024-03-20",
"vacationType": "annual"
}]
}
Payload example Documentation
{
"response":{
"actionGroup":"SmarterAgentActionGroup",
"function":"submitVacationRequest",
"functionResponse":{
"responseBody":{
"TEXT":{
"body":"Your vacation was scheduled!"
}
}
}
},
"messageVersion":"1.0"
}
Current implementation
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.Lambda.Core;
using Newtonsoft.Json;
public class Function
{
public ActionGroupInvocationOutput FunctionHandler(ActionGroupInvocationInput input, ILambdaContext context)
{
string actionGroup = input.ActionGroupName;
string function = input.Function;
List<Parameter> parameters = input.Parameters ?? new List<Parameter>();
string result;
// Execute the appropriate function based on the 'function' parameter
switch (function)
{
case "get_current_time":
result = GetCurrentTime();
break;
case "greet_user":
string name = parameters.FirstOrDefault()?.Value ?? "User";
result = GreetUser(name);
break;
case "simple_calculator":
if (parameters.Count >= 3)
{
float a = float.Parse(parameters[0].Value);
float b = float.Parse(parameters[1].Value);
string operation = parameters[2].Value;
result = SimpleCalculator(a, b, operation);
}
else
{
result = "Error: Insufficient parameters for simple_calculator";
}
break;
default:
result = $"Unknown function: {function}";
break;
}
var responseBody = new
{
TEXT = new
{
body = result
}
};
var actionResponse = new
{
actionGroup = actionGroup,
function = function,
functionResponse = new
{
responseBody = responseBody
}
};
var functionResponse = new
{
response = actionResponse,
messageVersion = input.InvocationId // Assuming InvocationId corresponds to messageVersion
};
Console.WriteLine($"Response: {JsonConvert.SerializeObject(functionResponse)}");
return new ActionGroupInvocationOutput
{
Text = JsonConvert.SerializeObject(functionResponse)
};
}
private string GetCurrentTime()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
private string GreetUser(string name)
{
return $"Hello, {name}!";
}
private string SimpleCalculator(float a, float b, string operation)
{
switch (operation.ToLower())
{
case "add":
return (a + b).ToString();
case "subtract":
return (a - b).ToString();
case "multiply":
return (a * b).ToString();
case "divide":
return b != 0 ? (a / b).ToString() : "Error: Division by zero";
default:
return "Error: Unknown operation";
}
}
}
Since the input event includes both the function and parameters fields, we can abstract most/all the boilerplate and provide a more streamlined experience.
For example, borrowing heavily from the aws-powertools/powertools-lambda-typescript#3500 we concluded a few weeks ago, we could implement a BedrockAgentFunctionResolver resolver that provides a structured way to register functions, resolve function calls, and handle requests within Lambda.
Proposed solutions
public class Function
{
private readonly BedrockAgentFunctionResolver _resolver;
public Function()
{
_resolver = new BedrockAgentFunctionResolver();
// Register tool with automatic service injection
_resolver.Tool<string>(
name: "GetCustomForecast",
description: "Get detailed forecast for a location",
handler: (string location, int days, HttpClient client, MyCustomService service, ILambdaContext ctx) => {
ctx.Logger.LogLine($"Getting forecast for {location}");
return $"{days}-day forecast for {location}";
}
);
// Simple parameter handler
_resolver.Tool<string>(
name: "Greet",
description: "Greet a user",
handler: (string name) => $"Hello {name}"
);
}
public ActionGroupInvocationOutput FunctionHandler(ActionGroupInvocationInput input, ILambdaContext context)
{
return _resolver.Resolve(input, context);
}
Acknowledgment
- This feature request meets Powertools for AWS Lambda (.NET) Tenets
- Should this be considered in other Powertools for AWS Lambda languages? i.e. Python, Java, and TypeScript
Metadata
Metadata
Assignees
Labels
Type
Projects
Status