Skip to content

Commit 3112527

Browse files
committed
make tool method generic for type Record<string, ParameterValue>>
1 parent bb483f3 commit 3112527

File tree

3 files changed

+170
-16
lines changed

3 files changed

+170
-16
lines changed

packages/event-handler/src/bedrock-agent-function/BedrockAgentFunctionResolver.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
BedrockAgentFunctionEvent,
55
BedrockAgentFunctionResponse,
66
Configuration,
7+
ParameterValue,
78
ResolverOptions,
89
ResponseOptions,
910
Tool,
@@ -13,7 +14,8 @@ import type { GenericLogger } from '../types/common.js';
1314
import { isPrimitive } from './utils.js';
1415

1516
export class BedrockAgentFunctionResolver {
16-
readonly #tools: Map<string, Tool> = new Map<string, Tool>();
17+
readonly #tools: Map<string, Tool<Record<string, ParameterValue>>> =
18+
new Map();
1719
readonly #envService: EnvironmentVariablesService;
1820
readonly #logger: Pick<GenericLogger, 'debug' | 'warn' | 'error'>;
1921

@@ -77,10 +79,15 @@ export class BedrockAgentFunctionResolver {
7779
* @param fn - The tool function
7880
* @param config - The configuration object for the tool
7981
*/
80-
public tool(fn: ToolFunction, config: Configuration): undefined;
81-
public tool(config: Configuration): MethodDecorator;
82-
public tool(
83-
fnOrConfig: ToolFunction | Configuration,
82+
public tool<TParams extends Record<string, ParameterValue>>(
83+
fn: ToolFunction<TParams>,
84+
config: Configuration
85+
): undefined;
86+
public tool<TParams extends Record<string, ParameterValue>>(
87+
config: Configuration
88+
): MethodDecorator;
89+
public tool<TParams extends Record<string, ParameterValue>>(
90+
fnOrConfig: ToolFunction<TParams> | Configuration,
8491
config?: Configuration
8592
): MethodDecorator | undefined {
8693
// When used as a method (not a decorator)
@@ -97,7 +104,10 @@ export class BedrockAgentFunctionResolver {
97104
};
98105
}
99106

100-
#registerTool(fn: ToolFunction, config: Configuration): void {
107+
#registerTool<TParams extends Record<string, ParameterValue>>(
108+
handler: ToolFunction<TParams>,
109+
config: Configuration
110+
): void {
101111
const { name } = config;
102112

103113
if (this.#tools.size >= 5) {
@@ -113,7 +123,10 @@ export class BedrockAgentFunctionResolver {
113123
);
114124
}
115125

116-
this.#tools.set(name, { function: fn, config });
126+
this.#tools.set(name, {
127+
handler: handler as ToolFunction<Record<string, ParameterValue>>,
128+
config,
129+
});
117130
this.#logger.debug(`Tool ${name} has been registered.`);
118131
}
119132

@@ -169,12 +182,29 @@ export class BedrockAgentFunctionResolver {
169182
});
170183
}
171184

172-
const parameterObject: Record<string, string> = Object.fromEntries(
173-
parameters.map((param) => [param.name, param.value])
174-
);
185+
const toolParams: Record<string, ParameterValue> = {};
186+
for (const param of parameters) {
187+
switch (param.type) {
188+
case 'boolean': {
189+
toolParams[param.name] = param.value === 'true';
190+
break;
191+
}
192+
case 'number':
193+
case 'integer': {
194+
toolParams[param.name] = Number(param.value);
195+
break;
196+
}
197+
// this default will also catch array types but we leave them as strings
198+
// because we cannot reliably parse them
199+
default: {
200+
toolParams[param.name] = param.value;
201+
break;
202+
}
203+
}
204+
}
175205

