diff --git a/packages/event-handler/README.md b/packages/event-handler/README.md index 3be14212f..c4350745d 100644 --- a/packages/event-handler/README.md +++ b/packages/event-handler/README.md @@ -6,7 +6,7 @@ You can use the library in both TypeScript and JavaScript code bases. ## Intro -Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Balancer (ALB), Lambda Function URLs, and VPC Lattice. +Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Balancer (ALB), Lambda Function URLs, VPC Lattice, AWS AppSync Events APIs, and Amazon Bedrock Agent Functions. ## Usage @@ -102,6 +102,8 @@ export const handler = async (event, context) => app.resolve(event, context); ``` +## Bedrock Agent Functions + See the [documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/features/event-handler/appsync-events) for more details on how to use the AppSync event handler. ## Contribute diff --git a/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts b/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts index 93b30f0ec..01ec21074 100644 --- a/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts +++ b/packages/event-handler/src/bedrock-agent/BedrockAgentFunctionResolver.ts @@ -1,25 +1,60 @@ -import { EnvironmentVariablesService } from '@aws-lambda-powertools/commons'; +import { isNullOrUndefined } from '@aws-lambda-powertools/commons/typeutils'; +import { getStringFromEnv } from '@aws-lambda-powertools/commons/utils/env'; import type { Context } from 'aws-lambda'; import type { BedrockAgentFunctionResponse, Configuration, ParameterValue, ResolverOptions, - ResponseOptions, Tool, ToolFunction, } from '../types/bedrock-agent.js'; import type { GenericLogger } from '../types/common.js'; +import { BedrockFunctionResponse } from './BedrockFunctionResponse.js'; import { assertBedrockAgentFunctionEvent } from './utils.js'; -export class BedrockAgentFunctionResolver { +/** + * Resolver for AWS Bedrock Agent Function invocations. + * + * This resolver is designed to handle function invocations from Bedrock Agents. + * + * @example + * ```ts + * import { + * BedrockAgentFunctionResolver + * } from '@aws-lambda-powertools/event-handler/bedrock-agent'; + * + * const app = new BedrockAgentFunctionResolver(); + * + * app.tool(async (params) => { + * const { name } = params; + * return `Hello, ${name}!`; + * }, { + * name: 'greeting', + * description: 'Greets a person by name', + * }); + * + * export const handler = async (event, context) => + * app.resolve(event, context); + * ``` + */ +class BedrockAgentFunctionResolver { + /** + * Registry of tools added to the Bedrock Agent Function Resolver. + */ readonly #tools: Map = new Map(); - readonly #envService: EnvironmentVariablesService; + /** + * A logger instance to be used for logging debug, warning, and error messages. + * + * When no logger is provided, we'll only log warnings and errors using the global `console` object. + */ readonly #logger: Pick; constructor(options?: ResolverOptions) { - this.#envService = new EnvironmentVariablesService(); - const alcLogLevel = this.#envService.get('AWS_LAMBDA_LOG_LEVEL'); + const alcLogLevel = getStringFromEnv({ + key: 'AWS_LAMBDA_LOG_LEVEL', + defaultValue: '', + }); this.#logger = options?.logger ?? { debug: alcLogLevel === 'DEBUG' ? console.debug : () => {}, error: console.error, @@ -34,7 +69,9 @@ export class BedrockAgentFunctionResolver { * * @example * ```ts - * import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent-function'; + * import { + * BedrockAgentFunctionResolver + * } from '@aws-lambda-powertools/event-handler/bedrock-agent'; * * const app = new BedrockAgentFunctionResolver(); * @@ -50,113 +87,106 @@ export class BedrockAgentFunctionResolver { * app.resolve(event, context); * ``` * - * The method also works as a class method decorator: + * If you know the function signature, you can also use a type parameter to specify the parameters of the tool function: + * + * @example + * ```ts + * import { + * BedrockAgentFunctionResolver, + * } from '@aws-lambda-powertools/event-handler/bedrock-agent'; + * + * const app = new BedrockAgentFunctionResolver(); + * + * app.tool<{ name: string }>(async (params) => { + * const { name } = params; + * // ^ name: string + * return `Hello, ${name}!`; + * }, { + * name: 'greeting', + * description: 'Greets a person by name', + * }); + * + * export const handler = async (event, context) => + * app.resolve(event, context); + * ``` + * + * When defining a tool, you can also access the original `event` and `context` objects from the Bedrock Agent function invocation. + * This is useful if you need to access the session attributes or other context-specific information. * * @example * ```ts - * import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent-function'; + * import { + * BedrockAgentFunctionResolver + * } from '@aws-lambda-powertools/event-handler/bedrock-agent'; * * const app = new BedrockAgentFunctionResolver(); * - * class Lambda { - * @app.tool({ name: 'greeting', description: 'Greets a person by name' }) - * async greeting(params) { - * const { name } = params; - * return `Hello, ${name}!`; - * } + * app.tool(async (params, { event, context }) => { + * const { name } = params; + * // Access session attributes from the event + * const sessionAttributes = event.sessionAttributes || {}; + * // You can also access the context if needed + * sessionAttributes.requestId = context.awsRequestId; * - * async handler(event, context) { - * return app.resolve(event, context); - * } - * } + * return `Hello, ${name}!`; + * }, { + * name: 'greetingWithContext', + * description: 'Greets a person by name', + * }); * - * const lambda = new Lambda(); - * export const handler = lambda.handler.bind(lambda); + * export const handler = async (event, context) => + * app.resolve(event, context); * ``` * * @param fn - The tool function * @param config - The configuration object for the tool + * @param config.name - The name of the tool, which must be unique across all registered tools. + * @param config.description - A description of the tool, which is optional but highly recommended. */ public tool>( fn: ToolFunction, config: Configuration - ): undefined; - public tool>( - config: Configuration - ): MethodDecorator; - public tool>( - fnOrConfig: ToolFunction | Configuration, - config?: Configuration - ): MethodDecorator | undefined { - // When used as a method (not a decorator) - if (typeof fnOrConfig === 'function') { - this.#registerTool(fnOrConfig, config as Configuration); - return; - } - - // When used as a decorator - return (_target, _propertyKey, descriptor: PropertyDescriptor) => { - const toolFn = descriptor.value as ToolFunction; - this.#registerTool(toolFn, fnOrConfig); - return descriptor; - }; - } - - #registerTool>( - handler: ToolFunction, - config: Configuration - ): void { + ): undefined { const { name } = config; - - if (this.#tools.size >= 5) { - this.#logger.warn( - `The maximum number of tools that can be registered is 5. Tool ${name} will not be registered.` - ); - return; - } - if (this.#tools.has(name)) { this.#logger.warn( - `Tool ${name} already registered. Overwriting with new definition.` + `Tool "${name}" already registered. Overwriting with new definition.` ); } this.#tools.set(name, { - handler: handler as ToolFunction, + handler: fn as ToolFunction, config, }); - this.#logger.debug(`Tool ${name} has been registered.`); - } - - #buildResponse(options: ResponseOptions): BedrockAgentFunctionResponse { - const { - actionGroup, - function: func, - body, - errorType, - sessionAttributes, - promptSessionAttributes, - } = options; - - return { - messageVersion: '1.0', - response: { - actionGroup, - function: func, - functionResponse: { - responseState: errorType, - responseBody: { - TEXT: { - body, - }, - }, - }, - }, - sessionAttributes, - promptSessionAttributes, - }; + this.#logger.debug(`Tool "${name}" has been registered.`); } + /** + * Resolve an incoming Bedrock Agent function invocation event. + * + * @example + * ```ts + * import { + * BedrockAgentFunctionResolver + * } from '@aws-lambda-powertools/event-handler/bedrock-agent'; + * + * const app = new BedrockAgentFunctionResolver(); + * + * app.tool(async (params) => { + * const { name } = params; + * return `Hello, ${name}!`; + * }, { + * name: 'greeting', + * description: 'Greets a person by name', + * }); + * + * export const handler = async (event, context) => + * app.resolve(event, context); + * ``` + * + * @param event - The incoming payload of the AWS Lambda function. + * @param context - The context object provided by AWS Lambda, which contains information about the invocation, function, and execution environment. + */ async resolve( event: unknown, context: Context @@ -169,16 +199,21 @@ export class BedrockAgentFunctionResolver { actionGroup, sessionAttributes, promptSessionAttributes, + knowledgeBasesConfiguration, } = event; const tool = this.#tools.get(toolName); if (tool == null) { - this.#logger.error(`Tool ${toolName} has not been registered.`); - return this.#buildResponse({ + this.#logger.error(`Tool "${toolName}" has not been registered.`); + return new BedrockFunctionResponse({ + body: `Error: tool "${toolName}" has not been registered.`, + sessionAttributes, + promptSessionAttributes, + knowledgeBasesConfiguration, + }).build({ actionGroup, - function: toolName, - body: 'Error: tool has not been registered in handler.', + func: toolName, }); } @@ -195,7 +230,7 @@ export class BedrockAgentFunctionResolver { break; } // this default will also catch array types but we leave them as strings - // because we cannot reliably parse them + // because we cannot reliably parse them - see discussion in #3710 default: { toolParams[param.name] = param.value; break; @@ -204,24 +239,43 @@ export class BedrockAgentFunctionResolver { } try { - const res = await tool.handler(toolParams, { event, context }); - const body = res == null ? '' : JSON.stringify(res); - return this.#buildResponse({ - actionGroup, - function: toolName, + const response = await tool.handler(toolParams, { event, context }); + if (response instanceof BedrockFunctionResponse) { + return response.build({ + actionGroup, + func: toolName, + }); + } + const body = + isNullOrUndefined(response) || response === '' + ? '' + : JSON.stringify(response); + return new BedrockFunctionResponse({ body, sessionAttributes, promptSessionAttributes, + knowledgeBasesConfiguration, + }).build({ + actionGroup, + func: toolName, }); } catch (error) { this.#logger.error(`An error occurred in tool ${toolName}.`, error); - return this.#buildResponse({ - actionGroup, - function: toolName, - body: `Error when invoking tool: ${error}`, + const errorMessage = + error instanceof Error + ? `${error.name} - ${error.message}` + : String(error); + return new BedrockFunctionResponse({ + body: `Unable to complete tool execution due to ${errorMessage}`, sessionAttributes, promptSessionAttributes, + knowledgeBasesConfiguration, + }).build({ + actionGroup, + func: toolName, }); } } } + +export { BedrockAgentFunctionResolver }; diff --git a/packages/event-handler/src/bedrock-agent/BedrockFunctionResponse.ts b/packages/event-handler/src/bedrock-agent/BedrockFunctionResponse.ts new file mode 100644 index 000000000..5656a6d6d --- /dev/null +++ b/packages/event-handler/src/bedrock-agent/BedrockFunctionResponse.ts @@ -0,0 +1,104 @@ +import type { + BedrockAgentFunctionEvent, + ResponseState, +} from '../types/bedrock-agent.js'; +import type { BedrockAgentFunctionResolver } from './BedrockAgentFunctionResolver.js'; +/** + * Class representing a response from a Bedrock agent function. + * + * You can use this class to customize the response sent back to the Bedrock agent with additional fields like: + * - session attributes + * - prompt session attributes + * - response state (`FAILURE` or `REPROMPT`) + * + * When working with the {@link BedrockAgentFunctionResolver} class, this is built automatically + * when you return anything from your function handler other than an instance of this class. + */ +class BedrockFunctionResponse { + /** + * The response object that defines the response from execution of the function. + */ + readonly body: string; + /** + * Optional field to indicate the whether the response is a failure or a reprompt. + * If not provided, the default is undefined, which means no specific response state is set. + * + * - `FAILURE`: The agent throws a `DependencyFailedException` for the current session. + * - `REPROMPT`: The agent passes a response string to the model to reprompt it. + */ + readonly responseState?: ResponseState; + /** + * Optional field to store session attributes and their values. + * @see {@link https://docs.aws.amazon.com/bedrock/latest/userguide/agents-session-state.html#session-state-attributes | Bedrock Agent Session State Attributes} for more details. + */ + readonly sessionAttributes: BedrockAgentFunctionEvent['sessionAttributes']; + /** + * Optional field to instruct the agent to prompt attributes and their values. + * @see {@link https://docs.aws.amazon.com/bedrock/latest/userguide/agents-session-state.html#session-state-attributes | Bedrock Agent Session State Attributes} for more details. + */ + readonly promptSessionAttributes: BedrockAgentFunctionEvent['promptSessionAttributes']; + /** + * Optional field to configure knowledge bases for the agent. + * @see {@link https://docs.aws.amazon.com/bedrock/latest/userguide/agents-session-state.html#session-state-kb | Bedrock Agent Knowledge Bases} for more details. + */ + readonly knowledgeBasesConfiguration?: BedrockAgentFunctionEvent['knowledgeBasesConfiguration']; + + constructor({ + body, + responseState = undefined, + sessionAttributes = {}, + promptSessionAttributes = {}, + knowledgeBasesConfiguration = undefined, + }: { + body: string; + responseState?: ResponseState; + sessionAttributes?: BedrockAgentFunctionEvent['sessionAttributes']; + promptSessionAttributes?: BedrockAgentFunctionEvent['promptSessionAttributes']; + knowledgeBasesConfiguration?: BedrockAgentFunctionEvent['knowledgeBasesConfiguration']; + }) { + this.body = body; + this.responseState = responseState; + this.sessionAttributes = sessionAttributes; + this.promptSessionAttributes = promptSessionAttributes; + this.knowledgeBasesConfiguration = knowledgeBasesConfiguration; + } + + /** + * Builds the Bedrock function response object according to the Bedrock agent function {@link https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-response | response format}. + * + * @param options - The options for building the response. + * @param options.actionGroup - The action group of the function, this comes from the `event.actionGroup` field in the Bedrock agent function event. + * @param options.func - The name of the function being invoked by the agent, this comes from the `event.function` field in the Bedrock agent function event. + */ + build(options: { + actionGroup: string; + func: string; + }) { + return { + messageVersion: '1.0', + response: { + actionGroup: options.actionGroup, + function: options.func, + functionResponse: { + ...(this.responseState && { responseState: this.responseState }), + responseBody: { + TEXT: { + body: this.body, + }, + }, + }, + }, + ...(this.sessionAttributes && { + sessionAttributes: this.sessionAttributes, + }), + ...(this.promptSessionAttributes && { + promptSessionAttributes: this.promptSessionAttributes, + }), + ...(this.knowledgeBasesConfiguration && { + knowledgeBasesConfiguration: this.knowledgeBasesConfiguration, + }), + }; + } +} + +export { BedrockFunctionResponse }; diff --git a/packages/event-handler/src/bedrock-agent/index.ts b/packages/event-handler/src/bedrock-agent/index.ts index a18e9dd72..1c8717f22 100644 --- a/packages/event-handler/src/bedrock-agent/index.ts +++ b/packages/event-handler/src/bedrock-agent/index.ts @@ -1 +1,2 @@ export { BedrockAgentFunctionResolver } from './BedrockAgentFunctionResolver.js'; +export { BedrockFunctionResponse } from './BedrockFunctionResponse.js'; diff --git a/packages/event-handler/src/types/bedrock-agent.ts b/packages/event-handler/src/types/bedrock-agent.ts index f70763688..b3657922d 100644 --- a/packages/event-handler/src/types/bedrock-agent.ts +++ b/packages/event-handler/src/types/bedrock-agent.ts @@ -1,45 +1,109 @@ import type { JSONValue } from '@aws-lambda-powertools/commons/types'; import type { Context } from 'aws-lambda'; +import type { BedrockAgentFunctionResolver } from '../bedrock-agent/BedrockAgentFunctionResolver.js'; +import type { BedrockFunctionResponse } from '../bedrock-agent/BedrockFunctionResponse.js'; import type { GenericLogger } from '../types/common.js'; +/** + * Configuration for a tool in the Bedrock Agent Function Resolver. + */ type Configuration = { + /** + * The name of the tool, which must be unique across all registered tools. + */ name: string; - description: string; + /** + * A description of the tool, which is optional but highly recommended. + */ + description?: string; }; +/** + * Parameter for a tool function in the Bedrock Agent Function Resolver. + * This is used to define the structure of parameters in tool functions. + */ type Parameter = { name: string; type: 'string' | 'number' | 'integer' | 'boolean' | 'array'; value: string; }; +/** + * Primitive types that can be used as parameter values in tool functions. + * This is used to define the structure of parameters in tool functions. + */ type ParameterPrimitives = string | number | boolean; +/** + * Represents a value for a parameter, which can be a primitive type or an array of values. + * This is used to define the structure of parameters in tool functions. + */ type ParameterValue = ParameterPrimitives | Array; +/** + * Function to handle tool invocations in the Bedrock Agent Function Resolver. + */ type ToolFunction> = ( params: TParams, options?: { event?: BedrockAgentFunctionEvent; context?: Context; } -) => Promise; +) => Promise; +/** + * Tool in the Bedrock Agent Function Resolver. + * + * Used to register a tool in {@link BedrockAgentFunctionResolver | `BedrockAgentFunctionResolver`}. + */ type Tool> = { handler: ToolFunction; config: Configuration; }; -type FunctionIdentifier = { +/** + * Function invocation in the Bedrock Agent Function Resolver. + * + * This is used to define the structure of function invocations in tool functions. + */ +type FunctionInvocation = { actionGroup: string; function: string; -}; - -type FunctionInvocation = FunctionIdentifier & { parameters?: Array; }; -type BedrockAgentFunctionEvent = FunctionInvocation & { +/** + * Event structure for Bedrock Agent Function invocations. + * + * @example + * ```json + * { + * "messageVersion": "1.0", + * "actionGroup": "exampleActionGroup", + * "function": "getWeather", + * "agent": { + * "name": "WeatherAgent", + * "id": "agent-id-123", + * "alias": "v1", + * "version": "1.0" + * }, + * "parameters": [{ + * "name": "location", + * "type": "string", + * "value": "Seattle" + * }], + * "inputText": "What's the weather like in Seattle?", + * "sessionId": "session-id-456", + * "sessionAttributes": { + * "userId": "user-789", + * }, + * "promptSessionAttributes": {}, + * } + * ``` + */ +type BedrockAgentFunctionEvent = { + actionGroup: string; + function: string; messageVersion: string; agent: { name: string; @@ -47,43 +111,45 @@ type BedrockAgentFunctionEvent = FunctionInvocation & { alias: string; version: string; }; + parameters?: Array; inputText: string; sessionId: string; - sessionAttributes: Record; - promptSessionAttributes: Record; -}; - -type ResponseState = 'ERROR' | 'REPROMPT'; - -type TextResponseBody = { - TEXT: { - body: string; - }; + sessionAttributes: Record; + promptSessionAttributes: Record; + knowledgeBasesConfiguration?: Record; }; -type SessionData = { - sessionAttributes?: Record; - promptSessionAttributes?: Record; -}; +/** + * Represents the state of the response from a Bedrock agent function: + * - `FAILURE`: The agent throws a `DependencyFailedException` for the current session. + * - `REPROMPT`: The agent passes a response string to the model to reprompt it. + */ +type ResponseState = 'FAILURE' | 'REPROMPT'; -type BedrockAgentFunctionResponse = SessionData & { +/** + * Response structure for a Bedrock agent function. + */ +type BedrockAgentFunctionResponse = { messageVersion: string; - response: FunctionIdentifier & { + response: { + actionGroup: string; + function: string; functionResponse: { responseState?: ResponseState; - responseBody: TextResponseBody; + responseBody: { + TEXT: { + body: string; + }; + }; }; }; + sessionAttributes?: BedrockAgentFunctionEvent['sessionAttributes']; + promptSessionAttributes?: BedrockAgentFunctionEvent['promptSessionAttributes']; + knowledgeBasesConfiguration?: BedrockAgentFunctionEvent['knowledgeBasesConfiguration']; }; -type ResponseOptions = FunctionIdentifier & - SessionData & { - body: string; - errorType?: ResponseState; - }; - /** - * Options for the {@link BedrockAgentFunctionResolver} class + * Options for the {@link BedrockAgentFunctionResolver | `BedrockAgentFunctionResolver`} class */ type ResolverOptions = { /** @@ -100,10 +166,9 @@ export type { ToolFunction, Parameter, ParameterValue, - FunctionIdentifier, FunctionInvocation, BedrockAgentFunctionEvent, BedrockAgentFunctionResponse, - ResponseOptions, ResolverOptions, + ResponseState, }; diff --git a/packages/event-handler/src/types/index.ts b/packages/event-handler/src/types/index.ts index eee1ee9f8..dfa9f7898 100644 --- a/packages/event-handler/src/types/index.ts +++ b/packages/event-handler/src/types/index.ts @@ -13,6 +13,8 @@ export type { BedrockAgentFunctionEvent, BedrockAgentFunctionResponse, ResolverOptions, + Parameter, + ResponseState, } from './bedrock-agent.js'; export type { diff --git a/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts b/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts index f0d929a66..1b21f6694 100644 --- a/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts +++ b/packages/event-handler/tests/unit/bedrock-agent/BedrockAgentFunctionResolver.test.ts @@ -1,13 +1,12 @@ import context from '@aws-lambda-powertools/testing-utils/context'; -import type { Context } from 'aws-lambda'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { BedrockFunctionResponse } from '../../../src/bedrock-agent/BedrockFunctionResponse.js'; import { BedrockAgentFunctionResolver } from '../../../src/bedrock-agent/index.js'; import type { - BedrockAgentFunctionEvent, Configuration, Parameter, ToolFunction, -} from '../../../src/types/bedrock-agent'; +} from '../../../src/types/bedrock-agent.js'; function createEvent(functionName: string, parameters?: Parameter[]) { return { @@ -115,58 +114,6 @@ describe('Class: BedrockAgentFunctionResolver', () => { expect(console.debug).toHaveBeenCalled(); }); - it('only allows five tools to be registered', async () => { - // Prepare - const app = new BedrockAgentFunctionResolver(); - - for (const num of [1, 2, 3, 4, 5]) { - app.tool( - async (params: { arg: string }) => { - return params.arg; - }, - { - name: `identity${num}`, - description: 'Returns its arg', - } - ); - } - - app.tool( - async (params: { a: number; b: number }) => { - return params.a + params.b; - }, - { - name: 'mult', - description: 'Multiplies two numbers', - } - ); - - const event = createEvent('mult', [ - { - name: 'a', - type: 'number', - value: '1', - }, - { - name: 'b', - type: 'number', - value: '2', - }, - ]); - - // Act - const actual = await app.resolve(event, context); - - // Assess - expect(console.warn).toHaveBeenLastCalledWith( - 'The maximum number of tools that can be registered is 5. Tool mult will not be registered.' - ); - expect(actual.response.function).toEqual('mult'); - expect(actual.response.functionResponse.responseBody.TEXT.body).toEqual( - 'Error: tool has not been registered in handler.' - ); - }); - it('overwrites tools with the same name and uses the latest definition', async () => { // Prepare const app = new BedrockAgentFunctionResolver(); @@ -294,52 +241,6 @@ describe('Class: BedrockAgentFunctionResolver', () => { ); }); - it('can be invoked using the decorator pattern', async () => { - // Prepare - const app = new BedrockAgentFunctionResolver(); - - class Lambda { - @app.tool({ name: 'hello', description: 'Says hello' }) - async helloWorld() { - return 'Hello, world!'; - } - - @app.tool({ name: 'add', description: 'Adds two numbers' }) - async add(params: { a: number; b: number }) { - const { a, b } = params; - return a + b; - } - - public async handler(event: BedrockAgentFunctionEvent, context: Context) { - return app.resolve(event, context); - } - } - - const lambda = new Lambda(); - - const addEvent = createEvent('add', [ - { - name: 'a', - type: 'number', - value: '1', - }, - { - name: 'b', - type: 'number', - value: '2', - }, - ]); - - // Act - const actual = await lambda.handler(addEvent, context); - - // Assess - expect(actual.response.function).toEqual('add'); - expect(actual.response.functionResponse.responseBody.TEXT.body).toEqual( - '3' - ); - }); - it.each([ { toolFunction: async () => ({ @@ -400,6 +301,13 @@ describe('Class: BedrockAgentFunctionResolver', () => { description: 'Returns undefined', }, }, + { + toolFunction: async () => '', + toolParams: { + name: 'empty-string', + description: 'Returns empty string', + }, + }, ])( 'handles functions that return $toolParams.name by returning an empty string', async ({ toolFunction, toolParams }) => { @@ -419,6 +327,35 @@ describe('Class: BedrockAgentFunctionResolver', () => { } ); + it('handles functions that return a BedrockAgentFunctionResponse', async () => { + // Prepare + const app = new BedrockAgentFunctionResolver(); + + app.tool( + async () => { + return new BedrockFunctionResponse({ + body: 'I am not sure', + responseState: 'REPROMPT', + sessionAttributes: { customAttr: 'value' }, + promptSessionAttributes: { customPromptAttr: 'promptValue' }, + }); + }, + { + name: 'custom-response', + description: 'Returns a custom BedrockAgentFunctionResponse', + } + ); + + // Act + const result = await app.resolve(createEvent('custom-response'), context); + + // Assess + expect(result.response.function).toEqual('custom-response'); + expect(result.response.functionResponse.responseBody.TEXT.body).toEqual( + 'I am not sure' + ); + }); + it('correctly parses boolean parameters', async () => { // Prepare const toolFunction: ToolFunction<{ arg: boolean }> = async ( @@ -579,33 +516,48 @@ describe('Class: BedrockAgentFunctionResolver', () => { ); }); - it('handles functions that throw errors', async () => { - // Prepare - const app = new BedrockAgentFunctionResolver(); + it.each([ + { + label: 'actual error', + toThrow: new Error('Something went wrong'), + expected: + 'Unable to complete tool execution due to Error - Something went wrong', + }, + { + label: 'string', + toThrow: 'Something went wrong', + expected: 'Unable to complete tool execution due to Something went wrong', + }, + ])( + 'handles functions that throw errors $label', + async ({ toThrow, expected }) => { + // Prepare + const app = new BedrockAgentFunctionResolver(); - app.tool( - async (_params, _options) => { - throw new Error('Something went wrong'); - }, - { - name: 'error-tool', - description: 'Throws an error', - } - ); + app.tool( + async (_params, _options) => { + throw toThrow; + }, + { + name: 'error-tool', + description: 'Throws an error', + } + ); - // Act - const actual = await app.resolve(createEvent('error-tool', []), context); + // Act + const actual = await app.resolve(createEvent('error-tool', []), context); - // Assess - expect(actual.response.function).toEqual('error-tool'); - expect(actual.response.functionResponse.responseBody.TEXT.body).toEqual( - 'Error when invoking tool: Error: Something went wrong' - ); - expect(console.error).toHaveBeenCalledWith( - 'An error occurred in tool error-tool.', - new Error('Something went wrong') - ); - }); + // Assess + expect(actual.response.function).toEqual('error-tool'); + expect(actual.response.functionResponse.responseBody.TEXT.body).toEqual( + expected + ); + expect(console.error).toHaveBeenCalledWith( + 'An error occurred in tool error-tool.', + new Error('Something went wrong') + ); + } + ); it('returns a fully structured BedrockAgentFunctionResponse', async () => { // Prepare @@ -621,14 +573,6 @@ describe('Class: BedrockAgentFunctionResolver', () => { } ); - const customSessionAttrs = { - sessionAttr: '12345', - }; - - const customPromptAttrs = { - promptAttr: 'promptAttr', - }; - const customEvent = { ...createEvent('greeting', [ { @@ -638,8 +582,16 @@ describe('Class: BedrockAgentFunctionResolver', () => { }, ]), actionGroup: 'actionGroup', - sessionAttributes: customSessionAttrs, - promptSessionAttributes: customPromptAttrs, + sessionAttributes: { + sessionAttr: '12345', + }, + promptSessionAttributes: { + promptAttr: 'promptAttr', + }, + knowledgeBasesConfiguration: { + knowledgeBase1: { enabled: true }, + knowledgeBase2: { enabled: false }, + }, }; // Act @@ -659,8 +611,9 @@ describe('Class: BedrockAgentFunctionResolver', () => { }, }, }, - sessionAttributes: customSessionAttrs, - promptSessionAttributes: customPromptAttrs, + sessionAttributes: customEvent.sessionAttributes, + promptSessionAttributes: customEvent.promptSessionAttributes, + knowledgeBasesConfiguration: customEvent.knowledgeBasesConfiguration, }); }); }); diff --git a/packages/event-handler/typedoc.json b/packages/event-handler/typedoc.json index 2ce51a11f..35095bca8 100644 --- a/packages/event-handler/typedoc.json +++ b/packages/event-handler/typedoc.json @@ -4,6 +4,7 @@ ], "entryPoints": [ "./src/appsync-events/index.ts", + "./src/bedrock-agent/index.ts", "./src/types/index.ts", ], "readme": "README.md"