1- import { EnvironmentVariablesService } from '@aws-lambda-powertools/commons' ;
1+ import { isNullOrUndefined } from '@aws-lambda-powertools/commons/typeutils' ;
2+ import { getStringFromEnv } from '@aws-lambda-powertools/commons/utils/env' ;
23import type { Context } from 'aws-lambda' ;
34import type {
45 BedrockAgentFunctionResponse ,
56 Configuration ,
67 ParameterValue ,
78 ResolverOptions ,
8- ResponseOptions ,
99 Tool ,
1010 ToolFunction ,
1111} from '../types/bedrock-agent.js' ;
1212import type { GenericLogger } from '../types/common.js' ;
13+ import { BedrockFunctionResponse } from './BedrockFunctionResponse.js' ;
1314import { assertBedrockAgentFunctionEvent } from './utils.js' ;
1415
15- export class BedrockAgentFunctionResolver {
16+ /**
17+ * Resolver for AWS Bedrock Agent Function invocations.
18+ *
19+ * This resolver is designed to handle function invocations from Bedrock Agents.
20+ *
21+ * @example
22+ * ```ts
23+ * import {
24+ * BedrockAgentFunctionResolver
25+ * } from '@aws-lambda-powertools/event-handler/bedrock-agent';
26+ *
27+ * const app = new BedrockAgentFunctionResolver();
28+ *
29+ * app.tool(async (params) => {
30+ * const { name } = params;
31+ * return `Hello, ${name}!`;
32+ * }, {
33+ * name: 'greeting',
34+ * description: 'Greets a person by name',
35+ * });
36+ *
37+ * export const handler = async (event, context) =>
38+ * app.resolve(event, context);
39+ * ```
40+ */
41+ class BedrockAgentFunctionResolver {
42+ /**
43+ * Registry of tools added to the Bedrock Agent Function Resolver.
44+ */
1645 readonly #tools: Map < string , Tool > = new Map ( ) ;
17- readonly #envService: EnvironmentVariablesService ;
46+ /**
47+ * A logger instance to be used for logging debug, warning, and error messages.
48+ *
49+ * When no logger is provided, we'll only log warnings and errors using the global `console` object.
50+ */
1851 readonly #logger: Pick < GenericLogger , 'debug' | 'warn' | 'error' > ;
1952
2053 constructor ( options ?: ResolverOptions ) {
21- this . #envService = new EnvironmentVariablesService ( ) ;
22- const alcLogLevel = this . #envService. get ( 'AWS_LAMBDA_LOG_LEVEL' ) ;
54+ const alcLogLevel = getStringFromEnv ( {
55+ key : 'AWS_LAMBDA_LOG_LEVEL' ,
56+ defaultValue : '' ,
57+ } ) ;
2358 this . #logger = options ?. logger ?? {
2459 debug : alcLogLevel === 'DEBUG' ? console . debug : ( ) => { } ,
2560 error : console . error ,
@@ -34,7 +69,9 @@ export class BedrockAgentFunctionResolver {
3469 *
3570 * @example
3671 * ```ts
37- * import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent-function';
72+ * import {
73+ * BedrockAgentFunctionResolver
74+ * } from '@aws-lambda-powertools/event-handler/bedrock-agent';
3875 *
3976 * const app = new BedrockAgentFunctionResolver();
4077 *
@@ -50,113 +87,106 @@ export class BedrockAgentFunctionResolver {
5087 * app.resolve(event, context);
5188 * ```
5289 *
53- * The method also works as a class method decorator:
90+ * If you know the function signature, you can also use a type parameter to specify the parameters of the tool function:
91+ *
92+ * @example
93+ * ```ts
94+ * import {
95+ * BedrockAgentFunctionResolver,
96+ * } from '@aws-lambda-powertools/event-handler/bedrock-agent';
97+ *
98+ * const app = new BedrockAgentFunctionResolver();
99+ *
100+ * app.tool<{ name: string }>(async (params) => {
101+ * const { name } = params;
102+ * // ^ name: string
103+ * return `Hello, ${name}!`;
104+ * }, {
105+ * name: 'greeting',
106+ * description: 'Greets a person by name',
107+ * });
108+ *
109+ * export const handler = async (event, context) =>
110+ * app.resolve(event, context);
111+ * ```
112+ *
113+ * When defining a tool, you can also access the original `event` and `context` objects from the Bedrock Agent function invocation.
114+ * This is useful if you need to access the session attributes or other context-specific information.
54115 *
55116 * @example
56117 * ```ts
57- * import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent-function';
118+ * import {
119+ * BedrockAgentFunctionResolver
120+ * } from '@aws-lambda-powertools/event-handler/bedrock-agent';
58121 *
59122 * const app = new BedrockAgentFunctionResolver();
60123 *
61- * class Lambda {
62- * @app .tool( { name: 'greeting', description: 'Greets a person by name' })
63- * async greeting(params) {
64- * const { name } = params ;
65- * return `Hello, ${name}!`;
66- * }
124+ * app.tool(async (params, { event, context }) => {
125+ * const { name } = params;
126+ * // Access session attributes from the event
127+ * const sessionAttributes = event.sessionAttributes || {} ;
128+ * // You can also access the context if needed
129+ * sessionAttributes.requestId = context.awsRequestId;
67130 *
68- * async handler(event, context) {
69- * return app.resolve(event, context);
70- * }
71- * }
131+ * return `Hello, ${name}!`;
132+ * }, {
133+ * name: 'greetingWithContext',
134+ * description: 'Greets a person by name',
135+ * });
72136 *
73- * const lambda = new Lambda();
74- * export const handler = lambda.handler.bind(lambda );
137+ * export const handler = async (event, context) =>
138+ * app.resolve(event, context );
75139 * ```
76140 *
77141 * @param fn - The tool function
78142 * @param config - The configuration object for the tool
143+ * @param config.name - The name of the tool, which must be unique across all registered tools.
144+ * @param config.description - A description of the tool, which is optional but highly recommended.
79145 */
80146 public tool < TParams extends Record < string , ParameterValue > > (
81147 fn : ToolFunction < TParams > ,
82148 config : Configuration
83- ) : undefined ;
84- public tool < TParams extends Record < string , ParameterValue > > (
85- config : Configuration
86- ) : MethodDecorator ;
87- public tool < TParams extends Record < string , ParameterValue > > (
88- fnOrConfig : ToolFunction < TParams > | Configuration ,
89- config ?: Configuration
90- ) : MethodDecorator | undefined {
91- // When used as a method (not a decorator)
92- if ( typeof fnOrConfig === 'function' ) {
93- this . #registerTool( fnOrConfig , config as Configuration ) ;
94- return ;
95- }
96-
97- // When used as a decorator
98- return ( _target , _propertyKey , descriptor : PropertyDescriptor ) => {
99- const toolFn = descriptor . value as ToolFunction ;
100- this . #registerTool( toolFn , fnOrConfig ) ;
101- return descriptor ;
102- } ;
103- }
104-
105- #registerTool< TParams extends Record < string , ParameterValue > > (
106- handler : ToolFunction < TParams > ,
107- config : Configuration
108- ) : void {
149+ ) : undefined {
109150 const { name } = config ;
110-
111- if ( this . #tools. size >= 5 ) {
112- this . #logger. warn (
113- `The maximum number of tools that can be registered is 5. Tool ${ name } will not be registered.`
114- ) ;
115- return ;
116- }
117-
118151 if ( this . #tools. has ( name ) ) {
119152 this . #logger. warn (
120- `Tool ${ name } already registered. Overwriting with new definition.`
153+ `Tool " ${ name } " already registered. Overwriting with new definition.`
121154 ) ;
122155 }
123156
124157 this . #tools. set ( name , {
125- handler : handler as ToolFunction ,
158+ handler : fn as ToolFunction ,
126159 config,
127160 } ) ;
128- this . #logger. debug ( `Tool ${ name } has been registered.` ) ;
129- }
130-
131- #buildResponse( options : ResponseOptions ) : BedrockAgentFunctionResponse {
132- const {
133- actionGroup,
134- function : func ,
135- body,
136- errorType,
137- sessionAttributes,
138- promptSessionAttributes,
139- } = options ;
140-
141- return {
142- messageVersion : '1.0' ,
143- response : {
144- actionGroup,
145- function : func ,
146- functionResponse : {
147- responseState : errorType ,
148- responseBody : {
149- TEXT : {
150- body,
151- } ,
152- } ,
153- } ,
154- } ,
155- sessionAttributes,
156- promptSessionAttributes,
157- } ;
161+ this . #logger. debug ( `Tool "${ name } " has been registered.` ) ;
158162 }
159163
164+ /**
165+ * Resolve an incoming Bedrock Agent function invocation event.
166+ *
167+ * @example
168+ * ```ts
169+ * import {
170+ * BedrockAgentFunctionResolver
171+ * } from '@aws-lambda-powertools/event-handler/bedrock-agent';
172+ *
173+ * const app = new BedrockAgentFunctionResolver();
174+ *
175+ * app.tool(async (params) => {
176+ * const { name } = params;
177+ * return `Hello, ${name}!`;
178+ * }, {
179+ * name: 'greeting',
180+ * description: 'Greets a person by name',
181+ * });
182+ *
183+ * export const handler = async (event, context) =>
184+ * app.resolve(event, context);
185+ * ```
186+ *
187+ * @param event - The incoming payload of the AWS Lambda function.
188+ * @param context - The context object provided by AWS Lambda, which contains information about the invocation, function, and execution environment.
189+ */
160190 async resolve (
161191 event : unknown ,
162192 context : Context
@@ -169,16 +199,21 @@ export class BedrockAgentFunctionResolver {
169199 actionGroup,
170200 sessionAttributes,
171201 promptSessionAttributes,
202+ knowledgeBasesConfiguration,
172203 } = event ;
173204
174205 const tool = this . #tools. get ( toolName ) ;
175206
176207 if ( tool == null ) {
177- this . #logger. error ( `Tool ${ toolName } has not been registered.` ) ;
178- return this . #buildResponse( {
208+ this . #logger. error ( `Tool "${ toolName } " has not been registered.` ) ;
209+ return new BedrockFunctionResponse ( {
210+ body : `Error: tool "${ toolName } " has not been registered.` ,
211+ sessionAttributes,
212+ promptSessionAttributes,
213+ knowledgeBasesConfiguration,
214+ } ) . build ( {
179215 actionGroup,
180- function : toolName ,
181- body : 'Error: tool has not been registered in handler.' ,
216+ func : toolName ,
182217 } ) ;
183218 }
184219
@@ -195,7 +230,7 @@ export class BedrockAgentFunctionResolver {
195230 break ;
196231 }
197232 // this default will also catch array types but we leave them as strings
198- // because we cannot reliably parse them
233+ // because we cannot reliably parse them - see discussion in #3710
199234 default : {
200235 toolParams [ param . name ] = param . value ;
201236 break ;
@@ -204,24 +239,43 @@ export class BedrockAgentFunctionResolver {
204239 }
205240
206241 try {
207- const res = await tool . handler ( toolParams , { event, context } ) ;
208- const body = res == null ? '' : JSON . stringify ( res ) ;
209- return this . #buildResponse( {
210- actionGroup,
211- function : toolName ,
242+ const response = await tool . handler ( toolParams , { event, context } ) ;
243+ if ( response instanceof BedrockFunctionResponse ) {
244+ return response . build ( {
245+ actionGroup,
246+ func : toolName ,
247+ } ) ;
248+ }
249+ const body =
250+ isNullOrUndefined ( response ) || response === ''
251+ ? ''
252+ : JSON . stringify ( response ) ;
253+ return new BedrockFunctionResponse ( {
212254 body,
213255 sessionAttributes,
214256 promptSessionAttributes,
257+ knowledgeBasesConfiguration,
258+ } ) . build ( {
259+ actionGroup,
260+ func : toolName ,
215261 } ) ;
216262 } catch ( error ) {
217263 this . #logger. error ( `An error occurred in tool ${ toolName } .` , error ) ;
218- return this . #buildResponse( {
219- actionGroup,
220- function : toolName ,
221- body : `Error when invoking tool: ${ error } ` ,
264+ const errorMessage =
265+ error instanceof Error
266+ ? `${ error . name } - ${ error . message } `
267+ : String ( error ) ;
268+ return new BedrockFunctionResponse ( {
269+ body : `Unable to complete tool execution due to ${ errorMessage } ` ,
222270 sessionAttributes,
223271 promptSessionAttributes,
272+ knowledgeBasesConfiguration,
273+ } ) . build ( {
274+ actionGroup,
275+ func : toolName ,
224276 } ) ;
225277 }
226278 }
227279}
280+
281+ export { BedrockAgentFunctionResolver } ;
0 commit comments