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

Fix API key and root path configurations #17

Merged
merged 9 commits into from
Jun 18, 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
6 changes: 3 additions & 3 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ROOT_PATH=/eidos/
API_KEY=API_KEY_GOES_HERE
FUNCTIONS_FOLDER=functions
EIDOS_ROOT_PATH=/eidos
EIDOS_API_KEY=API_KEY_GOES_HERE
EIDOS_FUNCTIONS_FOLDER=functions
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ COPY ./src /code/src
COPY ./pyproject.toml /code/pyproject.toml
COPY ./functions /functions

ENV FUNCTIONS_FOLDER /functions
ENV EIDOS_ENV production

ENV EIDOS_FUNCTIONS_FOLDER /functions

RUN pip install "."

EXPOSE 80

CMD ["uvicorn", "eidos.api:app", "--host", "0.0.0.0", "--port", "80"]
CMD ["uvicorn", "eidos.api:app", "--host", "0.0.0.0", "--port", "80"]
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
lint:
@python -m ruff check --extend-select I .

lint-fix:
@python -m ruff check --extend-select I --fix .

format:
@python -m ruff format .

tests:
@python -m pytest tests/
@python -m pytest tests/
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# eidos: Validation and execution of AI functions

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.
_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 @@ -11,13 +11,13 @@ From source:
```bash
git clone [email protected]:KhaosResearch/eidos.git
cd eidos
pip install -e .
python -m pip install -e .
```

Or directly from GitHub:

```bash
pip install "eidos @ git+ssh://[email protected]/KhaosResearch/eidos.git"
python -m pip install "eidos @ git+ssh://[email protected]/KhaosResearch/eidos.git"
```

