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

feat: add new component for serving python applications #35

Merged
merged 24 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
74c23a6
feat(resources): add new component for serving python applications
kkiani Oct 8, 2024
0732c38
docs(resources): reformat the docstrings
kkiani Oct 8, 2024
784b0f4
refactor: change lambda_component name to serverless_python_component
kkiani Oct 8, 2024
c6d30fc
fix(py-serverless): unable to deploy with dependency bigger than 250mb
kkiani Oct 17, 2024
a806acd
chore(example): add example for python serverless component
kkiani Oct 17, 2024
8d9368e
feat(llm): make the llm controller support hosting the scripts as well
kkiani Oct 24, 2024
f988bd9
test: disabling the api key
kkiani Oct 25, 2024
d8885cf
fix(llm): force set the region for the boto3 client
kkiani Oct 28, 2024
6bbbb92
refactor: seprating build time and runtime dependencies
kkiani Oct 28, 2024
7d7b274
fix(core): seprating run/build time dependencies
kkiani Oct 28, 2024
d667a7d
refactor(llm): revert back python serverless to lambda layer
kkiani Oct 28, 2024
ae33f82
feat(llm): remove openai chat method for a native method
kkiani Oct 28, 2024
8726352
fix(llm): endpoint parameter store not being created
kkiani Oct 29, 2024
24bf005
refactor(llm): using code mode instead of docker mode with lambda
kkiani Oct 29, 2024
13825c2
feat(core, llm): add supporting tags
kkiani Oct 30, 2024
bb0620c
feat(core, llm): cost managements supports within controllers
kkiani Oct 30, 2024
aeffe26
fix(llm): cannot access the api key
kkiani Oct 30, 2024
c091235
fix(llm): lambda function does not have access to ssm or secrets
kkiani Oct 30, 2024
f1bcf80
refactor(serverless): uploading dependencies directly to lambda
kkiani Oct 30, 2024
8325e4a
fix(serverless): pushing empty dependency packages to lambda layer
kkiani Oct 30, 2024
058c83c
fix(llm): cannon recreate secrets after destroying
kkiani Oct 31, 2024
7642e2f
fix(llm): secret key connot be found
kkiani Oct 31, 2024
b9c3fc9
chore: update the packages lock file
kkiani Oct 31, 2024
576f020
fix(test): making the test pipeline working
kkiani Oct 31, 2024
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
8 changes: 8 additions & 0 deletions examples/aws-serverless-python-resources/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM public.ecr.aws/lambda/python:3.11

# Copy function code
COPY . ${LAMBDA_TASK_ROOT}
CMD pip install -r requirements.txt

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "__main__.main" ]
7 changes: 7 additions & 0 deletions examples/aws-serverless-python-resources/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: aws-serverless-python
runtime:
name: python
options:
toolchain: pip
virtualenv: venv
description: A minimal Python on Lambda function Pulumi program
19 changes: 19 additions & 0 deletions examples/aws-serverless-python-resources/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pulumi_aws as aws

from damavand.cloud.aws.resources.serverless_python_component import (
AwsServerlessPythonComponent,
AwsServerlessPythonComponentArgs,
)


def main() -> None:
AwsServerlessPythonComponent(
name="python-serverless-example",
args=AwsServerlessPythonComponentArgs(
python_version=aws.lambda_.Runtime.PYTHON3D11,
),
)


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions examples/aws-serverless-python-resources/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-e ../../../damavand
pulumi
1,788 changes: 1,360 additions & 428 deletions pdm.lock

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,7 @@ authors = [
{name = "Kiarash Kiani", email = "kiarash@datachef.co"},
]
dependencies = [
"rich>=13.7.1",
"boto3>=1.34.147",
"psutil>=6.0.0",
"flask>=3.0.3",
"pulumi>=3.127.0",
"pulumi-aws>=6.47.0",
"pulumi-azure-native>=2.51.0",
"pulumi-random>=4.16.3",
"sparkle @ git+https://github.com/DataChefHQ/sparkle.git@v0.6.1",
"damavand @ file:///${PROJECT_ROOT}/",
]
requires-python = ">=3.11.0"
readme = "README.md"
Expand All @@ -23,9 +14,21 @@ dynamic = ["version"]


[project.optional-dependencies]
build = [
"pulumi-command>=1.0.1",
"pulumi>=3.127.0",
"pulumi-aws>=6.47.0",
"pulumi-azure-native>=2.51.0",
"pulumi-random>=4.16.3",
"pulumi-awsx>=2.16.1",
]
llm = [
"sagemaker>=2.232.0",
]
sparkle = [
"sparkle @ git+https://github.com/DataChefHQ/sparkle.git@v0.6.1",
"pyspark==3.3.2",
]
[tool.pdm]
distribution = false
path = "src/damavand/__init__.py"
Expand All @@ -44,7 +47,6 @@ dev = [
"pyright>=1.1.374",
"moto>=5.0.11",
"pip>=24.2",
"pyspark==3.3.2",
]
[tool.commitizen]
version = "1.0.0"
Expand Down
78 changes: 69 additions & 9 deletions src/damavand/base/controllers/base_controller.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataclasses import dataclass
import re
import logging
from functools import cache
from pulumi import Resource as PulumiResource
import pulumi

from damavand import utils
from damavand.environment import Environment
Expand Down Expand Up @@ -36,31 +36,90 @@ def wrapper(self, *args, **kwargs):
return wrapper


@dataclass
class CostManagement:
"""Cost management configuration for the application.

Parameters
----------
notification_subscribers : list[str]
List of email addresses to notify when the cost exceeds the limit.
monthly_limit_in_dollars : int
The monthly cost limit in dollars.
"""

notification_subscribers: list[str]
monthly_limit_in_dollars: int


