DEV Community

moritalous | Kazuaki Morita for AWS Community Builders

Posted on • Originally published at qiita.com

Rapid Development of Agents for Amazon Bedrock Using AWS Lambda Web Adapter

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

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

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

{
    "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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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
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

    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:

  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.

    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
    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.

    from bedrock_agent.middleware import BedrockAgentMiddleware
    
    app.add_middleware(BedrockAgentMiddleware)
    
  3. Running FastAPI Locally

    Install library

    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.

    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

    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)

    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.

    sam build
    
    sam deploy --guided
    
  6. Local Testing

    You can also test locally with the sam local invoke command.

    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.

Top comments (0)