Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement lambda handler for deployments in AWS lambda #16

Merged
merged 4 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker images
- name: Build and push the base Docker image
uses: docker/[email protected]
with:
context: .
Expand All @@ -28,3 +28,12 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ghcr.io/khaosresearch/eidos:latest,ghcr.io/khaosresearch/eidos:${{ github.event.release.tag_name }}
- name: Build and push the lambda Docker image
uses: docker/[email protected]
with:
context: .
file: ./Dockerfile.lambda
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ghcr.io/khaosresearch/eidos-lambda:latest,ghcr.io/khaosresearch/eidos-lambda:${{ github.event.release.tag_name }}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ENV EIDOS_ENV production

ENV EIDOS_FUNCTIONS_FOLDER /functions

RUN pip install "."
RUN pip install --no-cache-dir "."

EXPOSE 80

Expand Down
22 changes: 22 additions & 0 deletions Dockerfile.lambda
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM amazon/aws-lambda-python:3.10

# When sending request from the UI it crashes as described in this issue, this is a patch for it
# https://github.com/aws/aws-lambda-runtime-interface-emulator/issues/97#issuecomment-1707171018
RUN curl -Lo /usr/local/bin/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/download/v1.10/aws-lambda-rie \
&& chmod +x /usr/local/bin/aws-lambda-rie


COPY README.md /code/README.md
COPY src /code/src
COPY pyproject.toml /code/pyproject.toml
COPY ./functions /functions

ENV EIDOS_ENV production

ENV EIDOS_FUNCTIONS_FOLDER=/functions

RUN pip install --no-cache-dir "/code"

EXPOSE 8080

CMD [ "eidos.lambda.lambda_handler" ]
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# eidos: Validation and execution of AI functions

[![Linter: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FKhaosResearch%2Feidos%2Fmaster%2Fpyproject.toml)
![GitHub Release](https://img.shields.io/github/v/release/KhaosResearch/eidos)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/KhaosResearch/eidos/test.yaml?label=CI)

_eidos_ is an API for validating and executing AI functions. It aims to be a generic API to serve as a common interface to allow execution of functions by LLMs.

![Usage diagram of eidos](assets/eidos.png)
Expand All @@ -20,7 +25,9 @@ Or directly from GitHub:
python -m pip install "eidos @ git+ssh://[email protected]/KhaosResearch/eidos.git"
```

## Run
## Deployment

* Development

Run the API with the following command:

Expand All @@ -30,11 +37,13 @@ uvicorn eidos.api:app --host 0.0.0.0 --port 8090 --reload

You can override the default configuration by setting [environment variables](src/eidos/settings.py).

* Docker

Alternatively, you can use the provided [Dockerfile](Dockerfile) to build a Docker image and run the API in a container:

```bash
docker build -t eidos-server:latest .
docker run -v $(pwd)/functions:/functions -p 8090:80 eidos-server:latest
docker run --rm -v $(pwd)/functions:/functions -p 8090:80 eidos-server:latest
```

Example:
Expand All @@ -43,7 +52,28 @@ Example:
curl -X POST -H "Content-Type: application/json" -d '{"who": "me"}' http://localhost:8090/api/v1/execution/salute
```

To deploy the container in Kubernetes, a reference deployment is available and documented at [deployments](deployments/).
* Kubernetes

To deploy the container in Kubernetes, a reference deployment is available and documented at [manifests](manifests/).

* Serverless in AWS
Another docker image to deploy serverless in AWS Lambda is provided in [Dockerfile.lambda](Dockerfile.lambda). The image is based on the official AWS Lambda Python 3.11 image. For extending this image the process is the same as the main image.

```bash
docker build -t eidos-lambda -f Dockerfile.lambda .
```

Run the container locally with the following command or deploy in AWS Lambda as a docker container image:

```bash
docker run --rm -p 8091:8080 eidos-lambda
```

Invoke the function for local testing with sample query

```bash
curl -XPOST "http://localhost:8091/2015-03-31/functions/function/invocations" -d '{"command": "EXECUTE", "parameters": {"function": "salute", "args": {"who": "me, I am executing serverless"}}}'
```

## Testing

Expand Down
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions src/eidos/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def execute(function_name: str, arguments: dict | None) -> dict[str, Any]:
try:
function_definition = json_load(file_path)
except FileNotFoundError:
log.error("Error: function module not found.", function=function_name, file_path=file_path)
raise FileNotFoundError("Error: function module not found.")

# Validate input arguments against the function's schema.
Expand Down
93 changes: 93 additions & 0 deletions src/eidos/lambda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from enum import Enum
from typing import Any

import structlog

from eidos.execute import (
execute,
get_function_schema,
get_openai_function_definition,
list_functions_names,
list_functions_openai,
)

log = structlog.get_logger("eidos.lambda")


class ValidationCommands(Enum):
"""Enum to hold the different validation commands from eidos."""

LIST = "LIST"
LIST_NAMES = "LIST_NAMES"
GET_DEFINITION = "GET_DEFINITION"
GET_SCHEMA = "GET_SCHEMA"
EXECUTE = "EXECUTE"

def __str__(self):
return self.value

def __repr__(self):
return self.value


def lambda_handler(event: dict[str, Any], context: dict[str, Any]):
try:
log.info("Processing event", command=event["command"])
command = event["command"]
except KeyError:
raise ValueError(
f"There is a required field 'command' with the function to execute. "
f"Possible values: {ValidationCommands.__members__}"
)

try:
validation_function = ValidationCommands[command]
except KeyError:
raise ValueError(
f"Unknown function: {event['command']}. "
f"Possible values: {ValidationCommands.__members__}"
)

match validation_function:
case ValidationCommands.LIST:
return list_functions_openai()
case ValidationCommands.LIST_NAMES:
return list_functions_names()
case ValidationCommands.GET_DEFINITION:
if "function" in event.get("parameters", {}):
function = event["parameters"]["function"]
return get_openai_function_definition(function)
else:
return {
"statusCode": 400,
"body": "Missing function. Provide as parameters.function",
}
case ValidationCommands.GET_SCHEMA:
if "function" in event.get("parameters", {}):
function = event["parameters"]["function"]
return get_function_schema(function)
else:
return {
"statusCode": 400,
"body": "Missing function. Provide as parameters.function",
}
case ValidationCommands.EXECUTE:
if "function" in event.get("parameters", {}):
function = event["parameters"]["function"]
else:
return {
"statusCode": 400,
"body": "Missing function. Provide as parameters.function",
}

args = event["parameters"].get("args", {}) # Default to empty parameters
log.info("Executing function ", function=function, arguments=args)

result = execute(function, args)

return result
case _:
raise ValueError(
f"Unknown function: {event['command']}. "
f"Possible values: {ValidationCommands.__members__}"
)