Rapid Development of Agents for Amazon Bedrock Using AWS Lambda Web Adapter
Tags: bedrock, lambda, fastapi, serverless
Published: 2024-03-20 01:09:37

Hello everyone. This is my first post. I work as a system integrator in Japan. My work involves system architecture using services like Bedrock and serverless. I plan to continue posting here regularly, so please stay tuned. Thank you.

---

> 💡Original Japanese Version is here.
> https://qiita.com/moritalous/items/f828c5d7d2d116884f9a

There is a feature called Agents for Amazon Bedrock that allows you to build agents on Amazon Bedrock. This feature is built on Lambda, but requires the following:

* A Lambda function containing the business logic for the actions the agent will execute
* An OpenAPI schema containing the API description, structure, and parameters

Additionally, since the event JSON is in a dedicated format, you need to be mindful of what kind of events will arrive while developing.

AWS's AWS Lambda Web Adapter now supports Agents for Amazon Bedrock, so we'll introduce how to use it.


## What is AWS Lambda Web Adapter?

It's a tool for running web applications on AWS Lambda.

With AWS Lambda Web Adaptor, developers can build web apps (HTTP APIs) using their familiar frameworks (Express.js, Next.js, Flask, SpringBoot, ASP.NET, Laravel, etc., anything that uses HTTP 1.1/1.0) and run them on AWS Lambda. You can also run the same Docker image on AWS Lambda, Amazon EC2, AWS Fargate, and your local computer.

https://github.com/awslabs/aws-lambda-web-adapter

Here's an image showing the configuration (from the official site):

![an image showing the configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnbr401qil6uqdtq948o.png)

Until now, only HTTP events from API Gateway or ALB were supported, but **Non-HTTP Events** have now been supported! 🎉🎉🎉

Here's an image for Non-HTTP events:

![Non-HTTP Events](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqn3dyyi8vrb73hwssy4.png)

Non-HTTP events such as SNS or SQS can now be received. The event type is automatically identified, with HTTP events forwarded to their respective path, and Non-HTTP events forwarded to `/events` (which can be changed).

For Agents for Amazon Bedrock, the following JSON will be sent to `/events`. If the JSON received at `/events` can be parsed and routed to the desired path, it will work as intended.

Reference: [Lambda input event from Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-input)

```json
{
    "messageVersion": "1.0",
    "agent": {
        "name": "string",
        "id": "string",
        "alias": "string",
        "version": "string"
    },
    "inputText": "string",
    "sessionId": "string",
    "actionGroup": "string",
    "apiPath": "string",
    "httpMethod": "string",
    "parameters": [
        {
            "name": "string",
            "type": "string",
            "value": "string"
        },
    ...
    ],
    "requestBody": {
        "content": {
            "<content_type>": {
                "properties": [
                   {
                       "name": "string",
                       "type": "string",
                       "value": "string"
                    },
                            ...
                ]
            }
        }
    },
    "sessionAttributes": {
        "string": "string",
    },
    "promptSessionAttributes": {
        "string": "string"
    }
}
```

The routing process is common, so I've **published a library** using FastAPI's Middleware mechanism.

With this library, you can develop APIs with FastAPI without worrying about `/events`.

https://github.com/moritalous/lwa-fastapi-middleware-bedrock-agent

---

