roboml 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
roboml/__init__.py ADDED
File without changes
@@ -0,0 +1,5 @@
1
+ from .chroma import ChromaDB
2
+
3
+ __all__ = [
4
+ "ChromaDB",
5
+ ]
@@ -0,0 +1,166 @@
1
+ from abc import abstractmethod
2
+ from logging import Logger
3
+ import inspect
4
+
5
+ from pydantic import validate_call, ValidationError
6
+
7
+
8
+ from roboml.models._encoding import EncodingModel
9
+ from roboml.utils import Status, logging
10
+
11
+
12
+ class VectorDBTemplate:
13
+ """
14
+ This class describes a VectorDB template.
15
+ """
16
+
17
+ def __init__(self, *, name: str, **_):
18
+ """__init__.
19
+ :param name:
20
+ :type name: str
21
+ :param _:
22
+ """
23
+ self.name: str = name
24
+ self.encoding_model: EncodingModel
25
+ self.logger: Logger = logging.getLogger(self.name)
26
+ self.status: Status = Status.LOADED
27
+
28
+ def initialize(self, **kwargs) -> None:
29
+ """
30
+ Calls the db initialization function and sets status accordingly
31
+ """
32
+ if self.status == Status.READY:
33
+ return
34
+
35
+ self.status = Status.INITIALIZING
36
+ try:
37
+ validated_init = validate_call(self._initialize)
38
+ validated_init(**kwargs)
39
+ except Exception as e:
40
+ self.logger.error(f"Initialization Error: {e}")
41
+ self.status = Status.INITIALIZATION_ERROR
42
+ raise e
43
+ self.logger.info(f"{self.__class__.__name__} VectorDB initialized")
44
+ self.status = Status.READY
45
+
46
+ def get_status(self):
47
+ """Returns status of the model node"""
48
+ return self.status.name
49
+
50
+ def add(self, **kwargs) -> dict:
51
+ """VectorDB specific add function.
52
+ :param kwargs:
53
+ :rtype: None
54
+ """
55
+ if self.status is not Status.READY:
56
+ self.logger.error("Error: DB method called before initialization")
57
+ raise Exception("DB Method Called called before initialization")
58
+ try:
59
+ data_model = inspect.signature(self._add).parameters["data"].annotation
60
+ data = data_model(**kwargs)
61
+ except ValidationError:
62
+ self.logger.error("Validation Error occured for inference input")
63
+ raise
64
+ return self._add(data)
65
+
66
+ def conditional_add(self, **kwargs) -> dict:
67
+ """VectorDB specific conditional_add function.
68
+ :param kwargs:
69
+ :rtype: None
70
+ """
71
+ if self.status is not Status.READY:
72
+ self.logger.error("Error: DB method called before initialization")
73
+ raise Exception("DB Method Called called before initialization")
74
+ try:
75
+ data_model = (
76
+ inspect.signature(self._conditional_add).parameters["data"].annotation
77
+ )
78
+ data = data_model(**kwargs)
79
+ except ValidationError:
80
+ self.logger.error("Validation Error occured for inference input")
81
+ raise
82
+ return self._conditional_add(data)
83
+
84
+ def metadata_query(self, **kwargs) -> dict:
85
+ """VectorDB specific metadata_query function.
86
+ :param kwargs:
87
+ :rtype: None
88
+ """
89
+ if self.status is not Status.READY:
90
+ self.logger.error("Error: DB method called before initialization")
91
+ raise Exception("DB Method Called called before initialization")
92
+ try:
93
+ data_model = (
94
+ inspect.signature(self._metadata_query).parameters["data"].annotation
95
+ )
96
+ data = data_model(**kwargs)
97
+ except ValidationError:
98
+ self.logger.error("Validation Error occured for inference input")
99
+ raise
100
+ return self._metadata_query(data)
101
+
102
+ def query(self, **kwargs) -> dict:
103
+ """VectorDB specific query function.
104
+ :param kwargs:
105
+ :rtype: None
106
+ """
107
+ if self.status is not Status.READY:
108
+ self.logger.error("Error: DB method called before initialization")
109
+ raise Exception("DB Method Called called before initialization")
110
+ try:
111
+ data_model = inspect.signature(self._query).parameters["data"].annotation
112
+ data = data_model(**kwargs)
113
+ except ValidationError:
114
+ self.logger.error("Validation Error occured for inference input")
115
+ raise
116
+ return self._query(data)
117
+
118
+ @abstractmethod
119
+ def _initialize(self, *_, **__) -> None:
120
+ """VectorDB specific initialize function, to be implemented in derived classes.
121
+ :param kwargs:
122
+ :rtype: None
123
+ """
124
+ raise NotImplementedError(
125
+ "VectorDB specific initialize method needs to be implemented by derived classes"
126
+ )
127
+
128
+ @abstractmethod
129
+ def _add(self, *_, **__) -> dict:
130
+ """VectorDB specific add function, to be implemented in derived classes.
131
+ :param kwargs:
132
+ :rtype: None
133
+ """
134
+ raise NotImplementedError(
135
+ "VectorDB specific add method needs to be implemented by derived classes"
136
+ )
137
+
138
+ @abstractmethod
139
+ def _conditional_add(self, *_, **__) -> dict:
140
+ """VectorDB specific conditional_add function, to be implemented in derived classes.
141
+ :param kwargs:
142
+ :rtype: None
143
+ """
144
+ raise NotImplementedError(
145
+ "VectorDB specific conditional_add method needs to be implemented by derived classes"
146
+ )
147
+
148
+ @abstractmethod
149
+ def _metadata_query(self, *_, **__) -> dict:
150
+ """VectorDB specific metadata_query function, to be implemented in derived classes.
151
+ :param kwargs:
152
+ :rtype: None
153
+ """
154
+ raise NotImplementedError(
155
+ "VectorDB specific metadata_query method needs to be implemented by derived classes"
156
+ )
157
+
158
+ @abstractmethod
159
+ def _query(self, *_, **__) -> dict:
160
+ """VectorDB specific query function, to be implemented in derived classes.
161
+ :param kwargs:
162
+ :rtype: None
163
+ """
164
+ raise NotImplementedError(
165
+ "VectorDB specific query method needs to be implemented by derived classes"
166
+ )
@@ -0,0 +1,189 @@
1
+ from typing import Optional
2
+ from chromadb import PersistentClient
3
+ from chromadb.api import ClientAPI
4
+ from chromadb.config import Settings
5
+
6
+ from roboml.interfaces import DBAdd, DBMetadataQuery, DBQuery
7
+ from roboml.models._encoding import EncodingModel
8
+ from roboml.ray import app, ingress_decorator
9
+
10
+ from ._base import VectorDBTemplate
11
+
12
+
13
+ @ingress_decorator
14
+ class ChromaDB(VectorDBTemplate):
15
+ """
16
+ ChromaDB Wrapper.
17
+ """
18
+
19
+ def __init__(self, **kwargs):
20
+ """__init__.
21
+ :param kwargs:
22
+ """
23
+ super().__init__(**kwargs)
24
+ self.vectordb: ClientAPI
25
+
26
+ @app.post("/initialize")
27
+ def _initialize(
28
+ self,
29
+ db_location: str = "./data",
30
+ username: Optional[str] = None,
31
+ password: Optional[str] = None,
32
+ encoder: Optional[dict] = None,
33
+ ) -> None:
34
+ """
35
+ Initializes the db.
36
+ """
37
+ if username and password:
38
+ self.logger.warning(
39
+ "Username/password authentication can only be used with local ChromaDB client. Cannot use username/password authentication with roboml ChromaDB instance."
40
+ )
41
+ # initialize the encoding model
42
+ self.encoding_model = EncodingModel(name="encoding_model")
43
+ encoder = encoder or {}
44
+ self.encoding_model._initialize(**encoder)
45
+
46
+ # create a vectordb client
47
+ self.vectordb = PersistentClient(
48
+ settings=Settings(anonymized_telemetry=False),
49
+ path=db_location,
50
+ )
51
+
52
+ @app.post("/add")
53
+ def _add(self, data: DBAdd) -> dict:
54
+ """Add data to the given collection.
55
+ :param data:
56
+ :param type: DBAdd
57
+ :rtype: dict
58
+ """
59
+ # If specified reset existing collection
60
+ if data.reset_collection:
61
+ try:
62
+ self.vectordb.delete_collection(name=data.collection_name)
63
+ except ValueError:
64
+ self.logger.warning(
65
+ f"Cannot delete collection with name {data.collection_name} as it does not exist."
66
+ )
67
+ try:
68
+ # create a collection if one doesnt exist
69
+ collection = self.vectordb.get_or_create_collection(
70
+ name=data.collection_name, metadata={"hnsw:space": data.distance_func}
71
+ )
72
+ # create embeddings
73
+ embeddings = self.encoding_model.embed_documents(data.documents)
74
+ # add to collection
75
+ collection.add(
76
+ documents=data.documents,
77
+ embeddings=embeddings,
78
+ metadatas=data.metadatas,
79
+ ids=data.ids,
80
+ )
81
+ except Exception as e:
82
+ self.logger.error(f"Exception occured: {e}")
83
+ raise
84
+
85
+ return {"output": "Success"}
86
+
87
+ @app.post("/conditional_add")
88
+ def _conditional_add(self, data: DBAdd) -> dict:
89
+ """First check if id exists if not then add data to the collection provided
90
+ Update metadatas of the ids that exist
91
+ :param data:
92
+ :param type: DBAdd
93
+ :rtype: dict
94
+ """
95
+ try:
96
+ # create a collection if one doesnt exist
97
+ collection = self.vectordb.get_or_create_collection(
98
+ name=data.collection_name, metadata={"hnsw:space": data.distance_func}
99
+ )
100
+ # check for ids that already exist in DB
101
+ already_existing = collection.get(ids=data.ids)
102
+
103
+ # if ids found in database, remove them from main input lists
104
+ # update metadatas of already existing IDs
105
+ metadatas_to_update = []
106
+ to_be_deleted = []
107
+ if already_existing_ids := already_existing["ids"]:
108
+ for idx in range(len(data.ids)):
109
+ if data.ids[idx] in already_existing_ids:
110
+ to_be_deleted.append(idx)
111
+ metadatas_to_update.append(data.metadatas[idx].copy())
112
+ # do the metadata update
113
+ collection.update(
114
+ ids=already_existing_ids, metadatas=metadatas_to_update
115
+ )
116
+ except Exception as e:
117
+ self.logger.error(f"Exception occured: {e}")
118
+ raise
119
+
120
+ # delete from indices that were updated
121
+ for idx in sorted(to_be_deleted, reverse=True):
122
+ del data.ids[idx]
123
+ del data.metadatas[idx]
124
+ del data.documents[idx]
125
+ # add the remaining data
126
+ return self._add(data) if data.ids else {"output": "Success"}
127
+
128
+ @app.post("/metadata_query")
129
+ def _metadata_query(self, data: DBMetadataQuery) -> dict:
130
+ """Retreive data by metadata query.
131
+ :param data:
132
+ :param type: DBMetadataQuery
133
+ :rtype: dict
134
+ """
135
+ # create filters for all metadata values
136
+ all_filters = []
137
+ for metadata in data.metadatas:
138
+ # create filter for each metadata hashmap
139
+ if len(metadata) > 1:
140
+ filter = {"$and": [{i: {"$eq": metadata[i]}} for i in metadata]}
141
+ # if there is only one metadata entry, $and is not needed
142
+ elif len(metadata) == 1:
143
+ filter = {list(metadata.keys())[0]: {"$eq": list(metadata.values())[0]}}
144
+ else:
145
+ continue
146
+ all_filters.append(filter)
147
+
148
+ # if no filters, return output as None
149
+ if len(all_filters) == 0:
150
+ self.logger.warning(
151
+ "The metadata filters received were empty, please call query method for retreiving data without metadata filtering."
152
+ )
153
+ return {"output": []}
154
+
155
+ # if there are multiple filters, add an $or
156
+ filters = {"$or": all_filters} if len(all_filters) > 1 else all_filters[0]
157
+
158
+ try:
159
+ # get collection
160
+ collection = self.vectordb.get_collection(name=data.collection_name)
161
+ # get filtered data
162
+ output = collection.get(where=filters)
163
+ except Exception as e:
164
+ self.logger.error(f"Exception occured: {e}")
165
+ raise
166
+ return {"output": output}
167
+
168
+ @app.post("/query")
169
+ def _query(self, data: DBQuery) -> dict:
170
+ """
171
+ Retreives results for a given DB query
172
+ :param data:
173
+ :param type: DBQuery
174
+ :rtype: dict
175
+ """
176
+ query_vec = self.encoding_model.embed_query(data.query)
177
+ query_vec = query_vec.tolist()
178
+ try:
179
+ # create a collection for the map data
180
+ collection = self.vectordb.get_collection(name=data.collection_name)
181
+ output = (
182
+ collection.query(query_embeddings=query_vec, n_results=data.n_results)
183
+ or []
184
+ )
185
+ except Exception as e:
186
+ self.logger.error(f"Exception occured: {e}")
187
+ raise
188
+
189
+ return {"output": output}
roboml/interfaces.py ADDED
@@ -0,0 +1,137 @@
1
+ from typing import Optional, Union
2
+
3
+ import numpy as np
4
+ from pydantic import BaseModel, ConfigDict, Field
5
+
6
+
7
+ class NodeInit(BaseModel):
8
+ """NodeInit."""
9
+
10
+ node_name: str
11
+ node_type: str
12
+
13
+
14
+ class NodeDeinit(BaseModel):
15
+ """NodeDeinit."""
16
+
17
+ node_name: str
18
+
19
+
20
+ # IO Interfaces
21
+ class AudioInput(BaseModel):
22
+ """
23
+ Input values for audio inference
24
+ """
25
+
26
+ query: Union[str, bytes] = Field(title="Audio input raw bytes", min_length=1)
27
+
28
+
29
+ class ImageInput(BaseModel):
30
+ """
31
+ Input values for image inference
32
+ """
33
+
34
+ model_config = ConfigDict(arbitrary_types_allowed=True)
35
+
36
+ images: Union[list[str], list[np.ndarray]] = Field(
37
+ title="List of images as base64 strings or numpy arrays", min_length=1
38
+ )
39
+
40
+
41
+ class TextInput(BaseModel):
42
+ """
43
+ Input values for text inference
44
+ """
45
+
46
+ query: Union[str, list[dict]] = Field(title="Input to the model", min_length=1)
47
+
48
+
49
+ class TextToSpeechInput(TextInput):
50
+ """
51
+ Input values for text to speech inference
52
+ """
53
+
54
+ voice: Optional[str] = Field(title="Voice to use", default=None)
55
+ get_bytes: bool = Field(title="Get raw audio bytes", default=False)
56
+
57
+
58
+ class SpeechToTextInput(AudioInput):
59
+ """
60
+ Input values for speech to text inference
61
+ """
62
+
63
+ max_new_tokens: int = Field(
64
+ title="Maximum number of new tokens to be generated", default=128
65
+ )
66
+
67
+
68
+ class LLMInput(TextInput):
69
+ """
70
+ Input values for LLM inference
71
+ """
72
+
73
+ max_new_tokens: int = Field(
74
+ title="Maximum number of new tokens to be generated", default=100
75
+ )
76
+ temperature: float = Field(
77
+ title="Temperature with which inference is to be generated", default=0.7
78
+ )
79
+
80
+
81
+ class VLLMInput(ImageInput, LLMInput):
82
+ """
83
+ Input values for multi modal LLM inference
84
+ """
85
+
86
+ pass
87
+
88
+
89
+ class DetectionInput(ImageInput):
90
+ """Input for Detection models."""
91
+
92
+ threshold: float = Field(title="Detection confidence threshold", default=0.5)
93
+ get_dataset_labels: bool = Field(
94
+ title="Get dataset label string names", default=True
95
+ )
96
+ labels_to_track: Optional[list[str]] = Field(
97
+ title="List of labels to track. Only used if tracking is enabled during initialization",
98
+ default=None,
99
+ )
100
+
101
+
102
+ # DB Interfaces
103
+ class DBAdd(BaseModel):
104
+ """
105
+ Documents to be added to DB
106
+ """
107
+
108
+ collection_name: str = Field(title="DB Collection Name")
109
+ ids: list[str] = Field(title="Document IDs", min_length=1)
110
+ metadatas: list[dict] = Field(title="Document metadatas", min_length=1)
111
+ documents: list[str] = Field(title="Documents", min_length=1)
112
+ distance_func: str = Field(
113
+ title="Distance function for the collection", default="l2"
114
+ )
115
+ reset_collection: bool = Field(
116
+ title="Delete existing collection with the name defined in collection_name and create it again with the data provided",
117
+ default=False,
118
+ )
119
+
120
+
121
+ class DBMetadataQuery(BaseModel):
122
+ """
123
+ For retreiving documents based on metadata
124
+ """
125
+
126
+ collection_name: str = Field(title="DB Collection Name")
127
+ metadatas: list[dict] = Field(title="Document metadatas", min_length=1)
128
+
129
+
130
+ class DBQuery(BaseModel):
131
+ """
132
+ For retreiving documents based on query
133
+ """
134
+
135
+ collection_name: str = Field(title="DB Collection Name")
136
+ query: str = Field(title="Query string")
137
+ n_results: int = Field(title="Number of results", default=1)
roboml/main.py ADDED
@@ -0,0 +1,62 @@
1
+ import argparse
2
+
3
+ from ray import serve
4
+
5
+ from roboml.ray.app_factory import AppFactory
6
+ from roboml.resp_server.server import Server
7
+ from roboml.utils import logger
8
+
9
+
10
+ def parse_args_for_ray() -> argparse.Namespace:
11
+ """Parse arguments."""
12
+ parser = argparse.ArgumentParser(description="Run models and event handlers")
13
+ parser.add_argument(
14
+ "--host", type=str, help="Specify server host address. Default '127.0.0.1'"
15
+ )
16
+ parser.add_argument("--port", type=int, help="Specify server port. Default 8000")
17
+ parser.add_argument(
18
+ "--nodes_per_cpu",
19
+ type=int,
20
+ help="Specify number of nodes to run per CPU. Default None",
21
+ )
22
+ parser.add_argument(
23
+ "--nodes_per_gpu",
24
+ type=int,
25
+ help="Specify number of nodes to run per GPU. Default None",
26
+ )
27
+ return parser.parse_args()
28
+
29
+
30
+ def ray() -> None:
31
+ """Main entry function for ray"""
32
+
33
+ args = parse_args_for_ray()
34
+ host = args.host or "0.0.0.0"
35
+ port = args.port or 8000
36
+ nodes_per_cpu = args.nodes_per_cpu or None
37
+ nodes_per_gpu = args.nodes_per_gpu or None
38
+ app_factory = AppFactory.bind(
39
+ nodes_per_cpu=nodes_per_cpu, nodes_per_gpu=nodes_per_gpu
40
+ )
41
+ serve.start(http_options=serve.HTTPOptions(host=host, port=port))
42
+ serve.run(app_factory, name="app_factory", blocking=True)
43
+
44
+
45
+ def parse_args_for_resp() -> argparse.Namespace:
46
+ """Parse arguments."""
47
+ parser = argparse.ArgumentParser(description="Run models and event handlers")
48
+ parser.add_argument(
49
+ "--host", type=str, help="Specify server host address. Default '0.0.0.0'"
50
+ )
51
+ parser.add_argument("--port", type=int, help="Specify server port. Default 6379")
52
+ return parser.parse_args()
53
+
54
+
55
+ def resp() -> None:
56
+ """Main entry function for resp"""
57
+
58
+ args = parse_args_for_resp()
59
+ host = args.host or "0.0.0.0"
60
+ port = args.port or 6379
61
+ server = Server(logger)
62
+ server._start_server(host=host, port=port)
@@ -0,0 +1,13 @@
1
+ from .mllm import Idefics, TransformersMLLM
2
+ from .llm import TransformersLLM
3
+ from .speech_to_text import Whisper
4
+ from .text_to_speech import Bark, SpeechT5
5
+
6
+ __all__ = [
7
+ "Whisper",
8
+ "TransformersLLM",
9
+ "TransformersMLLM",
10
+ "SpeechT5",
11
+ "Idefics",
12
+ "Bark",
13
+ ]
roboml/models/_base.py ADDED
@@ -0,0 +1,86 @@
1
+ from abc import abstractmethod
2
+ from logging import Logger
3
+ from threading import Lock
4
+ from typing import Any, Optional
5
+
6
+ from pydantic import ValidationError, validate_call
7
+ import torch
8
+ import inspect
9
+
10
+ from roboml.utils import Status, logging
11
+
12
+
13
+ class ModelTemplate:
14
+ """
15
+ This class describes a model template.
16
+ """
17
+
18
+ def __init__(self, *, name: str, init_timeout: int = 600, **_):
19
+ self.name: str = name
20
+ self.init_timeout: Optional[int] = init_timeout # 10 minutes
21
+ self.device: str = "cuda" if torch.cuda.is_available() else "cpu"
22
+ self.model: Any = None
23
+ self.pre_processor: Any = None
24
+ self.status: Status = Status.LOADED
25
+ self.logger: Logger = logging.getLogger(self.name)
26
+ self.lock = Lock()
27
+
28
+ def initialize(self, **kwargs) -> None:
29
+ """
30
+ Calls the models initialization function and sets status accordingly
31
+ """
32
+ if self.status == Status.READY:
33
+ return
34
+
35
+ self.status = Status.INITIALIZING
36
+ try:
37
+ validated_init = validate_call(self._initialize)
38
+ validated_init(**kwargs)
39
+ except Exception as e:
40
+ self.logger.error(f"Initialization Error: {e}")
41
+ self.status = Status.INITIALIZATION_ERROR
42
+ raise e
43
+ self.logger.info(f"{self.__class__.__name__} Model initialized")
44
+ self.status = Status.READY
45
+
46
+ def inference(self, **kwargs) -> dict:
47
+ """
48
+ Calls the models inference function and sets status accordingly
49
+ """
50
+ if self.status is not Status.READY:
51
+ self.logger.error("Error: Inference called before initialization")
52
+ raise Exception("Inference called before initialization")
53
+ try:
54
+ data_model = (
55
+ inspect.signature(self._inference).parameters["data"].annotation
56
+ )
57
+ data = data_model(**kwargs)
58
+ except ValidationError:
59
+ self.logger.error("Validation Error occured for inference input")
60
+ raise
61
+ with self.lock:
62
+ result = self._inference(data)
63
+ return result
64
+
65
+ def get_status(self):
66
+ """Returns status of the model node"""
67
+ return self.status.name
68
+
69
+ @abstractmethod
70
+ def _initialize(self, *_, **__) -> None:
71
+ """Model specific initialization function, to be implemented in derived classes.
72
+ :param kwargs:
73
+ :rtype: None
74
+ """
75
+ raise NotImplementedError(
76
+ "Model specific initializaion needs to be implemented by derived model classes"
77
+ )
78
+
79
+ @abstractmethod
80
+ def _inference(self, *_, **__) -> dict:
81
+ """Model specific inference function, to be implemented in derived classes.
82
+ :rtype: dict
83
+ """
84
+ raise NotImplementedError(
85
+ "Model specific inference needs to be implemented by derived model classes"
86
+ )