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' ;
2
3
import type { Context } from 'aws-lambda' ;
3
4
import type {
4
5
BedrockAgentFunctionResponse ,
5
6
Configuration ,
6
7
ParameterValue ,
7
8
ResolverOptions ,
8
- ResponseOptions ,
9
9
Tool ,
10
10
ToolFunction ,
11
11
} from '../types/bedrock-agent.js' ;
12
12
import type { GenericLogger } from '../types/common.js' ;
13
+ import { BedrockFunctionResponse } from './BedrockFunctionResponse.js' ;
13
14
import { assertBedrockAgentFunctionEvent } from './utils.js' ;
14
15
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
+ */
16
45
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
+ */
18
51
readonly #logger: Pick < GenericLogger , 'debug' | 'warn' | 'error' > ;
19
52
20
53
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
+ } ) ;
23
58
this . #logger = options ?. logger ?? {
24
59
debug : alcLogLevel === 'DEBUG' ? console . debug : ( ) => { } ,
25
60
error : console . error ,
@@ -34,7 +69,9 @@ export class BedrockAgentFunctionResolver {
34
69
*
35
70
* @example
36
71
* ```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';
38
75
*
39
76
* const app = new BedrockAgentFunctionResolver();
40
77
*
@@ -50,113 +87,106 @@ export class BedrockAgentFunctionResolver {
50
87
* app.resolve(event, context);
51
88
* ```
52
89
*
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.
54
115
*
55
116
* @example
56
117
* ```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';
58
121
*
59
122
* const app = new BedrockAgentFunctionResolver();
60
123
*
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;
67
130
*
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
+ * });
72
136
*
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 );
75
139
* ```
76
140
*
77
141
* @param fn - The tool function
78
142
* @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.
79
145
*/
80
146
public tool < TParams extends Record < string , ParameterValue > > (
81
147
fn : ToolFunction < TParams > ,
82
148
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 {
109
150
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
-
118
151
if ( this . #tools. has ( name ) ) {
119
152
this . #logger. warn (
120
- `Tool ${ name } already registered. Overwriting with new definition.`
153
+ `Tool " ${ name } " already registered. Overwriting with new definition.`
121
154
) ;
122
155
}
123
156
124
157
this . #tools. set ( name , {
125
- handler : handler as ToolFunction ,
158
+ handler : fn as ToolFunction ,
126
159
config,
127
160
} ) ;
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.` ) ;
158
162
}
159
163
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
+ */
160
190
async resolve (
161
191
event : unknown ,
162
192
context : Context
@@ -169,16 +199,21 @@ export class BedrockAgentFunctionResolver {
169
199
actionGroup,
170
200
sessionAttributes,
171
201
promptSessionAttributes,
202
+ knowledgeBasesConfiguration,
172
203
} = event ;
173
204
174
205
const tool = this . #tools. get ( toolName ) ;
175
206
176
207
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 ( {
179
215
actionGroup,
180
- function : toolName ,
181
- body : 'Error: tool has not been registered in handler.' ,
216
+ func : toolName ,
182
217
} ) ;
183
218
}
184
219
@@ -195,7 +230,7 @@ export class BedrockAgentFunctionResolver {
195
230
break ;
196
231
}
197
232
// 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
199
234
default : {
200
235
toolParams [ param . name ] = param . value ;
201
236
break ;
@@ -204,24 +239,43 @@ export class BedrockAgentFunctionResolver {
204
239
}
205
240
206
241
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 ( {
212
254
body,
213
255
sessionAttributes,
214
256
promptSessionAttributes,
257
+ knowledgeBasesConfiguration,
258
+ } ) . build ( {
259
+ actionGroup,
260
+ func : toolName ,
215
261
} ) ;
216
262
} catch ( error ) {
217
263
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 } ` ,
222
270
sessionAttributes,
223
271
promptSessionAttributes,
272
+ knowledgeBasesConfiguration,
273
+ } ) . build ( {
274
+ actionGroup,
275
+ func : toolName ,
224
276
} ) ;
225
277
}
226
278
}
227
279
}
280
+
281
+ export { BedrockAgentFunctionResolver } ;
0 commit comments