> 💡Note
> I requested support for Agents for Amazon Bedrock for Lambda Web Adapter, which led to its implementation. I was also asked to create a sample and a general library.
> [I hope support Agents for Amazon Bedrock #317](https://github.com/awslabs/aws-lambda-web-adapter/issues/317)
> It was a very valuable experience.

---

## Sample App Explanation

Here's a sample app using this configuration:

https://github.com/moritalous/lwa-fastapi-middleware-bedrock-agent/tree/main/example/bedrock-agent-fastapi

* Directory structure

    ```shell
    bedrock-agent-fastapi
    ├── README.md
    ├── app
    │   ├── Dockerfile
    │   ├── __init__.py
    │   ├── main.py
    │   └── requirements.txt
    ├── events
    │   ├── s3_bucket_count.json
    │   ├── s3_object.json
    │   └── s3_object_count.json
    └── template.yaml
    ```

* Libraries used

    Main Python application libraries used:
    * [FastAPI](https://pypi.org/project/fastapi/)
    * [lwa-fastapi-middleware-bedrock-agent](https://pypi.org/project/lwa-fastapi-middleware-bedrock-agent/) (FastAPI Middleware for "Agents for Amazon Bedrock")

1. Integrating Lambda Web Adapter

    The deployment type for Lambda is containers. In the Dockerfile's second line, we copy the contents of Lambda Web Adapter. This single line is the minimum required to use Lambda Web Adapter.
    
    ```docker
    FROM public.ecr.aws/docker/library/python:3.12.0-slim
    COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.1 /lambda-adapter /opt/extensions/lambda-adapter
    ENV PORT=8000 AWS_LWA_READINESS_CHECK_PROTOCOL=tcp 
    WORKDIR /var/task
    COPY requirements.txt ./
    RUN python -m pip install -r requirements.txt
    COPY *.py ./
    CMD exec uvicorn --port=$PORT main:app
    ```

2. FastAPI API Development

    The program code is only in `main.py`. Except for specifying the Middleware, it's the same as a general FastAPI application.

    This sample includes the following APIs:
    
    | Path | Method | Description |
    | --- | --- | --- |
    | /s3_bucket_count | GET | Returns the number of S3 buckets |
    | /s3_object_count | GET | Returns the number of objects in the specified S3 bucket |
    | /s3_object | GET | Returns the last modified date of the specified S3 bucket and object key |
    
    ```python
    import datetime
    import logging
    
    import boto3
    from fastapi import FastAPI, Query
    from pydantic import BaseModel, Field
    
    from bedrock_agent.middleware import BedrockAgentMiddleware
    
    app = FastAPI(
        description="This agent allows you to query the S3 information in your AWS account.",
    )
    app.openapi_version = "3.0.2"
    app.add_middleware(BedrockAgentMiddleware)
    
    middleware_logger = logging.getLogger("bedrockagent-middleware")
    middleware_logger.setLevel(level=logging.DEBUG)
    
    s3 = boto3.resource("s3")
    
    
    class S3BucketCountResponse(BaseModel):
        count: int = Field(description="the number of S3 buckets")
    
    
    @app.get("/s3_bucket_count")
    async def get_s3_bucket_count() -> S3BucketCountResponse:
        """
        This method returns the number of S3 buckets in your AWS account.
    
        Return:
            S3BucketCountResponse: A json object containing the number of S3 buckets in your AWS account.
        """
    
        count = len(list(s3.buckets.all()))
    
        return S3BucketCountResponse(count=count)
    
    
    class S3ObjectCountResponse(BaseModel):
        count: int = Field(description="the number of S3 objects")
    
    
    @app.get("/s3_object_count")
    async def get_s3_object_count(
        bucket_name: str = Query(description="Bucket name"),
    ) -> S3ObjectCountResponse:
        """
        This method returns the number of S3 objects in your specified bucket.
    
        Return:
            S3ObjectCountResponse: A json object containing the number of S3 objects in your specified bucket.
        """
    
        count = len(list(s3.Bucket(bucket_name).objects.all()))
        return S3ObjectCountResponse(count=count)
    
    
    class S3GetObjectRequest(BaseModel):
        bucket_name: str = Field(description="Bucket name")
        object_key: str = Field(description="Object key")
    
    
    class S3GetObjectResponse(BaseModel):
        last_modified: datetime.datetime = Field(description="the last modified date")
    
    
    @app.post("/s3_object")
    async def get_s3_object(request: S3GetObjectRequest):
        """
        This method returns the last modified date of S3 object.
    
        Return:
            S3GetObjectResponse: A json object containing the last modified date of S3 objects.
        """
    
        object = s3.Object(request.bucket_name, request.object_key)
        last_modified = object.get()["LastModified"]
        return S3GetObjectResponse(last_modified=last_modified)
    ```
    
    The Middleware specification is in this part. This single specification routes events received at `/events` appropriately.
    
    ```python
    from bedrock_agent.middleware import BedrockAgentMiddleware
    
    app.add_middleware(BedrockAgentMiddleware)
    ```

3. Running FastAPI Locally

    Install library
    
    ```shell
    pip install fastapi uvicorn pydantic==1.10.13 lwa-fastapi-middleware-bedrock-agent boto3
    ```
    
    Since it's a FastAPI app, you can run it as is.
    
    ```shell
    uvicorn main:app --reload
    ```

    (console log)
    
    ```
    INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    INFO:     Started reloader process [4999] using StatReload
    INFO:     Started server process [5001]
    INFO:     Waiting for application startup.
    INFO:     pass_through_path: /events
    INFO:     Application startup complete.
    ```

    Accessing `http://127.0.0.1:8000/docs` will display the OpenAPI documentation via Swagger UI.

    ![OpenAPI documentation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oxklmdozvjatz7d1qxo.png)

    You can proceed with agent development as FastAPI APIs.

4. Generating OpenAPI Schema

    The OpenAPI schema can be output using FastAPI's functionality. This output can be used as-is for Agents for Amazon Bedrock.
    
    (Run in the app directory)
    
    ```shell
    python -c "import main;import json; print(json.dumps(main.app.openapi()))" > openapi.json
    ```

5. Build and Deploy

    Since it's a SAM project, you can build and deploy using standard commands.
    
    ```shell
    sam build
    
    sam deploy --guided
    ```

6. Local Testing

    You can also test locally with the `sam local invoke` command.
    
    ```shell
    sam local invoke --event events/s3_bucket_count.json
    ```

7. Creating Agents for Amazon Bedrock

    With the Lambda and OpenAPI schema ready, you can create the agent using the management console.


## Summary

Thank you for reading until the end. Since you can focus solely on API definition for agent development, please make use of it.