From c729d29a1342066ea552f127263b0d123d5e0aa2 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Mon, 8 Jan 2024 16:10:12 +0100 Subject: [PATCH 01/46] chore(docs): add bedrock agent resolver documentation --- .../_openapi_customization_intro.md | 5 + .../_openapi_customization_metadata.md | 15 ++ .../_openapi_customization_operations.md | 14 ++ .../_openapi_customization_parameters.md | 25 +++ .../_openapi_customization_swagger.md | 5 + docs/core/event_handler/_swagger_ui.md | 10 + docs/core/event_handler/api_gateway.md | 74 +------- docs/core/event_handler/bedrock_agents.md | 171 ++++++++++++++++++ .../sam/template.yaml | 23 +++ .../src/assert_bedrock_agent_response.py | 29 +++ .../assert_bedrock_agent_response_module.py | 25 +++ .../src/customizing_api_metadata.py | 33 ++++ .../src/customizing_api_operations.py | 33 ++++ .../src/customizing_swagger.py | 29 +++ .../src/customizing_swagger_middlewares.py | 40 ++++ .../src/enabling_swagger.py | 22 +++ .../src/generating_openapi_schema.json | 94 ++++++++++ .../src/generating_openapi_schema.py | 25 +++ .../src/getting_started.json | 28 +++ .../src/getting_started.py | 21 +++ .../src/getting_started_output.json | 26 +++ mkdocs.yml | 1 + 22 files changed, 680 insertions(+), 68 deletions(-) create mode 100644 docs/core/event_handler/_openapi_customization_intro.md create mode 100644 docs/core/event_handler/_openapi_customization_metadata.md create mode 100644 docs/core/event_handler/_openapi_customization_operations.md create mode 100644 docs/core/event_handler/_openapi_customization_parameters.md create mode 100644 docs/core/event_handler/_openapi_customization_swagger.md create mode 100644 docs/core/event_handler/_swagger_ui.md create mode 100644 docs/core/event_handler/bedrock_agents.md create mode 100644 examples/event_handler_bedrock_agents/sam/template.yaml create mode 100644 examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py create mode 100644 examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py create mode 100644 examples/event_handler_bedrock_agents/src/customizing_api_metadata.py create mode 100644 examples/event_handler_bedrock_agents/src/customizing_api_operations.py create mode 100644 examples/event_handler_bedrock_agents/src/customizing_swagger.py create mode 100644 examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py create mode 100644 examples/event_handler_bedrock_agents/src/enabling_swagger.py create mode 100644 examples/event_handler_bedrock_agents/src/generating_openapi_schema.json create mode 100644 examples/event_handler_bedrock_agents/src/generating_openapi_schema.py create mode 100644 examples/event_handler_bedrock_agents/src/getting_started.json create mode 100644 examples/event_handler_bedrock_agents/src/getting_started.py create mode 100644 examples/event_handler_bedrock_agents/src/getting_started_output.json diff --git a/docs/core/event_handler/_openapi_customization_intro.md b/docs/core/event_handler/_openapi_customization_intro.md new file mode 100644 index 00000000000..e7dd3f56f49 --- /dev/null +++ b/docs/core/event_handler/_openapi_customization_intro.md @@ -0,0 +1,5 @@ + +In OpenAPI documentation tools like [SwaggerUI](api-gateway.md#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. + +???+ note + We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE). diff --git a/docs/core/event_handler/_openapi_customization_metadata.md b/docs/core/event_handler/_openapi_customization_metadata.md new file mode 100644 index 00000000000..c578f343e84 --- /dev/null +++ b/docs/core/event_handler/_openapi_customization_metadata.md @@ -0,0 +1,15 @@ + +Defining and customizing OpenAPI metadata gives detailed, top-level information about your API. Here's the method to set and tailor this metadata: + +| Field Name | Type | Description | +| ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `title` | `str` | The title for your API. It should be a concise, specific name that can be used to identify the API in documentation or listings. | +| `version` | `str` | The version of the API you are documenting. This could reflect the release iteration of the API and helps clients understand the evolution of the API. | +| `openapi_version` | `str` | Specifies the version of the OpenAPI Specification on which your API is based. For most contemporary APIs, the default value would be `3.0.0` or higher. | +| `summary` | `str` | A short and informative summary that can provide an overview of what the API does. This can be the same as or different from the title but should add context or information. | +| `description` | `str` | A verbose description that can include Markdown formatting, providing a full explanation of the API's purpose, functionalities, and general usage instructions. | +| `tags` | `List[str]` | A collection of tags that categorize endpoints for better organization and navigation within the documentation. This can group endpoints by their functionality or other criteria. | +| `servers` | `List[Server]` | An array of Server objects, which specify the URL to the server and a description for its environment (production, staging, development, etc.), providing connectivity information. | +| `terms_of_service` | `str` | A URL that points to the terms of service for your API. This could provide legal information and user responsibilities related to the usage of the API. | +| `contact` | `Contact` | A Contact object containing contact details of the organization or individuals maintaining the API. This may include fields such as name, URL, and email. | +| `license_info` | `License` | A License object providing the license details for the API, typically including the name of the license and the URL to the full license text. | diff --git a/docs/core/event_handler/_openapi_customization_operations.md b/docs/core/event_handler/_openapi_customization_operations.md new file mode 100644 index 00000000000..7b0591830ed --- /dev/null +++ b/docs/core/event_handler/_openapi_customization_operations.md @@ -0,0 +1,14 @@ + +Customize your API endpoints by adding metadata to endpoint definitions. This provides descriptive documentation for API consumers and gives extra instructions to the framework. + +Here's a breakdown of various customizable fields: + +| Field Name | Type | Description | +| ---------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `summary` | `str` | A concise overview of the main functionality of the endpoint. This brief introduction is usually displayed in autogenerated API documentation and helps consumers quickly understand what the endpoint does. | +| `description` | `str` | A more detailed explanation of the endpoint, which can include information about the operation's behavior, including side effects, error states, and other operational guidelines. | +| `responses` | `Dict[int, Dict[str, Any]]` | A dictionary that maps each HTTP status code to a Response Object as defined by the [OpenAPI Specification](https://swagger.io/specification/#response-object). This allows you to describe expected responses, including default or error messages, and their corresponding schemas for different status codes. | +| `response_description` | `str` | Provides the default textual description of the response sent by the endpoint when the operation is successful. It is intended to give a human-readable understanding of the result. | +| `tags` | `List[str]` | Tags are a way to categorize and group endpoints within the API documentation. They can help organize the operations by resources or other heuristic. | +| `operation_id` | `str` | A unique identifier for the operation, which can be used for referencing this operation in documentation or code. This ID must be unique across all operations described in the API. | +| `include_in_schema` | `bool` | A boolean value that determines whether or not this operation should be included in the OpenAPI schema. Setting it to `False` can hide the endpoint from generated documentation and schema exports, which might be useful for private or experimental endpoints. | diff --git a/docs/core/event_handler/_openapi_customization_parameters.md b/docs/core/event_handler/_openapi_customization_parameters.md new file mode 100644 index 00000000000..f096441b33a --- /dev/null +++ b/docs/core/event_handler/_openapi_customization_parameters.md @@ -0,0 +1,25 @@ + +Whenever you use OpenAPI parameters to validate [query strings](api_gateway.md#validating-query-strings) or [path parameters](api_gateway.md#validating-path-parameters), you can enhance validation and OpenAPI documentation by using any of these parameters: + +| Field name | Type | Description | +| --------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data | +| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) | +| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) | +| `description` | `str` | Human-readable description | +| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers | +| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers | +| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers | +| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers | +| `min_length` | `int` | Minimum length for strings | +| `max_length` | `int` | Maximum length for strings | +| `pattern` | `string` | A regular expression that the string must match. | +| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details | +| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers | +| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers | +| `max_digits` | `int` | Maximum number of allow digits for strings | +| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers | +| `examples` | `List\[Any\]` | List of examples of the field | +| `deprecated` | `bool` | Marks the field as deprecated | +| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema | +| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property | diff --git a/docs/core/event_handler/_openapi_customization_swagger.md b/docs/core/event_handler/_openapi_customization_swagger.md new file mode 100644 index 00000000000..2aec2f08dee --- /dev/null +++ b/docs/core/event_handler/_openapi_customization_swagger.md @@ -0,0 +1,5 @@ + +???+note "Customizing the Swagger metadata" + The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](api_gateway.md#customizing-openapi-metadata). + +The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets. diff --git a/docs/core/event_handler/_swagger_ui.md b/docs/core/event_handler/_swagger_ui.md new file mode 100644 index 00000000000..00194bf950d --- /dev/null +++ b/docs/core/event_handler/_swagger_ui.md @@ -0,0 +1,10 @@ + +Behind the scenes, the [data validation](api_gateway.md#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. + +There are some important **caveats** that you should know before enabling it: + +| Caveat | Description | +| ------------------------------------------------ |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Swagger UI is **publicly accessible by default** | When using `enable_swagger` method, you can [protect sensitive API endpoints by implementing a custom middleware](api_gateway.md#customizing-swagger-ui) using your preferred authorization mechanism. | +| **No micro-functions support** yet | Swagger UI is enabled on a per resolver instance which will limit its accuracy here. | +| You need to expose **new routes** | You'll need to expose the following paths to Lambda: `/swagger`, `/swagger.css`, `/swagger.js`; ignore if you're routing all paths already. | diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 005ac3a4b7b..8d03cc87b37 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -476,15 +476,7 @@ We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 4 !!! note "This feature requires [data validation](#data-validation) feature to be enabled." -Behind the scenes, the [data validation](#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. - -There are some important **caveats** that you should know before enabling it: - -| Caveat | Description | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Swagger UI is **publicly accessible by default** | When using `enable_swagger` method, you can [protect sensitive API endpoints by implementing a custom middleware](#customizing-swagger-ui) using your preferred authorization mechanism. | -| **No micro-functions support** yet | Swagger UI is enabled on a per resolver instance which will limit its accuracy here. | -| You need to expose **new routes** | You'll need to expose the following paths to Lambda: `/swagger`, `/swagger.css`, `/swagger.js`; ignore if you're routing all paths already. | +--8<-- "docs/core/event_handler/_swagger_ui.md" ```python hl_lines="12-13" title="enabling_swagger.py" --8<-- "examples/event_handler_rest/src/enabling_swagger.py" @@ -917,53 +909,15 @@ This will enable full tracebacks errors in the response, print request and respo When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your API's parameters. -In OpenAPI documentation tools like [SwaggerUI](#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. - -???+ note - We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE). +--8<-- "docs/core/event_handler/_openapi_customization_intro.md" #### Customizing OpenAPI parameters -Whenever you use OpenAPI parameters to validate [query strings](#validating-query-strings) or [path parameters](#validating-path-parameters), you can enhance validation and OpenAPI documentation by using any of these parameters: - -| Field name | Type | Description | -| --------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data | -| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) | -| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) | -| `description` | `str` | Human-readable description | -| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers | -| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers | -| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers | -| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers | -| `min_length` | `int` | Minimum length for strings | -| `max_length` | `int` | Maximum length for strings | -| `pattern` | `string` | A regular expression that the string must match. | -| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details | -| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers | -| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers | -| `max_digits` | `int` | Maximum number of allow digits for strings | -| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers | -| `examples` | `List\[Any\]` | List of examples of the field | -| `deprecated` | `bool` | Marks the field as deprecated | -| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema | -| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property | +--8<-- "docs/core/event_handler/_openapi_customization_parameters.md" #### Customizing API operations -Customize your API endpoints by adding metadata to endpoint definitions. This provides descriptive documentation for API consumers and gives extra instructions to the framework. - -Here's a breakdown of various customizable fields: - -| Field Name | Type | Description | -| ---------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `summary` | `str` | A concise overview of the main functionality of the endpoint. This brief introduction is usually displayed in autogenerated API documentation and helps consumers quickly understand what the endpoint does. | -| `description` | `str` | A more detailed explanation of the endpoint, which can include information about the operation's behavior, including side effects, error states, and other operational guidelines. | -| `responses` | `Dict[int, Dict[str, Any]]` | A dictionary that maps each HTTP status code to a Response Object as defined by the [OpenAPI Specification](https://swagger.io/specification/#response-object). This allows you to describe expected responses, including default or error messages, and their corresponding schemas for different status codes. | -| `response_description` | `str` | Provides the default textual description of the response sent by the endpoint when the operation is successful. It is intended to give a human-readable understanding of the result. | -| `tags` | `List[str]` | Tags are a way to categorize and group endpoints within the API documentation. They can help organize the operations by resources or other heuristic. | -| `operation_id` | `str` | A unique identifier for the operation, which can be used for referencing this operation in documentation or code. This ID must be unique across all operations described in the API. | -| `include_in_schema` | `bool` | A boolean value that determines whether or not this operation should be included in the OpenAPI schema. Setting it to `False` can hide the endpoint from generated documentation and schema exports, which might be useful for private or experimental endpoints. | +--8<-- "docs/core/event_handler/_openapi_customization_operations.md" To implement these customizations, include extra parameters when defining your routes: @@ -973,10 +927,7 @@ To implement these customizations, include extra parameters when defining your r #### Customizing Swagger UI -???+note "Customizing the Swagger metadata" - The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](#customizing-openapi-metadata). - -The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets. +--8<-- "docs/core/event_handler/_openapi_customization_swagger.md" Below is an example configuration for serving Swagger UI from a custom path or CDN, with assets like CSS and JavaScript loading from a chosen CDN base URL. @@ -996,20 +947,7 @@ Below is an example configuration for serving Swagger UI from a custom path or C #### Customizing OpenAPI metadata -Defining and customizing OpenAPI metadata gives detailed, top-level information about your API. Here's the method to set and tailor this metadata: - -| Field Name | Type | Description | -| ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `title` | `str` | The title for your API. It should be a concise, specific name that can be used to identify the API in documentation or listings. | -| `version` | `str` | The version of the API you are documenting. This could reflect the release iteration of the API and helps clients understand the evolution of the API. | -| `openapi_version` | `str` | Specifies the version of the OpenAPI Specification on which your API is based. For most contemporary APIs, the default value would be `3.0.0` or higher. | -| `summary` | `str` | A short and informative summary that can provide an overview of what the API does. This can be the same as or different from the title but should add context or information. | -| `description` | `str` | A verbose description that can include Markdown formatting, providing a full explanation of the API's purpose, functionalities, and general usage instructions. | -| `tags` | `List[str]` | A collection of tags that categorize endpoints for better organization and navigation within the documentation. This can group endpoints by their functionality or other criteria. | -| `servers` | `List[Server]` | An array of Server objects, which specify the URL to the server and a description for its environment (production, staging, development, etc.), providing connectivity information. | -| `terms_of_service` | `str` | A URL that points to the terms of service for your API. This could provide legal information and user responsibilities related to the usage of the API. | -| `contact` | `Contact` | A Contact object containing contact details of the organization or individuals maintaining the API. This may include fields such as name, URL, and email. | -| `license_info` | `License` | A License object providing the license details for the API, typically including the name of the license and the URL to the full license text. | +--8<-- "docs/core/event_handler/_openapi_customization_metadata.md" Include extra parameters when exporting your OpenAPI specification to apply these customizations: diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md new file mode 100644 index 00000000000..e14c7ecab7d --- /dev/null +++ b/docs/core/event_handler/bedrock_agents.md @@ -0,0 +1,171 @@ +--- +title: Bedrock Agents +description: Core utility +--- + +Event handler for Amazon Bedrock Agents, including auto generation of OpenAPI schemas. + +## Key features + +* Same declarative syntax as the [other Powertools event handlers](api_gateway.md) +* Drastically reduce the boilerplate to build Agents for Amazon Bedrock +* Automatic generation of OpenAPI schemas from the API +* Built-in data validation for requests/responses + +## Getting started + +In order to build Bedrock Agents, you need: + +* Create the Lambda function that defines the business logic for the action that your agent will carry out +* Create an OpenAPI schema with the API description, structure, and parameters for the action group. +* Ensure that Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). + +AWS Lambda Powertools facilitates the process by providing support for the development of the Lambda function and the creation of the OpenAPI specification. + +!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." + +You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. + +As of now, both Pydantic V1 and V2 are supported. For a future major version, we will only support Pydantic V2. + +### Your first Agent + +To create a Bedrock Agent, use the `BedrockAgentResolver` to annotate your actions. This is +similar to the way [all the other Powertools](api_gateway.md) resolvers work. + +Be aware that it's important to include a description for each API endpoint. The description is essential because it provides Bedrock Agents with an understanding of the primary function of your API action. + +=== "Lambda handler" + + ```python hl_lines="4 9 12 21" + --8<-- "examples/event_handler_bedrock_agents/src/getting_started.py" + ``` + + 1. `description` is a required field in order for Bedrock Agents to work. + +=== "Input payload" + + ```json hl_lines="7 9 16" + --8<-- "examples/event_handler_bedrock_agents/src/getting_started.json" + ``` + +=== "Output payload" + + ```json hl_lines="12-14" + --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" + ``` + +The resolvers utilized by Bedrock Agents are also compatible with the full suite of Powertools utilities. This ensures seamless integration and functionality across the different tools provided by Powertools when working with Bedrock Agents. + +### Generating OpenAPI schemas + +To create a schema for your API, use the `get_openapi_json_schema` function provided by the Bedrock Agent resolver. This function will produce a JSON-serialized string that represents your schema. You have the option to either display this string output or save it to a file for future reference. + +=== "Generating the OpenAPI schem" + + ```python hl_lines="24 25" + --8<-- "examples/event_handler_bedrock_agents/src/generating_openapi_schema.py" + ``` + +=== "OpenAPI schema" + + ```json hl_lines="13 16 24" + --8<-- "examples/event_handler_bedrock_agents/src/generating_openapi_schema.json" + ``` + +To obtain the OpenAPI schema, execute the Python script from the command line interface (CLI). Upon execution, the script will generate the schema, which will be output directly to the console. + +### Creating your Agent in the AWS Console + +To create a Bedrock Agent, you should refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. + +During the creation process, when the user interface (UI) prompts you for an OpenAPI specification, you should input the specification that was generated in the previous step. This is the spec that you obtained by running your Python script, which produced the OpenAPI schema as output. + +### Enabling the Swagger UI + +--8<-- "docs/core/event_handler/_swagger_ui.md" + +```python hl_lines="10" title="enabling_swagger.py" +--8<-- "examples/event_handler_bedrock_agents/src/enabling_swagger.py" +``` + +1. `enable_swagger` creates a route to serve Swagger UI and allows quick customizations.

You can also include middlewares to protect or enhance the overall experience. + +## Advanced + +### Additional metadata + +To enrich the view that Bedrock Agents has of your Lambda functions, we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your API's parameters. + +--8<-- "docs/core/event_handler/_openapi_customization_intro.md" + +#### Customizing OpenAPI parameters + +--8<-- "docs/core/event_handler/_openapi_customization_parameters.md" + +#### Customizing API operations + +--8<-- "docs/core/event_handler/_openapi_customization_operations.md" + +To implement these customizations, include extra parameters when defining your routes: + +```python hl_lines="14-23 25" title="customizing_api_operations.py" +--8<-- "examples/event_handler_bedrock_agents/src/customizing_api_operations.py" +``` + +#### Customizing Swagger UI + +--8<-- "docs/core/event_handler/_openapi_customization_swagger.md" + +Below is an example configuration for serving Swagger UI from a custom path or CDN, with assets like CSS and JavaScript loading from a chosen CDN base URL. + +=== "customizing_swagger.py" + + ```python hl_lines="10" + --8<-- "examples/event_handler_bedrock_agents/src/customizing_swagger.py" + ``` + +=== "customizing_swagger_middlewares.py" + +A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. + + ```python hl_lines="7 13-18 21" + --8<-- "examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py" + ``` + +#### Customizing OpenAPI metadata + +--8<-- "docs/core/event_handler/_openapi_customization_metadata.md" + +Include extra parameters when exporting your OpenAPI specification to apply these customizations: + +=== "customizing_api_metadata.py" + + ```python hl_lines="25-31" + --8<-- "examples/event_handler_bedrock_agents/src/customizing_api_metadata.py" + ``` + +### Data validation + +The Bedrock Agents Resolver allows for the clear definition of the expected format for incoming data and responses. By delegating data validation tasks to the Event Handler resolvers, you can significantly reduce the amount of repetitive code in your project. + +For detailed guidance on implementing this feature, please consult the [REST API validation documentationi](api_gateway.md#data-validation). There, you'll find step-by-step instructions on how to apply data validation when using the resolver. + +???+ note + When using the Bedrock Agent resolver, there's no need to add the `enable_validation` parameter, as it's enabled by default. + +## Testing your code + +You can test your routes by passing a proxy event request with required params. + +=== "assert_bedrock_agent_response.py" + + ```python hl_lines="21-23" + --8<-- "examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py" + ``` + +=== "assert_bedrock_agent_response_module.py" + + ```python + --8<-- "examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py" + ``` diff --git a/examples/event_handler_bedrock_agents/sam/template.yaml b/examples/event_handler_bedrock_agents/sam/template.yaml new file mode 100644 index 00000000000..97b13b23ff6 --- /dev/null +++ b/examples/event_handler_bedrock_agents/sam/template.yaml @@ -0,0 +1,23 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: Hello world event handler Bedrock Agents + +Globals: + Function: + Timeout: 5 + Runtime: python3.12 + Tracing: Active + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: INFO + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_SERVICE_NAME: example + +Resources: + ApiFunction: + Type: AWS::Serverless::Function + Properties: + Handler: getting_started.lambda_handler + CodeUri: ../src + Description: Bedrock Agents handler function diff --git a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py new file mode 100644 index 00000000000..eceffe20434 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass + +import assert_bedrock_agent_response_module +import pytest + + +@pytest.fixture +def lambda_context(): + @dataclass + class LambdaContext: + function_name: str = "test" + memory_limit_in_mb: int = 128 + invoked_function_arn: str = "arn:aws:lambda:eu-west-1:123456789012:function:test" + aws_request_id: str = "da658bd3-2d6f-4e7b-8ec2-937234644fdc" + + return LambdaContext() + + +def test_lambda_handler(lambda_context): + minimal_event = { + "apiPath": "/todos", + "httpMethod": "GET", + "inputText": "What is the current time?", + } + # Example of Bedrock Agent API request event: + # https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-input + ret = assert_bedrock_agent_response_module.lambda_handler(minimal_event, lambda_context) + assert ret["response"]["httpStatuScode"] == 200 + assert ret["response"]["responseBody"]["application/json"]["body"] != "" diff --git a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py new file mode 100644 index 00000000000..58889da7110 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py @@ -0,0 +1,25 @@ +import requests +from requests import Response + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() +app = BedrockAgentResolver() + + +@app.get("/todos", description="Gets the first 10 todos") +@tracer.capture_method +def get_todos(): + todos: Response = requests.get("https://jsonplaceholder.typicode.com/todos") + todos.raise_for_status() + + return {"todos": todos.json()[:10]} + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/customizing_api_metadata.py b/examples/event_handler_bedrock_agents/src/customizing_api_metadata.py new file mode 100644 index 00000000000..f5d9ff6ba3d --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/customizing_api_metadata.py @@ -0,0 +1,33 @@ +import requests + +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.event_handler.openapi.models import Contact, Server +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = BedrockAgentResolver() + + +@app.get("/todos/", description="Gets a todo title by ID") +def get_todo_title(todo_id: int) -> str: + todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}") + todo.raise_for_status() + + return todo.json()["title"] + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) + + +if __name__ == "__main__": + print( + app.get_openapi_json_schema( + title="TODO's API", + version="1.21.3", + summary="API to manage TODOs", + description="This API implements all the CRUD operations for the TODO app", + tags=["todos"], + servers=[Server(url="https://stg.example.org/orders", description="Staging server")], + contact=Contact(name="John Smith", email="john@smith.com"), + ), + ) diff --git a/examples/event_handler_bedrock_agents/src/customizing_api_operations.py b/examples/event_handler_bedrock_agents/src/customizing_api_operations.py new file mode 100644 index 00000000000..004ef77438e --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/customizing_api_operations.py @@ -0,0 +1,33 @@ +from typing import Annotated + +import requests + +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.event_handler.openapi.params import Query +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = BedrockAgentResolver() + + +@app.get( + "/todos/", + summary="Retrieves a todo item, returning it's title", + description="Loads a todo item identified by the `todo_id`", + response_description="The todo title", + responses={ + 200: {"description": "Todo item found"}, + 404: { + "description": "Item not found", + }, + }, + tags=["Todos"], +) +def get_todo_title(todo_id: Annotated[int, Query(description="The ID of the todo item to get the title from")]) -> str: + todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}") + todo.raise_for_status() + + return todo.json()["title"] + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/customizing_swagger.py b/examples/event_handler_bedrock_agents/src/customizing_swagger.py new file mode 100644 index 00000000000..ecd4c9c315a --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/customizing_swagger.py @@ -0,0 +1,29 @@ +from typing import List + +import requests +from pydantic import BaseModel, EmailStr, Field + +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = BedrockAgentResolver() +app.enable_swagger(path="/_swagger", swagger_base_url="https://cdn.example.com/path/to/assets/") + + +class Todo(BaseModel): + userId: int + id_: int = Field(alias="id") + title: str + completed: bool + + +@app.get("/todos", description="Finds all todos by email") +def get_todos_by_email(email: EmailStr) -> List[Todo]: + todos = requests.get(f"https://jsonplaceholder.typicode.com/todos?email={email}") + todos.raise_for_status() + + return todos.json() + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py b/examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py new file mode 100644 index 00000000000..a574ede5035 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py @@ -0,0 +1,40 @@ +from typing import List + +import requests +from pydantic import BaseModel, EmailStr, Field + +from aws_lambda_powertools.event_handler import BedrockAgentResolver, Response +from aws_lambda_powertools.event_handler.middlewares import NextMiddleware +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = BedrockAgentResolver() + + +def swagger_middleware(app: BedrockAgentResolver, next_middleware: NextMiddleware) -> Response: + is_authenticated = ... + if not is_authenticated: + return Response(status_code=400, body="Unauthorized") + + return next_middleware(app) + + +app.enable_swagger(middlewares=[swagger_middleware]) + + +class Todo(BaseModel): + userId: int + id_: int = Field(alias="id") + title: str + completed: bool + + +@app.get("/todos", description="Finds all todos by email") +def get_todos_by_email(email: EmailStr) -> List[Todo]: + todos = requests.get(f"https://jsonplaceholder.typicode.com/todos?email={email}") + todos.raise_for_status() + + return todos.json() + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/enabling_swagger.py b/examples/event_handler_bedrock_agents/src/enabling_swagger.py new file mode 100644 index 00000000000..651d7034170 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/enabling_swagger.py @@ -0,0 +1,22 @@ +from time import time + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() +app = BedrockAgentResolver() +app.enable_swagger(path="/swagger") # (1)! + + +@app.get("/current_time", description="Gets the current time in seconds") +@tracer.capture_method +def current_time() -> int: + return int(time()) + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event: dict, context: LambdaContext): + return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/generating_openapi_schema.json b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.json new file mode 100644 index 00000000000..2ef91f5392f --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.json @@ -0,0 +1,94 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Powertools API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/" + } + ], + "paths": { + "/current_time": { + "get": { + "summary": "GET /current_time", + "description": "Gets the current time in seconds", + "operationId": "current_time_current_time_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "integer", + "title": "Return" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + } + } +} diff --git a/examples/event_handler_bedrock_agents/src/generating_openapi_schema.py b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.py new file mode 100644 index 00000000000..64bedee743e --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.py @@ -0,0 +1,25 @@ +from time import time + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() +app = BedrockAgentResolver() + + +@app.get("/current_time", description="Gets the current time in seconds") +@tracer.capture_method +def current_time() -> int: + return int(time()) + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event: dict, context: LambdaContext): + return app.resolve(event, context) + + +if __name__ == "__main__": + print(app.get_openapi_json_schema()) diff --git a/examples/event_handler_bedrock_agents/src/getting_started.json b/examples/event_handler_bedrock_agents/src/getting_started.json new file mode 100644 index 00000000000..79b55099e1e --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/getting_started.json @@ -0,0 +1,28 @@ +{ + "level": "INFO", + "location": "lambda_handler:451", + "message": { + "sessionId": "123456789012345", + "sessionAttributes": {}, + "inputText": "What is the current time?", + "promptSessionAttributes": {}, + "apiPath": "/current_time", + "agent": { + "name": "TimeAgent", + "version": "DRAFT", + "id": "XLHH72XNF2", + "alias": "TSTALIASID" + }, + "httpMethod": "GET", + "messageVersion": "1.0", + "actionGroup": "CurrentTime" + }, + "timestamp": "2024-01-08 09:42:45,135+0000", + "service": "bedrock", + "cold_start": true, + "function_name": "bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", + "function_memory_size": "256", + "function_arn": "arn:aws:lambda:us-east-1:123456789012:function:bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", + "function_request_id": "3c0d5437-7873-4a9c-86c9-a55c262f22d9", + "xray_trace_id": "1-659bc393-6d3332e24321ce6b333c398c" +} diff --git a/examples/event_handler_bedrock_agents/src/getting_started.py b/examples/event_handler_bedrock_agents/src/getting_started.py new file mode 100644 index 00000000000..2a9b07b7a2f --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/getting_started.py @@ -0,0 +1,21 @@ +from time import time + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() +app = BedrockAgentResolver() + + +@app.get("/current_time", description="Gets the current time in seconds") # (1)! +@tracer.capture_method +def current_time() -> int: + return int(time()) + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event: dict, context: LambdaContext): + return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/getting_started_output.json b/examples/event_handler_bedrock_agents/src/getting_started_output.json new file mode 100644 index 00000000000..1aeacee9a75 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/getting_started_output.json @@ -0,0 +1,26 @@ +{ + "level": "INFO", + "location": "lambda_handler:54", + "message": { + "messageVersion": "1.0", + "response": { + "actionGroup": "CurrentTime", + "apiPath": "/current_time", + "httpMethod": "GET", + "httpStatusCode": 200, + "responseBody": { + "application/json": { + "body": "1704708165" + } + } + } + }, + "timestamp": "2024-01-08 10:01:38,082+0000", + "service": "bedrock", + "cold_start": true, + "function_name": "bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", + "function_memory_size": "256", + "function_arn": "arn:aws:lambda:us-east-1:123456789012:function:bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", + "function_request_id": "3c0d5437-7873-4a9c-86c9-a55c262f22d9", + "xray_trace_id": "1-659bc393-6d3332e24321ce6b333c398c" +} diff --git a/mkdocs.yml b/mkdocs.yml index 0a844fd392f..075624d0455 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,6 +22,7 @@ nav: - Event Handler: - core/event_handler/api_gateway.md - core/event_handler/appsync.md + - core/event_handler/bedrock_agents.md - utilities/parameters.md - utilities/batch.md - utilities/typing.md From 28958b1331b74a3140f43ef361d19c766e6c38ff Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 9 Feb 2024 18:07:04 +0000 Subject: [PATCH 02/46] chore: refactor documentation --- .../_openapi_customization_swagger.md | 5 - docs/core/event_handler/_swagger_ui.md | 10 -- docs/core/event_handler/api_gateway.md | 15 ++- docs/core/event_handler/bedrock_agents.md | 93 +++++++++---------- .../src/generating_openapi_schema.py | 4 +- .../src/getting_started.py | 2 +- 6 files changed, 61 insertions(+), 68 deletions(-) delete mode 100644 docs/core/event_handler/_openapi_customization_swagger.md delete mode 100644 docs/core/event_handler/_swagger_ui.md diff --git a/docs/core/event_handler/_openapi_customization_swagger.md b/docs/core/event_handler/_openapi_customization_swagger.md deleted file mode 100644 index 2aec2f08dee..00000000000 --- a/docs/core/event_handler/_openapi_customization_swagger.md +++ /dev/null @@ -1,5 +0,0 @@ - -???+note "Customizing the Swagger metadata" - The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](api_gateway.md#customizing-openapi-metadata). - -The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets. diff --git a/docs/core/event_handler/_swagger_ui.md b/docs/core/event_handler/_swagger_ui.md deleted file mode 100644 index 00194bf950d..00000000000 --- a/docs/core/event_handler/_swagger_ui.md +++ /dev/null @@ -1,10 +0,0 @@ - -Behind the scenes, the [data validation](api_gateway.md#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. - -There are some important **caveats** that you should know before enabling it: - -| Caveat | Description | -| ------------------------------------------------ |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Swagger UI is **publicly accessible by default** | When using `enable_swagger` method, you can [protect sensitive API endpoints by implementing a custom middleware](api_gateway.md#customizing-swagger-ui) using your preferred authorization mechanism. | -| **No micro-functions support** yet | Swagger UI is enabled on a per resolver instance which will limit its accuracy here. | -| You need to expose **new routes** | You'll need to expose the following paths to Lambda: `/swagger`, `/swagger.css`, `/swagger.js`; ignore if you're routing all paths already. | diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 8d03cc87b37..58b1adae5d1 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -476,7 +476,15 @@ We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 4 !!! note "This feature requires [data validation](#data-validation) feature to be enabled." ---8<-- "docs/core/event_handler/_swagger_ui.md" +Behind the scenes, the [data validation](api_gateway.md#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. + +There are some important **caveats** that you should know before enabling it: + +| Caveat | Description | +| ------------------------------------------------ |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Swagger UI is **publicly accessible by default** | When using `enable_swagger` method, you can [protect sensitive API endpoints by implementing a custom middleware](api_gateway.md#customizing-swagger-ui) using your preferred authorization mechanism. | +| **No micro-functions support** yet | Swagger UI is enabled on a per resolver instance which will limit its accuracy here. | +| You need to expose **new routes** | You'll need to expose the following paths to Lambda: `/swagger`, `/swagger.css`, `/swagger.js`; ignore if you're routing all paths already. | ```python hl_lines="12-13" title="enabling_swagger.py" --8<-- "examples/event_handler_rest/src/enabling_swagger.py" @@ -927,7 +935,10 @@ To implement these customizations, include extra parameters when defining your r #### Customizing Swagger UI ---8<-- "docs/core/event_handler/_openapi_customization_swagger.md" +???+note "Customizing the Swagger metadata" + The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](api_gateway.md#customizing-openapi-metadata). + +The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets. Below is an example configuration for serving Swagger UI from a custom path or CDN, with assets like CSS and JavaScript loading from a chosen CDN base URL. diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index e14c7ecab7d..23ab064ba5a 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -9,31 +9,41 @@ Event handler for Amazon Bedrock Agents, including auto generation of OpenAPI sc * Same declarative syntax as the [other Powertools event handlers](api_gateway.md) * Drastically reduce the boilerplate to build Agents for Amazon Bedrock -* Automatic generation of OpenAPI schemas from the API +* Automatic generation of OpenAPI schemas from your business logic code * Built-in data validation for requests/responses ## Getting started -In order to build Bedrock Agents, you need: +```mermaid +flowchart LR + Bedrock <--> Agent + Agent -- invokes --> Lambda[Lambda function] + Agent -- consults --> OpenAPI{{OpenAPI schema}} + OpenAPI -. generated from .-> Lambda +``` + +To build Bedrock Agents, you need: -* Create the Lambda function that defines the business logic for the action that your agent will carry out +* Create the Lambda function that defines the business logic for the action that your agent carries out. * Create an OpenAPI schema with the API description, structure, and parameters for the action group. * Ensure that Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). -AWS Lambda Powertools facilitates the process by providing support for the development of the Lambda function and the creation of the OpenAPI specification. +Powertools makes it easier to author the Lambda function and the creation of the OpenAPI schema. -!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." +!!! info "This is unnecessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. -As of now, both Pydantic V1 and V2 are supported. For a future major version, we will only support Pydantic V2. +At this time, we support both Pydantic V1 and V2. For a future major version, we will only support Pydantic V2. ### Your first Agent -To create a Bedrock Agent, use the `BedrockAgentResolver` to annotate your actions. This is -similar to the way [all the other Powertools](api_gateway.md) resolvers work. +To create a Bedrock Agent, use the `BedrockAgentResolver` to annotate your actions. +This is similar to the way [all the other Powertools](api_gateway.md) resolvers work. -Be aware that it's important to include a description for each API endpoint. The description is essential because it provides Bedrock Agents with an understanding of the primary function of your API action. +???+ note + It's important to include a description for each API endpoint. + This provides Bedrock Agents with an understanding of your API action. === "Lambda handler" @@ -42,6 +52,7 @@ Be aware that it's important to include a description for each API endpoint. The ``` 1. `description` is a required field in order for Bedrock Agents to work. + 2. Powertools take care of parsing, validate, routing and responding to the request. === "Input payload" @@ -55,47 +66,51 @@ Be aware that it's important to include a description for each API endpoint. The --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" ``` -The resolvers utilized by Bedrock Agents are also compatible with the full suite of Powertools utilities. This ensures seamless integration and functionality across the different tools provided by Powertools when working with Bedrock Agents. +The resolvers used by Bedrock Agents are compatible with the full suite of Powertools utilities. +This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). ### Generating OpenAPI schemas -To create a schema for your API, use the `get_openapi_json_schema` function provided by the Bedrock Agent resolver. This function will produce a JSON-serialized string that represents your schema. You have the option to either display this string output or save it to a file for future reference. +Use the `get_openapi_json_schema` function provided by the Bedrock Agent resolver. +This function will produce a JSON-serialized string that represents your OpenAPI schema. +You can print this string or save it to a file for future reference. -=== "Generating the OpenAPI schem" +=== "Generating the OpenAPI schema" ```python hl_lines="24 25" --8<-- "examples/event_handler_bedrock_agents/src/generating_openapi_schema.py" ``` + 1. This ensures that it's only executed when running the file directly, and not when running on the Lambda runtime. + 2. You can use [additional options](#customizing-openapi-metadata) to customize the OpenAPI schema. + === "OpenAPI schema" ```json hl_lines="13 16 24" --8<-- "examples/event_handler_bedrock_agents/src/generating_openapi_schema.json" ``` -To obtain the OpenAPI schema, execute the Python script from the command line interface (CLI). Upon execution, the script will generate the schema, which will be output directly to the console. - -### Creating your Agent in the AWS Console +To get the OpenAPI schema, run the Python script from your terminal. +The script will generate the schema directly to standard output, which you can redirect to a file. -To create a Bedrock Agent, you should refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. - -During the creation process, when the user interface (UI) prompts you for an OpenAPI specification, you should input the specification that was generated in the previous step. This is the spec that you obtained by running your Python script, which produced the OpenAPI schema as output. +```sh +python app.py > schema.json +``` -### Enabling the Swagger UI +### Creating your Agent on the AWS Console ---8<-- "docs/core/event_handler/_swagger_ui.md" +To create an Agent for Bedrock, refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. -```python hl_lines="10" title="enabling_swagger.py" ---8<-- "examples/event_handler_bedrock_agents/src/enabling_swagger.py" -``` +The following video demonstrates the end-to-end process: -1. `enable_swagger` creates a route to serve Swagger UI and allows quick customizations.

You can also include middlewares to protect or enhance the overall experience. +During the creation process, you can use the schema generated in the previous step when prompted for an OpenAPI specification. ## Advanced ### Additional metadata -To enrich the view that Bedrock Agents has of your Lambda functions, we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your API's parameters. +To enrich the view that Agents for Bedrock has of your Lambda functions, +use a combination of [Pydantic Models](https://docs.pydantic.dev/latest/concepts/models/){target="_blank"} and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your APIs parameters. --8<-- "docs/core/event_handler/_openapi_customization_intro.md" @@ -113,26 +128,6 @@ To implement these customizations, include extra parameters when defining your r --8<-- "examples/event_handler_bedrock_agents/src/customizing_api_operations.py" ``` -#### Customizing Swagger UI - ---8<-- "docs/core/event_handler/_openapi_customization_swagger.md" - -Below is an example configuration for serving Swagger UI from a custom path or CDN, with assets like CSS and JavaScript loading from a chosen CDN base URL. - -=== "customizing_swagger.py" - - ```python hl_lines="10" - --8<-- "examples/event_handler_bedrock_agents/src/customizing_swagger.py" - ``` - -=== "customizing_swagger_middlewares.py" - -A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. - - ```python hl_lines="7 13-18 21" - --8<-- "examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py" - ``` - #### Customizing OpenAPI metadata --8<-- "docs/core/event_handler/_openapi_customization_metadata.md" @@ -147,16 +142,18 @@ Include extra parameters when exporting your OpenAPI specification to apply thes ### Data validation -The Bedrock Agents Resolver allows for the clear definition of the expected format for incoming data and responses. By delegating data validation tasks to the Event Handler resolvers, you can significantly reduce the amount of repetitive code in your project. +The Bedrock Agents Resolver allows for the clear definition of the expected format for incoming data and responses. +By delegating data validation tasks to Powertools, you can significantly reduce the amount of repetitive code in your project. -For detailed guidance on implementing this feature, please consult the [REST API validation documentationi](api_gateway.md#data-validation). There, you'll find step-by-step instructions on how to apply data validation when using the resolver. +For detailed guidance on implementing this feature, see the [REST API validation documentation](api_gateway.md#data-validation). +There, you'll find step-by-step instructions on how to apply data validation when using the resolver. ???+ note When using the Bedrock Agent resolver, there's no need to add the `enable_validation` parameter, as it's enabled by default. ## Testing your code -You can test your routes by passing a proxy event request with required params. +Test your routes by passing a Bedrock Agents proxy event request: === "assert_bedrock_agent_response.py" diff --git a/examples/event_handler_bedrock_agents/src/generating_openapi_schema.py b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.py index 64bedee743e..fba9d10d2ce 100644 --- a/examples/event_handler_bedrock_agents/src/generating_openapi_schema.py +++ b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.py @@ -21,5 +21,5 @@ def lambda_handler(event: dict, context: LambdaContext): return app.resolve(event, context) -if __name__ == "__main__": - print(app.get_openapi_json_schema()) +if __name__ == "__main__": # (1)! + print(app.get_openapi_json_schema()) # (2)! diff --git a/examples/event_handler_bedrock_agents/src/getting_started.py b/examples/event_handler_bedrock_agents/src/getting_started.py index 2a9b07b7a2f..b937ac2953a 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started.py +++ b/examples/event_handler_bedrock_agents/src/getting_started.py @@ -18,4 +18,4 @@ def current_time() -> int: @logger.inject_lambda_context @tracer.capture_lambda_handler def lambda_handler(event: dict, context: LambdaContext): - return app.resolve(event, context) + return app.resolve(event, context) # (2)! From 9fce88ba98b5babbd6c9e9c307e15f653cd88d47 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 9 Feb 2024 18:20:42 +0000 Subject: [PATCH 03/46] chore: fix mistakes --- docs/core/event_handler/api_gateway.md | 185 ++++++++++++------------- 1 file changed, 91 insertions(+), 94 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index cacf64d1e90..1b286bee02a 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -7,20 +7,20 @@ Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Bala ## Key Features -- Lightweight routing to reduce boilerplate for API Gateway REST/HTTP API, ALB and Lambda Function URLs. -- Support for CORS, binary and Gzip compression, Decimals JSON encoding and bring your own JSON serializer -- Built-in integration with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="\_blank"} for self-documented event schema -- Works with micro function (one or a few routes) and monolithic functions (all routes) -- Support for OpenAPI and data validation for requests/responses +* Lightweight routing to reduce boilerplate for API Gateway REST/HTTP API, ALB and Lambda Function URLs. +* Support for CORS, binary and Gzip compression, Decimals JSON encoding and bring your own JSON serializer +* Built-in integration with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"} for self-documented event schema +* Works with micro function (one or a few routes) and monolithic functions (all routes) +* Support for OpenAPI and data validation for requests/responses ## Getting started ???+ tip -All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="\_blank"}. +All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. ### Install -!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="\_blank"}." +!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." **When using the data validation feature**, you need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. @@ -29,10 +29,9 @@ As of now, both Pydantic V1 and V2 are supported. For a future major version, we ### Required resources +If you're using any API Gateway integration, you must have an existing [API Gateway Proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html){target="_blank"} or [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html){target="_blank"} configured to invoke your Lambda function. -If you're using any API Gateway integration, you must have an existing [API Gateway Proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html){target="\_blank"} or [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html){target="\_blank"} configured to invoke your Lambda function. - -In case of using [VPC Lattice](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="\_blank"}, you must have a service network configured to invoke your Lambda function. +In case of using [VPC Lattice](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="_blank"}, you must have a service network configured to invoke your Lambda function. This is the sample infrastructure for API Gateway and Lambda Function URLs we are using for the examples in this documentation. @@ -116,7 +115,7 @@ When using Amazon Application Load Balancer (ALB) to front your Lambda functions #### Lambda Function URL -When using [AWS Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html){target="\_blank"}, you can use `LambdaFunctionUrlResolver`. +When using [AWS Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html){target="_blank"}, you can use `LambdaFunctionUrlResolver`. === "getting_started_lambda_function_url_resolver.py" @@ -132,7 +131,7 @@ When using [AWS Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/d #### VPC Lattice -When using [VPC Lattice with AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="\_blank"}, you can use `VPCLatticeV2Resolver`. +When using [VPC Lattice with AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="_blank"}, you can use `VPCLatticeV2Resolver`. === "Payload v2 (Recommended)" @@ -187,7 +186,7 @@ You can also nest dynamic paths, for example `/todos//`. ???+ note We recommend having explicit routes whenever possible; use catch-all routes sparingly. -You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="\_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`. +You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`. You can also combine nested paths with greedy regex to catch in between routes. @@ -234,7 +233,7 @@ It is generally better to have separate functions for each HTTP method, as the f ### Data validation !!! note "This changes the authoring experience by relying on Python's type annotations" -It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="\_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. +It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. For brevity, we'll focus on Pydantic only. @@ -338,7 +337,7 @@ Even better, we can also let Event Handler validate and convert our response acc ##### Validating payload subset -With the addition of the [`Annotated` type starting in Python 3.9](https://docs.python.org/3/library/typing.html#typing.Annotated){target="\_blank" rel="nofollow"}, types can contain additional metadata, allowing us to represent anything we want. +With the addition of the [`Annotated` type starting in Python 3.9](https://docs.python.org/3/library/typing.html#typing.Annotated){target="_blank" rel="nofollow"}, types can contain additional metadata, allowing us to represent anything we want. We use the `Annotated` and OpenAPI `Body` type to instruct Event Handler that our payload is located in a particular JSON key. @@ -373,9 +372,9 @@ We use the `Annotated` type to tell the Event Handler that a particular paramete In the following example, we use a new `Query` OpenAPI type to add [one out of many possible constraints](#customizing-openapi-parameters), which should read as: -- `completed` is a query string with a `None` as its default value -- `completed`, when set, should have at minimum 4 characters -- No match? Event Handler will return a validation error response +* `completed` is a query string with a `None` as its default value +* `completed`, when set, should have at minimum 4 characters +* No match? Event Handler will return a validation error response @@ -429,13 +428,13 @@ For example, we could validate that `` dynamic path should be no greate We use the `Annotated` type to tell the Event Handler that a particular parameter is a header that needs to be validated. -!!! info "We adhere to [HTTP RFC standards](https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2){target="\_blank" rel="nofollow"}, which means we treat HTTP headers as case-insensitive." +!!! info "We adhere to [HTTP RFC standards](https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2){target="_blank" rel="nofollow"}, which means we treat HTTP headers as case-insensitive." In the following example, we use a new `Header` OpenAPI type to add [one out of many possible constraints](#customizing-openapi-parameters), which should read as: -- `correlation_id` is a header that must be present in the request -- `correlation_id` should have 16 characters -- No match? Event Handler will return a validation error response +* `correlation_id` is a header that must be present in the request +* `correlation_id` should have 16 characters +* No match? Event Handler will return a validation error response @@ -461,13 +460,13 @@ In the following example, we use a new `Header` OpenAPI type to add [one out of ### Accessing request details -Event Handler integrates with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="\_blank"}, and it exposes their respective resolver request details and convenient methods under `app.current_event`. +Event Handler integrates with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"}, and it exposes their respective resolver request details and convenient methods under `app.current_event`. -That is why you see `app.resolve(event, context)` in every example. This allows Event Handler to resolve requests, and expose data like `app.lambda_context` and `app.current_event`. +That is why you see `app.resolve(event, context)` in every example. This allows Event Handler to resolve requests, and expose data like `app.lambda_context` and `app.current_event`. #### Query strings and payload -Within `app.current_event` property, you can access all available query strings as a dictionary via `query_string_parameters`, or a specific one via `get_query_string_value` method. +Within `app.current_event` property, you can access all available query strings as a dictionary via `query_string_parameters`, or a specific one via `get_query_string_value` method. You can access the raw payload via `body` property, or if it's a JSON string you can quickly deserialize it via `json_body` property - like the earlier example in the [HTTP Methods](#http-methods) section. @@ -521,7 +520,7 @@ We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 4 !!! note "This feature requires [data validation](#data-validation) feature to be enabled." -Behind the scenes, the [data validation](api_gateway.md#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="\_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. +Behind the scenes, the [data validation](#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. There are some important **caveats** that you should know before enabling it: @@ -529,13 +528,13 @@ There are some important **caveats** that you should know before enabling it: | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Swagger UI is **publicly accessible by default** | When using `enable_swagger` method, you can [protect sensitive API endpoints by implementing a custom middleware](#customizing-swagger-ui) using your preferred authorization mechanism. | | **No micro-functions support** yet | Swagger UI is enabled on a per resolver instance which will limit its accuracy here. | -| You need to expose a **new route** | You'll need to expose the following path to Lambda: `/swagger`; ignore if you're routing this path already. | +| You need to expose a **new route** | You'll need to expose the following path to Lambda: `/swagger`; ignore if you're routing this path already. | ```python hl_lines="12-13" title="enabling_swagger.py" --8<-- "examples/event_handler_rest/src/enabling_swagger.py" ``` -1. `enable_swagger` creates a route to serve Swagger UI and allows quick customizations.

You can also include middlewares to protect or enhance the overall experience. +1. `enable_swagger` creates a route to serve Swagger UI and allows quick customizations.

You can also include middlewares to protect or enhance the overall experience. Here's an example of what it looks like by default: @@ -543,11 +542,11 @@ Here's an example of what it looks like by default: ### Custom Domain API Mappings -When using [Custom Domain API Mappings feature](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-mappings.html){target="\_blank"}, you must use **`strip_prefixes`** param in the `APIGatewayRestResolver` constructor. +When using [Custom Domain API Mappings feature](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-mappings.html){target="_blank"}, you must use **`strip_prefixes`** param in the `APIGatewayRestResolver` constructor. **Scenario**: You have a custom domain `api.mydomain.dev`. Then you set `/payment` API Mapping to forward any payment requests to your Payments API. -**Challenge**: This means your `path` value for any API requests will always contain `/payment/`, leading to HTTP 404 as Event Handler is trying to match what's after `payment/`. This gets further complicated with an [arbitrary level of nesting](https://github.com/aws-powertools/powertools-lambda/issues/34){target="\_blank"}. +**Challenge**: This means your `path` value for any API requests will always contain `/payment/`, leading to HTTP 404 as Event Handler is trying to match what's after `payment/`. This gets further complicated with an [arbitrary level of nesting](https://github.com/aws-powertools/powertools-lambda/issues/34){target="_blank"}. To address this API Gateway behavior, we use `strip_prefixes` parameter to account for these prefixes that are now injected into the path regardless of which type of API Gateway you're using. @@ -566,7 +565,7 @@ To address this API Gateway behavior, we use `strip_prefixes` parameter to accou ???+ note After removing a path prefix with `strip_prefixes`, the new root path will automatically be mapped to the path argument of `/`. - For example, when using `strip_prefixes` value of `/pay`, there is no difference between a request path of `/pay` and `/pay/`; and the path argument would be defined as `/`. + For example, when using `strip_prefixes` value of `/pay`, there is no difference between a request path of `/pay` and `/pay/`; and the path argument would be defined as `/`. For added flexibility, you can use regexes to strip a prefix. This is helpful when you have many options due to different combinations of prefixes (e.g: multiple environments, multiple versions). @@ -628,14 +627,14 @@ Always configure `allow_origin` when using in production. ???+ tip "Multiple origins?" If you need to allow multiple origins, pass the additional origins using the `extra_origins` key. -| Key | Value | Note | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **[allow_origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="\_blank" rel="nofollow"}**: `str` | `*` | Only use the default value for development. **Never use `*` for production** unless your use case requires it | -| **[extra_origins](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="\_blank" rel="nofollow"}**: `List[str]` | `[]` | Additional origins to be allowed, in addition to the one specified in `allow_origin` | -| **[allow_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers){target="\_blank" rel="nofollow"}**: `List[str]` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Additional headers will be appended to the default list for your convenience | -| **[expose_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers){target="\_blank" rel="nofollow"}**: `List[str]` | `[]` | Any additional header beyond the [safe listed by CORS specification](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header){target="\_blank" rel="nofollow"}. | -| **[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="\_blank" rel="nofollow"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway | -| **[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="\_blank" rel="nofollow"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. | +| Key | Value | Note | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **[allow_origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `str` | `*` | Only use the default value for development. **Never use `*` for production** unless your use case requires it | +| **[extra_origins](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `List[str]` | `[]` | Additional origins to be allowed, in addition to the one specified in `allow_origin` | +| **[allow_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers){target="_blank" rel="nofollow"}**: `List[str]` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Additional headers will be appended to the default list for your convenience | +| **[expose_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers){target="_blank" rel="nofollow"}**: `List[str]` | `[]` | Any additional header beyond the [safe listed by CORS specification](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header){target="_blank" rel="nofollow"}. | +| **[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="_blank" rel="nofollow"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway | +| **[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="_blank" rel="nofollow"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. | ### Middleware @@ -700,7 +699,6 @@ Here's a sample middleware that extracts and injects correlation ID, using `APIG ![Combining middlewares](../../media/middlewares_normal_processing-dark.svg#only-dark) _Request flowing through multiple registered middlewares_ - You can use `app.use` to register middlewares that should always run regardless of the route, also known as global middlewares. @@ -742,7 +740,6 @@ Event Handler **calls global middlewares first**, then middlewares defined at th ![Short-circuiting middleware chain](../../media/middlewares_early_return-dark.svg#only-dark) _Interrupting request flow by returning early_ - Imagine you want to stop processing a request if something is missing, or return immediately if you've seen this request before. @@ -780,7 +777,7 @@ Here's an example where we prevent any request that doesn't include a correlatio By default, any unhandled exception in the middleware chain is eventually propagated as a HTTP 500 back to the client. -While there isn't anything special on how to use [`try/catch`](https://docs.python.org/3/tutorial/errors.html#handling-exceptions){target="\_blank" rel="nofollow"} for middlewares, it is important to visualize how Event Handler deals with them under the following scenarios: +While there isn't anything special on how to use [`try/catch`](https://docs.python.org/3/tutorial/errors.html#handling-exceptions){target="_blank" rel="nofollow"} for middlewares, it is important to visualize how Event Handler deals with them under the following scenarios: === "Unhandled exception from route handler" @@ -837,9 +834,9 @@ When registering a middleware, we expect a callable in both cases. For class-bas These are native middlewares that may become native features depending on customer demand. -| Middleware | Purpose | -| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| [SchemaValidationMiddleware](/lambda/python/latest/api/event_handler/middlewares/schema_validation.html){target="\_blank"} | Validates API request body and response against JSON Schema, using [Validation utility](../../utilities/validation.md){target="\_blank"} | +| Middleware | Purpose | +| ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| [SchemaValidationMiddleware](/lambda/python/latest/api/event_handler/middlewares/schema_validation.html){target="_blank"} | Validates API request body and response against JSON Schema, using [Validation utility](../../utilities/validation.md){target="_blank"} | #### Being a good citizen @@ -862,7 +859,7 @@ Powertools for AWS Lambda (Python) serializes headers and cookies according to t Some event sources require headers and cookies to be encoded as `multiValueHeaders`. ???+ warning "Using multiple values for HTTP headers in ALB?" -Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="\_blank"} to serialize response headers correctly. +Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="_blank"} to serialize response headers correctly. === "fine_grained_responses.py" @@ -947,7 +944,7 @@ Like `compress` feature, the client must send the `Accept` header with the corre ### Debug mode -You can enable debug mode via `debug` param, or via `POWERTOOLS_DEV` [environment variable](../../index.md#environment-variables){target="\_blank"}. +You can enable debug mode via `debug` param, or via `POWERTOOLS_DEV` [environment variable](../../index.md#environment-variables){target="_blank"}. This will enable full tracebacks errors in the response, print request and responses, and set CORS in development mode. @@ -962,7 +959,7 @@ This might reveal sensitive information in your logs and relax CORS restrictions ### OpenAPI -When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="\_blank"} type annotations to add constraints to your API's parameters. +When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your API's parameters. --8<-- "docs/core/event_handler/_openapi_customization_intro.md" @@ -983,7 +980,7 @@ To implement these customizations, include extra parameters when defining your r #### Customizing Swagger UI ???+note "Customizing the Swagger metadata" -The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](api_gateway.md#customizing-openapi-metadata). +The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](#customizing-openapi-metadata). The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets. @@ -999,9 +996,9 @@ Below is an example configuration for serving Swagger UI from a custom path or C A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. -```python hl_lines="7 13-18 21" ---8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py" -``` + ```python hl_lines="7 13-18 21" + --8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py" + ``` #### Customizing OpenAPI metadata @@ -1033,7 +1030,7 @@ Let's assume you have `split_route.py` as your Lambda function entrypoint and ro === "split_route_module.py" - We import **Router** instead of **APIGatewayRestResolver**; syntax wise is exactly the same. + We import **Router** instead of **APIGatewayRestResolver**; syntax wise is exactly the same. !!! info This means all methods, including [middleware](#middleware) will work as usual. @@ -1044,19 +1041,18 @@ Let's assume you have `split_route.py` as your Lambda function entrypoint and ro === "split_route.py" - We use `include_router` method and include all user routers registered in the `router` global object. + We use `include_router` method and include all user routers registered in the `router` global object. !!! note This method merges routes, [context](#sharing-contextual-data) and [middleware](#middleware) from `Router` into the main resolver instance (`APIGatewayRestResolver()`). - ```python hl_lines="11" + ```python hl_lines="11" --8<-- "examples/event_handler_rest/src/split_route.py" - ``` + ``` 1. When using [middleware](#middleware) in both `Router` and main resolver, you can make `Router` middlewares to take precedence by using `include_router` before `app.use()`. - #### Route prefix In the previous example, `split_route_module.py` routes had a `/todos` prefix. This might grow over time and become repetitive. @@ -1065,9 +1061,9 @@ When necessary, you can set a prefix when including a router object. This means === "split_route_prefix.py" - ```python hl_lines="12" + ```python hl_lines="12" --8<-- "examples/event_handler_rest/src/split_route_prefix.py" - ``` + ``` === "split_route_prefix_module.py" @@ -1099,15 +1095,15 @@ This can be useful for middlewares injecting contextual information before a req === "split_route_append_context.py" - ```python hl_lines="18" + ```python hl_lines="18" --8<-- "examples/event_handler_rest/src/split_route_append_context.py" - ``` + ``` === "split_route_append_context_module.py" - ```python hl_lines="16" + ```python hl_lines="16" --8<-- "examples/event_handler_rest/src/split_route_append_context_module.py" - ``` + ``` #### Sample layout @@ -1159,16 +1155,16 @@ A monolithic function means that your final code artifact will be deployed to a _**Benefits**_ -- **Code reuse**. It's easier to reason about your service, modularize it and reuse code as it grows. Eventually, it can be turned into a standalone library. -- **No custom tooling**. Monolithic functions are treated just like normal Python packages; no upfront investment in tooling. -- **Faster deployment and debugging**. Whether you use all-at-once, linear, or canary deployments, a monolithic function is a single deployable unit. IDEs like PyCharm and VSCode have tooling to quickly profile, visualize, and step through debug any Python package. +* **Code reuse**. It's easier to reason about your service, modularize it and reuse code as it grows. Eventually, it can be turned into a standalone library. +* **No custom tooling**. Monolithic functions are treated just like normal Python packages; no upfront investment in tooling. +* **Faster deployment and debugging**. Whether you use all-at-once, linear, or canary deployments, a monolithic function is a single deployable unit. IDEs like PyCharm and VSCode have tooling to quickly profile, visualize, and step through debug any Python package. _**Downsides**_ -- **Cold starts**. Frequent deployments and/or high load can diminish the benefit of monolithic functions depending on your latency requirements, due to [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="\_blank"}. Always load test to pragmatically balance between your customer experience and development cognitive load. -- **Granular security permissions**. The micro function approach enables you to use fine-grained permissions & access controls, separate external dependencies & code signing at the function level. Conversely, you could have multiple functions while duplicating the final code artifact in a monolithic approach. - - Regardless, least privilege can be applied to either approaches. -- **Higher risk per deployment**. A misconfiguration or invalid import can cause disruption if not caught earlier in automated testing. Multiple functions can mitigate misconfigurations but they would still share the same code artifact. You can further minimize risks with multiple environments in your CI/CD pipeline. +* **Cold starts**. Frequent deployments and/or high load can diminish the benefit of monolithic functions depending on your latency requirements, due to [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"}. Always load test to pragmatically balance between your customer experience and development cognitive load. +* **Granular security permissions**. The micro function approach enables you to use fine-grained permissions & access controls, separate external dependencies & code signing at the function level. Conversely, you could have multiple functions while duplicating the final code artifact in a monolithic approach. + * Regardless, least privilege can be applied to either approaches. +* **Higher risk per deployment**. A misconfiguration or invalid import can cause disruption if not caught earlier in automated testing. Multiple functions can mitigate misconfigurations but they would still share the same code artifact. You can further minimize risks with multiple environments in your CI/CD pipeline. #### Micro function @@ -1178,48 +1174,49 @@ A micro function means that your final code artifact will be different to each f **Benefits** -- **Granular scaling**. A micro function can benefit from the [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="\_blank"} to scale differently depending on each part of your application. Concurrency controls and provisioned concurrency can also be used at a granular level for capacity management. -- **Discoverability**. Micro functions are easier to visualize when using distributed tracing. Their high-level architectures can be self-explanatory, and complexity is highly visible — assuming each function is named to the business purpose it serves. -- **Package size**. An independent function can be significant smaller (KB vs MB) depending on external dependencies it require to perform its purpose. Conversely, a monolithic approach can benefit from [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html){target="\_blank"} to optimize builds for external dependencies. +* **Granular scaling**. A micro function can benefit from the [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"} to scale differently depending on each part of your application. Concurrency controls and provisioned concurrency can also be used at a granular level for capacity management. +* **Discoverability**. Micro functions are easier to visualize when using distributed tracing. Their high-level architectures can be self-explanatory, and complexity is highly visible — assuming each function is named to the business purpose it serves. +* **Package size**. An independent function can be significant smaller (KB vs MB) depending on external dependencies it require to perform its purpose. Conversely, a monolithic approach can benefit from [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html){target="_blank"} to optimize builds for external dependencies. **Downsides** -- **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="\_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. - - Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. -- **Harder to share code**. Shared code must be carefully evaluated to avoid unnecessary deployments when that changes. Equally, if shared code isn't a library, +* **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. + * Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. +* **Harder to share code**. Shared code must be carefully evaluated to avoid unnecessary deployments when that changes. Equally, if shared code isn't a library, your development, building, deployment tooling need to accommodate the distinct layout. -- **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. - - Automated testing, operational and security reviews are essential to stability in either approaches. +* **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. + * Automated testing, operational and security reviews are essential to stability in either approaches. **Example** Consider a simplified micro function structured REST API that has two routes: -- `/users` - an endpoint that will return all users of the application on `GET` requests -- `/users/` - an endpoint that looks up a single users details by ID on `GET` requests +* `/users` - an endpoint that will return all users of the application on `GET` requests +* `/users/` - an endpoint that looks up a single users details by ID on `GET` requests -Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="\_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). +Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). === "`/users` Endpoint" -`python - --8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" - ` + +```python +--8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" +``` === "`/users/` Endpoint" -`python - --8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" - ` + +```python +--8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" +``` === "Micro Function Example SAM Template" -`yaml - --8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" - ` - +```yaml +--8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" +``` + ???+ note -You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="\_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="\_blank" rel="nofollow"}. - +You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="_blank" rel="nofollow"}. ## Testing your code @@ -1288,7 +1285,7 @@ You can test your routes by passing a proxy event request with required params. Chalice is a full featured microframework that manages application and infrastructure. This utility, however, is largely focused on routing to reduce boilerplate and expects you to setup and manage infrastructure with your framework of choice. -That said, [Chalice has native integration with Lambda Powertools](https://aws.github.io/chalice/topics/middleware.html){target="\_blank" rel="nofollow"} if you're looking for a more opinionated and web framework feature set. +That said, [Chalice has native integration with Lambda Powertools](https://aws.github.io/chalice/topics/middleware.html){target="_blank" rel="nofollow"} if you're looking for a more opinionated and web framework feature set. **What happened to `ApiGatewayResolver`?** From 476aed1a91986a564682fae3250d5aef6cf92831 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 9 Feb 2024 18:24:35 +0000 Subject: [PATCH 04/46] chore: fix more mistakes --- docs/core/event_handler/api_gateway.md | 83 +++++++++++++------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 1b286bee02a..5a2230f1463 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -16,7 +16,7 @@ Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Bala ## Getting started ???+ tip -All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. + All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. ### Install @@ -58,8 +58,8 @@ A resolver will handle request resolution, including [one or more routers](#spli For resolvers, we provide: `APIGatewayRestResolver`, `APIGatewayHttpResolver`, `ALBResolver`, `LambdaFunctionUrlResolver`, and `VPCLatticeResolver`. From here on, we will default to `APIGatewayRestResolver` across examples. ???+ info "Auto-serialization" -We serialize `Dict` responses as JSON, trim whitespace for compact responses, set content-type to `application/json`, and -return a 200 OK HTTP status. You can optionally set a different HTTP status code as the second argument of the tuple: + We serialize `Dict` responses as JSON, trim whitespace for compact responses, set content-type to `application/json`, and + return a 200 OK HTTP status. You can optionally set a different HTTP status code as the second argument of the tuple: ```python hl_lines="15 16" --8<-- "examples/event_handler_rest/src/getting_started_return_tuple.py" @@ -72,7 +72,7 @@ When using Amazon API Gateway REST API to front your Lambda functions, you can u Here's an example on how we can handle the `/todos` path. ???+ info "Trailing slash in routes" -For `APIGatewayRestResolver`, we seamless handle routes with a trailing slash (`/todos/`). + For `APIGatewayRestResolver`, we seamless handle routes with a trailing slash (`/todos/`). === "getting_started_rest_api_resolver.py" @@ -99,7 +99,7 @@ For `APIGatewayRestResolver`, we seamless handle routes with a trailing slash (` When using Amazon API Gateway HTTP API to front your Lambda functions, you can use `APIGatewayHttpResolver`. ???+ note -Using HTTP API v1 payload? Use `APIGatewayRestResolver` instead. `APIGatewayHttpResolver` defaults to v2 payload. + Using HTTP API v1 payload? Use `APIGatewayRestResolver` instead. `APIGatewayHttpResolver` defaults to v2 payload. ```python hl_lines="5 11" title="Using HTTP API resolver" --8<-- "examples/event_handler_rest/src/getting_started_http_api_resolver.py" @@ -164,7 +164,7 @@ You can use `/todos/` to configure dynamic URL paths, where `` Each dynamic route you set must be part of your function signature. This allows us to call your function using keyword arguments when matching your dynamic route. ???+ note -For brevity, we will only include the necessary keys for each sample request for the example to work. + For brevity, we will only include the necessary keys for each sample request for the example to work. === "dynamic_routes.py" @@ -179,19 +179,19 @@ For brevity, we will only include the necessary keys for each sample request for ``` ???+ tip -You can also nest dynamic paths, for example `/todos//`. + You can also nest dynamic paths, for example `/todos//`. #### Catch-all routes ???+ note -We recommend having explicit routes whenever possible; use catch-all routes sparingly. + We recommend having explicit routes whenever possible; use catch-all routes sparingly. You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`. You can also combine nested paths with greedy regex to catch in between routes. ???+ warning -We choose the most explicit registered route that matches an incoming event. + We choose the most explicit registered route that matches an incoming event. === "dynamic_routes_catch_all.py" @@ -228,12 +228,12 @@ If you need to accept multiple HTTP methods in a single function, you can use th ``` ???+ note -It is generally better to have separate functions for each HTTP method, as the functionality tends to differ depending on which method is used. + It is generally better to have separate functions for each HTTP method, as the functionality tends to differ depending on which method is used. ### Data validation !!! note "This changes the authoring experience by relying on Python's type annotations" -It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. + It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. For brevity, we'll focus on Pydantic only. @@ -501,14 +501,14 @@ You can use **`exception_handler`** decorator with any Python exception. This al ``` ???+ info -The `exception_handler` also supports passing a list of exception types you wish to handle with one handler. + The `exception_handler` also supports passing a list of exception types you wish to handle with one handler. ### Raising HTTP errors You can easily raise any HTTP Error back to the client using `ServiceError` exception. This ensures your Lambda function doesn't fail but return the correct HTTP response signalling the error. ???+ info -If you need to send custom headers, use [Response](#fine-grained-responses) class instead. + If you need to send custom headers, use [Response](#fine-grained-responses) class instead. We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 404, 500. @@ -563,7 +563,7 @@ To address this API Gateway behavior, we use `strip_prefixes` parameter to accou ``` ???+ note -After removing a path prefix with `strip_prefixes`, the new root path will automatically be mapped to the path argument of `/`. + After removing a path prefix with `strip_prefixes`, the new root path will automatically be mapped to the path argument of `/`. For example, when using `strip_prefixes` value of `/pay`, there is no difference between a request path of `/pay` and `/pay/`; and the path argument would be defined as `/`. @@ -585,7 +585,7 @@ This will ensure that CORS headers are returned as part of the response when you matches one of the allowed values. ???+ tip -Optionally disable CORS on a per path basis with `cors=False` parameter. + Optionally disable CORS on a per path basis with `cors=False` parameter. === "setting_cors.py" @@ -622,10 +622,10 @@ For convenience, we automatically handle that for you as long as you [setup CORS For convenience, these are the default values when using `CORSConfig` to enable CORS: ???+ warning -Always configure `allow_origin` when using in production. + Always configure `allow_origin` when using in production. ???+ tip "Multiple origins?" -If you need to allow multiple origins, pass the additional origins using the `extra_origins` key. + If you need to allow multiple origins, pass the additional origins using the `extra_origins` key. | Key | Value | Note | | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -828,7 +828,7 @@ As a practical example, let's refactor our correlation ID middleware so it accep 4. Register an instance of `CorrelationIdMiddleware`. !!! note "Class-based **vs** function-based middlewares" -When registering a middleware, we expect a callable in both cases. For class-based middlewares, `BaseMiddlewareHandler` is doing the work of calling your `handler` method with the correct parameters, hence why we expect an instance of it. + When registering a middleware, we expect a callable in both cases. For class-based middlewares, `BaseMiddlewareHandler` is doing the work of calling your `handler` method with the correct parameters, hence why we expect an instance of it. #### Native middlewares @@ -855,11 +855,11 @@ Keep the following in mind when authoring middlewares for Event Handler: You can use the `Response` class to have full control over the response. For example, you might want to add additional headers, cookies, or set a custom Content-type. ???+ info -Powertools for AWS Lambda (Python) serializes headers and cookies according to the type of input event. -Some event sources require headers and cookies to be encoded as `multiValueHeaders`. + Powertools for AWS Lambda (Python) serializes headers and cookies according to the type of input event. + Some event sources require headers and cookies to be encoded as `multiValueHeaders`. ???+ warning "Using multiple values for HTTP headers in ALB?" -Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="_blank"} to serialize response headers correctly. + Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="_blank"} to serialize response headers correctly. === "fine_grained_responses.py" @@ -878,10 +878,10 @@ Make sure you [enable the multi value headers feature](https://docs.aws.amazon.c You can compress with gzip and base64 encode your responses via `compress` parameter. You have the option to pass the `compress` parameter when working with a specific route or using the Response object. ???+ info -The `compress` parameter used in the Response object takes precedence over the one used in the route. + The `compress` parameter used in the Response object takes precedence over the one used in the route. ???+ warning -The client must send the `Accept-Encoding` header, otherwise a normal response will be sent. + The client must send the `Accept-Encoding` header, otherwise a normal response will be sent. === "compressing_responses_using_route.py" @@ -910,7 +910,7 @@ The client must send the `Accept-Encoding` header, otherwise a normal response w ### Binary responses ???+ warning "Amazon API Gateway does not support `*/*` binary media type [when CORS is also configured](https://github.com/aws-powertools/powertools-lambda-python/issues/3373#issuecomment-1821144779){target='blank'}." -This feature requires API Gateway to configure binary media types, see [our sample infrastructure](#required-resources) for reference. + This feature requires API Gateway to configure binary media types, see [our sample infrastructure](#required-resources) for reference. For convenience, we automatically base64 encode binary responses. You can also use in combination with `compress` parameter if your client supports gzip. @@ -949,7 +949,7 @@ You can enable debug mode via `debug` param, or via `POWERTOOLS_DEV` [environmen This will enable full tracebacks errors in the response, print request and responses, and set CORS in development mode. ???+ danger -This might reveal sensitive information in your logs and relax CORS restrictions, use it sparingly. + This might reveal sensitive information in your logs and relax CORS restrictions, use it sparingly. It's best to use for local development only! @@ -980,7 +980,7 @@ To implement these customizations, include extra parameters when defining your r #### Customizing Swagger UI ???+note "Customizing the Swagger metadata" -The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](#customizing-openapi-metadata). + The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](#customizing-openapi-metadata). The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets. @@ -994,7 +994,7 @@ Below is an example configuration for serving Swagger UI from a custom path or C === "customizing_swagger_middlewares.py" -A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. + A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. ```python hl_lines="7 13-18 21" --8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py" @@ -1091,7 +1091,7 @@ You can use specialized router classes according to the type of event that you a You can use `append_context` when you want to share data between your App and Router instances. Any data you share will be available via the `context` dictionary available in your App or Router context. ???+ info "We always clear data available in `context` after each invocation." -This can be useful for middlewares injecting contextual information before a request is processed. + This can be useful for middlewares injecting contextual information before a request is processed. === "split_route_append_context.py" @@ -1145,7 +1145,7 @@ Event Handler naturally leads to a single Lambda function handling multiple rout Both single (monolithic) and multiple functions (micro) offer different set of trade-offs worth knowing. ???+ tip -TL;DR. Start with a monolithic function, add additional functions with new handlers, and possibly break into micro functions if necessary. + TL;DR. Start with a monolithic function, add additional functions with new handlers, and possibly break into micro functions if necessary. #### Monolithic function @@ -1183,7 +1183,7 @@ A micro function means that your final code artifact will be different to each f * **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. * Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. * **Harder to share code**. Shared code must be carefully evaluated to avoid unnecessary deployments when that changes. Equally, if shared code isn't a library, - your development, building, deployment tooling need to accommodate the distinct layout. +your development, building, deployment tooling need to accommodate the distinct layout. * **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. * Automated testing, operational and security reviews are essential to stability in either approaches. @@ -1197,26 +1197,23 @@ Consider a simplified micro function structured REST API that has two routes: Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). === "`/users` Endpoint" - -```python ---8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" -``` + ```python + --8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" + ``` === "`/users/` Endpoint" - -```python ---8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" -``` + ```python + --8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" + ``` === "Micro Function Example SAM Template" - -```yaml ---8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" -``` + ```yaml + --8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" + ``` ???+ note -You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="_blank" rel="nofollow"}. + You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="_blank" rel="nofollow"}. ## Testing your code From 561371d34c707f30dfce7bd6c6fc662124262cf1 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 13 Feb 2024 22:38:47 +0100 Subject: [PATCH 05/46] fix: some notes --- docs/core/event_handler/bedrock_agents.md | 27 ++++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 23ab064ba5a..ed4d8874f9d 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -1,22 +1,23 @@ --- -title: Bedrock Agents +title: Agents for Amazon Bedrock description: Core utility --- -Event handler for Amazon Bedrock Agents, including auto generation of OpenAPI schemas. +Event handler for [Amazon Bedrock Agents](https://aws.amazon.com/bedrock/agents/), including auto generation of OpenAPI schemas. ## Key features * Same declarative syntax as the [other Powertools event handlers](api_gateway.md) * Drastically reduce the boilerplate to build Agents for Amazon Bedrock -* Automatic generation of OpenAPI schemas from your business logic code -* Built-in data validation for requests/responses +* Automatic generation of [OpenAPI schemas](https://www.openapis.org/){target="_blank"} from your business logic code +* Built-in data validation for requests and responses ## Getting started ```mermaid flowchart LR - Bedrock <--> Agent + Bedrock[Bedrock FM] <-- calls --> Agent + You --> Agent Agent -- invokes --> Lambda[Lambda function] Agent -- consults --> OpenAPI{{OpenAPI schema}} OpenAPI -. generated from .-> Lambda @@ -30,6 +31,8 @@ To build Bedrock Agents, you need: Powertools makes it easier to author the Lambda function and the creation of the OpenAPI schema. +### Install + !!! info "This is unnecessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. @@ -39,11 +42,7 @@ At this time, we support both Pydantic V1 and V2. For a future major version, we ### Your first Agent To create a Bedrock Agent, use the `BedrockAgentResolver` to annotate your actions. -This is similar to the way [all the other Powertools](api_gateway.md) resolvers work. - -???+ note - It's important to include a description for each API endpoint. - This provides Bedrock Agents with an understanding of your API action. +This is similar to the way [all the other Event Handler](api_gateway.md) resolvers work. === "Lambda handler" @@ -51,8 +50,8 @@ This is similar to the way [all the other Powertools](api_gateway.md) resolvers --8<-- "examples/event_handler_bedrock_agents/src/getting_started.py" ``` - 1. `description` is a required field in order for Bedrock Agents to work. - 2. Powertools take care of parsing, validate, routing and responding to the request. + 1. `description` is a recommended field that should contain a human readable descriptin of your action + 2. We take care of **parsing**, **validating**, **routing** and **responding** to the request. === "Input payload" @@ -66,7 +65,9 @@ This is similar to the way [all the other Powertools](api_gateway.md) resolvers --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" ``` -The resolvers used by Bedrock Agents are compatible with the full suite of Powertools utilities. +!!! note "It's important to include a `description` for each API endpoint because reasons" + +The resolvers used by Bedrock Agents are compatible with the full suite of Powertools for AWS Lambda utilities. This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). ### Generating OpenAPI schemas From ebc47dbc01b888840761dca091b6b939834776e8 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 15 Feb 2024 14:19:53 +0100 Subject: [PATCH 06/46] chore: trademarks --- docs/core/event_handler/bedrock_agents.md | 66 ++++++++++++++--------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index ed4d8874f9d..86a6ba80bf0 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -3,33 +3,39 @@ title: Agents for Amazon Bedrock description: Core utility --- -Event handler for [Amazon Bedrock Agents](https://aws.amazon.com/bedrock/agents/), including auto generation of OpenAPI schemas. +Author [Agents for Amazon Bedrock](https://aws.amazon.com/bedrock/agents/) with an event handler and auto generation of OpenAPI schemas. + +```mermaid +flowchart LR + Bedrock[Foundational Model] <-- calls --> Agent + You[User input] --> Agent + Agent[Agents for Amazon Bedrock] -- invokes --> Lambda[AWS Lambda function] + Agent -- consults --> OpenAPI{{OpenAPI schema}} + OpenAPI -. generated from .-> Lambda +``` ## Key features -* Same declarative syntax as the [other Powertools event handlers](api_gateway.md) -* Drastically reduce the boilerplate to build Agents for Amazon Bedrock +* Same declarative syntax as the [other event handler resolvers](api_gateway.md) +* Drastic reduction of the boilerplate to build Agents for Amazon Bedrock * Automatic generation of [OpenAPI schemas](https://www.openapis.org/){target="_blank"} from your business logic code * Built-in data validation for requests and responses -## Getting started +## Terminology -```mermaid -flowchart LR - Bedrock[Bedrock FM] <-- calls --> Agent - You --> Agent - Agent -- invokes --> Lambda[Lambda function] - Agent -- consults --> OpenAPI{{OpenAPI schema}} - OpenAPI -. generated from .-> Lambda -``` +**Action group** is lorem ipsum + +**Another term** is another thing totally different -To build Bedrock Agents, you need: +## Getting started + +To build Agents for Amazon Bedrock, you need: * Create the Lambda function that defines the business logic for the action that your agent carries out. * Create an OpenAPI schema with the API description, structure, and parameters for the action group. -* Ensure that Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). +* Ensure that Amazon Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). -Powertools makes it easier to author the Lambda function and the creation of the OpenAPI schema. +Powertools for AWS Lambda makes it easier to author the Lambda function and the creation of the OpenAPI schema. ### Install @@ -39,9 +45,17 @@ You need to add `pydantic` as a dependency in your preferred tool _e.g., require At this time, we support both Pydantic V1 and V2. For a future major version, we will only support Pydantic V2. +### Required resources + +SAM example here otherwise it's not enough to say "create a Lambda function". + +#### IAM permissions + +A table explaining the required permissions, summarizing the official AWS docs + ### Your first Agent -To create a Bedrock Agent, use the `BedrockAgentResolver` to annotate your actions. +To create an Agent for Amazon Bedrock, use the `BedrockAgentResolver` to annotate your actions. This is similar to the way [all the other Event Handler](api_gateway.md) resolvers work. === "Lambda handler" @@ -67,12 +81,16 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve !!! note "It's important to include a `description` for each API endpoint because reasons" -The resolvers used by Bedrock Agents are compatible with the full suite of Powertools for AWS Lambda utilities. +The resolvers used by Agents for Amazon Bedrock are compatible with the full suite of Powertools for AWS Lambda utilities. This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). +### Validating parameters + +Add an example for validating parameters like EmailStr from Pydantic. + ### Generating OpenAPI schemas -Use the `get_openapi_json_schema` function provided by the Bedrock Agent resolver. +Use the `get_openapi_json_schema` function provided by the resolver. This function will produce a JSON-serialized string that represents your OpenAPI schema. You can print this string or save it to a file for future reference. @@ -100,7 +118,7 @@ python app.py > schema.json ### Creating your Agent on the AWS Console -To create an Agent for Bedrock, refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. +To create an Agent for Amazon Bedrock, refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. The following video demonstrates the end-to-end process: @@ -110,7 +128,7 @@ During the creation process, you can use the schema generated in the previous st ### Additional metadata -To enrich the view that Agents for Bedrock has of your Lambda functions, +To enrich the view that Agents for Amazon Bedrock has of your Lambda functions, use a combination of [Pydantic Models](https://docs.pydantic.dev/latest/concepts/models/){target="_blank"} and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your APIs parameters. --8<-- "docs/core/event_handler/_openapi_customization_intro.md" @@ -143,18 +161,18 @@ Include extra parameters when exporting your OpenAPI specification to apply thes ### Data validation -The Bedrock Agents Resolver allows for the clear definition of the expected format for incoming data and responses. -By delegating data validation tasks to Powertools, you can significantly reduce the amount of repetitive code in your project. +The Agents for Amazon Bedrock resolver allows for the clear definition of the expected format for incoming data and responses. +By delegating data validation tasks to Powertools for AWS Lambda, you can significantly reduce the amount of repetitive code in your project. For detailed guidance on implementing this feature, see the [REST API validation documentation](api_gateway.md#data-validation). There, you'll find step-by-step instructions on how to apply data validation when using the resolver. ???+ note - When using the Bedrock Agent resolver, there's no need to add the `enable_validation` parameter, as it's enabled by default. + When using the Agent for Amazon Bedrock resolver, there's no need to add the `enable_validation` parameter, as it's enabled by default. ## Testing your code -Test your routes by passing a Bedrock Agents proxy event request: +Test your routes by passing an [Agent for Amazon Bedrock proxy event](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-input) request: === "assert_bedrock_agent_response.py" From 6f1350bda642fc306f8f2d794c0021ce38b14315 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 15 Feb 2024 15:45:30 +0100 Subject: [PATCH 07/46] chore: add sam --- docs/core/event_handler/bedrock_agents.md | 33 +++++++++++++----- .../sam/bedrock_service_role.yaml | 34 +++++++++++++++++++ .../sam/template.yaml | 22 ++++++++---- 3 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 86a6ba80bf0..02478f8c642 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -3,7 +3,7 @@ title: Agents for Amazon Bedrock description: Core utility --- -Author [Agents for Amazon Bedrock](https://aws.amazon.com/bedrock/agents/) with an event handler and auto generation of OpenAPI schemas. +Author [Agents for Amazon Bedrock](https://aws.amazon.com/bedrock/agents/){target="_blank"} using event handlers and auto generation of OpenAPI schemas. ```mermaid flowchart LR @@ -23,16 +23,20 @@ flowchart LR ## Terminology -**Action group** is lorem ipsum +**Data validation** automatically validates the user input and the response of your Lambda function against a set of constraints defined by you -**Another term** is another thing totally different +**Event handler** is a Powertools for AWS feature that processes an event, runs data parsing and validation, routes the request to a specific function, and returns a response to the caller in the proper format + +**[OpenAPI schema](https://www.openapis.org/){target="_blank"}** is an industry standard JSON-serialized string that represents the structure and parameters of your API + +**Action group** is a collection of two resources where you define the actions that the agent should carry out: an OpenAPI schema to define the APIs that the agent can invoke to carry out its tasks, and a Lambda function to execute those actions ## Getting started To build Agents for Amazon Bedrock, you need: -* Create the Lambda function that defines the business logic for the action that your agent carries out. -* Create an OpenAPI schema with the API description, structure, and parameters for the action group. +* [Create the Lambda function](#your-first-agent) that defines the business logic for the action that your agent carries out. +* [Create an OpenAPI schema](#generating-openapi-schemas) with the API description, structure, and parameters for the action group. * Ensure that Amazon Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). Powertools for AWS Lambda makes it easier to author the Lambda function and the creation of the OpenAPI schema. @@ -47,11 +51,24 @@ At this time, we support both Pydantic V1 and V2. For a future major version, we ### Required resources -SAM example here otherwise it's not enough to say "create a Lambda function". +Before you start, you need to the following permissions: + +* A **service role**, which allows Amazon Bedrock to invoke foundation models +* **Lambda permissions** allowing Amazon Bedrock to invoke it -#### IAM permissions +=== "Service Role Example using AWS CloudFormation" + ```yaml hl_lines="14-17 24-29 34" + --8<-- "examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml" + ``` + + 1. You need the role ARN when creating the Agent for Amazon Bedrock + +=== "Lambda permissions using AWS Serverless Application Model (SAM)" + ```yaml hl_lines="27-33" + --8<-- "examples/event_handler_bedrock_agents/sam/template.yaml" + ``` -A table explaining the required permissions, summarizing the official AWS docs + 1. Amazon Bedrock needs permissions to invoke this Lambda function ### Your first Agent diff --git a/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml b/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml new file mode 100644 index 00000000000..e50e4560e79 --- /dev/null +++ b/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml @@ -0,0 +1,34 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + Amazon Bedrock service role + +Resources: + BedrockServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Action: + - sts:assumeRole + Service: + - bedrock.amazonaws.com + Policies: + - PolicyName: bedrock + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - bedrock:InvokeModel + Resource: + - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2 + - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2:1 + - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-instant-v1 + +Outputs: + BedrockServiceRole: + Description: The role ARN to be used by Amazon Bedrock + Value: !GetAtt BedrockServiceRole.Arn # (1)! diff --git a/examples/event_handler_bedrock_agents/sam/template.yaml b/examples/event_handler_bedrock_agents/sam/template.yaml index 97b13b23ff6..7af68bff7e7 100644 --- a/examples/event_handler_bedrock_agents/sam/template.yaml +++ b/examples/event_handler_bedrock_agents/sam/template.yaml @@ -1,23 +1,33 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 -Description: Hello world event handler Bedrock Agents +Description: > + Agents for Amazon Bedrock example with Powertools for AWS Lambda (Python) -Globals: +Globals: # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html Function: Timeout: 5 Runtime: python3.12 Tracing: Active Environment: Variables: + POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld POWERTOOLS_LOG_LEVEL: INFO - POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 - POWERTOOLS_LOGGER_LOG_EVENT: true - POWERTOOLS_SERVICE_NAME: example Resources: ApiFunction: Type: AWS::Serverless::Function + DeletionPolicy: Delete + UpdateReplacePolicy: Delete Properties: Handler: getting_started.lambda_handler + Description: Agent for Amazon Bedrock handler function CodeUri: ../src - Description: Bedrock Agents handler function + + + BedrockPermission: # (1)! + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: !GetAtt ApiFunction.Arn + Principal: bedrock.amazonaws.com + SourceAccount: !Sub ${AWS::AccountId} From a3ecb8ccee894cd734015fc496886ea4f3da8742 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 15 Feb 2024 16:07:55 +0100 Subject: [PATCH 08/46] fix: input/output --- docs/core/event_handler/bedrock_agents.md | 6 ++- .../src/getting_started.json | 38 +++++++------------ .../src/getting_started_output.json | 32 +++++----------- 3 files changed, 27 insertions(+), 49 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 02478f8c642..69c4ec4cf32 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -5,6 +5,7 @@ description: Core utility Author [Agents for Amazon Bedrock](https://aws.amazon.com/bedrock/agents/){target="_blank"} using event handlers and auto generation of OpenAPI schemas. +
```mermaid flowchart LR Bedrock[Foundational Model] <-- calls --> Agent @@ -13,6 +14,7 @@ flowchart LR Agent -- consults --> OpenAPI{{OpenAPI schema}} OpenAPI -. generated from .-> Lambda ``` +
## Key features @@ -86,13 +88,13 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve === "Input payload" - ```json hl_lines="7 9 16" + ```json hl_lines="4 6 13" --8<-- "examples/event_handler_bedrock_agents/src/getting_started.json" ``` === "Output payload" - ```json hl_lines="12-14" + ```json hl_lines="10" --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" ``` diff --git a/examples/event_handler_bedrock_agents/src/getting_started.json b/examples/event_handler_bedrock_agents/src/getting_started.json index 79b55099e1e..bc0f462de6d 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started.json +++ b/examples/event_handler_bedrock_agents/src/getting_started.json @@ -1,28 +1,16 @@ { - "level": "INFO", - "location": "lambda_handler:451", - "message": { - "sessionId": "123456789012345", - "sessionAttributes": {}, - "inputText": "What is the current time?", - "promptSessionAttributes": {}, - "apiPath": "/current_time", - "agent": { - "name": "TimeAgent", - "version": "DRAFT", - "id": "XLHH72XNF2", - "alias": "TSTALIASID" - }, - "httpMethod": "GET", - "messageVersion": "1.0", - "actionGroup": "CurrentTime" + "sessionId": "123456789012345", + "sessionAttributes": {}, + "inputText": "What is the current time?", + "promptSessionAttributes": {}, + "apiPath": "/current_time", + "agent": { + "name": "TimeAgent", + "version": "DRAFT", + "id": "XLHH72XNF2", + "alias": "TSTALIASID" }, - "timestamp": "2024-01-08 09:42:45,135+0000", - "service": "bedrock", - "cold_start": true, - "function_name": "bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", - "function_memory_size": "256", - "function_arn": "arn:aws:lambda:us-east-1:123456789012:function:bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", - "function_request_id": "3c0d5437-7873-4a9c-86c9-a55c262f22d9", - "xray_trace_id": "1-659bc393-6d3332e24321ce6b333c398c" + "httpMethod": "GET", + "messageVersion": "1.0", + "actionGroup": "CurrentTime" } diff --git a/examples/event_handler_bedrock_agents/src/getting_started_output.json b/examples/event_handler_bedrock_agents/src/getting_started_output.json index 1aeacee9a75..373d8b604bd 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_output.json +++ b/examples/event_handler_bedrock_agents/src/getting_started_output.json @@ -1,26 +1,14 @@ { - "level": "INFO", - "location": "lambda_handler:54", - "message": { - "messageVersion": "1.0", - "response": { - "actionGroup": "CurrentTime", - "apiPath": "/current_time", - "httpMethod": "GET", - "httpStatusCode": 200, - "responseBody": { - "application/json": { - "body": "1704708165" - } + "messageVersion": "1.0", + "response": { + "actionGroup": "CurrentTime", + "apiPath": "/current_time", + "httpMethod": "GET", + "httpStatusCode": 200, + "responseBody": { + "application/json": { + "body": "1704708165" } } - }, - "timestamp": "2024-01-08 10:01:38,082+0000", - "service": "bedrock", - "cold_start": true, - "function_name": "bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", - "function_memory_size": "256", - "function_arn": "arn:aws:lambda:us-east-1:123456789012:function:bedrock-agents-current-time-HelloWorldFunction-bx2uCCm3Vw9Y", - "function_request_id": "3c0d5437-7873-4a9c-86c9-a55c262f22d9", - "xray_trace_id": "1-659bc393-6d3332e24321ce6b333c398c" + } } From cc72a3e131474051381cabcdf6345e6aa4285b33 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 15 Feb 2024 17:09:43 +0100 Subject: [PATCH 09/46] chore: default description --- aws_lambda_powertools/event_handler/api_gateway.py | 2 +- docs/core/event_handler/bedrock_agents.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index 43b5bf139ea..20a8265d88b 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -1734,7 +1734,7 @@ def register_resolver(func: Callable): compress, cache_control, summary, - description, + description or getattr(func, "__name__", repr(func)), # defaults description to the function name responses, response_description, tags, diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 69c4ec4cf32..bca1cf0567f 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -83,7 +83,7 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve --8<-- "examples/event_handler_bedrock_agents/src/getting_started.py" ``` - 1. `description` is a recommended field that should contain a human readable descriptin of your action + 1. `description` is a recommended field that should contain a human readable description of your action 2. We take care of **parsing**, **validating**, **routing** and **responding** to the request. === "Input payload" @@ -98,7 +98,7 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" ``` -!!! note "It's important to include a `description` for each API endpoint because reasons" +!!! note "It's important to include a `description` for each API endpoint because it will improve the understanding Amazon Bedrock has of your actions" The resolvers used by Agents for Amazon Bedrock are compatible with the full suite of Powertools for AWS Lambda utilities. This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). From c42ee6dd13c79dfc8c90fc7c4d3979f9b0ec103f Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 16 Feb 2024 11:26:46 +0100 Subject: [PATCH 10/46] fix: mypy --- ...py => customizing_bedrock_api_metadata.py} | 0 ... => customizing_bedrock_api_operations.py} | 0 .../src/customizing_swagger.py | 29 -------------- .../src/customizing_swagger_middlewares.py | 40 ------------------- .../src/enabling_swagger.py | 22 ---------- 5 files changed, 91 deletions(-) rename examples/event_handler_bedrock_agents/src/{customizing_api_metadata.py => customizing_bedrock_api_metadata.py} (100%) rename examples/event_handler_bedrock_agents/src/{customizing_api_operations.py => customizing_bedrock_api_operations.py} (100%) delete mode 100644 examples/event_handler_bedrock_agents/src/customizing_swagger.py delete mode 100644 examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py delete mode 100644 examples/event_handler_bedrock_agents/src/enabling_swagger.py diff --git a/examples/event_handler_bedrock_agents/src/customizing_api_metadata.py b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_metadata.py similarity index 100% rename from examples/event_handler_bedrock_agents/src/customizing_api_metadata.py rename to examples/event_handler_bedrock_agents/src/customizing_bedrock_api_metadata.py diff --git a/examples/event_handler_bedrock_agents/src/customizing_api_operations.py b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py similarity index 100% rename from examples/event_handler_bedrock_agents/src/customizing_api_operations.py rename to examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py diff --git a/examples/event_handler_bedrock_agents/src/customizing_swagger.py b/examples/event_handler_bedrock_agents/src/customizing_swagger.py deleted file mode 100644 index ecd4c9c315a..00000000000 --- a/examples/event_handler_bedrock_agents/src/customizing_swagger.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import List - -import requests -from pydantic import BaseModel, EmailStr, Field - -from aws_lambda_powertools.event_handler import BedrockAgentResolver -from aws_lambda_powertools.utilities.typing import LambdaContext - -app = BedrockAgentResolver() -app.enable_swagger(path="/_swagger", swagger_base_url="https://cdn.example.com/path/to/assets/") - - -class Todo(BaseModel): - userId: int - id_: int = Field(alias="id") - title: str - completed: bool - - -@app.get("/todos", description="Finds all todos by email") -def get_todos_by_email(email: EmailStr) -> List[Todo]: - todos = requests.get(f"https://jsonplaceholder.typicode.com/todos?email={email}") - todos.raise_for_status() - - return todos.json() - - -def lambda_handler(event: dict, context: LambdaContext) -> dict: - return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py b/examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py deleted file mode 100644 index a574ede5035..00000000000 --- a/examples/event_handler_bedrock_agents/src/customizing_swagger_middlewares.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import List - -import requests -from pydantic import BaseModel, EmailStr, Field - -from aws_lambda_powertools.event_handler import BedrockAgentResolver, Response -from aws_lambda_powertools.event_handler.middlewares import NextMiddleware -from aws_lambda_powertools.utilities.typing import LambdaContext - -app = BedrockAgentResolver() - - -def swagger_middleware(app: BedrockAgentResolver, next_middleware: NextMiddleware) -> Response: - is_authenticated = ... - if not is_authenticated: - return Response(status_code=400, body="Unauthorized") - - return next_middleware(app) - - -app.enable_swagger(middlewares=[swagger_middleware]) - - -class Todo(BaseModel): - userId: int - id_: int = Field(alias="id") - title: str - completed: bool - - -@app.get("/todos", description="Finds all todos by email") -def get_todos_by_email(email: EmailStr) -> List[Todo]: - todos = requests.get(f"https://jsonplaceholder.typicode.com/todos?email={email}") - todos.raise_for_status() - - return todos.json() - - -def lambda_handler(event: dict, context: LambdaContext) -> dict: - return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/enabling_swagger.py b/examples/event_handler_bedrock_agents/src/enabling_swagger.py deleted file mode 100644 index 651d7034170..00000000000 --- a/examples/event_handler_bedrock_agents/src/enabling_swagger.py +++ /dev/null @@ -1,22 +0,0 @@ -from time import time - -from aws_lambda_powertools import Logger, Tracer -from aws_lambda_powertools.event_handler import BedrockAgentResolver -from aws_lambda_powertools.utilities.typing import LambdaContext - -tracer = Tracer() -logger = Logger() -app = BedrockAgentResolver() -app.enable_swagger(path="/swagger") # (1)! - - -@app.get("/current_time", description="Gets the current time in seconds") -@tracer.capture_method -def current_time() -> int: - return int(time()) - - -@logger.inject_lambda_context -@tracer.capture_lambda_handler -def lambda_handler(event: dict, context: LambdaContext): - return app.resolve(event, context) From e7017cb436e40851f294ea4db43f17b01a8d9658 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 16 Feb 2024 11:27:35 +0100 Subject: [PATCH 11/46] fix: referenecs --- docs/core/event_handler/bedrock_agents.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index bca1cf0567f..981499588c6 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -163,7 +163,7 @@ use a combination of [Pydantic Models](https://docs.pydantic.dev/latest/concepts To implement these customizations, include extra parameters when defining your routes: ```python hl_lines="14-23 25" title="customizing_api_operations.py" ---8<-- "examples/event_handler_bedrock_agents/src/customizing_api_operations.py" +--8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py" ``` #### Customizing OpenAPI metadata @@ -175,7 +175,7 @@ Include extra parameters when exporting your OpenAPI specification to apply thes === "customizing_api_metadata.py" ```python hl_lines="25-31" - --8<-- "examples/event_handler_bedrock_agents/src/customizing_api_metadata.py" + --8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_metadata.py" ``` ### Data validation From 221e025bd068aa462b9206bca9fbecb91c1b0f08 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 16 Feb 2024 11:44:03 +0100 Subject: [PATCH 12/46] chore: add data validation example --- docs/core/event_handler/bedrock_agents.md | 24 ++++++------ .../src/getting_started_with_validation.py | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 examples/event_handler_bedrock_agents/src/getting_started_with_validation.py diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 981499588c6..30203580e3f 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -103,9 +103,18 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve The resolvers used by Agents for Amazon Bedrock are compatible with the full suite of Powertools for AWS Lambda utilities. This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). -### Validating parameters +### Validating input and output -Add an example for validating parameters like EmailStr from Pydantic. +You can define the expected format for incoming data and responses by using type annotations. + +```python hl_lines="5 17-20 25" +--8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" +``` + +1. No need to add the `enable_validation` parameter, as it's enabled by default. +2. You can define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) +3. Describe each input and output using human-readable descriptions +4. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest ### Generating OpenAPI schemas @@ -178,17 +187,6 @@ Include extra parameters when exporting your OpenAPI specification to apply thes --8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_metadata.py" ``` -### Data validation - -The Agents for Amazon Bedrock resolver allows for the clear definition of the expected format for incoming data and responses. -By delegating data validation tasks to Powertools for AWS Lambda, you can significantly reduce the amount of repetitive code in your project. - -For detailed guidance on implementing this feature, see the [REST API validation documentation](api_gateway.md#data-validation). -There, you'll find step-by-step instructions on how to apply data validation when using the resolver. - -???+ note - When using the Agent for Amazon Bedrock resolver, there's no need to add the `enable_validation` parameter, as it's enabled by default. - ## Testing your code Test your routes by passing an [Agent for Amazon Bedrock proxy event](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-input) request: diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py new file mode 100644 index 00000000000..f09fe6ced48 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -0,0 +1,37 @@ +import datetime +from time import time +from typing import Annotated + +from pydantic import BaseModel, EmailStr + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.event_handler.openapi.params import Body +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() +app = BedrockAgentResolver() # (1)! + + +class ScheduleMeetingResponse(BaseModel): # (2)! + date: Annotated[datetime.datetime, Body(description="The date of the scheduled meeting")] # (3)! + team: Annotated[str, Body(description="The team that will handle the support request")] + cancellationEmail: Annotated[EmailStr, Body(description="The email address to request a meeting cancellation")] + + +@app.get("/schedule_meeting", description="Schedules a meeting with the team") +@tracer.capture_method +def schedule_meeting(email: EmailStr) -> ScheduleMeetingResponse: # (4)! + logger.info("Scheduling a meeting", email=email) + return ScheduleMeetingResponse( + date=datetime.datetime.fromtimestamp(time() + 60 * 60 * 24 * 7), # 7 days from now + team="Customer Support Team B", + cancellationEmail=EmailStr("cancel@example.org"), + ) + + +@logger.inject_lambda_context +@tracer.capture_lambda_handler +def lambda_handler(event: dict, context: LambdaContext): + return app.resolve(event, context) From cbcaf3b324d39f01fe751df940aa29cd32a79402 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 16 Feb 2024 14:34:59 +0100 Subject: [PATCH 13/46] fix: typing import --- .../src/getting_started_with_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py index f09fe6ced48..89dac607b0d 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -1,8 +1,8 @@ import datetime from time import time -from typing import Annotated from pydantic import BaseModel, EmailStr +from typing_extensions import Annotated from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.event_handler import BedrockAgentResolver From d9ff550b80922a7272f0c7f4d46ad1560df83351 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 16 Feb 2024 16:28:46 +0100 Subject: [PATCH 14/46] fix: typing --- docs/core/event_handler/bedrock_agents.md | 2 +- .../src/customizing_bedrock_api_operations.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 30203580e3f..5dab9c05e01 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -171,7 +171,7 @@ use a combination of [Pydantic Models](https://docs.pydantic.dev/latest/concepts To implement these customizations, include extra parameters when defining your routes: -```python hl_lines="14-23 25" title="customizing_api_operations.py" +```python hl_lines="13-22 24" title="customizing_api_operations.py" --8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py" ``` diff --git a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py index 004ef77438e..739eb84b8ff 100644 --- a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py +++ b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py @@ -1,6 +1,5 @@ -from typing import Annotated - import requests +from typing_extensions import Annotated from aws_lambda_powertools.event_handler import BedrockAgentResolver from aws_lambda_powertools.event_handler.openapi.params import Query From 2ece75c7e5e502a31e90d96afe4d7fdb920f37ad Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 16 Feb 2024 16:42:16 +0100 Subject: [PATCH 15/46] chore: add video --- docs/core/event_handler/bedrock_agents.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 5dab9c05e01..3b412a5e269 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -150,6 +150,10 @@ To create an Agent for Amazon Bedrock, refer to the [official documentation](htt The following video demonstrates the end-to-end process: +
+ +
+ During the creation process, you can use the schema generated in the previous step when prompted for an OpenAPI specification. ## Advanced From 8352778e6a67e8026aab42030afdcd1d21f919f9 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 16 Feb 2024 17:07:00 +0100 Subject: [PATCH 16/46] chore: add example of customizing parameters --- .../_openapi_customization_intro.md | 5 --- .../_openapi_customization_parameters.md | 44 +++++++++---------- docs/core/event_handler/api_gateway.md | 5 ++- docs/core/event_handler/bedrock_agents.md | 38 ++++++++++++++-- .../src/accessing_request_fields.py | 25 +++++++++++ .../src/customizing_bedrock_api_parameters.py | 23 ++++++++++ 6 files changed, 109 insertions(+), 31 deletions(-) delete mode 100644 docs/core/event_handler/_openapi_customization_intro.md create mode 100644 examples/event_handler_bedrock_agents/src/accessing_request_fields.py create mode 100644 examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py diff --git a/docs/core/event_handler/_openapi_customization_intro.md b/docs/core/event_handler/_openapi_customization_intro.md deleted file mode 100644 index e7dd3f56f49..00000000000 --- a/docs/core/event_handler/_openapi_customization_intro.md +++ /dev/null @@ -1,5 +0,0 @@ - -In OpenAPI documentation tools like [SwaggerUI](api-gateway.md#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. - -???+ note - We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE). diff --git a/docs/core/event_handler/_openapi_customization_parameters.md b/docs/core/event_handler/_openapi_customization_parameters.md index f096441b33a..6b87ce5c598 100644 --- a/docs/core/event_handler/_openapi_customization_parameters.md +++ b/docs/core/event_handler/_openapi_customization_parameters.md @@ -1,25 +1,25 @@ Whenever you use OpenAPI parameters to validate [query strings](api_gateway.md#validating-query-strings) or [path parameters](api_gateway.md#validating-path-parameters), you can enhance validation and OpenAPI documentation by using any of these parameters: -| Field name | Type | Description | -| --------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data | -| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) | -| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) | -| `description` | `str` | Human-readable description | -| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers | -| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers | -| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers | -| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers | -| `min_length` | `int` | Minimum length for strings | -| `max_length` | `int` | Maximum length for strings | -| `pattern` | `string` | A regular expression that the string must match. | -| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details | -| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers | -| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers | -| `max_digits` | `int` | Maximum number of allow digits for strings | -| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers | -| `examples` | `List\[Any\]` | List of examples of the field | -| `deprecated` | `bool` | Marks the field as deprecated | -| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema | -| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property | +| Field name | Type | Description | +|-----------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `alias` | `str` | Alternative name for a field, used when serializing and deserializing data | +| `validation_alias` | `str` | Alternative name for a field during validation (but not serialization) | +| `serialization_alias` | `str` | Alternative name for a field during serialization (but not during validation) | +| `description` | `str` | Human-readable description | +| `gt` | `float` | Greater than. If set, value must be greater than this. Only applicable to numbers | +| `ge` | `float` | Greater than or equal. If set, value must be greater than or equal to this. Only applicable to numbers | +| `lt` | `float` | Less than. If set, value must be less than this. Only applicable to numbers | +| `le` | `float` | Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers | +| `min_length` | `int` | Minimum length for strings | +| `max_length` | `int` | Maximum length for strings | +| `pattern` | `string` | A regular expression that the string must match. | +| `strict` | `bool` | If `True`, strict validation is applied to the field. See [Strict Mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target"_blank" rel="nofollow"} for details | +| `multiple_of` | `float` | Value must be a multiple of this. Only applicable to numbers | +| `allow_inf_nan` | `bool` | Allow `inf`, `-inf`, `nan`. Only applicable to numbers | +| `max_digits` | `int` | Maximum number of allow digits for strings | +| `decimal_places` | `int` | Maximum number of decimal places allowed for numbers | +| `examples` | `List[Any]` | List of examples of the field | +| `deprecated` | `bool` | Marks the field as deprecated | +| `include_in_schema` | `bool` | If `False` the field will not be part of the exported OpenAPI schema | +| `json_schema_extra` | `JsonDict` | Any additional JSON schema data for the schema property | diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 5a2230f1463..0eb23a09b7a 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -961,7 +961,10 @@ This will enable full tracebacks errors in the response, print request and respo When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your API's parameters. ---8<-- "docs/core/event_handler/_openapi_customization_intro.md" +In OpenAPI documentation tools like [SwaggerUI](api-gateway.md#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. + +???+ note + We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE). #### Customizing OpenAPI parameters diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 3b412a5e269..d2b8b7e3603 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -154,21 +154,53 @@ The following video demonstrates the end-to-end process: -During the creation process, you can use the schema generated in the previous step when prompted for an OpenAPI specification. +During the creation process, you should use the schema generated in the previous step when prompted for an OpenAPI specification. ## Advanced +### Accessing custom request fields + +The event sent by Agents for Amazon Bedrock into your Lambda function contains a number of event fields that might be interesting. The event handler exposes them in the `app.current_event` field: + +=== "accessing_request_fields.py" + + ```python hl_lines="14-16" + --8<-- "examples/event_handler_bedrock_agents/src/accessing_request_fields.py" + ``` + +The input event fields are: + +| Name | Type | Description | +|---------------------------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| message_version | `str` | The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. | +| agent | `BedrockAgentInfo` | Contains information about the name, ID, alias, and version of the agent that the action group belongs to. | +| input_text | `str` | The user input for the conversation turn. | +| session_id | `str` | The unique identifier of the agent session. | +| action_group | `str` | The name of the action group. | +| api_path | `str` | The path to the API operation, as defined in the OpenAPI schema. | +| http_method | `str` | The method of the API operation, as defined in the OpenAPI schema. | +| parameters | `List[BedrockAgentProperty]` | Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema. | +| request_body | `BedrockAgentRequestBody` | Contains the request body and its properties, as defined in the OpenAPI schema. | +| session_attributes | `Dict[str, str]` | Contains session attributes and their values. | +| prompt_session_attributes | `Dict[str, str]` | Contains prompt attributes and their values. | + ### Additional metadata To enrich the view that Agents for Amazon Bedrock has of your Lambda functions, use a combination of [Pydantic Models](https://docs.pydantic.dev/latest/concepts/models/){target="_blank"} and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your APIs parameters. ---8<-- "docs/core/event_handler/_openapi_customization_intro.md" - #### Customizing OpenAPI parameters --8<-- "docs/core/event_handler/_openapi_customization_parameters.md" +To implement these customizations, include extra constraints when defining your parameters: + +```python hl_lines="15" title="customizing_api_parameters.py" +--8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py" +``` + +1. Here we say that the title should never be bigger than 200 characters. + #### Customizing API operations --8<-- "docs/core/event_handler/_openapi_customization_operations.md" diff --git a/examples/event_handler_bedrock_agents/src/accessing_request_fields.py b/examples/event_handler_bedrock_agents/src/accessing_request_fields.py new file mode 100644 index 00000000000..6c2599b5c53 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/accessing_request_fields.py @@ -0,0 +1,25 @@ +from time import time + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() +app = BedrockAgentResolver() + + +@app.get("/current_time", description="Gets the current time in seconds") # (1)! +def current_time() -> int: + logger.info( + "Serving current_time", + action_group=app.current_event.action_group, + input_text=app.current_event.input_text, + session_attributes=app.current_event.session_attributes, + ) + + return int(time()) + + +@logger.inject_lambda_context +def lambda_handler(event: dict, context: LambdaContext): + return app.resolve(event, context) diff --git a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py new file mode 100644 index 00000000000..dcf0ebc200d --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py @@ -0,0 +1,23 @@ +import requests +from typing_extensions import Annotated + +from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.event_handler.openapi.params import Query +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = BedrockAgentResolver() + + +@app.post( + "/todos", + summary="Creates a todo", +) +def create_todo(title: Annotated[str, Query(max_length=200, strict=True, description="The todo title")]) -> str: # (1)! + todo = requests.post("https://jsonplaceholder.typicode.com/todos", data={"title": title}) + todo.raise_for_status() + + return todo.json()["title"] + + +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) From 5ebdc3a6c270d859be0a9728f53dac6455435c53 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Mon, 19 Feb 2024 10:57:58 +0100 Subject: [PATCH 17/46] chore: add diagram --- docs/core/event_handler/bedrock_agents.md | 7 +---- .../core/event_handler/bedrock_agents.mermaid | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 docs/core/event_handler/bedrock_agents.mermaid diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index d2b8b7e3603..0214a44fec0 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -7,12 +7,7 @@ Author [Agents for Amazon Bedrock](https://aws.amazon.com/bedrock/agents/){targe
```mermaid -flowchart LR - Bedrock[Foundational Model] <-- calls --> Agent - You[User input] --> Agent - Agent[Agents for Amazon Bedrock] -- invokes --> Lambda[AWS Lambda function] - Agent -- consults --> OpenAPI{{OpenAPI schema}} - OpenAPI -. generated from .-> Lambda +--8<-- "docs/core/event_handler/bedrock_agents.mermaid" ```
diff --git a/docs/core/event_handler/bedrock_agents.mermaid b/docs/core/event_handler/bedrock_agents.mermaid new file mode 100644 index 00000000000..af7023b9b57 --- /dev/null +++ b/docs/core/event_handler/bedrock_agents.mermaid @@ -0,0 +1,28 @@ +flowchart LR + Bedrock[Foundation Model] <-- uses --> Agent + You[User input] --> Agent + Agent -- consults --> OpenAPI + Agent[Agents for Amazon Bedrock] -- invokes --> Lambda + + subgraph OpenAPI + Schema + end + + subgraph Lambda[Lambda Function] + direction TB + Parsing[Parameter Parsing] --> Validation + Validation[Parameter Validation] --> Routing + Routing --> Code[Your code] + Code --> ResponseValidation[Response Validation] + ResponseValidation --> ResponseBuilding[Response Building] + end + + subgraph ActionGroup[Action Group] + OpenAPI -. generated from .-> Lambda + end + + style Code fill:#ffa500,color:black,font-weight:bold,stroke-width:2px + style You stroke:#0F0,fill:#fff + + + From ca76fd3d420e87c6fae0af4c9bbcfabeb1580f0c Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Mon, 26 Feb 2024 17:52:08 +0100 Subject: [PATCH 18/46] chore(deps): feedback part 1 --- docs/core/event_handler/bedrock_agents.md | 35 ++++++++++++---- .../core/event_handler/bedrock_agents.mermaid | 2 +- .../cdk/bedrock_agent_stack.py | 40 +++++++++++++++++++ .../sam/bedrock_service_role.yaml | 4 +- .../sam/template.yaml | 4 +- .../src/getting_started_with_validation.py | 4 +- 6 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 0214a44fec0..c3693fd8112 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -28,8 +28,13 @@ Author [Agents for Amazon Bedrock](https://aws.amazon.com/bedrock/agents/){targe **Action group** is a collection of two resources where you define the actions that the agent should carry out: an OpenAPI schema to define the APIs that the agent can invoke to carry out its tasks, and a Lambda function to execute those actions +**Large Language Models (LLM)** are very large deep learning models that are pre-trained on vast amounts of data, capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases on it. + ## Getting started +???+ tip + All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. + To build Agents for Amazon Bedrock, you need: * [Create the Lambda function](#your-first-agent) that defines the business logic for the action that your agent carries out. @@ -54,11 +59,14 @@ Before you start, you need to the following permissions: * **Lambda permissions** allowing Amazon Bedrock to invoke it === "Service Role Example using AWS CloudFormation" + You'll need this role when [creating an Agent in the AWS Console](#walkthrough-for-creating-the-agent). + ```yaml hl_lines="14-17 24-29 34" --8<-- "examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml" ``` - 1. You need the role ARN when creating the Agent for Amazon Bedrock + 1. Check the [supported foundational models](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-supported.html){target="_blank"} + 2. You need the role ARN when creating the Agent for Amazon Bedrock === "Lambda permissions using AWS Serverless Application Model (SAM)" ```yaml hl_lines="27-33" @@ -67,6 +75,16 @@ Before you start, you need to the following permissions: 1. Amazon Bedrock needs permissions to invoke this Lambda function +=== "Using AWS Cloud Developer Kit (CDK)" + Use the [Generative AI CDK constructs](https://awslabs.github.io/generative-ai-cdk-constructs/src/cdk-lib/bedrock/#agents){target="_blank"} to create your Agent with CDK. + + ```python + --8<-- "examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py" + ``` + + 1. The path to your Lambda function handler + 2. The path to the OpenAPI schema describing your API + ### Your first Agent To create an Agent for Amazon Bedrock, use the `BedrockAgentResolver` to annotate your actions. @@ -74,6 +92,9 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve === "Lambda handler" + The resolvers used by Agents for Amazon Bedrock are compatible with the full suite of Powertools for AWS Lambda utilities. + This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). + ```python hl_lines="4 9 12 21" --8<-- "examples/event_handler_bedrock_agents/src/getting_started.py" ``` @@ -95,14 +116,11 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve !!! note "It's important to include a `description` for each API endpoint because it will improve the understanding Amazon Bedrock has of your actions" -The resolvers used by Agents for Amazon Bedrock are compatible with the full suite of Powertools for AWS Lambda utilities. -This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). - ### Validating input and output You can define the expected format for incoming data and responses by using type annotations. -```python hl_lines="5 17-20 25" +```python hl_lines="5 17-20 26" title="Automatic validation of inputs and outputs" --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" ``` @@ -111,11 +129,14 @@ You can define the expected format for incoming data and responses by using type 3. Describe each input and output using human-readable descriptions 4. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest +If the request validation, your event handler will not be called, and an error message is returned to Bedrock. +Similarly, if the response fails validation, your handler will abort the response. + ### Generating OpenAPI schemas Use the `get_openapi_json_schema` function provided by the resolver. This function will produce a JSON-serialized string that represents your OpenAPI schema. -You can print this string or save it to a file for future reference. +You can print this string or save it to a file. You'll use the file later when creating the Agent. === "Generating the OpenAPI schema" @@ -139,7 +160,7 @@ The script will generate the schema directly to standard output, which you can r python app.py > schema.json ``` -### Creating your Agent on the AWS Console +### Walkthrough for creating the agent To create an Agent for Amazon Bedrock, refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. diff --git a/docs/core/event_handler/bedrock_agents.mermaid b/docs/core/event_handler/bedrock_agents.mermaid index af7023b9b57..4703596d821 100644 --- a/docs/core/event_handler/bedrock_agents.mermaid +++ b/docs/core/event_handler/bedrock_agents.mermaid @@ -1,5 +1,5 @@ flowchart LR - Bedrock[Foundation Model] <-- uses --> Agent + Bedrock[LLM] <-- uses --> Agent You[User input] --> Agent Agent -- consults --> OpenAPI Agent[Agents for Amazon Bedrock] -- invokes --> Lambda diff --git a/examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py b/examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py new file mode 100644 index 00000000000..ef220209d6d --- /dev/null +++ b/examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py @@ -0,0 +1,40 @@ +from aws_cdk import ( + Stack, +) +from aws_cdk.aws_lambda import Runtime +from aws_cdk.aws_lambda_python_alpha import PythonFunction +from cdklabs.generative_ai_cdk_constructs.bedrock import ( + Agent, + ApiSchema, + BedrockFoundationModel, +) +from constructs import Construct + + +class AgentsCdkStack(Stack): + + def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + action_group_function = PythonFunction( + self, + "LambdaFunction", + runtime=Runtime.PYTHON_3_12, + entry="./lambda", # (1)! + index="app.py", + handler="lambda_handler", + ) + + agent = Agent( + self, + "Agent", + foundation_model=BedrockFoundationModel.ANTHROPIC_CLAUDE_INSTANT_V1_2, + instruction="You are a helpful and friendly agent that answers questions about insurance claims.", + ) + agent.add_action_group( + action_group_name="InsureClaimsSupport", + description="Use these functions for insurance claims support", + action_group_executor=action_group_function, + action_group_state="ENABLED", + api_schema=ApiSchema.from_asset("./lambda/openapi.json"), # (2)! + ) diff --git a/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml b/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml index e50e4560e79..8b57f966a72 100644 --- a/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml +++ b/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml @@ -23,7 +23,7 @@ Resources: - Effect: Allow Action: - bedrock:InvokeModel - Resource: + Resource: # (1)! - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2 - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2:1 - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-instant-v1 @@ -31,4 +31,4 @@ Resources: Outputs: BedrockServiceRole: Description: The role ARN to be used by Amazon Bedrock - Value: !GetAtt BedrockServiceRole.Arn # (1)! + Value: !GetAtt BedrockServiceRole.Arn # (2)! diff --git a/examples/event_handler_bedrock_agents/sam/template.yaml b/examples/event_handler_bedrock_agents/sam/template.yaml index 7af68bff7e7..4fdbd718c6e 100644 --- a/examples/event_handler_bedrock_agents/sam/template.yaml +++ b/examples/event_handler_bedrock_agents/sam/template.yaml @@ -5,7 +5,7 @@ Description: > Globals: # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html Function: - Timeout: 5 + Timeout: 30 Runtime: python3.12 Tracing: Active Environment: @@ -16,8 +16,6 @@ Globals: # https://docs.aws.amazon.com/serverless-application-model/latest/devel Resources: ApiFunction: Type: AWS::Serverless::Function - DeletionPolicy: Delete - UpdateReplacePolicy: Delete Properties: Handler: getting_started.lambda_handler Description: Agent for Amazon Bedrock handler function diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py index 89dac607b0d..520c14fa773 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -22,7 +22,9 @@ class ScheduleMeetingResponse(BaseModel): # (2)! @app.get("/schedule_meeting", description="Schedules a meeting with the team") @tracer.capture_method -def schedule_meeting(email: EmailStr) -> ScheduleMeetingResponse: # (4)! +def schedule_meeting( + email: Annotated[EmailStr, Body(description="The email address of the customer")], +) -> ScheduleMeetingResponse: # (4)! logger.info("Scheduling a meeting", email=email) return ScheduleMeetingResponse( date=datetime.datetime.fromtimestamp(time() + 60 * 60 * 24 * 7), # 7 days from now From 8b5b7920521c899aea99026a8fd83a52ea80f364 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 11:15:37 +0100 Subject: [PATCH 19/46] chore: xxx --- docs/core/event_handler/bedrock_agents.md | 26 +++- .../get_started_with_validation_output.json | 14 ++ .../src/getting_started_with_validation.json | 23 +++ .../src/getting_started_with_validation.py | 46 +++++- ...etting_started_with_validation_schema.json | 141 ++++++++++++++++++ 5 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 examples/event_handler_bedrock_agents/src/get_started_with_validation_output.json create mode 100644 examples/event_handler_bedrock_agents/src/getting_started_with_validation.json create mode 100644 examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index c3693fd8112..977bfa1d7c5 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -120,9 +120,29 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve You can define the expected format for incoming data and responses by using type annotations. -```python hl_lines="5 17-20 26" title="Automatic validation of inputs and outputs" ---8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" -``` +=== "Lambda handler" + + ```python hl_lines="5 17-20 26" + --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" + ``` + +=== "OpenAPI schema" + + ```json + --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json" + ``` + +=== "Input payload" + + ```json hl_lines="6-13 20" + --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.json" + ``` + +=== "Output payload" + + ```json hl_lines="10" + --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json" + ``` 1. No need to add the `enable_validation` parameter, as it's enabled by default. 2. You can define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) diff --git a/examples/event_handler_bedrock_agents/src/get_started_with_validation_output.json b/examples/event_handler_bedrock_agents/src/get_started_with_validation_output.json new file mode 100644 index 00000000000..31d5d91bd34 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/get_started_with_validation_output.json @@ -0,0 +1,14 @@ +{ + "messageVersion": "1.0", + "response": { + "actionGroup": "SupportAssistant", + "apiPath": "/schedule_meeting", + "httpMethod": "GET", + "httpStatusCode": 200, + "responseBody": { + "application/json": { + "body": "{\"date\": \"\", \"team\": \"\", \"cancellationEmail\": \"\"}" + } + } + } +} diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.json b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.json new file mode 100644 index 00000000000..5af2d830577 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.json @@ -0,0 +1,23 @@ +{ + "sessionId": "123456789012345", + "sessionAttributes": {}, + "inputText": "Schedule a meeting with the team. My email is foo@example.org", + "promptSessionAttributes": {}, + "apiPath": "/schedule_meeting", + "parameters": [ + { + "name": "string", + "type": "string", + "value": "foo@example.org" + } + ], + "agent": { + "name": "TimeAgent", + "version": "DRAFT", + "id": "XLHH72XNF2", + "alias": "TSTALIASID" + }, + "httpMethod": "GET", + "messageVersion": "1.0", + "actionGroup": "SupportAssistant" +} diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py index 520c14fa773..5b56daa1eb5 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -6,7 +6,7 @@ from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.event_handler import BedrockAgentResolver -from aws_lambda_powertools.event_handler.openapi.params import Body +from aws_lambda_powertools.event_handler.openapi.params import Body, Query from aws_lambda_powertools.utilities.typing import LambdaContext tracer = Tracer() @@ -23,8 +23,8 @@ class ScheduleMeetingResponse(BaseModel): # (2)! @app.get("/schedule_meeting", description="Schedules a meeting with the team") @tracer.capture_method def schedule_meeting( - email: Annotated[EmailStr, Body(description="The email address of the customer")], -) -> ScheduleMeetingResponse: # (4)! + email: Annotated[EmailStr, Query(description="The email address of the customer")], # (4)! +) -> Annotated[ScheduleMeetingResponse, Body(description="Scheduled meeting details")]: logger.info("Scheduling a meeting", email=email) return ScheduleMeetingResponse( date=datetime.datetime.fromtimestamp(time() + 60 * 60 * 24 * 7), # 7 days from now @@ -37,3 +37,43 @@ def schedule_meeting( @tracer.capture_lambda_handler def lambda_handler(event: dict, context: LambdaContext): return app.resolve(event, context) + + +if __name__ == "__main__": + from dataclasses import dataclass + + @dataclass + class FakeLambdaContext: + function_name: str = "test" + memory_limit_in_mb: int = 128 + invoked_function_arn: str = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + aws_request_id: str = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + + print( + lambda_handler( + { + "sessionId": "123456789012345", + "sessionAttributes": {}, + "inputText": "Schedule a meeting with the team. My email is foo@example.org", + "promptSessionAttributes": {}, + "apiPath": "/schedule_meeting", + "parameters": [ + { + "name": "email", + "type": "string", + "value": "rubefons@amazon.com", + }, + ], + "agent": { + "name": "TimeAgent", + "version": "DRAFT", + "id": "XLHH72XNF2", + "alias": "TSTALIASID", + }, + "httpMethod": "GET", + "messageVersion": "1.0", + "actionGroup": "SupportAssistant", + }, + FakeLambdaContext(), + ), + ) diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json new file mode 100644 index 00000000000..e345f90b63f --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json @@ -0,0 +1,141 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Powertools API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/" + } + ], + "paths": { + "/schedule_meeting": { + "get": { + "summary": "GET /schedule_meeting", + "description": "Schedules a meeting with the team", + "operationId": "schedule_meeting_schedule_meeting_get", + "requestBody": { + "description": "The email address of the customer", + "content": { + "application/json": { + "schema": { + "type": "string", + "format": "email", + "title": "Email", + "description": "The email address of the customer" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ScheduleMeetingResponse" + } + ], + "title": "Return", + "description": "Scheduled meeting details" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "ScheduleMeetingResponse": { + "properties": { + "date": { + "type": "string", + "format": "date-time", + "title": "Date", + "description": "The date of the scheduled meeting" + }, + "team": { + "type": "string", + "title": "Team", + "description": "The team that will handle the support request" + }, + "cancellationEmail": { + "type": "string", + "format": "email", + "title": "Cancellationemail", + "description": "The email address to request a meeting cancellation" + } + }, + "type": "object", + "required": [ + "date", + "team", + "cancellationEmail" + ], + "title": "ScheduleMeetingResponse" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + } + } +} From 695cf8d1fbb76812808cd31b2cc207692a24f2e5 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 13:20:32 +0100 Subject: [PATCH 20/46] chore: make description required --- .../event_handler/bedrock_agent.py | 163 +++++++++++++++++- docs/core/event_handler/bedrock_agents.md | 10 +- .../src/customizing_bedrock_api_parameters.py | 2 +- .../src/getting_started_with_validation.json | 2 +- .../src/getting_started_with_validation.py | 42 +---- ...tting_started_with_validation_output.json} | 2 +- .../event_handler/test_bedrock_agent.py | 12 +- 7 files changed, 177 insertions(+), 56 deletions(-) rename examples/event_handler_bedrock_agents/src/{get_started_with_validation_output.json => getting_started_with_validation_output.json} (63%) diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py index 9c65547d9a2..5e42f6eeede 100644 --- a/aws_lambda_powertools/event_handler/bedrock_agent.py +++ b/aws_lambda_powertools/event_handler/bedrock_agent.py @@ -1,13 +1,15 @@ from re import Match -from typing import Any, Dict +from typing import Any, Callable, Dict, List, Optional from typing_extensions import override from aws_lambda_powertools.event_handler import ApiGatewayResolver from aws_lambda_powertools.event_handler.api_gateway import ( + _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, ProxyEventType, ResponseBuilder, ) +from aws_lambda_powertools.event_handler.openapi.types import OpenAPIResponse from aws_lambda_powertools.utilities.data_classes import BedrockAgentEvent @@ -83,6 +85,165 @@ def __init__(self, debug: bool = False, enable_validation: bool = True): ) self._response_builder_class = BedrockResponseBuilder + @override + def get( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + return super(BedrockAgentResolver, self).get( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def post( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + return super().post( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def put( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + return super().put( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def patch( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable]] = None, + ): + return super().patch( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + middlewares, + ) + + # Note: we need ignore[override] because we are making the optional `description` field required. + @override + def delete( # type: ignore[override] + self, + rule: str, + description: str, + cors: Optional[bool] = None, + compress: bool = False, + cache_control: Optional[str] = None, + summary: Optional[str] = None, + responses: Optional[Dict[int, OpenAPIResponse]] = None, + response_description: str = _DEFAULT_OPENAPI_RESPONSE_DESCRIPTION, + tags: Optional[List[str]] = None, + operation_id: Optional[str] = None, + include_in_schema: bool = True, + middlewares: Optional[List[Callable[..., Any]]] = None, + ): + return super().delete( + rule, + cors, + compress, + cache_control, + summary, + description, + responses, + response_description, + tags, + operation_id, + include_in_schema, + middlewares, + ) + @override def _convert_matches_into_route_keys(self, match: Match) -> Dict[str, str]: # In Bedrock Agents, all the parameters come inside the "parameters" key, not on the apiPath diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 977bfa1d7c5..699053e4b1e 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -132,6 +132,11 @@ You can define the expected format for incoming data and responses by using type --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json" ``` + 1. No need to add the `enable_validation` parameter, as it's enabled by default. + 2. You can define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) + 3. Describe each input and output using human-readable descriptions + 4. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest + === "Input payload" ```json hl_lines="6-13 20" @@ -144,11 +149,6 @@ You can define the expected format for incoming data and responses by using type --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json" ``` -1. No need to add the `enable_validation` parameter, as it's enabled by default. -2. You can define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) -3. Describe each input and output using human-readable descriptions -4. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest - If the request validation, your event handler will not be called, and an error message is returned to Bedrock. Similarly, if the response fails validation, your handler will abort the response. diff --git a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py index dcf0ebc200d..2ee8a046157 100644 --- a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py +++ b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py @@ -10,7 +10,7 @@ @app.post( "/todos", - summary="Creates a todo", + description="Creates a todo", ) def create_todo(title: Annotated[str, Query(max_length=200, strict=True, description="The todo title")]) -> str: # (1)! todo = requests.post("https://jsonplaceholder.typicode.com/todos", data={"title": title}) diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.json b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.json index 5af2d830577..ccabb84a737 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.json +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.json @@ -6,7 +6,7 @@ "apiPath": "/schedule_meeting", "parameters": [ { - "name": "string", + "name": "email", "type": "string", "value": "foo@example.org" } diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py index 5b56daa1eb5..6f37145b966 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -29,7 +29,7 @@ def schedule_meeting( return ScheduleMeetingResponse( date=datetime.datetime.fromtimestamp(time() + 60 * 60 * 24 * 7), # 7 days from now team="Customer Support Team B", - cancellationEmail=EmailStr("cancel@example.org"), + cancellationEmail="cancel@example.org", ) @@ -37,43 +37,3 @@ def schedule_meeting( @tracer.capture_lambda_handler def lambda_handler(event: dict, context: LambdaContext): return app.resolve(event, context) - - -if __name__ == "__main__": - from dataclasses import dataclass - - @dataclass - class FakeLambdaContext: - function_name: str = "test" - memory_limit_in_mb: int = 128 - invoked_function_arn: str = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - aws_request_id: str = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - - print( - lambda_handler( - { - "sessionId": "123456789012345", - "sessionAttributes": {}, - "inputText": "Schedule a meeting with the team. My email is foo@example.org", - "promptSessionAttributes": {}, - "apiPath": "/schedule_meeting", - "parameters": [ - { - "name": "email", - "type": "string", - "value": "rubefons@amazon.com", - }, - ], - "agent": { - "name": "TimeAgent", - "version": "DRAFT", - "id": "XLHH72XNF2", - "alias": "TSTALIASID", - }, - "httpMethod": "GET", - "messageVersion": "1.0", - "actionGroup": "SupportAssistant", - }, - FakeLambdaContext(), - ), - ) diff --git a/examples/event_handler_bedrock_agents/src/get_started_with_validation_output.json b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json similarity index 63% rename from examples/event_handler_bedrock_agents/src/get_started_with_validation_output.json rename to examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json index 31d5d91bd34..77ae3f9bd5e 100644 --- a/examples/event_handler_bedrock_agents/src/get_started_with_validation_output.json +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json @@ -7,7 +7,7 @@ "httpStatusCode": 200, "responseBody": { "application/json": { - "body": "{\"date\": \"\", \"team\": \"\", \"cancellationEmail\": \"\"}" + "body": "{\"date\":\"2024-03-05T11:18:28.023979\",\"team\":\"Customer Support Team B\",\"cancellationEmail\":\"cancel@example.org\"}" } } } diff --git a/tests/functional/event_handler/test_bedrock_agent.py b/tests/functional/event_handler/test_bedrock_agent.py index 266edd10de0..9f9a64427bf 100644 --- a/tests/functional/event_handler/test_bedrock_agent.py +++ b/tests/functional/event_handler/test_bedrock_agent.py @@ -13,7 +13,7 @@ def test_bedrock_agent_event(): # GIVEN a Bedrock Agent event app = BedrockAgentResolver() - @app.get("/claims") + @app.get("/claims", description="Gets claims") def claims() -> Dict[str, Any]: assert isinstance(app.current_event, BedrockAgentEvent) assert app.lambda_context == {} @@ -38,7 +38,7 @@ def test_bedrock_agent_with_path_params(): # GIVEN a Bedrock Agent event app = BedrockAgentResolver() - @app.get("/claims/") + @app.get("/claims/", description="Gets claims by ID") def claims(claim_id: str): assert isinstance(app.current_event, BedrockAgentEvent) assert app.lambda_context == {} @@ -61,7 +61,7 @@ def test_bedrock_agent_event_with_response(): app = BedrockAgentResolver() output = {"output": claims_response} - @app.get("/claims") + @app.get("/claims", description="Gets claims") def claims(): assert isinstance(app.current_event, BedrockAgentEvent) assert app.lambda_context == {} @@ -86,7 +86,7 @@ def test_bedrock_agent_event_with_no_matches(): # GIVEN a Bedrock Agent event app = BedrockAgentResolver() - @app.get("/no_match") + @app.get("/no_match", description="Matches nothing") def claims(): raise RuntimeError() @@ -106,7 +106,7 @@ def test_bedrock_agent_event_with_validation_error(): # GIVEN a Bedrock Agent event app = BedrockAgentResolver() - @app.get("/claims") + @app.get("/claims", description="Gets claims") def claims() -> Dict[str, Any]: return "oh no, this is not a dict" # type: ignore @@ -140,7 +140,7 @@ def handle_runtime_error(ex: RuntimeError): body="Something went wrong", ) - @app.get("/claims") + @app.get("/claims", description="Gets claims") def claims(): raise RuntimeError() From c485de060f6d28d3cd02796da83ecb95db577eb3 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 13:21:51 +0100 Subject: [PATCH 21/46] chore: remove the magic --- aws_lambda_powertools/event_handler/api_gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index b2e5298752c..271c767c060 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -1740,7 +1740,7 @@ def register_resolver(func: Callable): compress, cache_control, summary, - description or getattr(func, "__name__", repr(func)), # defaults description to the function name + description, responses, response_description, tags, From 3dec9ab6a8bcb4e07088489590b7e7289c91d5ef Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 13:22:30 +0100 Subject: [PATCH 22/46] chore: re-add magic comment --- aws_lambda_powertools/event_handler/bedrock_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aws_lambda_powertools/event_handler/bedrock_agent.py b/aws_lambda_powertools/event_handler/bedrock_agent.py index 5e42f6eeede..0ce0f3ff725 100644 --- a/aws_lambda_powertools/event_handler/bedrock_agent.py +++ b/aws_lambda_powertools/event_handler/bedrock_agent.py @@ -85,6 +85,7 @@ def __init__(self, debug: bool = False, enable_validation: bool = True): ) self._response_builder_class = BedrockResponseBuilder + # Note: we need ignore[override] because we are making the optional `description` field required. @override def get( # type: ignore[override] self, From 1438a12b3512dddb63e35daceafac06781a3d145 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 13:29:14 +0100 Subject: [PATCH 23/46] chore: add missing dependencies --- poetry.lock | 103 ++++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 2 + 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 678896ba721..eaac0c5fd37 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "anyio" @@ -156,6 +156,24 @@ jsii = ">=1.92.0,<2.0.0" publication = ">=0.0.3" typeguard = ">=2.13.3,<2.14.0" +[[package]] +name = "aws-cdk-aws-lambda-python-alpha" +version = "2.130.0a0" +description = "The CDK Construct Library for AWS Lambda in Python" +optional = false +python-versions = "~=3.8" +files = [ + {file = "aws-cdk.aws-lambda-python-alpha-2.130.0a0.tar.gz", hash = "sha256:979c7930abebaff0e5945db8c4d200f5f4b625be4a13c4b66bf1bfe31dcccd19"}, + {file = "aws_cdk.aws_lambda_python_alpha-2.130.0a0-py3-none-any.whl", hash = "sha256:b8d010b6a479673a82ec7d1ce215fe0d41c42936b732f969be7a8721c8916704"}, +] + +[package.dependencies] +aws-cdk-lib = ">=2.130.0,<3.0.0" +constructs = ">=10.0.0,<11.0.0" +jsii = ">=1.94.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + [[package]] name = "aws-cdk-lib" version = "2.130.0" @@ -330,17 +348,17 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.34.49" +version = "1.34.50" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.8" files = [ - {file = "boto3-1.34.49-py3-none-any.whl", hash = "sha256:ce8d1de03024f52a1810e8d71ad4dba3a5b9bb48b35567191500e3432a9130b4"}, - {file = "boto3-1.34.49.tar.gz", hash = "sha256:96b9dc85ce8d52619b56ca7b1ac1423eaf0af5ce132904bcc8aa81396eec2abf"}, + {file = "boto3-1.34.50-py3-none-any.whl", hash = "sha256:8d709365231234bc4f0ca98fdf33a25eeebf78072853c6aa3d259f0f5cf09877"}, + {file = "boto3-1.34.50.tar.gz", hash = "sha256:290952be7899560039cb0042e8a2354f61a7dead0d0ca8bea6ba901930df0468"}, ] [package.dependencies] -botocore = ">=1.34.49,<1.35.0" +botocore = ">=1.34.50,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -349,13 +367,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.49" +version = "1.34.50" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.8" files = [ - {file = "botocore-1.34.49-py3-none-any.whl", hash = "sha256:4ed9d7603a04b5bb5bd5de63b513bc2c8a7e8b1cd0088229c5ceb461161f43b6"}, - {file = "botocore-1.34.49.tar.gz", hash = "sha256:d89410bc60673eaff1699f3f1fdcb0e3a5e1f7a6a048c0d88c3ce5c3549433ec"}, + {file = "botocore-1.34.50-py3-none-any.whl", hash = "sha256:fda510559dbe796eefdb59561cc81be1b99afba3dee53fd23db9a3d587adc0ab"}, + {file = "botocore-1.34.50.tar.gz", hash = "sha256:33ab82cb96c4bb684f0dbafb071808e4817d83debc88b223e7d988256370c6d7"}, ] [package.dependencies] @@ -408,6 +426,43 @@ pyyaml = ["pyyaml (>=6.0)"] tomlkit = ["tomlkit (>=0.11.8)"] ujson = ["ujson (>=5.7.0)"] +[[package]] +name = "cdk-nag" +version = "2.28.47" +description = "Check CDK v2 applications for best practices using a combination on available rule packs." +optional = false +python-versions = "~=3.8" +files = [ + {file = "cdk-nag-2.28.47.tar.gz", hash = "sha256:0e4966751f008a6fc95342a6efe35492ecfab9b2286a5a199dafc4e32afd58b5"}, + {file = "cdk_nag-2.28.47-py3-none-any.whl", hash = "sha256:7fdc76e45d82797e2f669ae739753a7e328040d2302cf8ebe8467eb60e2dac98"}, +] + +[package.dependencies] +aws-cdk-lib = ">=2.116.0,<3.0.0" +constructs = ">=10.0.5,<11.0.0" +jsii = ">=1.94.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "cdklabs-generative-ai-cdk-constructs" +version = "0.1.74" +description = "AWS Generative AI CDK Constructs is a library for well-architected generative AI patterns." +optional = false +python-versions = "~=3.8" +files = [ + {file = "cdklabs.generative-ai-cdk-constructs-0.1.74.tar.gz", hash = "sha256:88b5e0305766134f1286e8f4f6ed656cab33a9c0b807d08bec21dd2987ed06f3"}, + {file = "cdklabs.generative_ai_cdk_constructs-0.1.74-py3-none-any.whl", hash = "sha256:40e2e860f9e204dfc5d541017a5675d8baeb411ac5f6d4770dd62e034e11fde6"}, +] + +[package.dependencies] +aws-cdk-lib = ">=2.116.0,<3.0.0" +cdk-nag = ">=2.28.46,<3.0.0" +constructs = ">=10.3.0,<11.0.0" +jsii = ">=1.94.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + [[package]] name = "certifi" version = "2024.2.2" @@ -1226,6 +1281,17 @@ files = [ {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a3a6a2fbbe7550ffe52d151cf76065e6b89cfb3e9d0463e49a7e322a25d0426"}, {file = "ijson-3.2.3-cp311-cp311-win32.whl", hash = "sha256:6a4db2f7fb9acfb855c9ae1aae602e4648dd1f88804a0d5cfb78c3639bcf156c"}, {file = "ijson-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccd6be56335cbb845f3d3021b1766299c056c70c4c9165fb2fbe2d62258bae3f"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:055b71bbc37af5c3c5861afe789e15211d2d3d06ac51ee5a647adf4def19c0ea"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c075a547de32f265a5dd139ab2035900fef6653951628862e5cdce0d101af557"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:457f8a5fc559478ac6b06b6d37ebacb4811f8c5156e997f0d87d708b0d8ab2ae"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9788f0c915351f41f0e69ec2618b81ebfcf9f13d9d67c6d404c7f5afda3e4afb"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa234ab7a6a33ed51494d9d2197fb96296f9217ecae57f5551a55589091e7853"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd0dc5da4f9dc6d12ab6e8e0c57d8b41d3c8f9ceed31a99dae7b2baf9ea769a"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c6beb80df19713e39e68dc5c337b5c76d36ccf69c30b79034634e5e4c14d6904"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a2973ce57afb142d96f35a14e9cfec08308ef178a2c76b8b5e1e98f3960438bf"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:105c314fd624e81ed20f925271ec506523b8dd236589ab6c0208b8707d652a0e"}, + {file = "ijson-3.2.3-cp312-cp312-win32.whl", hash = "sha256:ac44781de5e901ce8339352bb5594fcb3b94ced315a34dbe840b4cff3450e23b"}, + {file = "ijson-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:0567e8c833825b119e74e10a7c29761dc65fcd155f5d4cb10f9d3b8916ef9912"}, {file = "ijson-3.2.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eeb286639649fb6bed37997a5e30eefcacddac79476d24128348ec890b2a0ccb"}, {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:396338a655fb9af4ac59dd09c189885b51fa0eefc84d35408662031023c110d1"}, {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e0243d166d11a2a47c17c7e885debf3b19ed136be2af1f5d1c34212850236ac"}, @@ -2515,6 +2581,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2522,8 +2589,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2540,6 +2615,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2547,6 +2623,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2940,13 +3017,13 @@ pbr = "*" [[package]] name = "sentry-sdk" -version = "1.40.5" +version = "1.40.6" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, - {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, + {file = "sentry-sdk-1.40.6.tar.gz", hash = "sha256:f143f3fb4bb57c90abef6e2ad06b5f6f02b2ca13e4060ec5c0549c7a9ccce3fa"}, + {file = "sentry_sdk-1.40.6-py2.py3-none-any.whl", hash = "sha256:becda09660df63e55f307570e9817c664392655a7328bbc414b507e9cb874c67"}, ] [package.dependencies] @@ -2972,7 +3049,7 @@ huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] +pure-eval = ["asttokens", "executing", "pure_eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -3432,4 +3509,4 @@ validation = ["fastjsonschema"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0.0" -content-hash = "ff8aabf1c8d72613c8b43d6f85bd1127acfbc2f4d6ad7cc352e4f87ebaf6222a" +content-hash = "aadd7a8c6d3a32a4d6cdd8ccb5096c1bc75486a8e109def36843d2c72619b823" diff --git a/pyproject.toml b/pyproject.toml index 0df5354c4f3..ac255b24a68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,8 @@ aws-cdk-lib = "^2.130.0" "aws-cdk.aws-apigatewayv2-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-integrations-alpha" = "^2.38.1-alpha.0" "aws-cdk.aws-apigatewayv2-authorizers-alpha" = "^2.38.1-alpha.0" +"aws-cdk.aws-lambda-python-alpha" = "^2.130.0a0" +"cdklabs.generative-ai-cdk-constructs" = "^0.1.74" pytest-benchmark = "^4.0.0" mypy-boto3-appconfig = "^1.34.0" mypy-boto3-cloudformation = "^1.34.32" From 251c93930aca27ff98d7ee11e4a4d4e9dd3a4007 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 13:40:44 +0100 Subject: [PATCH 24/46] chore: add ugly note about pydantic v1 --- .../src/getting_started_with_validation.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py index 6f37145b966..d8e21c9153f 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -1,7 +1,8 @@ import datetime from time import time +from typing import TYPE_CHECKING -from pydantic import BaseModel, EmailStr +from pydantic import BaseModel from typing_extensions import Annotated from aws_lambda_powertools import Logger, Tracer @@ -9,6 +10,11 @@ from aws_lambda_powertools.event_handler.openapi.params import Body, Query from aws_lambda_powertools.utilities.typing import LambdaContext +if TYPE_CHECKING: # Pydantic's V1 EmailStr is not compatible with mypy + EmailStr = Annotated[str, ...] # https://github.com/pydantic/pydantic/issues/1490#issuecomment-630131270 +else: + from pydantic import EmailStr + tracer = Tracer() logger = Logger() app = BedrockAgentResolver() # (1)! From 91de6b11ee904339be8045efd9dea5ce89fd3c85 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 14:27:17 +0100 Subject: [PATCH 25/46] chore: more feedback --- .../_openapi_customization_operations.md | 2 +- docs/core/event_handler/bedrock_agents.md | 45 +++++---- .../src/assert_bedrock_agent_response.py | 2 +- .../assert_bedrock_agent_response_module.py | 14 ++- .../src/customizing_bedrock_api_operations.py | 18 ++-- .../src/customizing_bedrock_api_parameters.py | 20 ++-- .../src/getting_started_schema.json | 94 +++++++++++++++++++ 7 files changed, 148 insertions(+), 47 deletions(-) create mode 100644 examples/event_handler_bedrock_agents/src/getting_started_schema.json diff --git a/docs/core/event_handler/_openapi_customization_operations.md b/docs/core/event_handler/_openapi_customization_operations.md index 7a50366e75c..df842b2b7fc 100644 --- a/docs/core/event_handler/_openapi_customization_operations.md +++ b/docs/core/event_handler/_openapi_customization_operations.md @@ -1,6 +1,6 @@ -Customize your API endpoints by adding metadata to endpoint definitions. This provides descriptive documentation for API consumers and gives extra instructions to the framework. +Customize your API endpoints by adding metadata to endpoint definitions. Here's a breakdown of various customizable fields: diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 699053e4b1e..d5f1f95e414 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -102,6 +102,14 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve 1. `description` is a recommended field that should contain a human readable description of your action 2. We take care of **parsing**, **validating**, **routing** and **responding** to the request. +=== "OpenAPI schema" + + Powertools for AWS Lambda [generates this automatically](#generating-openapi-schemas) from the Lambda handler. + + ```json + --8<-- "examples/event_handler_bedrock_agents/src/getting_started_schema.json" + ``` + === "Input payload" ```json hl_lines="4 6 13" @@ -122,21 +130,21 @@ You can define the expected format for incoming data and responses by using type === "Lambda handler" - ```python hl_lines="5 17-20 26" + ```python hl_lines="5 23-26 32-33" --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" ``` + 1. No need to add the `enable_validation` parameter, as it's enabled by default. + 2. You can define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) + 3. Describe each input and output using human-readable descriptions + 4. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest + === "OpenAPI schema" ```json --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json" ``` - 1. No need to add the `enable_validation` parameter, as it's enabled by default. - 2. You can define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) - 3. Describe each input and output using human-readable descriptions - 4. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest - === "Input payload" ```json hl_lines="6-13 20" @@ -225,17 +233,20 @@ The input event fields are: To enrich the view that Agents for Amazon Bedrock has of your Lambda functions, use a combination of [Pydantic Models](https://docs.pydantic.dev/latest/concepts/models/){target="_blank"} and [OpenAPI](https://www.openapis.org/){target="_blank"} type annotations to add constraints to your APIs parameters. +???+ info "When is this useful?" + Adding constraints to your function parameters can help you to enforce data validation and improve the understanding of your APIs by Amazon Bedrock. + #### Customizing OpenAPI parameters --8<-- "docs/core/event_handler/_openapi_customization_parameters.md" To implement these customizations, include extra constraints when defining your parameters: -```python hl_lines="15" title="customizing_api_parameters.py" +```python hl_lines="19" title="customizing_api_parameters.py" title="Customizing API parameters" --8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py" ``` -1. Here we say that the title should never be bigger than 200 characters. +1. Title should not be larger than 200 characters and [strict mode](https://docs.pydantic.dev/latest/concepts/strict_mode/){target="_blank"} is activated #### Customizing API operations @@ -243,34 +254,22 @@ To implement these customizations, include extra constraints when defining your To implement these customizations, include extra parameters when defining your routes: -```python hl_lines="13-22 24" title="customizing_api_operations.py" +```python hl_lines="13-22" title="customizing_api_operations.py" title="Customzing API operations" --8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py" ``` -#### Customizing OpenAPI metadata - ---8<-- "docs/core/event_handler/_openapi_customization_metadata.md" - -Include extra parameters when exporting your OpenAPI specification to apply these customizations: - -=== "customizing_api_metadata.py" - - ```python hl_lines="25-31" - --8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_metadata.py" - ``` - ## Testing your code Test your routes by passing an [Agent for Amazon Bedrock proxy event](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-input) request: === "assert_bedrock_agent_response.py" - ```python hl_lines="21-23" + ```python hl_lines="21-23 27" --8<-- "examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py" ``` === "assert_bedrock_agent_response_module.py" - ```python + ```python hl_lines="14-17" --8<-- "examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py" ``` diff --git a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py index eceffe20434..07f3273961e 100644 --- a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py +++ b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response.py @@ -18,7 +18,7 @@ class LambdaContext: def test_lambda_handler(lambda_context): minimal_event = { - "apiPath": "/todos", + "apiPath": "/current_time", "httpMethod": "GET", "inputText": "What is the current time?", } diff --git a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py index 58889da7110..97e3026adce 100644 --- a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py +++ b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py @@ -1,8 +1,9 @@ -import requests -from requests import Response +import time +from typing import Annotated from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.event_handler import BedrockAgentResolver +from aws_lambda_powertools.event_handler.openapi.params import Body from aws_lambda_powertools.utilities.typing import LambdaContext tracer = Tracer() @@ -10,13 +11,10 @@ app = BedrockAgentResolver() -@app.get("/todos", description="Gets the first 10 todos") +@app.get("/current_time", description="Gets the current time") @tracer.capture_method -def get_todos(): - todos: Response = requests.get("https://jsonplaceholder.typicode.com/todos") - todos.raise_for_status() - - return {"todos": todos.json()[:10]} +def current_time() -> Annotated[int, Body(description="Current time in milliseconds")]: + return round(time.time() * 1000) @logger.inject_lambda_context diff --git a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py index 739eb84b8ff..6eb2393b263 100644 --- a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py +++ b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py @@ -2,7 +2,7 @@ from typing_extensions import Annotated from aws_lambda_powertools.event_handler import BedrockAgentResolver -from aws_lambda_powertools.event_handler.openapi.params import Query +from aws_lambda_powertools.event_handler.openapi.params import Body, Query from aws_lambda_powertools.utilities.typing import LambdaContext app = BedrockAgentResolver() @@ -10,18 +10,20 @@ @app.get( "/todos/", - summary="Retrieves a todo item, returning it's title", - description="Loads a todo item identified by the `todo_id`", - response_description="The todo title", + summary="Retrieves a TODO item, returning it's title", + description="Loads a TODO item identified by the `todo_id`", + response_description="The TODO title", responses={ - 200: {"description": "Todo item found"}, + 200: {"description": "TODO item found"}, 404: { - "description": "Item not found", + "description": "TODO not found", }, }, - tags=["Todos"], + tags=["todos"], ) -def get_todo_title(todo_id: Annotated[int, Query(description="The ID of the todo item to get the title from")]) -> str: +def get_todo_title( + todo_id: Annotated[int, Query(description="The ID of the TODO item to get the title from")], +) -> Annotated[str, Body(description="The TODO title")]: todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}") todo.raise_for_status() diff --git a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py index 2ee8a046157..5834959d15b 100644 --- a/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py +++ b/examples/event_handler_bedrock_agents/src/customizing_bedrock_api_parameters.py @@ -1,22 +1,30 @@ import requests from typing_extensions import Annotated +from aws_lambda_powertools import Logger from aws_lambda_powertools.event_handler import BedrockAgentResolver -from aws_lambda_powertools.event_handler.openapi.params import Query +from aws_lambda_powertools.event_handler.openapi.params import Body, Query from aws_lambda_powertools.utilities.typing import LambdaContext app = BedrockAgentResolver() +logger = Logger() + @app.post( "/todos", - description="Creates a todo", + description="Creates a TODO", ) -def create_todo(title: Annotated[str, Query(max_length=200, strict=True, description="The todo title")]) -> str: # (1)! +def create_todo( + title: Annotated[str, Query(max_length=200, strict=True, description="The TODO title")], # (1)! +) -> Annotated[bool, Body(description="Was the TODO created correctly?")]: todo = requests.post("https://jsonplaceholder.typicode.com/todos", data={"title": title}) - todo.raise_for_status() - - return todo.json()["title"] + try: + todo.raise_for_status() + return True + except Exception: + logger.exception("Error creating TODO") + return False def lambda_handler(event: dict, context: LambdaContext) -> dict: diff --git a/examples/event_handler_bedrock_agents/src/getting_started_schema.json b/examples/event_handler_bedrock_agents/src/getting_started_schema.json new file mode 100644 index 00000000000..2ef91f5392f --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/getting_started_schema.json @@ -0,0 +1,94 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Powertools API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/" + } + ], + "paths": { + "/current_time": { + "get": { + "summary": "GET /current_time", + "description": "Gets the current time in seconds", + "operationId": "current_time_current_time_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "integer", + "title": "Return" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + } + } +} From 1268937496b3331b0b920b3fc79a23b22eb1fdae Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 14:37:31 +0100 Subject: [PATCH 26/46] chore: added Alex's comments --- docs/core/event_handler/bedrock_agents.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index d5f1f95e414..218514e7431 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -3,7 +3,7 @@ title: Agents for Amazon Bedrock description: Core utility --- -Author [Agents for Amazon Bedrock](https://aws.amazon.com/bedrock/agents/){target="_blank"} using event handlers and auto generation of OpenAPI schemas. +Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html#agents-how){target="_blank"} using event handlers and auto generation of OpenAPI schemas.
```mermaid @@ -99,7 +99,7 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve --8<-- "examples/event_handler_bedrock_agents/src/getting_started.py" ``` - 1. `description` is a recommended field that should contain a human readable description of your action + 1. `description` is a **required** field that should contain a human readable description of your action 2. We take care of **parsing**, **validating**, **routing** and **responding** to the request. === "OpenAPI schema" @@ -188,6 +188,17 @@ The script will generate the schema directly to standard output, which you can r python app.py > schema.json ``` +### Crafting effective OpenAPI schemas + +Working with Agents for Amazon Bedrock will introduce [non-deterministic behaviour to your system](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-how.html#agents-rt){target="_blank"}. +The OpenAPI schema provides context and semantics to the Agent that will support the decision process for invoking our Lambda function. Sparse or ambiguous schema can result in unexpected outcomes. + +We recommend enriching your OpenAPI schema with as many details as possible, which will help the Agent understand your functions and make correct invocations. To achieve that: + +* always describe your function behaviour using the `description` field in your annotations +* in case of refactoring, update your description field to match the function outcomes +* use distinct `description` for each function to have clear separation of semantics + ### Walkthrough for creating the agent To create an Agent for Amazon Bedrock, refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. From a9c56a32032bbcdc39e0bce0e317162e1aae18bb Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 15:15:30 +0100 Subject: [PATCH 27/46] chore: adding second option for intro overview --- docs/core/event_handler/bedrock_agents.md | 11 +- docs/diagram_src/bedrock-agents.drawio | 1 + docs/media/bedrock_agents_intro.svg | 363 ++++++++++++++++++++++ 3 files changed, 371 insertions(+), 4 deletions(-) create mode 100644 docs/diagram_src/bedrock-agents.drawio create mode 100644 docs/media/bedrock_agents_intro.svg diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 218514e7431..30c1294ea26 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -11,6 +11,8 @@ Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us ```
+![Agents for Bedrock Overview](../../media/bedrock_agents_intro.svg) + ## Key features * Same declarative syntax as the [other event handler resolvers](api_gateway.md) @@ -76,7 +78,8 @@ Before you start, you need to the following permissions: 1. Amazon Bedrock needs permissions to invoke this Lambda function === "Using AWS Cloud Developer Kit (CDK)" - Use the [Generative AI CDK constructs](https://awslabs.github.io/generative-ai-cdk-constructs/src/cdk-lib/bedrock/#agents){target="_blank"} to create your Agent with CDK. + Use the [Generative AI CDK constructs](https://awslabs.github.io/generative-ai-cdk-constructs/src/cdk-lib/bedrock/#agents){target="_blank"} to create your Agent with [AWS CDK](https://aws.amazon.com/cdk/){target="_blank"}. + These constructs abstract all the underlying permission setup and bundling of your Lambda function. ```python --8<-- "examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py" @@ -122,7 +125,7 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" ``` -!!! note "It's important to include a `description` for each API endpoint because it will improve the understanding Amazon Bedrock has of your actions" +!!! note "It's required to include a `description` for each API endpoint and input parameter. This will improve the understanding Amazon Bedrock has of your actions." ### Validating input and output @@ -217,9 +220,9 @@ During the creation process, you should use the schema generated in the previous The event sent by Agents for Amazon Bedrock into your Lambda function contains a number of event fields that might be interesting. The event handler exposes them in the `app.current_event` field: -=== "accessing_request_fields.py" +=== "Accessing request fields" - ```python hl_lines="14-16" + ```python hl_lines="15-17" --8<-- "examples/event_handler_bedrock_agents/src/accessing_request_fields.py" ``` diff --git a/docs/diagram_src/bedrock-agents.drawio b/docs/diagram_src/bedrock-agents.drawio new file mode 100644 index 00000000000..da11d0e513c --- /dev/null +++ b/docs/diagram_src/bedrock-agents.drawio @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/media/bedrock_agents_intro.svg b/docs/media/bedrock_agents_intro.svg new file mode 100644 index 00000000000..3803e2ce624 --- /dev/null +++ b/docs/media/bedrock_agents_intro.svg @@ -0,0 +1,363 @@ + + + + + + + + + + + + +
+
+
+ User input +
+
+
+
+ + User input + +
+
+ + + + + + +
+
+
+ uses +
+
+
+
+ + uses + +
+
+ + + + + +
+
+
+ invokes +
+
+
+
+ + invokes + +
+
+ + + + + +
+
+
+ consults +
+
+
+
+ + consults + +
+
+ + + + +
+
+
+ Agents for Amazon Bedrock +
+
+
+
+ + Agents for Amazon Be... + +
+
+ + + + +
+
+
+ LLM +
+
+
+
+ + LLM + +
+
+ + + + + +
+
+
+ automatically + generated from + +
+
+
+
+ + automatically generated from + +
+
+ + + + +
+
+
+ OpenAPI schema +
+
+
+
+ + OpenAPI schema + +
+
+ + + + + +
+
+
+ Action Group +
+
+
+
+ + Action Group + +
+
+ + + + + +
+
+
+ Parameter parsing +
+
+
+
+ + Parameter parsing + +
+
+ + + + +
+
+
+ Parameter validation +
+
+
+
+ + Parameter validation + +
+
+ + + + +
+
+
+ Routing +
+
+
+
+ + Routing + +
+
+ + + + +
+
+
+ Your code +
+
+
+
+ Your + code + +
+
+ + + + +
+
+
+ Response validation +
+
+
+
+ + Response validation + +
+
+ + + + +
+
+
+ Response building +
+
+
+
+ + Response building + +
+
+
+ + + + Text is not SVG - cannot display + + +
From cd89d475b1ae1b3f98a699eef98562fb11b48f46 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 15:17:58 +0100 Subject: [PATCH 28/46] chore: add agents definition --- docs/core/event_handler/bedrock_agents.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 30c1294ea26..f278cd72315 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -32,6 +32,8 @@ Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us **Large Language Models (LLM)** are very large deep learning models that are pre-trained on vast amounts of data, capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases on it. +**Agent for Amazon Bedrock** is an AWS service to build and deploy conversational agents that can interact with your customers using Large Language Models (LLM) and AWS Lambda functions. + ## Getting started ???+ tip From 6430262e279f937525137dfa6bc146bdb4eb1f65 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 27 Feb 2024 16:54:12 +0100 Subject: [PATCH 29/46] fix: types --- .../src/assert_bedrock_agent_response_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py index 97e3026adce..d197e470595 100644 --- a/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py +++ b/examples/event_handler_bedrock_agents/src/assert_bedrock_agent_response_module.py @@ -1,9 +1,9 @@ import time -from typing import Annotated from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.event_handler import BedrockAgentResolver from aws_lambda_powertools.event_handler.openapi.params import Body +from aws_lambda_powertools.shared.types import Annotated from aws_lambda_powertools.utilities.typing import LambdaContext tracer = Tracer() From 631ad7a5b2a8097c5db788d108d23f32acd09caa Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Wed, 28 Feb 2024 10:39:21 +0100 Subject: [PATCH 30/46] chore: addressed feedback --- docs/core/event_handler/bedrock_agents.md | 63 ++-- docs/media/bedrock_agents_intro.svg | 363 ---------------------- 2 files changed, 28 insertions(+), 398 deletions(-) delete mode 100644 docs/media/bedrock_agents_intro.svg diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index f278cd72315..57b8f1ef686 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -11,24 +11,22 @@ Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us ``` -![Agents for Bedrock Overview](../../media/bedrock_agents_intro.svg) - ## Key features -* Same declarative syntax as the [other event handler resolvers](api_gateway.md) -* Drastic reduction of the boilerplate to build Agents for Amazon Bedrock +* Similar experience when authoring [REST](api_gateway.md){target="_blank"} and [GraphQL APIs](appsync.md){target="_blank"} +* Minimal boilerplate to build Agents for Amazon Bedrock * Automatic generation of [OpenAPI schemas](https://www.openapis.org/){target="_blank"} from your business logic code * Built-in data validation for requests and responses ## Terminology -**Data validation** automatically validates the user input and the response of your Lambda function against a set of constraints defined by you +**Data validation** automatically validates the user input and the response of your AWS Lambda function against a set of constraints defined by you. -**Event handler** is a Powertools for AWS feature that processes an event, runs data parsing and validation, routes the request to a specific function, and returns a response to the caller in the proper format +**Event handler** is a Powertools for AWS feature that processes an event, runs data parsing and validation, routes the request to a specific function, and returns a response to the caller in the proper format. -**[OpenAPI schema](https://www.openapis.org/){target="_blank"}** is an industry standard JSON-serialized string that represents the structure and parameters of your API +**[OpenAPI schema](https://www.openapis.org/){target="_blank"}** is an industry standard JSON-serialized string that represents the structure and parameters of your API. -**Action group** is a collection of two resources where you define the actions that the agent should carry out: an OpenAPI schema to define the APIs that the agent can invoke to carry out its tasks, and a Lambda function to execute those actions +**Action group** is a collection of two resources where you define the actions that the agent should carry out: an OpenAPI schema to define the APIs that the agent can invoke to carry out its tasks, and a Lambda function to execute those actions. **Large Language Models (LLM)** are very large deep learning models that are pre-trained on vast amounts of data, capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases on it. @@ -36,34 +34,29 @@ Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us ## Getting started -???+ tip - All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. - -To build Agents for Amazon Bedrock, you need: - -* [Create the Lambda function](#your-first-agent) that defines the business logic for the action that your agent carries out. -* [Create an OpenAPI schema](#generating-openapi-schemas) with the API description, structure, and parameters for the action group. -* Ensure that Amazon Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). - -Powertools for AWS Lambda makes it easier to author the Lambda function and the creation of the OpenAPI schema. +???+ tip "All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples)" ### Install !!! info "This is unnecessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." -You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. - -At this time, we support both Pydantic V1 and V2. For a future major version, we will only support Pydantic V2. +You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. At this time, we support both Pydantic V1 and V2. For a future major version, we will only support Pydantic V2. ### Required resources +To build Agents for Amazon Bedrock, you will need: + +* [Create the Lambda function](#your-first-agent) that defines the business logic for the action that your agent carries out. +* [Create an OpenAPI schema](#generating-openapi-schemas) with the API description, structure, and parameters for the action group. +* Ensure that Amazon Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). + Before you start, you need to the following permissions: * A **service role**, which allows Amazon Bedrock to invoke foundation models * **Lambda permissions** allowing Amazon Bedrock to invoke it === "Service Role Example using AWS CloudFormation" - You'll need this role when [creating an Agent in the AWS Console](#walkthrough-for-creating-the-agent). + You'll need this role when [creating an Agent in the AWS Console](#video-walkthrough). ```yaml hl_lines="14-17 24-29 34" --8<-- "examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml" @@ -80,8 +73,8 @@ Before you start, you need to the following permissions: 1. Amazon Bedrock needs permissions to invoke this Lambda function === "Using AWS Cloud Developer Kit (CDK)" - Use the [Generative AI CDK constructs](https://awslabs.github.io/generative-ai-cdk-constructs/src/cdk-lib/bedrock/#agents){target="_blank"} to create your Agent with [AWS CDK](https://aws.amazon.com/cdk/){target="_blank"}. - These constructs abstract all the underlying permission setup and bundling of your Lambda function. + This example uses the [Generative AI CDK constructs](https://awslabs.github.io/generative-ai-cdk-constructs/src/cdk-lib/bedrock/#agents){target="_blank"} to create your Agent with [AWS CDK](https://aws.amazon.com/cdk/){target="_blank"}. + These constructs abstract the underlying permission setup and code bundling of your Lambda function. ```python --8<-- "examples/event_handler_bedrock_agents/cdk/bedrock_agent_stack.py" @@ -95,10 +88,12 @@ Before you start, you need to the following permissions: To create an Agent for Amazon Bedrock, use the `BedrockAgentResolver` to annotate your actions. This is similar to the way [all the other Event Handler](api_gateway.md) resolvers work. +It's required to include a `description` for each API endpoint and input parameter. This will improve the understanding Amazon Bedrock has of your actions. + === "Lambda handler" - The resolvers used by Agents for Amazon Bedrock are compatible with the full suite of Powertools for AWS Lambda utilities. - This includes [Logger](../logger.md), [Metrics](../metrics.md) and [Tracer](../tracer.md). + The resolvers used by Agents for Amazon Bedrock are compatible with all Powertools for AWS Lambda [features](../../index.md#features){target="blank"}. + For reference, we use [Logger](../logger.md) and [Tracer](../tracer.md) in this example. ```python hl_lines="4 9 12 21" --8<-- "examples/event_handler_bedrock_agents/src/getting_started.py" @@ -127,8 +122,6 @@ This is similar to the way [all the other Event Handler](api_gateway.md) resolve --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" ``` -!!! note "It's required to include a `description` for each API endpoint and input parameter. This will improve the understanding Amazon Bedrock has of your actions." - ### Validating input and output You can define the expected format for incoming data and responses by using type annotations. @@ -171,7 +164,7 @@ Use the `get_openapi_json_schema` function provided by the resolver. This function will produce a JSON-serialized string that represents your OpenAPI schema. You can print this string or save it to a file. You'll use the file later when creating the Agent. -=== "Generating the OpenAPI schema" +=== "app.py" ```python hl_lines="24 25" --8<-- "examples/event_handler_bedrock_agents/src/generating_openapi_schema.py" @@ -190,7 +183,7 @@ To get the OpenAPI schema, run the Python script from your terminal. The script will generate the schema directly to standard output, which you can redirect to a file. ```sh -python app.py > schema.json +python3 app.py > schema.json ``` ### Crafting effective OpenAPI schemas @@ -198,13 +191,13 @@ python app.py > schema.json Working with Agents for Amazon Bedrock will introduce [non-deterministic behaviour to your system](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-how.html#agents-rt){target="_blank"}. The OpenAPI schema provides context and semantics to the Agent that will support the decision process for invoking our Lambda function. Sparse or ambiguous schema can result in unexpected outcomes. -We recommend enriching your OpenAPI schema with as many details as possible, which will help the Agent understand your functions and make correct invocations. To achieve that: +We recommend enriching your OpenAPI schema with as many details as possible to help the Agent understand your functions, and make correct invocations. To achieve that, keep the following suggestions in mind: -* always describe your function behaviour using the `description` field in your annotations -* in case of refactoring, update your description field to match the function outcomes -* use distinct `description` for each function to have clear separation of semantics +* Always describe your function behaviour using the `description` field in your annotations +* When refactoring, update your description field to match the function outcomes +* Use distinct `description` for each function to have clear separation of semantics -### Walkthrough for creating the agent +### Video walkthrough To create an Agent for Amazon Bedrock, refer to the [official documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html) provided by AWS. diff --git a/docs/media/bedrock_agents_intro.svg b/docs/media/bedrock_agents_intro.svg deleted file mode 100644 index 3803e2ce624..00000000000 --- a/docs/media/bedrock_agents_intro.svg +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - -
-
-
- User input -
-
-
-
- - User input - -
-
- - - - - - -
-
-
- uses -
-
-
-
- - uses - -
-
- - - - - -
-
-
- invokes -
-
-
-
- - invokes - -
-
- - - - - -
-
-
- consults -
-
-
-
- - consults - -
-
- - - - -
-
-
- Agents for Amazon Bedrock -
-
-
-
- - Agents for Amazon Be... - -
-
- - - - -
-
-
- LLM -
-
-
-
- - LLM - -
-
- - - - - -
-
-
- automatically - generated from - -
-
-
-
- - automatically generated from - -
-
- - - - -
-
-
- OpenAPI schema -
-
-
-
- - OpenAPI schema - -
-
- - - - - -
-
-
- Action Group -
-
-
-
- - Action Group - -
-
- - - - - -
-
-
- Parameter parsing -
-
-
-
- - Parameter parsing - -
-
- - - - -
-
-
- Parameter validation -
-
-
-
- - Parameter validation - -
-
- - - - -
-
-
- Routing -
-
-
-
- - Routing - -
-
- - - - -
-
-
- Your code -
-
-
-
- Your - code - -
-
- - - - -
-
-
- Response validation -
-
-
-
- - Response validation - -
-
- - - - -
-
-
- Response building -
-
-
-
- - Response building - -
-
-
- - - - Text is not SVG - cannot display - - -
From 0789da1db00b9acd1e079cb2bf2a916863c16074 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Wed, 28 Feb 2024 11:06:51 +0100 Subject: [PATCH 31/46] chore: addressed permissions feedback --- docs/core/event_handler/bedrock_agents.md | 30 ++++++---------- .../sam/bedrock_service_role.yaml | 34 ------------------- .../sam/template.yaml | 30 ++++++++++++++++ 3 files changed, 41 insertions(+), 53 deletions(-) delete mode 100644 examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 57b8f1ef686..b71866d682d 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -46,31 +46,23 @@ You need to add `pydantic` as a dependency in your preferred tool _e.g., require To build Agents for Amazon Bedrock, you will need: -* [Create the Lambda function](#your-first-agent) that defines the business logic for the action that your agent carries out. -* [Create an OpenAPI schema](#generating-openapi-schemas) with the API description, structure, and parameters for the action group. -* Ensure that Amazon Bedrock and your Lambda functions have the [necessary permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html). +| Requirement | Description | SAM Supported | CDK Supported | +|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|:-------------:|:-------------:| +| [Lambda Function](#your-first-agent) | Defines your business logic for the action group | ✅ | ✅ | +| [OpenAPI Schema](#generating-openapi-schemas) | API description, structure, and action group parameters | ❌ | ✅ | +| Bedrock [Service Role](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html){target="_blank"} | Allows Amazon Bedrock to invoke foundation models | ✅ | ✅ | +| Agent for Bedrock | The service that will combine all the above to create the conversational agent | ❌ | ✅ | -Before you start, you need to the following permissions: +=== "Using AWS Serverless Application Model (SAM)" + Using [AWS SAM](https://aws.amazon.com/serverless/sam/){target="_blank"} you can create your Lambda function and the necessary permissions. However, you still have to create your Agent for Amazon Bedrock [using the AWS console](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html){target="_blank"}. -* A **service role**, which allows Amazon Bedrock to invoke foundation models -* **Lambda permissions** allowing Amazon Bedrock to invoke it - -=== "Service Role Example using AWS CloudFormation" - You'll need this role when [creating an Agent in the AWS Console](#video-walkthrough). - - ```yaml hl_lines="14-17 24-29 34" - --8<-- "examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml" - ``` - - 1. Check the [supported foundational models](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-supported.html){target="_blank"} - 2. You need the role ARN when creating the Agent for Amazon Bedrock - -=== "Lambda permissions using AWS Serverless Application Model (SAM)" - ```yaml hl_lines="27-33" + ```yaml hl_lines="18 26 34 61" --8<-- "examples/event_handler_bedrock_agents/sam/template.yaml" ``` 1. Amazon Bedrock needs permissions to invoke this Lambda function + 2. Check the [supported foundational models](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-supported.html){target="_blank"} + 3. You need the role ARN when creating the Agent for Amazon Bedrock === "Using AWS Cloud Developer Kit (CDK)" This example uses the [Generative AI CDK constructs](https://awslabs.github.io/generative-ai-cdk-constructs/src/cdk-lib/bedrock/#agents){target="_blank"} to create your Agent with [AWS CDK](https://aws.amazon.com/cdk/){target="_blank"}. diff --git a/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml b/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml deleted file mode 100644 index 8b57f966a72..00000000000 --- a/examples/event_handler_bedrock_agents/sam/bedrock_service_role.yaml +++ /dev/null @@ -1,34 +0,0 @@ -AWSTemplateFormatVersion: "2010-09-09" -Description: > - Amazon Bedrock service role - -Resources: - BedrockServiceRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Principal: - Action: - - sts:assumeRole - Service: - - bedrock.amazonaws.com - Policies: - - PolicyName: bedrock - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - bedrock:InvokeModel - Resource: # (1)! - - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2 - - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2:1 - - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-instant-v1 - -Outputs: - BedrockServiceRole: - Description: The role ARN to be used by Amazon Bedrock - Value: !GetAtt BedrockServiceRole.Arn # (2)! diff --git a/examples/event_handler_bedrock_agents/sam/template.yaml b/examples/event_handler_bedrock_agents/sam/template.yaml index 4fdbd718c6e..34d4cb25ec7 100644 --- a/examples/event_handler_bedrock_agents/sam/template.yaml +++ b/examples/event_handler_bedrock_agents/sam/template.yaml @@ -29,3 +29,33 @@ Resources: FunctionName: !GetAtt ApiFunction.Arn Principal: bedrock.amazonaws.com SourceAccount: !Sub ${AWS::AccountId} + + BedrockServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Action: + - sts:assumeRole + Service: + - bedrock.amazonaws.com + Policies: + - PolicyName: bedrock + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - bedrock:InvokeModel + Resource: # (2)! + - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2 + - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-v2:1 + - !Sub arn:aws:${AWS::Region}:region::foundation-model/anthropic.claude-instant-v1 + +Outputs: + BedrockServiceRole: + Description: The role ARN to be used by Amazon Bedrock + Value: !GetAtt BedrockServiceRole.Arn # (3)! From 9864188e0a6b4f5752355d02e185fc43f67c7bc5 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Wed, 28 Feb 2024 11:14:13 +0100 Subject: [PATCH 32/46] chore: expanded pydantic explanation --- docs/core/event_handler/bedrock_agents.md | 7 ++++--- .../src/getting_started_with_validation.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index b71866d682d..4c1060dd443 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -117,17 +117,18 @@ It's required to include a `description` for each API endpoint and input paramet ### Validating input and output You can define the expected format for incoming data and responses by using type annotations. +Define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/). === "Lambda handler" + This example uses [Pydantic's EmailStr](https://docs.pydantic.dev/2.0/usage/types/string_types/#emailstr){target="_blank"} to validate the email address passed to the `schedule_meeting` function. That function returns an object containing the scheduled meeting details. ```python hl_lines="5 23-26 32-33" --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" ``` 1. No need to add the `enable_validation` parameter, as it's enabled by default. - 2. You can define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) - 3. Describe each input and output using human-readable descriptions - 4. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest + 2. Describe each input and output using human-readable descriptions + 3. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest === "OpenAPI schema" diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py index d8e21c9153f..12114ffcc8d 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -20,8 +20,8 @@ app = BedrockAgentResolver() # (1)! -class ScheduleMeetingResponse(BaseModel): # (2)! - date: Annotated[datetime.datetime, Body(description="The date of the scheduled meeting")] # (3)! +class ScheduleMeetingResponse(BaseModel): + date: Annotated[datetime.datetime, Body(description="The date of the scheduled meeting")] # (2)! team: Annotated[str, Body(description="The team that will handle the support request")] cancellationEmail: Annotated[EmailStr, Body(description="The email address to request a meeting cancellation")] @@ -29,7 +29,7 @@ class ScheduleMeetingResponse(BaseModel): # (2)! @app.get("/schedule_meeting", description="Schedules a meeting with the team") @tracer.capture_method def schedule_meeting( - email: Annotated[EmailStr, Query(description="The email address of the customer")], # (4)! + email: Annotated[EmailStr, Query(description="The email address of the customer")], # (3)! ) -> Annotated[ScheduleMeetingResponse, Body(description="Scheduled meeting details")]: logger.info("Scheduling a meeting", email=email) return ScheduleMeetingResponse( From b29112c83dc9e6d10a24589de6b2c546539bd816 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Wed, 28 Feb 2024 11:17:58 +0100 Subject: [PATCH 33/46] chore: add sentence on regenerating openapi schemas --- docs/core/event_handler/bedrock_agents.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 4c1060dd443..c65d409a58b 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -157,6 +157,8 @@ Use the `get_openapi_json_schema` function provided by the resolver. This function will produce a JSON-serialized string that represents your OpenAPI schema. You can print this string or save it to a file. You'll use the file later when creating the Agent. +You'll need to regenerate the OpenAPI schema and update your Agent everytime your API changes. + === "app.py" ```python hl_lines="24 25" From ada255cdba3a824b1743f3253843e3e67436d59d Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Wed, 28 Feb 2024 14:06:42 +0100 Subject: [PATCH 34/46] chore: color contrast --- docs/core/event_handler/bedrock_agents.mermaid | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.mermaid b/docs/core/event_handler/bedrock_agents.mermaid index 4703596d821..19ae2270234 100644 --- a/docs/core/event_handler/bedrock_agents.mermaid +++ b/docs/core/event_handler/bedrock_agents.mermaid @@ -21,8 +21,8 @@ flowchart LR OpenAPI -. generated from .-> Lambda end - style Code fill:#ffa500,color:black,font-weight:bold,stroke-width:2px - style You stroke:#0F0,fill:#fff + style Code fill:#ffa500,color:black,font-weight:bold,stroke-width:3px + style You stroke:#0F0,stroke-width:2px From 59411ad3afdeb81460f3cc6ad3dc67f8a7c49431 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 29 Feb 2024 14:35:30 +0100 Subject: [PATCH 35/46] chore: improved input validation --- docs/core/event_handler/bedrock_agents.md | 21 ++++-- ...agents_validation_sequence_diagram.mermaid | 16 +++++ .../src/getting_started_with_validation.py | 27 ++------ ...etting_started_with_validation_output.json | 2 +- ...etting_started_with_validation_schema.json | 64 +++++-------------- 5 files changed, 54 insertions(+), 76 deletions(-) create mode 100644 docs/core/event_handler/bedrock_agents_validation_sequence_diagram.mermaid diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index c65d409a58b..046357c5895 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -118,16 +118,18 @@ It's required to include a `description` for each API endpoint and input paramet You can define the expected format for incoming data and responses by using type annotations. Define constraints using standard Python types, [dataclasses](https://docs.python.org/3/library/dataclasses.html) or [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/). +Pydantic is a popular library for data validation using Python type annotations. === "Lambda handler" - This example uses [Pydantic's EmailStr](https://docs.pydantic.dev/2.0/usage/types/string_types/#emailstr){target="_blank"} to validate the email address passed to the `schedule_meeting` function. That function returns an object containing the scheduled meeting details. + This example uses [Pydantic's EmailStr](https://docs.pydantic.dev/2.0/usage/types/string_types/#emailstr){target="_blank"} to validate the email address passed to the `schedule_meeting` function. + The function then returns a boolean indicating if the meeting was successfully scheduled. - ```python hl_lines="5 23-26 32-33" + ```python hl_lines="1 2 16-18" --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" ``` 1. No need to add the `enable_validation` parameter, as it's enabled by default. - 2. Describe each input and output using human-readable descriptions + 2. Describe each input using human-readable descriptions 3. Add the typing annotations to your parameters and return types, and let the event handler take care of the rest === "OpenAPI schema" @@ -148,9 +150,20 @@ Define constraints using standard Python types, [dataclasses](https://docs.pytho --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json" ``` -If the request validation, your event handler will not be called, and an error message is returned to Bedrock. +If the request validation fails, your event handler will not be called, and an error message is returned to Bedrock. Similarly, if the response fails validation, your handler will abort the response. +???+ info "What does this mean for my Agent?" + The event handler will always return a response according to the OpenAPI schema. + A validation failure always results in a [422 response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422). + However, how Amazon Bedrock interprets that failure is non-deterministic, since it depends on the characteristics of the LLM being used. + +
+```mermaid +--8<-- "docs/core/event_handler/bedrock_agents_validation_sequence_diagram.mermaid" +``` +
+ ### Generating OpenAPI schemas Use the `get_openapi_json_schema` function provided by the resolver. diff --git a/docs/core/event_handler/bedrock_agents_validation_sequence_diagram.mermaid b/docs/core/event_handler/bedrock_agents_validation_sequence_diagram.mermaid new file mode 100644 index 00000000000..96158c76786 --- /dev/null +++ b/docs/core/event_handler/bedrock_agents_validation_sequence_diagram.mermaid @@ -0,0 +1,16 @@ +sequenceDiagram + Agent->>Lambda: input payload + activate Lambda + Lambda->>Parsing: parses input parameters + Parsing->>Validation: validates input + Validation-->Validation: failure + box BedrockAgentResolver + participant Lambda + participant Parsing + participant Validation + participant Routing + participant Your Code + end + Note right of Validation: Your code is never called + Validation->>Agent: 422 response + deactivate Lambda diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py index 12114ffcc8d..1a2ce9742a0 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation.py @@ -1,8 +1,4 @@ -import datetime -from time import time -from typing import TYPE_CHECKING - -from pydantic import BaseModel +from pydantic import EmailStr from typing_extensions import Annotated from aws_lambda_powertools import Logger, Tracer @@ -10,33 +6,18 @@ from aws_lambda_powertools.event_handler.openapi.params import Body, Query from aws_lambda_powertools.utilities.typing import LambdaContext -if TYPE_CHECKING: # Pydantic's V1 EmailStr is not compatible with mypy - EmailStr = Annotated[str, ...] # https://github.com/pydantic/pydantic/issues/1490#issuecomment-630131270 -else: - from pydantic import EmailStr - tracer = Tracer() logger = Logger() app = BedrockAgentResolver() # (1)! -class ScheduleMeetingResponse(BaseModel): - date: Annotated[datetime.datetime, Body(description="The date of the scheduled meeting")] # (2)! - team: Annotated[str, Body(description="The team that will handle the support request")] - cancellationEmail: Annotated[EmailStr, Body(description="The email address to request a meeting cancellation")] - - @app.get("/schedule_meeting", description="Schedules a meeting with the team") @tracer.capture_method def schedule_meeting( - email: Annotated[EmailStr, Query(description="The email address of the customer")], # (3)! -) -> Annotated[ScheduleMeetingResponse, Body(description="Scheduled meeting details")]: + email: Annotated[EmailStr, Query(description="The email address of the customer")], # (2)! +) -> Annotated[bool, Body(description="Whether the meeting was scheduled successfully")]: # (3)! logger.info("Scheduling a meeting", email=email) - return ScheduleMeetingResponse( - date=datetime.datetime.fromtimestamp(time() + 60 * 60 * 24 * 7), # 7 days from now - team="Customer Support Team B", - cancellationEmail="cancel@example.org", - ) + return True @logger.inject_lambda_context diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json index 77ae3f9bd5e..6f791a3155f 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json @@ -7,7 +7,7 @@ "httpStatusCode": 200, "responseBody": { "application/json": { - "body": "{\"date\":\"2024-03-05T11:18:28.023979\",\"team\":\"Customer Support Team B\",\"cancellationEmail\":\"cancel@example.org\"}" + "body": "true" } } } diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json index e345f90b63f..f2847bf27f8 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json @@ -15,33 +15,29 @@ "summary": "GET /schedule_meeting", "description": "Schedules a meeting with the team", "operationId": "schedule_meeting_schedule_meeting_get", - "requestBody": { - "description": "The email address of the customer", - "content": { - "application/json": { - "schema": { - "type": "string", - "format": "email", - "title": "Email", - "description": "The email address of the customer" - } - } - }, - "required": true - }, + "parameters": [ + { + "description": "The email address of the customer", + "required": true, + "schema": { + "type": "string", + "format": "email", + "title": "Email", + "description": "The email address of the customer" + }, + "name": "email", + "in": "query" + } + ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/ScheduleMeetingResponse" - } - ], + "type": "boolean", "title": "Return", - "description": "Scheduled meeting details" + "description": "Whether the meeting was scheduled successfully" } } } @@ -75,34 +71,6 @@ "type": "object", "title": "HTTPValidationError" }, - "ScheduleMeetingResponse": { - "properties": { - "date": { - "type": "string", - "format": "date-time", - "title": "Date", - "description": "The date of the scheduled meeting" - }, - "team": { - "type": "string", - "title": "Team", - "description": "The team that will handle the support request" - }, - "cancellationEmail": { - "type": "string", - "format": "email", - "title": "Cancellationemail", - "description": "The email address to request a meeting cancellation" - } - }, - "type": "object", - "required": [ - "date", - "team", - "cancellationEmail" - ], - "title": "ScheduleMeetingResponse" - }, "ValidationError": { "properties": { "loc": { From 954298963412e68ac30babe64b39ec13232e3a2d Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 29 Feb 2024 15:01:19 +0100 Subject: [PATCH 36/46] chore: sample of additional properties --- docs/core/event_handler/bedrock_agents.md | 22 +++++++++++++++---- .../src/accessing_request_fields.py | 6 ++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 046357c5895..f7a22faff97 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -197,9 +197,17 @@ python3 app.py > schema.json ### Crafting effective OpenAPI schemas Working with Agents for Amazon Bedrock will introduce [non-deterministic behaviour to your system](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-how.html#agents-rt){target="_blank"}. -The OpenAPI schema provides context and semantics to the Agent that will support the decision process for invoking our Lambda function. Sparse or ambiguous schema can result in unexpected outcomes. -We recommend enriching your OpenAPI schema with as many details as possible to help the Agent understand your functions, and make correct invocations. To achieve that, keep the following suggestions in mind: +???+ note "Why is that?" + Amazon Bedrock uses LLMs to understand and respond to user input. + These models are trained on vast amounts of data and are capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases on it. + However, this means that the same input can result in different outputs, depending on the characteristics of the LLM being used. + +The OpenAPI schema provides context and semantics to the Agent that will support the decision process for invoking our Lambda function. +Sparse or ambiguous schemas can result in unexpected outcomes. + +We recommend enriching your OpenAPI schema with as many details as possible to help the Agent understand your functions, and make correct invocations. +To achieve that, keep the following suggestions in mind: * Always describe your function behaviour using the `description` field in your annotations * When refactoring, update your description field to match the function outcomes @@ -221,11 +229,17 @@ During the creation process, you should use the schema generated in the previous ### Accessing custom request fields -The event sent by Agents for Amazon Bedrock into your Lambda function contains a number of event fields that might be interesting. The event handler exposes them in the `app.current_event` field: +The event sent by Agents for Amazon Bedrock into your Lambda function contains a number of extra event fields, exposed in the `app.current_event` field. + +???+ note "Why is this useful?" + You can for instance identify new conversations (`session_id`) or store and analyze entire conversations (`input_text`). === "Accessing request fields" - ```python hl_lines="15-17" + In this example, we [append correlation data](../logger.md#appending-additional-keys) to all generated logs. + This can be used to aggregate logs by `session_id` and observe the entire conversation between a user and the Agent. + + ```python hl_lines="13-16" --8<-- "examples/event_handler_bedrock_agents/src/accessing_request_fields.py" ``` diff --git a/examples/event_handler_bedrock_agents/src/accessing_request_fields.py b/examples/event_handler_bedrock_agents/src/accessing_request_fields.py index 6c2599b5c53..529c9343702 100644 --- a/examples/event_handler_bedrock_agents/src/accessing_request_fields.py +++ b/examples/event_handler_bedrock_agents/src/accessing_request_fields.py @@ -10,13 +10,13 @@ @app.get("/current_time", description="Gets the current time in seconds") # (1)! def current_time() -> int: - logger.info( - "Serving current_time", + logger.append_keys( + session_id=app.current_event.session_id, action_group=app.current_event.action_group, input_text=app.current_event.input_text, - session_attributes=app.current_event.session_attributes, ) + logger.info("Serving current_time") return int(time()) From 4a765fb82285b151aa056566ca7b3e3e7457ef33 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Thu, 29 Feb 2024 15:27:57 +0100 Subject: [PATCH 37/46] chore: add more diagrams --- docs/core/event_handler/bedrock_agents.md | 24 ++++++++++++++ .../bedrock_agents_getting_started.mermaid | 33 +++++++++++++++++++ .../src/validation_failure_input.json | 23 +++++++++++++ .../src/validation_failure_output.json | 14 ++++++++ 4 files changed, 94 insertions(+) create mode 100644 docs/core/event_handler/bedrock_agents_getting_started.mermaid create mode 100644 examples/event_handler_bedrock_agents/src/validation_failure_input.json create mode 100644 examples/event_handler_bedrock_agents/src/validation_failure_output.json diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index f7a22faff97..5f3dcdeb3cf 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -114,6 +114,16 @@ It's required to include a `description` for each API endpoint and input paramet --8<-- "examples/event_handler_bedrock_agents/src/getting_started_output.json" ``` +??? note "What happens under the hood?" + Powertools will handle the request from the Agent, parse, validate, and route it to the correct method in your code. + The response is then validated and formatted back to the Agent. + +
+ ```mermaid + --8<-- "docs/core/event_handler/bedrock_agents_getting_started.mermaid" + ``` +
+ ### Validating input and output You can define the expected format for incoming data and responses by using type annotations. @@ -150,6 +160,8 @@ Pydantic is a popular library for data validation using Python type annotations. --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation_output.json" ``` +#### When validation fails + If the request validation fails, your event handler will not be called, and an error message is returned to Bedrock. Similarly, if the response fails validation, your handler will abort the response. @@ -158,6 +170,18 @@ Similarly, if the response fails validation, your handler will abort the respons A validation failure always results in a [422 response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422). However, how Amazon Bedrock interprets that failure is non-deterministic, since it depends on the characteristics of the LLM being used. +=== "Input payload" + + ```json hl_lines="11" + --8<-- "examples/event_handler_bedrock_agents/src/validation_failure_input.json" + ``` + +=== "Output payload" + + ```json hl_lines="10" + --8<-- "examples/event_handler_bedrock_agents/src/validation_failure_output.json" + ``` +
```mermaid --8<-- "docs/core/event_handler/bedrock_agents_validation_sequence_diagram.mermaid" diff --git a/docs/core/event_handler/bedrock_agents_getting_started.mermaid b/docs/core/event_handler/bedrock_agents_getting_started.mermaid new file mode 100644 index 00000000000..29f3a26e323 --- /dev/null +++ b/docs/core/event_handler/bedrock_agents_getting_started.mermaid @@ -0,0 +1,33 @@ +sequenceDiagram + actor User + + User->>Agent: What is the current time? + Agent->>OpenAPI schema: consults + OpenAPI schema-->>Agent: GET /current_time + Agent-->>Agent: LLM interaction + + box Powertools + participant Lambda + participant Parsing + participant Validation + participant Routing + participant Your Code + end + + Agent->>Lambda: GET /current_time + activate Lambda + Lambda->>Parsing: parses parameters + Parsing->>Validation: validates input + Validation->>Routing: finds method to call + Routing->>Your Code: executes + activate Your Code + Your Code->>Routing: 1709215709 + deactivate Your Code + Routing->>Validation: returns output + Validation->>Parsing: validates output + Parsing->>Lambda: formats response + Lambda->>Agent: 1709215709 + deactivate Lambda + + Agent-->>Agent: LLM interaction + Agent->>User: "The current time is 14:08:29 GMT" diff --git a/examples/event_handler_bedrock_agents/src/validation_failure_input.json b/examples/event_handler_bedrock_agents/src/validation_failure_input.json new file mode 100644 index 00000000000..ca8e73b0fcc --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/validation_failure_input.json @@ -0,0 +1,23 @@ +{ + "sessionId": "123456789012345", + "sessionAttributes": {}, + "inputText": "Schedule a meeting with the team. My email is foo@example@org", + "promptSessionAttributes": {}, + "apiPath": "/schedule_meeting", + "parameters": [ + { + "name": "email", + "type": "string", + "value": "foo@example@org" + } + ], + "agent": { + "name": "TimeAgent", + "version": "DRAFT", + "id": "XLHH72XNF2", + "alias": "TSTALIASID" + }, + "httpMethod": "GET", + "messageVersion": "1.0", + "actionGroup": "SupportAssistant" +} diff --git a/examples/event_handler_bedrock_agents/src/validation_failure_output.json b/examples/event_handler_bedrock_agents/src/validation_failure_output.json new file mode 100644 index 00000000000..e5faa7fe524 --- /dev/null +++ b/examples/event_handler_bedrock_agents/src/validation_failure_output.json @@ -0,0 +1,14 @@ +{ + "messageVersion": "1.0", + "response": { + "actionGroup": "SupportAssistant", + "apiPath": "/schedule_meeting", + "httpMethod": "GET", + "httpStatusCode": 200, + "responseBody": { + "application/json": { + "body": "{\"statusCode\":422,\"detail\":[{\"loc\":[\"query\",\"email\"],\"type\":\"value_error.email\"}]}" + } + } + } +} From afe8fe04841a0bf3e82f3e0ec6abb498805b819c Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 1 Mar 2024 12:26:13 +0100 Subject: [PATCH 38/46] fix: applied feedback --- docs/core/event_handler/bedrock_agents.md | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 5f3dcdeb3cf..938158fdd17 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -3,7 +3,7 @@ title: Agents for Amazon Bedrock description: Core utility --- -Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html#agents-how){target="_blank"} using event handlers and auto generation of OpenAPI schemas. +Create [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html#agents-how){target="_blank"} using event handlers and auto generation of OpenAPI schemas.
```mermaid @@ -13,10 +13,10 @@ Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us ## Key features -* Similar experience when authoring [REST](api_gateway.md){target="_blank"} and [GraphQL APIs](appsync.md){target="_blank"} * Minimal boilerplate to build Agents for Amazon Bedrock * Automatic generation of [OpenAPI schemas](https://www.openapis.org/){target="_blank"} from your business logic code * Built-in data validation for requests and responses +* Similar experience when authoring [REST](api_gateway.md){target="_blank"} and [GraphQL APIs](appsync.md){target="_blank"} ## Terminology @@ -30,11 +30,11 @@ Author [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us **Large Language Models (LLM)** are very large deep learning models that are pre-trained on vast amounts of data, capable of extracting meanings from a sequence of text and understanding the relationship between words and phrases on it. -**Agent for Amazon Bedrock** is an AWS service to build and deploy conversational agents that can interact with your customers using Large Language Models (LLM) and AWS Lambda functions. +**Agent for Amazon Bedrock** is an Amazon Bedrock feature to build and deploy conversational agents that can interact with your customers using Large Language Models (LLM) and AWS Lambda functions. ## Getting started -???+ tip "All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples)" +!!! tip "All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples)" ### Install @@ -50,8 +50,8 @@ To build Agents for Amazon Bedrock, you will need: |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|:-------------:|:-------------:| | [Lambda Function](#your-first-agent) | Defines your business logic for the action group | ✅ | ✅ | | [OpenAPI Schema](#generating-openapi-schemas) | API description, structure, and action group parameters | ❌ | ✅ | -| Bedrock [Service Role](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html){target="_blank"} | Allows Amazon Bedrock to invoke foundation models | ✅ | ✅ | -| Agent for Bedrock | The service that will combine all the above to create the conversational agent | ❌ | ✅ | +| [Bedrock Service Role](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-permissions.html){target="_blank"} | Allows Amazon Bedrock to invoke foundation models | ✅ | ✅ | +| Agents for Bedrock | The service that will combine all the above to create the conversational agent | ❌ | ✅ | === "Using AWS Serverless Application Model (SAM)" Using [AWS SAM](https://aws.amazon.com/serverless/sam/){target="_blank"} you can create your Lambda function and the necessary permissions. However, you still have to create your Agent for Amazon Bedrock [using the AWS console](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html){target="_blank"}. @@ -77,10 +77,10 @@ To build Agents for Amazon Bedrock, you will need: ### Your first Agent -To create an Agent for Amazon Bedrock, use the `BedrockAgentResolver` to annotate your actions. +To create an agent, use the `BedrockAgentResolver` to annotate your actions. This is similar to the way [all the other Event Handler](api_gateway.md) resolvers work. -It's required to include a `description` for each API endpoint and input parameter. This will improve the understanding Amazon Bedrock has of your actions. +You are required to add a `description` parameter in each endpoint, doing so will improve Bedrock's understanding of your actions. === "Lambda handler" @@ -190,8 +190,7 @@ Similarly, if the response fails validation, your handler will abort the respons ### Generating OpenAPI schemas -Use the `get_openapi_json_schema` function provided by the resolver. -This function will produce a JSON-serialized string that represents your OpenAPI schema. +Use the `get_openapi_json_schema` function provided by the resolver to produce a JSON-serialized string that represents your OpenAPI schema. You can print this string or save it to a file. You'll use the file later when creating the Agent. You'll need to regenerate the OpenAPI schema and update your Agent everytime your API changes. @@ -247,13 +246,13 @@ The following video demonstrates the end-to-end process:
-During the creation process, you should use the schema generated in the previous step when prompted for an OpenAPI specification. +During the creation process, you should use the schema [previously generated](#generating-openapi-schemas) when prompted for an OpenAPI specification. ## Advanced ### Accessing custom request fields -The event sent by Agents for Amazon Bedrock into your Lambda function contains a number of extra event fields, exposed in the `app.current_event` field. +The event sent by Agents for Amazon Bedrock into your Lambda function contains a [number of extra event fields](#request_fields_table), exposed in the `app.current_event` field. ???+ note "Why is this useful?" You can for instance identify new conversations (`session_id`) or store and analyze entire conversations (`input_text`). @@ -267,6 +266,7 @@ The event sent by Agents for Amazon Bedrock into your Lambda function contains a --8<-- "examples/event_handler_bedrock_agents/src/accessing_request_fields.py" ``` + The input event fields are: | Name | Type | Description | From 0d19c1e79e0938738d83c2e0656633d31e403b34 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 16:46:47 +0100 Subject: [PATCH 39/46] chore: apply upstream changes --- docs/core/event_handler/_openapi_customization_metadata.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/core/event_handler/_openapi_customization_metadata.md b/docs/core/event_handler/_openapi_customization_metadata.md index c578f343e84..5a96db582cb 100644 --- a/docs/core/event_handler/_openapi_customization_metadata.md +++ b/docs/core/event_handler/_openapi_customization_metadata.md @@ -1,11 +1,12 @@ + Defining and customizing OpenAPI metadata gives detailed, top-level information about your API. Here's the method to set and tailor this metadata: | Field Name | Type | Description | | ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `title` | `str` | The title for your API. It should be a concise, specific name that can be used to identify the API in documentation or listings. | | `version` | `str` | The version of the API you are documenting. This could reflect the release iteration of the API and helps clients understand the evolution of the API. | -| `openapi_version` | `str` | Specifies the version of the OpenAPI Specification on which your API is based. For most contemporary APIs, the default value would be `3.0.0` or higher. | +| `openapi_version` | `str` | Specifies the version of the OpenAPI Specification on which your API is based. When using Pydantic v1 it defaults to 3.0.3, and when using Pydantic v2, it defaults to 3.1.0. | | `summary` | `str` | A short and informative summary that can provide an overview of what the API does. This can be the same as or different from the title but should add context or information. | | `description` | `str` | A verbose description that can include Markdown formatting, providing a full explanation of the API's purpose, functionalities, and general usage instructions. | | `tags` | `List[str]` | A collection of tags that categorize endpoints for better organization and navigation within the documentation. This can group endpoints by their functionality or other criteria. | From 0da305708571a79a3c7c19d859e45272ccc71006 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 17:36:03 +0100 Subject: [PATCH 40/46] fix: weird characters --- docs/core/event_handler/api_gateway.md | 80 +++++++++++++------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index cd2310ce2b3..8b1ea99f309 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -9,18 +9,18 @@ Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Bala - Lightweight routing to reduce boilerplate for API Gateway REST/HTTP API, ALB and Lambda Function URLs. - Support for CORS, binary and Gzip compression, Decimals JSON encoding and bring your own JSON serializer -- Built-in integration with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="\_blank"} for self-documented event schema +- Built-in integration with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"} for self-documented event schema - Works with micro function (one or a few routes) and monolithic functions (all routes) - Support for OpenAPI and data validation for requests/responses ## Getting started ???+ tip -All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="\_blank"}. +All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. ### Install -!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="\_blank"}." +!!! info "This is not necessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." **When using the data validation feature**, you need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. @@ -30,9 +30,9 @@ As of now, both Pydantic V1 and V2 are supported. For a future major version, we -If you're using any API Gateway integration, you must have an existing [API Gateway Proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html){target="\_blank"} or [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html){target="\_blank"} configured to invoke your Lambda function. +If you're using any API Gateway integration, you must have an existing [API Gateway Proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html){target="_blank"} or [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html){target="_blank"} configured to invoke your Lambda function. -In case of using [VPC Lattice](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="\_blank"}, you must have a service network configured to invoke your Lambda function. +In case of using [VPC Lattice](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="_blank"}, you must have a service network configured to invoke your Lambda function. This is the sample infrastructure for API Gateway and Lambda Function URLs we are using for the examples in this documentation. @@ -116,7 +116,7 @@ When using Amazon Application Load Balancer (ALB) to front your Lambda functions #### Lambda Function URL -When using [AWS Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html){target="\_blank"}, you can use `LambdaFunctionUrlResolver`. +When using [AWS Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html){target="_blank"}, you can use `LambdaFunctionUrlResolver`. === "getting_started_lambda_function_url_resolver.py" @@ -132,7 +132,7 @@ When using [AWS Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/d #### VPC Lattice -When using [VPC Lattice with AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="\_blank"}, you can use `VPCLatticeV2Resolver`. +When using [VPC Lattice with AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="_blank"}, you can use `VPCLatticeV2Resolver`. === "Payload v2 (Recommended)" @@ -187,7 +187,7 @@ You can also nest dynamic paths, for example `/todos//`. ???+ note We recommend having explicit routes whenever possible; use catch-all routes sparingly. -You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="\_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`. +You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`. You can also combine nested paths with greedy regex to catch in between routes. @@ -234,7 +234,7 @@ It is generally better to have separate functions for each HTTP method, as the f ### Data validation !!! note "This changes the authoring experience by relying on Python's type annotations" -It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="\_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. +It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. For brevity, we'll focus on Pydantic only. @@ -338,7 +338,7 @@ Even better, we can also let Event Handler validate and convert our response acc ##### Validating payload subset -With the addition of the [`Annotated` type starting in Python 3.9](https://docs.python.org/3/library/typing.html#typing.Annotated){target="\_blank" rel="nofollow"}, types can contain additional metadata, allowing us to represent anything we want. +With the addition of the [`Annotated` type starting in Python 3.9](https://docs.python.org/3/library/typing.html#typing.Annotated){target="_blank" rel="nofollow"}, types can contain additional metadata, allowing us to represent anything we want. We use the `Annotated` and OpenAPI `Body` type to instruct Event Handler that our payload is located in a particular JSON key. @@ -429,7 +429,7 @@ For example, we could validate that `` dynamic path should be no greate We use the `Annotated` type to tell the Event Handler that a particular parameter is a header that needs to be validated. -!!! info "We adhere to [HTTP RFC standards](https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2){target="\_blank" rel="nofollow"}, which means we treat HTTP headers as case-insensitive." +!!! info "We adhere to [HTTP RFC standards](https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2){target="_blank" rel="nofollow"}, which means we treat HTTP headers as case-insensitive." In the following example, we use a new `Header` OpenAPI type to add [one out of many possible constraints](#customizing-openapi-parameters), which should read as: @@ -461,7 +461,7 @@ In the following example, we use a new `Header` OpenAPI type to add [one out of ### Accessing request details -Event Handler integrates with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="\_blank"}, and it exposes their respective resolver request details and convenient methods under `app.current_event`. +Event Handler integrates with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"}, and it exposes their respective resolver request details and convenient methods under `app.current_event`. That is why you see `app.resolve(event, context)` in every example. This allows Event Handler to resolve requests, and expose data like `app.lambda_context` and `app.current_event`. @@ -521,7 +521,7 @@ We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 4 !!! note "This feature requires [data validation](#data-validation) feature to be enabled." -Behind the scenes, the [data validation](#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="\_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. +Behind the scenes, the [data validation](#data-validation) feature auto-generates an OpenAPI specification from your routes and type annotations. You can use [Swagger UI](https://swagger.io/tools/swagger-ui/){target="_blank" rel="nofollow"} to visualize and interact with your newly auto-documented API. There are some important **caveats** that you should know before enabling it: @@ -543,11 +543,11 @@ Here's an example of what it looks like by default: ### Custom Domain API Mappings -When using [Custom Domain API Mappings feature](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-mappings.html){target="\_blank"}, you must use **`strip_prefixes`** param in the `APIGatewayRestResolver` constructor. +When using [Custom Domain API Mappings feature](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-mappings.html){target="_blank"}, you must use **`strip_prefixes`** param in the `APIGatewayRestResolver` constructor. **Scenario**: You have a custom domain `api.mydomain.dev`. Then you set `/payment` API Mapping to forward any payment requests to your Payments API. -**Challenge**: This means your `path` value for any API requests will always contain `/payment/`, leading to HTTP 404 as Event Handler is trying to match what's after `payment/`. This gets further complicated with an [arbitrary level of nesting](https://github.com/aws-powertools/powertools-lambda/issues/34){target="\_blank"}. +**Challenge**: This means your `path` value for any API requests will always contain `/payment/`, leading to HTTP 404 as Event Handler is trying to match what's after `payment/`. This gets further complicated with an [arbitrary level of nesting](https://github.com/aws-powertools/powertools-lambda/issues/34){target="_blank"}. To address this API Gateway behavior, we use `strip_prefixes` parameter to account for these prefixes that are now injected into the path regardless of which type of API Gateway you're using. @@ -630,12 +630,12 @@ If you need to allow multiple origins, pass the additional origins using the `ex | Key | Value | Note | | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **[allow_origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="\_blank" rel="nofollow"}**: `str` | `*` | Only use the default value for development. **Never use `*` for production** unless your use case requires it | -| **[extra_origins](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="\_blank" rel="nofollow"}**: `List[str]` | `[]` | Additional origins to be allowed, in addition to the one specified in `allow_origin` | -| **[allow_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers){target="\_blank" rel="nofollow"}**: `List[str]` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Additional headers will be appended to the default list for your convenience | -| **[expose_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers){target="\_blank" rel="nofollow"}**: `List[str]` | `[]` | Any additional header beyond the [safe listed by CORS specification](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header){target="\_blank" rel="nofollow"}. | -| **[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="\_blank" rel="nofollow"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway | -| **[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="\_blank" rel="nofollow"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. | +| **[allow_origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `str` | `*` | Only use the default value for development. **Never use `*` for production** unless your use case requires it | +| **[extra_origins](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `List[str]` | `[]` | Additional origins to be allowed, in addition to the one specified in `allow_origin` | +| **[allow_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers){target="_blank" rel="nofollow"}**: `List[str]` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Additional headers will be appended to the default list for your convenience | +| **[expose_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers){target="_blank" rel="nofollow"}**: `List[str]` | `[]` | Any additional header beyond the [safe listed by CORS specification](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header){target="_blank" rel="nofollow"}. | +| **[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="_blank" rel="nofollow"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway | +| **[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="_blank" rel="nofollow"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. | ### Middleware @@ -780,7 +780,7 @@ Here's an example where we prevent any request that doesn't include a correlatio By default, any unhandled exception in the middleware chain is eventually propagated as a HTTP 500 back to the client. -While there isn't anything special on how to use [`try/catch`](https://docs.python.org/3/tutorial/errors.html#handling-exceptions){target="\_blank" rel="nofollow"} for middlewares, it is important to visualize how Event Handler deals with them under the following scenarios: +While there isn't anything special on how to use [`try/catch`](https://docs.python.org/3/tutorial/errors.html#handling-exceptions){target="_blank" rel="nofollow"} for middlewares, it is important to visualize how Event Handler deals with them under the following scenarios: === "Unhandled exception from route handler" @@ -839,7 +839,7 @@ These are native middlewares that may become native features depending on custom | Middleware | Purpose | | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| [SchemaValidationMiddleware](/lambda/python/latest/api/event_handler/middlewares/schema_validation.html){target="\_blank"} | Validates API request body and response against JSON Schema, using [Validation utility](../../utilities/validation.md){target="\_blank"} | +| [SchemaValidationMiddleware](/lambda/python/latest/api/event_handler/middlewares/schema_validation.html){target="_blank"} | Validates API request body and response against JSON Schema, using [Validation utility](../../utilities/validation.md){target="_blank"} | #### Being a good citizen @@ -862,7 +862,7 @@ Powertools for AWS Lambda (Python) serializes headers and cookies according to t Some event sources require headers and cookies to be encoded as `multiValueHeaders`. ???+ warning "Using multiple values for HTTP headers in ALB?" -Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="\_blank"} to serialize response headers correctly. +Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="_blank"} to serialize response headers correctly. === "fine_grained_responses.py" @@ -947,7 +947,7 @@ Like `compress` feature, the client must send the `Accept` header with the corre ### Debug mode -You can enable debug mode via `debug` param, or via `POWERTOOLS_DEV` [environment variable](../../index.md#environment-variables){target="\_blank"}. +You can enable debug mode via `debug` param, or via `POWERTOOLS_DEV` [environment variable](../../index.md#environment-variables){target="_blank"}. This will enable full tracebacks errors in the response, print request and responses, and set CORS in development mode. @@ -962,10 +962,10 @@ This might reveal sensitive information in your logs and relax CORS restrictions ### OpenAPI -When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="\_blank" rel="nofollow"} type annotations to add constraints to your API's parameters. +When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank" rel="nofollow"} type annotations to add constraints to your API's parameters. ???+ warning "OpenAPI schema version depends on the installed version of Pydantic" -Pydantic v1 generates [valid OpenAPI 3.0.3 schemas](https://docs.pydantic.dev/1.10/usage/schema/){target="\_blank" rel="nofollow"}, and Pydantic v2 generates [valid OpenAPI 3.1.0 schemas](https://docs.pydantic.dev/latest/why/#json-schema){target="\_blank" rel="nofollow"}. +Pydantic v1 generates [valid OpenAPI 3.0.3 schemas](https://docs.pydantic.dev/1.10/usage/schema/){target="_blank" rel="nofollow"}, and Pydantic v2 generates [valid OpenAPI 3.1.0 schemas](https://docs.pydantic.dev/latest/why/#json-schema){target="_blank" rel="nofollow"}. In OpenAPI documentation tools like [SwaggerUI](api-gateway.md#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. @@ -974,11 +974,11 @@ We don't have support for files, form data, and header parameters at the moment. #### Customizing OpenAPI parameters ---8<-- "docs/core/event_handler/\_openapi_customization_parameters.md" +--8<-- "docs/core/event_handler/_openapi_customization_parameters.md" #### Customizing API operations ---8<-- "docs/core/event_handler/\_openapi_customization_operations.md" +--8<-- "docs/core/event_handler/_openapi_customization_operations.md" To implement these customizations, include extra parameters when defining your routes: @@ -1011,7 +1011,7 @@ A Middleware can handle tasks such as adding security headers, user authenticati #### Customizing OpenAPI metadata ---8<-- "docs/core/event_handler/\_openapi_customization_metadata.md" +--8<-- "docs/core/event_handler/_openapi_customization_metadata.md" Include extra parameters when exporting your OpenAPI specification to apply these customizations: @@ -1171,9 +1171,9 @@ _**Benefits**_ _**Downsides**_ -- **Cold starts**. Frequent deployments and/or high load can diminish the benefit of monolithic functions depending on your latency requirements, due to [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="\_blank"}. Always load test to pragmatically balance between your customer experience and development cognitive load. +- **Cold starts**. Frequent deployments and/or high load can diminish the benefit of monolithic functions depending on your latency requirements, due to [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"}. Always load test to pragmatically balance between your customer experience and development cognitive load. - **Granular security permissions**. The micro function approach enables you to use fine-grained permissions & access controls, separate external dependencies & code signing at the function level. Conversely, you could have multiple functions while duplicating the final code artifact in a monolithic approach. - - Regardless, least privilege can be applied to either approaches. + - Regardless, least privilege can be applied to either approaches. - **Higher risk per deployment**. A misconfiguration or invalid import can cause disruption if not caught earlier in automated testing. Multiple functions can mitigate misconfigurations but they would still share the same code artifact. You can further minimize risks with multiple environments in your CI/CD pipeline. #### Micro function @@ -1184,18 +1184,18 @@ A micro function means that your final code artifact will be different to each f **Benefits** -- **Granular scaling**. A micro function can benefit from the [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="\_blank"} to scale differently depending on each part of your application. Concurrency controls and provisioned concurrency can also be used at a granular level for capacity management. +- **Granular scaling**. A micro function can benefit from the [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"} to scale differently depending on each part of your application. Concurrency controls and provisioned concurrency can also be used at a granular level for capacity management. - **Discoverability**. Micro functions are easier to visualize when using distributed tracing. Their high-level architectures can be self-explanatory, and complexity is highly visible — assuming each function is named to the business purpose it serves. -- **Package size**. An independent function can be significant smaller (KB vs MB) depending on external dependencies it require to perform its purpose. Conversely, a monolithic approach can benefit from [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html){target="\_blank"} to optimize builds for external dependencies. +- **Package size**. An independent function can be significant smaller (KB vs MB) depending on external dependencies it require to perform its purpose. Conversely, a monolithic approach can benefit from [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html){target="_blank"} to optimize builds for external dependencies. **Downsides** -- **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="\_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. - - Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. +- **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. + - Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. - **Harder to share code**. Shared code must be carefully evaluated to avoid unnecessary deployments when that changes. Equally, if shared code isn't a library, your development, building, deployment tooling need to accommodate the distinct layout. - **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. - - Automated testing, operational and security reviews are essential to stability in either approaches. + - Automated testing, operational and security reviews are essential to stability in either approaches. **Example** @@ -1204,7 +1204,7 @@ Consider a simplified micro function structured REST API that has two routes: - `/users` - an endpoint that will return all users of the application on `GET` requests - `/users/` - an endpoint that looks up a single users details by ID on `GET` requests -Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="\_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). +Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). === "`/users` Endpoint" `python @@ -1224,7 +1224,7 @@ Each endpoint will be it's own Lambda function that is configured as a [Lambda i ???+ note -You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="\_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="\_blank" rel="nofollow"}. +You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="_blank" rel="nofollow"}. @@ -1294,7 +1294,7 @@ You can test your routes by passing a proxy event request with required params. Chalice is a full featured microframework that manages application and infrastructure. This utility, however, is largely focused on routing to reduce boilerplate and expects you to setup and manage infrastructure with your framework of choice. -That said, [Chalice has native integration with Lambda Powertools](https://aws.github.io/chalice/topics/middleware.html){target="\_blank" rel="nofollow"} if you're looking for a more opinionated and web framework feature set. +That said, [Chalice has native integration with Lambda Powertools](https://aws.github.io/chalice/topics/middleware.html){target="_blank" rel="nofollow"} if you're looking for a more opinionated and web framework feature set. **What happened to `ApiGatewayResolver`?** From 81945df604ff52fa358cc8f882f371621cfc45da Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 17:38:58 +0100 Subject: [PATCH 41/46] fix: wording --- docs/core/event_handler/bedrock_agents.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 938158fdd17..8680e44ab20 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -16,7 +16,7 @@ Create [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us * Minimal boilerplate to build Agents for Amazon Bedrock * Automatic generation of [OpenAPI schemas](https://www.openapis.org/){target="_blank"} from your business logic code * Built-in data validation for requests and responses -* Similar experience when authoring [REST](api_gateway.md){target="_blank"} and [GraphQL APIs](appsync.md){target="_blank"} +* Similar experience to authoring [REST and HTTP APIs](api_gateway.md){target="_blank"} ## Terminology From 8e848fe448296ac220765318b44b71f8f8da7617 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 17:40:27 +0100 Subject: [PATCH 42/46] fix: wording --- docs/core/event_handler/bedrock_agents.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 8680e44ab20..822e942fff4 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -40,7 +40,7 @@ Create [Agents for Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/us !!! info "This is unnecessary if you're installing Powertools for AWS Lambda (Python) via [Lambda Layer/SAR](../../index.md#lambda-layer){target="_blank"}." -You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. At this time, we support both Pydantic V1 and V2. For a future major version, we will only support Pydantic V2. +You need to add `pydantic` as a dependency in your preferred tool _e.g., requirements.txt, pyproject.toml_. At this time, we only support Pydantic V1, due to an incompatibility with Pydantic V2 generated schemas and the Agents' API. ### Required resources From 9e5b052145a313067809a935ebb8697277811bcd Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 17:43:10 +0100 Subject: [PATCH 43/46] fix: make all schemas 3.0.3 --- .../src/generating_openapi_schema.json | 2 +- .../src/getting_started_schema.json | 2 +- .../src/getting_started_with_validation_schema.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/event_handler_bedrock_agents/src/generating_openapi_schema.json b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.json index 2ef91f5392f..7e492082d43 100644 --- a/examples/event_handler_bedrock_agents/src/generating_openapi_schema.json +++ b/examples/event_handler_bedrock_agents/src/generating_openapi_schema.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.0", + "openapi": "3.0.3", "info": { "title": "Powertools API", "version": "1.0.0" diff --git a/examples/event_handler_bedrock_agents/src/getting_started_schema.json b/examples/event_handler_bedrock_agents/src/getting_started_schema.json index 2ef91f5392f..7e492082d43 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_schema.json +++ b/examples/event_handler_bedrock_agents/src/getting_started_schema.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.0", + "openapi": "3.0.3", "info": { "title": "Powertools API", "version": "1.0.0" diff --git a/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json index f2847bf27f8..0d307287096 100644 --- a/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json +++ b/examples/event_handler_bedrock_agents/src/getting_started_with_validation_schema.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.0", + "openapi": "3.0.3", "info": { "title": "Powertools API", "version": "1.0.0" From 59ea6272025be5611ba9b1b2afb98fdd4639f140 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 17:44:30 +0100 Subject: [PATCH 44/46] fix: highlight --- docs/core/event_handler/bedrock_agents.md | 2 +- docs/diagram_src/bedrock-agents.drawio | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 docs/diagram_src/bedrock-agents.drawio diff --git a/docs/core/event_handler/bedrock_agents.md b/docs/core/event_handler/bedrock_agents.md index 822e942fff4..32aa2835491 100644 --- a/docs/core/event_handler/bedrock_agents.md +++ b/docs/core/event_handler/bedrock_agents.md @@ -134,7 +134,7 @@ Pydantic is a popular library for data validation using Python type annotations. This example uses [Pydantic's EmailStr](https://docs.pydantic.dev/2.0/usage/types/string_types/#emailstr){target="_blank"} to validate the email address passed to the `schedule_meeting` function. The function then returns a boolean indicating if the meeting was successfully scheduled. - ```python hl_lines="1 2 16-18" + ```python hl_lines="1 2 6 16-18" --8<-- "examples/event_handler_bedrock_agents/src/getting_started_with_validation.py" ``` diff --git a/docs/diagram_src/bedrock-agents.drawio b/docs/diagram_src/bedrock-agents.drawio deleted file mode 100644 index da11d0e513c..00000000000 --- a/docs/diagram_src/bedrock-agents.drawio +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 1efcad83767d3aff106fcc5ddba478fc543d8066 Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 17:47:27 +0100 Subject: [PATCH 45/46] fix: fix --- docs/core/event_handler/api_gateway.md | 139 ++++++++++++------------- 1 file changed, 68 insertions(+), 71 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 8b1ea99f309..cb3b9891272 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -7,11 +7,11 @@ Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Bala ## Key Features -- Lightweight routing to reduce boilerplate for API Gateway REST/HTTP API, ALB and Lambda Function URLs. -- Support for CORS, binary and Gzip compression, Decimals JSON encoding and bring your own JSON serializer -- Built-in integration with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"} for self-documented event schema -- Works with micro function (one or a few routes) and monolithic functions (all routes) -- Support for OpenAPI and data validation for requests/responses +* Lightweight routing to reduce boilerplate for API Gateway REST/HTTP API, ALB and Lambda Function URLs. +* Support for CORS, binary and Gzip compression, Decimals JSON encoding and bring your own JSON serializer +* Built-in integration with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"} for self-documented event schema +* Works with micro function (one or a few routes) and monolithic functions (all routes) +* Support for OpenAPI and data validation for requests/responses ## Getting started @@ -29,7 +29,6 @@ As of now, both Pydantic V1 and V2 are supported. For a future major version, we ### Required resources - If you're using any API Gateway integration, you must have an existing [API Gateway Proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html){target="_blank"} or [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html){target="_blank"} configured to invoke your Lambda function. In case of using [VPC Lattice](https://docs.aws.amazon.com/lambda/latest/dg/services-vpc-lattice.html){target="_blank"}, you must have a service network configured to invoke your Lambda function. @@ -373,9 +372,9 @@ We use the `Annotated` type to tell the Event Handler that a particular paramete In the following example, we use a new `Query` OpenAPI type to add [one out of many possible constraints](#customizing-openapi-parameters), which should read as: -- `completed` is a query string with a `None` as its default value -- `completed`, when set, should have at minimum 4 characters -- No match? Event Handler will return a validation error response +* `completed` is a query string with a `None` as its default value +* `completed`, when set, should have at minimum 4 characters +* No match? Event Handler will return a validation error response @@ -433,9 +432,9 @@ We use the `Annotated` type to tell the Event Handler that a particular paramete In the following example, we use a new `Header` OpenAPI type to add [one out of many possible constraints](#customizing-openapi-parameters), which should read as: -- `correlation_id` is a header that must be present in the request -- `correlation_id` should have 16 characters -- No match? Event Handler will return a validation error response +* `correlation_id` is a header that must be present in the request +* `correlation_id` should have 16 characters +* No match? Event Handler will return a validation error response @@ -463,11 +462,11 @@ In the following example, we use a new `Header` OpenAPI type to add [one out of Event Handler integrates with [Event Source Data Classes utilities](../../utilities/data_classes.md){target="_blank"}, and it exposes their respective resolver request details and convenient methods under `app.current_event`. -That is why you see `app.resolve(event, context)` in every example. This allows Event Handler to resolve requests, and expose data like `app.lambda_context` and `app.current_event`. +That is why you see `app.resolve(event, context)` in every example. This allows Event Handler to resolve requests, and expose data like `app.lambda_context` and `app.current_event`. #### Query strings and payload -Within `app.current_event` property, you can access all available query strings as a dictionary via `query_string_parameters`, or a specific one via `get_query_string_value` method. +Within `app.current_event` property, you can access all available query strings as a dictionary via `query_string_parameters`, or a specific one via `get_query_string_value` method. You can access the raw payload via `body` property, or if it's a JSON string you can quickly deserialize it via `json_body` property - like the earlier example in the [HTTP Methods](#http-methods) section. @@ -529,13 +528,13 @@ There are some important **caveats** that you should know before enabling it: | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Swagger UI is **publicly accessible by default** | When using `enable_swagger` method, you can [protect sensitive API endpoints by implementing a custom middleware](#customizing-swagger-ui) using your preferred authorization mechanism. | | **No micro-functions support** yet | Swagger UI is enabled on a per resolver instance which will limit its accuracy here. | -| You need to expose a **new route** | You'll need to expose the following path to Lambda: `/swagger`; ignore if you're routing this path already. | +| You need to expose a **new route** | You'll need to expose the following path to Lambda: `/swagger`; ignore if you're routing this path already. | ```python hl_lines="12-13" title="enabling_swagger.py" --8<-- "examples/event_handler_rest/src/enabling_swagger.py" ``` -1. `enable_swagger` creates a route to serve Swagger UI and allows quick customizations.

You can also include middlewares to protect or enhance the overall experience. +1. `enable_swagger` creates a route to serve Swagger UI and allows quick customizations.

You can also include middlewares to protect or enhance the overall experience. Here's an example of what it looks like by default: @@ -566,7 +565,7 @@ To address this API Gateway behavior, we use `strip_prefixes` parameter to accou ???+ note After removing a path prefix with `strip_prefixes`, the new root path will automatically be mapped to the path argument of `/`. - For example, when using `strip_prefixes` value of `/pay`, there is no difference between a request path of `/pay` and `/pay/`; and the path argument would be defined as `/`. + For example, when using `strip_prefixes` value of `/pay`, there is no difference between a request path of `/pay` and `/pay/`; and the path argument would be defined as `/`. For added flexibility, you can use regexes to strip a prefix. This is helpful when you have many options due to different combinations of prefixes (e.g: multiple environments, multiple versions). @@ -628,14 +627,14 @@ Always configure `allow_origin` when using in production. ???+ tip "Multiple origins?" If you need to allow multiple origins, pass the additional origins using the `extra_origins` key. -| Key | Value | Note | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **[allow_origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `str` | `*` | Only use the default value for development. **Never use `*` for production** unless your use case requires it | -| **[extra_origins](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `List[str]` | `[]` | Additional origins to be allowed, in addition to the one specified in `allow_origin` | -| **[allow_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers){target="_blank" rel="nofollow"}**: `List[str]` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Additional headers will be appended to the default list for your convenience | +| Key | Value | Note | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **[allow_origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `str` | `*` | Only use the default value for development. **Never use `*` for production** unless your use case requires it | +| **[extra_origins](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin){target="_blank" rel="nofollow"}**: `List[str]` | `[]` | Additional origins to be allowed, in addition to the one specified in `allow_origin` | +| **[allow_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers){target="_blank" rel="nofollow"}**: `List[str]` | `[Authorization, Content-Type, X-Amz-Date, X-Api-Key, X-Amz-Security-Token]` | Additional headers will be appended to the default list for your convenience | | **[expose_headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers){target="_blank" rel="nofollow"}**: `List[str]` | `[]` | Any additional header beyond the [safe listed by CORS specification](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header){target="_blank" rel="nofollow"}. | -| **[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="_blank" rel="nofollow"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway | -| **[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="_blank" rel="nofollow"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. | +| **[max_age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age){target="_blank" rel="nofollow"}**: `int` | `` | Only for pre-flight requests if you choose to have your function to handle it instead of API Gateway | +| **[allow_credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials){target="_blank" rel="nofollow"}**: `bool` | `False` | Only necessary when you need to expose cookies, authorization headers or TLS client certificates. | ### Middleware @@ -700,7 +699,6 @@ Here's a sample middleware that extracts and injects correlation ID, using `APIG ![Combining middlewares](../../media/middlewares_normal_processing-dark.svg#only-dark) _Request flowing through multiple registered middlewares_ -
You can use `app.use` to register middlewares that should always run regardless of the route, also known as global middlewares. @@ -742,7 +740,6 @@ Event Handler **calls global middlewares first**, then middlewares defined at th ![Short-circuiting middleware chain](../../media/middlewares_early_return-dark.svg#only-dark) _Interrupting request flow by returning early_ - Imagine you want to stop processing a request if something is missing, or return immediately if you've seen this request before. @@ -837,8 +834,8 @@ When registering a middleware, we expect a callable in both cases. For class-bas These are native middlewares that may become native features depending on customer demand. -| Middleware | Purpose | -| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| Middleware | Purpose | +| ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | [SchemaValidationMiddleware](/lambda/python/latest/api/event_handler/middlewares/schema_validation.html){target="_blank"} | Validates API request body and response against JSON Schema, using [Validation utility](../../utilities/validation.md){target="_blank"} | #### Being a good citizen @@ -967,7 +964,7 @@ When you enable [Data Validation](#data-validation), we use a combination of Pyd ???+ warning "OpenAPI schema version depends on the installed version of Pydantic" Pydantic v1 generates [valid OpenAPI 3.0.3 schemas](https://docs.pydantic.dev/1.10/usage/schema/){target="_blank" rel="nofollow"}, and Pydantic v2 generates [valid OpenAPI 3.1.0 schemas](https://docs.pydantic.dev/latest/why/#json-schema){target="_blank" rel="nofollow"}. -In OpenAPI documentation tools like [SwaggerUI](api-gateway.md#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. +In OpenAPI documentation tools like [SwaggerUI](#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. ???+ note We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE). @@ -1005,9 +1002,9 @@ Below is an example configuration for serving Swagger UI from a custom path or C A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. -```python hl_lines="7 13-18 21" ---8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py" -``` + ```python hl_lines="7 13-18 21" + --8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py" + ``` #### Customizing OpenAPI metadata @@ -1039,7 +1036,7 @@ Let's assume you have `split_route.py` as your Lambda function entrypoint and ro === "split_route_module.py" - We import **Router** instead of **APIGatewayRestResolver**; syntax wise is exactly the same. + We import **Router** instead of **APIGatewayRestResolver**; syntax wise is exactly the same. !!! info This means all methods, including [middleware](#middleware) will work as usual. @@ -1050,19 +1047,18 @@ Let's assume you have `split_route.py` as your Lambda function entrypoint and ro === "split_route.py" - We use `include_router` method and include all user routers registered in the `router` global object. + We use `include_router` method and include all user routers registered in the `router` global object. !!! note This method merges routes, [context](#sharing-contextual-data) and [middleware](#middleware) from `Router` into the main resolver instance (`APIGatewayRestResolver()`). - ```python hl_lines="11" + ```python hl_lines="11" --8<-- "examples/event_handler_rest/src/split_route.py" - ``` + ``` 1. When using [middleware](#middleware) in both `Router` and main resolver, you can make `Router` middlewares to take precedence by using `include_router` before `app.use()`. - #### Route prefix In the previous example, `split_route_module.py` routes had a `/todos` prefix. This might grow over time and become repetitive. @@ -1071,9 +1067,9 @@ When necessary, you can set a prefix when including a router object. This means === "split_route_prefix.py" - ```python hl_lines="12" + ```python hl_lines="12" --8<-- "examples/event_handler_rest/src/split_route_prefix.py" - ``` + ``` === "split_route_prefix_module.py" @@ -1105,15 +1101,15 @@ This can be useful for middlewares injecting contextual information before a req === "split_route_append_context.py" - ```python hl_lines="18" + ```python hl_lines="18" --8<-- "examples/event_handler_rest/src/split_route_append_context.py" - ``` + ``` === "split_route_append_context_module.py" - ```python hl_lines="16" + ```python hl_lines="16" --8<-- "examples/event_handler_rest/src/split_route_append_context_module.py" - ``` + ``` #### Sample layout @@ -1165,16 +1161,16 @@ A monolithic function means that your final code artifact will be deployed to a _**Benefits**_ -- **Code reuse**. It's easier to reason about your service, modularize it and reuse code as it grows. Eventually, it can be turned into a standalone library. -- **No custom tooling**. Monolithic functions are treated just like normal Python packages; no upfront investment in tooling. -- **Faster deployment and debugging**. Whether you use all-at-once, linear, or canary deployments, a monolithic function is a single deployable unit. IDEs like PyCharm and VSCode have tooling to quickly profile, visualize, and step through debug any Python package. +* **Code reuse**. It's easier to reason about your service, modularize it and reuse code as it grows. Eventually, it can be turned into a standalone library. +* **No custom tooling**. Monolithic functions are treated just like normal Python packages; no upfront investment in tooling. +* **Faster deployment and debugging**. Whether you use all-at-once, linear, or canary deployments, a monolithic function is a single deployable unit. IDEs like PyCharm and VSCode have tooling to quickly profile, visualize, and step through debug any Python package. _**Downsides**_ -- **Cold starts**. Frequent deployments and/or high load can diminish the benefit of monolithic functions depending on your latency requirements, due to [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"}. Always load test to pragmatically balance between your customer experience and development cognitive load. -- **Granular security permissions**. The micro function approach enables you to use fine-grained permissions & access controls, separate external dependencies & code signing at the function level. Conversely, you could have multiple functions while duplicating the final code artifact in a monolithic approach. - - Regardless, least privilege can be applied to either approaches. -- **Higher risk per deployment**. A misconfiguration or invalid import can cause disruption if not caught earlier in automated testing. Multiple functions can mitigate misconfigurations but they would still share the same code artifact. You can further minimize risks with multiple environments in your CI/CD pipeline. +* **Cold starts**. Frequent deployments and/or high load can diminish the benefit of monolithic functions depending on your latency requirements, due to [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"}. Always load test to pragmatically balance between your customer experience and development cognitive load. +* **Granular security permissions**. The micro function approach enables you to use fine-grained permissions & access controls, separate external dependencies & code signing at the function level. Conversely, you could have multiple functions while duplicating the final code artifact in a monolithic approach. + * Regardless, least privilege can be applied to either approaches. +* **Higher risk per deployment**. A misconfiguration or invalid import can cause disruption if not caught earlier in automated testing. Multiple functions can mitigate misconfigurations but they would still share the same code artifact. You can further minimize risks with multiple environments in your CI/CD pipeline. #### Micro function @@ -1184,48 +1180,49 @@ A micro function means that your final code artifact will be different to each f **Benefits** -- **Granular scaling**. A micro function can benefit from the [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"} to scale differently depending on each part of your application. Concurrency controls and provisioned concurrency can also be used at a granular level for capacity management. -- **Discoverability**. Micro functions are easier to visualize when using distributed tracing. Their high-level architectures can be self-explanatory, and complexity is highly visible — assuming each function is named to the business purpose it serves. -- **Package size**. An independent function can be significant smaller (KB vs MB) depending on external dependencies it require to perform its purpose. Conversely, a monolithic approach can benefit from [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html){target="_blank"} to optimize builds for external dependencies. +* **Granular scaling**. A micro function can benefit from the [Lambda scaling model](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html){target="_blank"} to scale differently depending on each part of your application. Concurrency controls and provisioned concurrency can also be used at a granular level for capacity management. +* **Discoverability**. Micro functions are easier to visualize when using distributed tracing. Their high-level architectures can be self-explanatory, and complexity is highly visible — assuming each function is named to the business purpose it serves. +* **Package size**. An independent function can be significant smaller (KB vs MB) depending on external dependencies it require to perform its purpose. Conversely, a monolithic approach can benefit from [Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html){target="_blank"} to optimize builds for external dependencies. **Downsides** -- **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. - - Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. -- **Harder to share code**. Shared code must be carefully evaluated to avoid unnecessary deployments when that changes. Equally, if shared code isn't a library, +* **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. + * Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. +* **Harder to share code**. Shared code must be carefully evaluated to avoid unnecessary deployments when that changes. Equally, if shared code isn't a library, your development, building, deployment tooling need to accommodate the distinct layout. -- **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. - - Automated testing, operational and security reviews are essential to stability in either approaches. +* **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. + * Automated testing, operational and security reviews are essential to stability in either approaches. **Example** Consider a simplified micro function structured REST API that has two routes: -- `/users` - an endpoint that will return all users of the application on `GET` requests -- `/users/` - an endpoint that looks up a single users details by ID on `GET` requests +* `/users` - an endpoint that will return all users of the application on `GET` requests +* `/users/` - an endpoint that looks up a single users details by ID on `GET` requests Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). === "`/users` Endpoint" -`python - --8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" - ` + +```python +--8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" +``` === "`/users/` Endpoint" -`python - --8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" - ` + +```python +--8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" +``` === "Micro Function Example SAM Template" -`yaml - --8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" - ` - +```yaml +--8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" +``` + ???+ note You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="_blank" rel="nofollow"}. - ## Testing your code From a9d3a63db6836f2d6f3ed54303307e95c688783b Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Tue, 5 Mar 2024 17:48:55 +0100 Subject: [PATCH 46/46] fix: fix --- docs/core/event_handler/api_gateway.md | 87 +++++++++++++------------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index cb3b9891272..97a1bf3c68e 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -16,7 +16,7 @@ Event handler for Amazon API Gateway REST and HTTP APIs, Application Loader Bala ## Getting started ???+ tip -All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. + All examples shared in this documentation are available within the [project repository](https://github.com/aws-powertools/powertools-lambda-python/tree/develop/examples){target="_blank"}. ### Install @@ -58,8 +58,8 @@ A resolver will handle request resolution, including [one or more routers](#spli For resolvers, we provide: `APIGatewayRestResolver`, `APIGatewayHttpResolver`, `ALBResolver`, `LambdaFunctionUrlResolver`, and `VPCLatticeResolver`. From here on, we will default to `APIGatewayRestResolver` across examples. ???+ info "Auto-serialization" -We serialize `Dict` responses as JSON, trim whitespace for compact responses, set content-type to `application/json`, and -return a 200 OK HTTP status. You can optionally set a different HTTP status code as the second argument of the tuple: + We serialize `Dict` responses as JSON, trim whitespace for compact responses, set content-type to `application/json`, and + return a 200 OK HTTP status. You can optionally set a different HTTP status code as the second argument of the tuple: ```python hl_lines="15 16" --8<-- "examples/event_handler_rest/src/getting_started_return_tuple.py" @@ -72,7 +72,7 @@ When using Amazon API Gateway REST API to front your Lambda functions, you can u Here's an example on how we can handle the `/todos` path. ???+ info "Trailing slash in routes" -For `APIGatewayRestResolver`, we seamless handle routes with a trailing slash (`/todos/`). + For `APIGatewayRestResolver`, we seamless handle routes with a trailing slash (`/todos/`). === "getting_started_rest_api_resolver.py" @@ -99,7 +99,7 @@ For `APIGatewayRestResolver`, we seamless handle routes with a trailing slash (` When using Amazon API Gateway HTTP API to front your Lambda functions, you can use `APIGatewayHttpResolver`. ???+ note -Using HTTP API v1 payload? Use `APIGatewayRestResolver` instead. `APIGatewayHttpResolver` defaults to v2 payload. + Using HTTP API v1 payload? Use `APIGatewayRestResolver` instead. `APIGatewayHttpResolver` defaults to v2 payload. ```python hl_lines="5 11" title="Using HTTP API resolver" --8<-- "examples/event_handler_rest/src/getting_started_http_api_resolver.py" @@ -164,7 +164,7 @@ You can use `/todos/` to configure dynamic URL paths, where `` Each dynamic route you set must be part of your function signature. This allows us to call your function using keyword arguments when matching your dynamic route. ???+ note -For brevity, we will only include the necessary keys for each sample request for the example to work. + For brevity, we will only include the necessary keys for each sample request for the example to work. === "dynamic_routes.py" @@ -179,19 +179,19 @@ For brevity, we will only include the necessary keys for each sample request for ``` ???+ tip -You can also nest dynamic paths, for example `/todos//`. + You can also nest dynamic paths, for example `/todos//`. #### Catch-all routes ???+ note -We recommend having explicit routes whenever possible; use catch-all routes sparingly. + We recommend having explicit routes whenever possible; use catch-all routes sparingly. You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`. You can also combine nested paths with greedy regex to catch in between routes. ???+ warning -We choose the most explicit registered route that matches an incoming event. + We choose the most explicit registered route that matches an incoming event. === "dynamic_routes_catch_all.py" @@ -228,12 +228,12 @@ If you need to accept multiple HTTP methods in a single function, you can use th ``` ???+ note -It is generally better to have separate functions for each HTTP method, as the functionality tends to differ depending on which method is used. + It is generally better to have separate functions for each HTTP method, as the functionality tends to differ depending on which method is used. ### Data validation !!! note "This changes the authoring experience by relying on Python's type annotations" -It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. + It's inspired by [FastAPI framework](https://fastapi.tiangolo.com/){target="_blank" rel="nofollow"} for ergonomics and to ease migrations in either direction. We support both Pydantic models and Python's dataclass. For brevity, we'll focus on Pydantic only. @@ -501,14 +501,14 @@ You can use **`exception_handler`** decorator with any Python exception. This al ``` ???+ info -The `exception_handler` also supports passing a list of exception types you wish to handle with one handler. + The `exception_handler` also supports passing a list of exception types you wish to handle with one handler. ### Raising HTTP errors You can easily raise any HTTP Error back to the client using `ServiceError` exception. This ensures your Lambda function doesn't fail but return the correct HTTP response signalling the error. ???+ info -If you need to send custom headers, use [Response](#fine-grained-responses) class instead. + If you need to send custom headers, use [Response](#fine-grained-responses) class instead. We provide pre-defined errors for the most popular ones such as HTTP 400, 401, 404, 500. @@ -563,7 +563,7 @@ To address this API Gateway behavior, we use `strip_prefixes` parameter to accou ``` ???+ note -After removing a path prefix with `strip_prefixes`, the new root path will automatically be mapped to the path argument of `/`. + After removing a path prefix with `strip_prefixes`, the new root path will automatically be mapped to the path argument of `/`. For example, when using `strip_prefixes` value of `/pay`, there is no difference between a request path of `/pay` and `/pay/`; and the path argument would be defined as `/`. @@ -585,7 +585,7 @@ This will ensure that CORS headers are returned as part of the response when you matches one of the allowed values. ???+ tip -Optionally disable CORS on a per path basis with `cors=False` parameter. + Optionally disable CORS on a per path basis with `cors=False` parameter. === "setting_cors.py" @@ -622,10 +622,10 @@ For convenience, we automatically handle that for you as long as you [setup CORS For convenience, these are the default values when using `CORSConfig` to enable CORS: ???+ warning -Always configure `allow_origin` when using in production. + Always configure `allow_origin` when using in production. ???+ tip "Multiple origins?" -If you need to allow multiple origins, pass the additional origins using the `extra_origins` key. + If you need to allow multiple origins, pass the additional origins using the `extra_origins` key. | Key | Value | Note | | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -828,7 +828,7 @@ As a practical example, let's refactor our correlation ID middleware so it accep 4. Register an instance of `CorrelationIdMiddleware`. !!! note "Class-based **vs** function-based middlewares" -When registering a middleware, we expect a callable in both cases. For class-based middlewares, `BaseMiddlewareHandler` is doing the work of calling your `handler` method with the correct parameters, hence why we expect an instance of it. + When registering a middleware, we expect a callable in both cases. For class-based middlewares, `BaseMiddlewareHandler` is doing the work of calling your `handler` method with the correct parameters, hence why we expect an instance of it. #### Native middlewares @@ -855,11 +855,11 @@ Keep the following in mind when authoring middlewares for Event Handler: You can use the `Response` class to have full control over the response. For example, you might want to add additional headers, cookies, or set a custom Content-type. ???+ info -Powertools for AWS Lambda (Python) serializes headers and cookies according to the type of input event. -Some event sources require headers and cookies to be encoded as `multiValueHeaders`. + Powertools for AWS Lambda (Python) serializes headers and cookies according to the type of input event. + Some event sources require headers and cookies to be encoded as `multiValueHeaders`. ???+ warning "Using multiple values for HTTP headers in ALB?" -Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="_blank"} to serialize response headers correctly. + Make sure you [enable the multi value headers feature](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers){target="_blank"} to serialize response headers correctly. === "fine_grained_responses.py" @@ -878,10 +878,10 @@ Make sure you [enable the multi value headers feature](https://docs.aws.amazon.c You can compress with gzip and base64 encode your responses via `compress` parameter. You have the option to pass the `compress` parameter when working with a specific route or using the Response object. ???+ info -The `compress` parameter used in the Response object takes precedence over the one used in the route. + The `compress` parameter used in the Response object takes precedence over the one used in the route. ???+ warning -The client must send the `Accept-Encoding` header, otherwise a normal response will be sent. + The client must send the `Accept-Encoding` header, otherwise a normal response will be sent. === "compressing_responses_using_route.py" @@ -910,7 +910,7 @@ The client must send the `Accept-Encoding` header, otherwise a normal response w ### Binary responses ???+ warning "Amazon API Gateway does not support `*/*` binary media type [when CORS is also configured](https://github.com/aws-powertools/powertools-lambda-python/issues/3373#issuecomment-1821144779){target='blank'}." -This feature requires API Gateway to configure binary media types, see [our sample infrastructure](#required-resources) for reference. + This feature requires API Gateway to configure binary media types, see [our sample infrastructure](#required-resources) for reference. For convenience, we automatically base64 encode binary responses. You can also use in combination with `compress` parameter if your client supports gzip. @@ -949,7 +949,7 @@ You can enable debug mode via `debug` param, or via `POWERTOOLS_DEV` [environmen This will enable full tracebacks errors in the response, print request and responses, and set CORS in development mode. ???+ danger -This might reveal sensitive information in your logs and relax CORS restrictions, use it sparingly. + This might reveal sensitive information in your logs and relax CORS restrictions, use it sparingly. It's best to use for local development only! @@ -962,12 +962,12 @@ This might reveal sensitive information in your logs and relax CORS restrictions When you enable [Data Validation](#data-validation), we use a combination of Pydantic Models and [OpenAPI](https://www.openapis.org/){target="_blank" rel="nofollow"} type annotations to add constraints to your API's parameters. ???+ warning "OpenAPI schema version depends on the installed version of Pydantic" -Pydantic v1 generates [valid OpenAPI 3.0.3 schemas](https://docs.pydantic.dev/1.10/usage/schema/){target="_blank" rel="nofollow"}, and Pydantic v2 generates [valid OpenAPI 3.1.0 schemas](https://docs.pydantic.dev/latest/why/#json-schema){target="_blank" rel="nofollow"}. + Pydantic v1 generates [valid OpenAPI 3.0.3 schemas](https://docs.pydantic.dev/1.10/usage/schema/){target="_blank" rel="nofollow"}, and Pydantic v2 generates [valid OpenAPI 3.1.0 schemas](https://docs.pydantic.dev/latest/why/#json-schema){target="_blank" rel="nofollow"}. In OpenAPI documentation tools like [SwaggerUI](#enabling-swaggerui), these annotations become readable descriptions, offering a self-explanatory API interface. This reduces boilerplate code while improving functionality and enabling auto-documentation. ???+ note -We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE). + We don't have support for files, form data, and header parameters at the moment. If you're interested in this, please [open an issue](https://github.com/aws-powertools/powertools-lambda-python/issues/new?assignees=&labels=feature-request%2Ctriage&projects=&template=feature_request.yml&title=Feature+request%3A+TITLE). #### Customizing OpenAPI parameters @@ -986,7 +986,7 @@ To implement these customizations, include extra parameters when defining your r #### Customizing Swagger UI ???+note "Customizing the Swagger metadata" -The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](#customizing-openapi-metadata). + The `enable_swagger` method accepts the same metadata as described at [Customizing OpenAPI metadata](#customizing-openapi-metadata). The Swagger UI appears by default at the `/swagger` path, but you can customize this to serve the documentation from another path and specify the source for Swagger UI assets. @@ -1000,7 +1000,7 @@ Below is an example configuration for serving Swagger UI from a custom path or C === "customizing_swagger_middlewares.py" -A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. + A Middleware can handle tasks such as adding security headers, user authentication, or other request processing for serving the Swagger UI. ```python hl_lines="7 13-18 21" --8<-- "examples/event_handler_rest/src/customizing_swagger_middlewares.py" @@ -1097,7 +1097,7 @@ You can use specialized router classes according to the type of event that you a You can use `append_context` when you want to share data between your App and Router instances. Any data you share will be available via the `context` dictionary available in your App or Router context. ???+ info "We always clear data available in `context` after each invocation." -This can be useful for middlewares injecting contextual information before a request is processed. + This can be useful for middlewares injecting contextual information before a request is processed. === "split_route_append_context.py" @@ -1151,7 +1151,7 @@ Event Handler naturally leads to a single Lambda function handling multiple rout Both single (monolithic) and multiple functions (micro) offer different set of trade-offs worth knowing. ???+ tip -TL;DR. Start with a monolithic function, add additional functions with new handlers, and possibly break into micro functions if necessary. + TL;DR. Start with a monolithic function, add additional functions with new handlers, and possibly break into micro functions if necessary. #### Monolithic function @@ -1189,7 +1189,7 @@ A micro function means that your final code artifact will be different to each f * **Upfront investment**. You need custom build tooling to bundle assets, including [C bindings for runtime compatibility](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html){target="_blank"}. Operations become more elaborate — you need to standardize tracing labels/annotations, structured logging, and metrics to pinpoint root causes. * Engineering discipline is necessary for both approaches. Micro-function approach however requires further attention in consistency as the number of functions grow, just like any distributed system. * **Harder to share code**. Shared code must be carefully evaluated to avoid unnecessary deployments when that changes. Equally, if shared code isn't a library, - your development, building, deployment tooling need to accommodate the distinct layout. +your development, building, deployment tooling need to accommodate the distinct layout. * **Slower safe deployments**. Safely deploying multiple functions require coordination — AWS CodeDeploy deploys and verifies each function sequentially. This increases lead time substantially (minutes to hours) depending on the deployment strategy you choose. You can mitigate it by selectively enabling it in prod-like environments only, and where the risk profile is applicable. * Automated testing, operational and security reviews are essential to stability in either approaches. @@ -1203,26 +1203,23 @@ Consider a simplified micro function structured REST API that has two routes: Each endpoint will be it's own Lambda function that is configured as a [Lambda integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html){target="_blank"}. This allows you to set different configurations for each lambda (memory size, layers, etc.). === "`/users` Endpoint" - -```python ---8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" -``` + ```python + --8<-- "examples/event_handler_rest/src/micro_function_all_users_route.py" + ``` === "`/users/` Endpoint" - -```python ---8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" -``` + ```python + --8<-- "examples/event_handler_rest/src/micro_function_user_by_id_route.py" + ``` === "Micro Function Example SAM Template" - -```yaml ---8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" -``` + ```yaml + --8<-- "examples/event_handler_rest/sam/micro_function_template.yaml" + ``` ???+ note -You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="_blank" rel="nofollow"}. + You can see some of the downsides in this example such as some code reuse. If set up with proper build tooling, the `User` class could be shared across functions. This could be accomplished by packaging shared code as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html){target="_blank"} or [Pants](https://www.pantsbuild.org/docs/awslambda-python){target="_blank" rel="nofollow"}. ## Testing your code