## Run
Expand All @@ -33,8 +33,8 @@ You can override the default configuration by setting [environment variables](sr
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 .
docker run -v $(pwd)/functions:/functions -p 8090:80 eidos
docker build -t eidos-server:latest .
docker run -v $(pwd)/functions:/functions -p 8090:80 eidos-server:latest
```

Example:
Expand All @@ -51,4 +51,4 @@ To deploy the container in Kubernetes, a reference deployment is available and d

```bash
pytest tests/
```
```
9 changes: 5 additions & 4 deletions deployments/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Kubernetes deployment of eidos
This folder contains all required files and configuration to have a working deployment of `eidos` in Kubernetes.
# Kubernetes Manifests

The configuration provided creates a deployment with 3 replicas using the base `eidos` instance (to deploy a instance with custom functions just change the docker image) behind a service for load balancing between the different pods.
This directory includes all necessary files and configurations to deploy _eidos_ in a Kubernetes environment.

The provided configuration sets up a deployment with 3 replicas of the base _eidos_ instance. To deploy an instance with custom functions, simply modify the base Docker image.

# Disclaimer

The provided Kubernetes manifests are for reference only. You must review and adjust these manifests to suit your specific cluster and configuration. Ensure that resource requests and limits, image names, environment variables, and other configurations are appropriate for your setup.
These manifests are intended for reference only. You must review and modify these manifests to fit your specific cluster and configuration requirements. Ensure that resource requests and limits, image names, environment variables, and other configurations are appropriately set for your environment.
66 changes: 36 additions & 30 deletions deployments/eidos-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,39 @@ spec:
namespace: eidos-namespace
spec:
containers:
- name: eidos
image: ghcr.io/khaosresearch/eidos:v1.0.0 # This image must be changed to deploy a custom functions
imagePullPolicy: Always
ports:
- containerPort: 80
resources:
limits:
memory: "1Gi"
cpu: "1"
requests:
memory: "256Mi"
cpu: "500m"
env:
- name: API_KEY
valueFrom:
configMapKeyRef:
name: eidos-configmap
key: EIDOS_API_KEY
- name: ROOT_PATH
valueFrom:
configMapKeyRef:
name: eidos-configmap
key: EIDOS_ROOT_PATH
- name: FUNCTIONS_FOLDER
valueFrom:
configMapKeyRef:
name: eidos-configmap
key: EIDOS_FUNCTIONS_FOLDER
- name: eidos
image: ghcr.io/khaosresearch/eidos:v1.0.0 # This image must be changed to deploy a custom functions
imagePullPolicy: Always
ports:
- containerPort: 80
resources:
limits:
memory: "1Gi"
cpu: "1"
requests:
memory: "256Mi"
cpu: "500m"
env:
- name: EIDOS_ENV
valueFrom:
configMapKeyRef:
name: eidos-configmap
key: EIDOS_ENV
- name: EIDOS_ROOT_PATH
valueFrom:
configMapKeyRef:
name: eidos-configmap
key: EIDOS_ROOT_PATH
- name: EIDOS_API_KEY
valueFrom:
configMapKeyRef:
name: eidos-configmap
key: EIDOS_API_KEY
- name: EIDOS_FUNCTIONS_FOLDER
valueFrom:
configMapKeyRef:
name: eidos-configmap
key: EIDOS_FUNCTIONS_FOLDER
---
apiVersion: v1
kind: Service
Expand All @@ -73,6 +78,7 @@ metadata:
name: eidos-configmap
namespace: eidos-namespace
data:
EIDOS_API_KEY: "Sample_API_key_for_eidos"
EIDOS_ENV: "production"
EIDOS_ROOT_PATH: "/eidos"
EIDOS_FUNCTIONS_FOLDER: "functions"
EIDOS_API_KEY: "changeme"
EIDOS_FUNCTIONS_FOLDER: "functions"
benhid marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 7 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ build-backend = "hatchling.build"
[project]
name = "eidos"
version = "1.0.0"
authors = [
authors = [
{ name="José F. Aldana Martín", email="[email protected]" },
{ name="Antonio Benítez Hidalgo", email="[email protected]" },
]
description = "Validation and execution of AI functions"
readme = "README.md"
Expand All @@ -31,8 +32,8 @@ dependencies = [
"Bug Tracker" = "https://github.com/KhaosResearch/eidos/issues"

[project.optional-dependencies]
dev = [
"ruff",
"pytest",
"httpx",
]
dev = ["ruff", "pytest", "httpx"]

[tool.pyright]
venv = ".venv"
venvPath = "."
18 changes: 18 additions & 0 deletions scripts/openapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json

from eidos.api import app
from fastapi.openapi.utils import get_openapi

with open("openapi.json", "w") as f:
json.dump(
get_openapi(
title=app.title,
version=app.version,
openapi_version=app.openapi_version,
summary=app.summary,
description=app.description,
routes=app.routes,
servers=app.servers,
),
f,
)
9 changes: 8 additions & 1 deletion src/eidos/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

log = structlog.get_logger("app")

if not settings.api_key and settings.is_production():
log.warning("No API key defined. Please set an API key in the settings.")

if settings.root_path:
log.info(f"Root path set to {settings.root_path}")

openapi_tags = [
{
"name": "health",
Expand All @@ -33,7 +39,8 @@
version=__version__,
root_path=settings.root_path,
openapi_tags=openapi_tags,
docs_url="/api/docs",
docs_url=None if settings.is_production() else "/docs",
redoc_url=None if settings.is_production() else "/redoc",
)

origins = ["*"]
Expand Down
2 changes: 1 addition & 1 deletion src/eidos/routes/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ async def execute_endpoint(
function_name: str, arguments: dict | None = None, _: str = Security(query_scheme)
) -> JSONResponse:
"""Executes an AI function with the given arguments."""
log.info("Running function", function=function_name, arguments=arguments)
try:
log.info("Executing function", function=function_name, arguments=arguments)
data = execute(function_name, arguments)
response, status = (
{
Expand Down
17 changes: 10 additions & 7 deletions src/eidos/secure.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from fastapi import Depends
from fastapi import HTTPException, Security
from fastapi.security import APIKeyQuery
from starlette.status import HTTP_403_FORBIDDEN

from eidos.settings import settings

api_key_query = APIKeyQuery(name="X-API-Key", auto_error=False)

def validate_api_key(
api_key: str = Depends(APIKeyQuery(name=settings.api_key, auto_error=False)),
):
if settings.api_key is None:
return True
return api_key == settings.api_key

def validate_api_key(api_key: str = Security(api_key_query)) -> str | None:
if not api_key:
return None
if api_key == settings.api_key:
return api_key
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Invalid API key")


query_scheme = validate_api_key
32 changes: 27 additions & 5 deletions src/eidos/settings.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from enum import Enum
from pathlib import Path

import structlog
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

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


class Environment(str, Enum):
development = "development"
production = "production"


class Settings(BaseSettings):
model_config = SettingsConfigDict(
# `.env.prod` takes priority over `.env`
env_file=(".env", ".env.prod"),
extra="ignore"
)
env: Environment = Environment.development

# The root path of the API. Useful when deploying the API behind a reverse proxy.
root_path: str = ""
Expand All @@ -23,5 +26,24 @@ class Settings(BaseSettings):
# The path to the folder containing the AI functions.
functions_folder: Path = Path("functions")

model_config = SettingsConfigDict(
env_prefix="eidos_",
# `.env.prod` takes priority over `.env`
env_file=(".env", ".env.prod"),
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)

@field_validator("root_path")
@classmethod
def root_path_must_not_end_with_slash(cls, v: str) -> str:
if v.endswith("/"):
raise ValueError('must not end with "/"')
return v

def is_production(self) -> bool:
return self.env == Environment.production


settings = Settings()
14 changes: 13 additions & 1 deletion tests/test_routes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
from eidos.api import app
from eidos.secure import query_scheme
from fastapi.testclient import TestClient

client = TestClient(app)
client = TestClient(
app,
# Remove the root path (if any).
root_path="",
)


async def override_query_scheme():
return


app.dependency_overrides[query_scheme] = override_query_scheme


def test_healthz():
Expand Down