176206
try {
177-
const res = (await tool.function(parameterObject)) ?? '';
207+
const res = (await tool.handler(toolParams, event, context)) ?? '';
178208
const body = isPrimitive(res) ? String(res) : JSON.stringify(res);
179209
return this.#buildResponse({
180210
actionGroup,

packages/event-handler/src/types/bedrock-agent-function.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { JSONValue } from '@aws-lambda-powertools/commons/types';
2+
import type { Context } from 'aws-lambda';
23
import type { GenericLogger } from '../types/common.js';
34

45
type Configuration = {
@@ -16,14 +17,18 @@ type ParameterPrimitives = string | number | boolean;
1617

1718
type ParameterValue = ParameterPrimitives | Array<ParameterValue>;
1819

19-
type Tool<TParams = Record<string, ParameterValue>> = {
20+
type ToolFunction<TParams = Record<string, ParameterValue>> = (
21+
params: TParams,
22+
event: BedrockAgentFunctionEvent,
23+
context: Context
2024
// biome-ignore lint/suspicious/noConfusingVoidType: we need to support async functions that don't have an explicit return value
21-
function: (params: TParams) => Promise<JSONValue | void>;
25+
) => Promise<JSONValue | void>;
26+
27+
type Tool<TParams = Record<string, ParameterValue>> = {
28+
handler: ToolFunction<TParams>;
2229
config: Configuration;
2330
};
2431

25-
type ToolFunction = Tool['function'];
26-
2732
type Attributes = Record<string, string>;
2833

2934
type FunctionIdentifier = {
@@ -96,6 +101,7 @@ export type {
96101
ToolFunction,
97102
Parameter,
98103
Attributes,
104+
ParameterValue,
99105
FunctionIdentifier,
100106
FunctionInvocation,
101107
BedrockAgentFunctionEvent,

packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import context from '@aws-lambda-powertools/testing-utils/context';
22
import type { Context } from 'aws-lambda';
33
import { beforeEach, describe, expect, it, vi } from 'vitest';
44
import { BedrockAgentFunctionResolver } from '../../../src/bedrock-agent-function/index.js';
5-
import type { BedrockAgentFunctionEvent, Parameter } from '../../../src/types';
5+
import type {
6+
BedrockAgentFunctionEvent,
7+
Configuration,
8+
Parameter,
9+
ParameterValue,
10+
ToolFunction,
11+
} from '../../../src/types/bedrock-agent-function.js';
612

713
function createEvent(functionName: string, parameters?: Parameter[]) {
814
return {
@@ -178,6 +184,32 @@ describe('Class: BedrockAgentFunctionResolver', () => {
178184
).toEqual('20');
179185
});
180186

187+
it('tool function has access to the event variable', async () => {
188+
// Prepare
189+
const app = new BedrockAgentFunctionResolver();
190+
191+
app.tool(
192+
async (_params, event) => {
193+
return event;
194+
},
195+
{
196+
name: 'event-accessor',
197+
description: 'Accesses the event object',
198+
}
199+
);
200+
201+
const event = createEvent('event-accessor');
202+
203+
// Act
204+
const result = await app.resolve(event, context);
205+
206+
// Assess
207+
expect(result.response.function).toEqual('event-accessor');
208+
expect(result.response.functionResponse.responseBody.TEXT.body).toEqual(
209+
JSON.stringify(event)
210+
);
211+
});
212+
181213
it('can be invoked using the decorator pattern', async () => {
182214
// Prepare
183215
const app = new BedrockAgentFunctionResolver();
@@ -327,6 +359,92 @@ describe('Class: BedrockAgentFunctionResolver', () => {
327359
}
328360
);
329361

362+
it.each<{
363+
toolFunction: ToolFunction<Record<string, ParameterValue>>;
364+
toolParams: Configuration;
365+
parameters: Parameter[];
366+
expected: string;
367+
}>([
368+
{
369+
toolFunction: async (params: Record<string, ParameterValue>) =>
370+
params.arg,
371+
toolParams: {
372+
name: 'boolean-handler',
373+
description: 'Handles boolean parameters',
374+
},
375+
parameters: [
376+
{ name: 'arg', type: 'boolean', value: 'true' },
377+
] as Parameter[],
378+
expected: 'true',
379+
},
380+
{
381+
toolFunction: async (params: Record<string, ParameterValue>) =>
382+
(params.arg as number) + 10,
383+
toolParams: {
384+
name: 'number-handler',
385+
description: 'Handles number parameters',
386+
},
387+
parameters: [{ name: 'arg', type: 'number', value: '42' }] as Parameter[],
388+
expected: '52',
389+
},
390+
{
391+
toolFunction: async (params: Record<string, ParameterValue>) =>
392+
(params.arg as number) + 10,
393+
toolParams: {
394+
name: 'integer-handler',
395+
description: 'Handles integer parameters',
396+
},
397+
parameters: [
398+
{ name: 'arg', type: 'integer', value: '37' },
399+
] as Parameter[],
400+
expected: '47',
401+
},
402+
{
403+
toolFunction: async (params: Record<string, ParameterValue>) =>
404+
`String: ${params.arg}`,
405+
toolParams: {
406+
name: 'string-handler',
407+
description: 'Handles string parameters',
408+
},
409+
parameters: [
410+
{ name: 'arg', type: 'string', value: 'hello world' },
411+
] as Parameter[],
412+
expected: 'String: hello world',
413+
},
414+
{
415+
toolFunction: async (params: Record<string, ParameterValue>) =>
416+
`Array as string: ${params.arg}`,
417+
toolParams: {
418+
name: 'array-handler',
419+
description: 'Handles array parameters (as string)',
420+
},
421+
parameters: [
422+
{ name: 'arg', type: 'array', value: '[1,2,3]' },
423+
] as Parameter[],
424+
expected: 'Array as string: [1,2,3]',
425+
},
426+
])(
427+
'correctly parses $toolParams.name parameters',
428+
async ({ toolFunction, toolParams, parameters, expected }) => {
429+
// Prepare
430+
const app = new BedrockAgentFunctionResolver();
431+
432+
app.tool(toolFunction, toolParams);
433+
434+
// Act
435+
const actual = await app.resolve(
436+
createEvent(toolParams.name, parameters),
437+
context
438+
);
439+
440+
// Assert
441+
expect(actual.response.function).toEqual(toolParams.name);
442+
expect(actual.response.functionResponse.responseBody.TEXT.body).toEqual(
443+
expected
444+
);
445+
}
446+
);
447+
330448
it('handles functions that throw errors', async () => {
331449
// Prepare
332450
const app = new BedrockAgentFunctionResolver();

0 commit comments

Comments
 (0)