Skip to content

Commit

Permalink
Merge pull request #35 from DataChefHQ/feature/prompt-engineering-res…
Browse files Browse the repository at this point in the history
…ource

feat: add new component for serving python applications
  • Loading branch information
kkiani authored Oct 31, 2024
2 parents 2259cfa + 576f020 commit d51c628
Show file tree
Hide file tree
Showing 24 changed files with 2,580 additions and 525 deletions.
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 = "[email protected]"},
]
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/[email protected]",
"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/[email protected]",
"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

0 comments on commit d51c628

Please sign in to comment.