class ApplicationController(object):
def __init__(
self,
name: str,
cost: CostManagement,
tags: dict[str, str] = {},
**kwargs,
) -> None:
self.name = name
self.tags = tags
self._userdefined_tags = tags
self._cost = cost
self.extra_args = kwargs
self._pulumi_object = None

@property
def name(self) -> str:
"""Return the name of the controller."""

return self._name

@name.setter
def name(self, value: str) -> None:
"""Set the name of the controller."""

pattern = re.compile(r"^[a-z0-9-]+$")

if not pattern.match(value):
raise ValueError(
f"Invalid name: `{value}`. Name must be lowercase letters, numbers, and hyphens."
)

self._name = value

@buildtime
@cache
def build_config(self) -> pulumi.Config:
return pulumi.Config()
def resource(self) -> "PulumiResource": # type: ignore # noqa
"""A lazy property that provision the resource if it is not provisioned yet and return the pulumi object."""

raise NotImplementedError()

@buildtime
@cache
def resource(self) -> PulumiResource:
"""A lazy property that provision the resource if it is not provisioned yet and return the pulumi object."""
def cost_controls(self) -> "PulumiResource": # type: ignore # noqa
"""Apply cost controls to the resources."""

raise NotImplementedError()

@property
def userdefined_tags(self) -> dict[str, str]:
"""Return the user-defined tags."""

return self._userdefined_tags

@property
@buildtime
def default_tags(self) -> dict[str, str]:
"""Return the default tags for the resources."""

return {
"application": self.name,
"tool": "datachef:damavand",
}

@property
def all_tags(self) -> dict[str, str]:
"""Return all tags for the resource."""

return {**self.default_tags, **self.userdefined_tags}

@property
def environment(self) -> Environment:
"""Return the environment that controller is being executed in."""
Expand All @@ -77,6 +136,7 @@ def is_runtime_execution(self) -> bool:
return not utils.is_building()

def provision(self) -> None:
"""Provision the resource in not provisioned yet."""
"""Provision all the resources and apply cost controls."""

_ = self.resource()
_ = self.cost_controls()
55 changes: 38 additions & 17 deletions src/damavand/base/controllers/llm.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from functools import cache
import requests
import logging
from typing import Optional
from functools import cache
from typing import List, Optional

from damavand.base.controllers import ApplicationController
from damavand.base.controllers.base_controller import runtime
from damavand.base.controllers.base_controller import CostManagement, runtime
from damavand.errors import RuntimeException


Expand Down Expand Up @@ -41,12 +42,17 @@ class LlmController(ApplicationController):
def __init__(
self,
name,
cost: CostManagement,
model: Optional[str] = None,
python_version: str = "python3.11",
python_runtime_requirements_file: str = "../requirements-run.txt",
tags: dict[str, str] = {},
**kwargs,
) -> None:
ApplicationController.__init__(self, name, tags, **kwargs)
ApplicationController.__init__(self, name, cost, tags, **kwargs)
self._model_name = model
self._python_version = python_version
self._python_runtime_requirements_file = python_runtime_requirements_file

@property
def model_id(self) -> str:
Expand Down Expand Up @@ -78,20 +84,35 @@ def chat_completions_url(self) -> str:

return f"{self.base_url}/chat/completions"

@property
@runtime
@cache
def client(self) -> "openai.OpenAI": # type: ignore # noqa
"""Return an OpenAI client as an standared interface for interacting with deployed LLM APIs."""
def create_chat(
self,
messages: List[dict],
parameters: dict = {"max_new_tokens": 400},
should_stream: bool = False,
) -> dict:
"""Create a chat completion."""

headers = {
"Content-Type": "application/json",
"x-api-key": self.default_api_key,
}

json_data = {
"messages": messages,
"parameters": parameters,
"stream": should_stream,
}

response = requests.post(
self.chat_completions_url,
headers=headers,
json=json_data,
)

try:
import openai # type: ignore # noqa
except ImportError:
if response.status_code != 200:
raise RuntimeException(
"Failed to import OpenAI library. Damavand provide this library as an optional dependency. Try to install it using `pip install damavand[openai]` or directly install it using pip or your dependency manager."
f"Failed to create chat completion. Response: {response.json()}"
)

return openai.OpenAI(
api_key=self.default_api_key,
base_url=f"{self.base_url}",
)
else:
return response.json()
4 changes: 3 additions & 1 deletion src/damavand/base/controllers/object_storage.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from typing import Iterable

from damavand.base.controllers import ApplicationController
from damavand.base.controllers.base_controller import CostManagement


class ObjectStorageController(ApplicationController):
def __init__(
self,
name,
cost: CostManagement,
tags: dict[str, str] = {},
**kwargs,
) -> None:
super().__init__(name, tags, **kwargs)
super().__init__(name, cost, tags, **kwargs)

def read(self, path: str) -> bytes:
"""Read an object from the storage."""
Expand Down
5 changes: 3 additions & 2 deletions src/damavand/base/controllers/spark.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging

from damavand.base.controllers import ApplicationController
from damavand.base.controllers.base_controller import runtime
from damavand.base.controllers.base_controller import CostManagement, runtime

from sparkle.application import Sparkle

Expand Down Expand Up @@ -38,11 +38,12 @@ class SparkController(ApplicationController):
def __init__(
self,
name,
cost: CostManagement,
applications: list[Sparkle],
tags: dict[str, str] = {},
**kwargs,
) -> None:
ApplicationController.__init__(self, name, tags, **kwargs)
ApplicationController.__init__(self, name, cost, tags, **kwargs)
self.applications: list[Sparkle] = applications

def application_with_id(self, app_id: str) -> Sparkle:
Expand Down
Loading
Loading