llmstudio 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (30) hide show
  1. {llmstudio-0.2.0 → llmstudio-0.2.2}/PKG-INFO +3 -2
  2. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio/__init__.py +1 -3
  3. llmstudio-0.2.2/llmstudio/engine/__init__.py +199 -0
  4. llmstudio-0.2.2/llmstudio/engine/config.py +405 -0
  5. llmstudio-0.2.2/llmstudio/engine/constants.py +28 -0
  6. llmstudio-0.2.2/llmstudio/engine/utils.py +59 -0
  7. llmstudio-0.2.2/llmstudio/models/__init__.py +4 -0
  8. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio/models/bedrock.py +3 -3
  9. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio/models/openai.py +3 -2
  10. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio/models/vertexai.py +3 -2
  11. llmstudio-0.2.2/llmstudio/ui/__init__.py +21 -0
  12. llmstudio-0.2.2/llmstudio/utils/rest_utils.py +53 -0
  13. llmstudio-0.2.2/llmstudio/validators/__init__.py +3 -0
  14. llmstudio-0.2.2/llmstudio/validators/bedrock.py +35 -0
  15. llmstudio-0.2.2/llmstudio/validators/openai.py +22 -0
  16. llmstudio-0.2.2/llmstudio/validators/vertexai.py +20 -0
  17. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio.egg-info/PKG-INFO +3 -2
  18. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio.egg-info/SOURCES.txt +11 -1
  19. {llmstudio-0.2.0 → llmstudio-0.2.2}/setup.py +14 -2
  20. llmstudio-0.2.0/llmstudio/models/__init__.py +0 -0
  21. {llmstudio-0.2.0 → llmstudio-0.2.2}/LICENSE +0 -0
  22. {llmstudio-0.2.0 → llmstudio-0.2.2}/README.md +0 -0
  23. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio/cli.py +0 -0
  24. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio/client.py +0 -0
  25. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio/models/models.py +0 -0
  26. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio.egg-info/dependency_links.txt +0 -0
  27. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio.egg-info/entry_points.txt +0 -0
  28. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio.egg-info/requires.txt +0 -0
  29. {llmstudio-0.2.0 → llmstudio-0.2.2}/llmstudio.egg-info/top_level.txt +0 -0
  30. {llmstudio-0.2.0 → llmstudio-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: llmstudio
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Prompt Perfection at Your Fingertips
5
5
  Home-page: https://llmstudio.ai/
6
6
  Author: TensorOps
@@ -8,5 +8,6 @@ Author-email: contact@tensorops.ai
8
8
  Project-URL: Source Code, https://github.com/tensoropsai/llmstudio
9
9
  Project-URL: Bug Tracker, https://github.com/tensoropsai/llmstudio/issues
10
10
  Project-URL: Documentation, https://docs.llmstudio.ai
11
- Keywords: ml ai llm llmstudio tensorops
11
+ Keywords: ml ai llm llmops openai langchain chatgpt llmstudio tensorops
12
+ Requires-Python: ~=3.9
12
13
  License-File: LICENSE
@@ -1,8 +1,7 @@
1
1
  name = "version"
2
- __version__ = "0.2.0"
2
+ __version__ = "0.2.2"
3
3
 
4
4
  __requirements__ = [
5
- # core
6
5
  "pydantic",
7
6
  "requests",
8
7
  "pydantic",
@@ -12,7 +11,6 @@ __requirements__ = [
12
11
  "fastapi",
13
12
  "uvicorn",
14
13
  "PyYaml",
15
- # engine
16
14
  "openai",
17
15
  "tiktoken",
18
16
  "google-auth",
@@ -0,0 +1,199 @@
1
+ from typing import Any, Callable, Dict, Optional
2
+
3
+ from fastapi import FastAPI, HTTPException, Request
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+
6
+ from llmstudio.engine.config import EngineRouteConfig, Route, RouteType
7
+ from llmstudio.engine.constants import ENGINE_HEALTH_ENDPOINT, ENGINE_ROUTE_BASE, VERSION
8
+ from llmstudio.engine.providers import get_provider
9
+
10
+
11
+ class EngineAPI(FastAPI):
12
+ """
13
+ Extends FastAPI to provide an API engine with dynamic routes based on the given configuration.
14
+
15
+ Attributes:
16
+ dynamic_routes (Dict[str, Route]): A dictionary mapping from route names to Route objects.
17
+ """
18
+
19
+ def __init__(self, config: EngineRouteConfig, *args: Any, **kwargs: Any):
20
+ """
21
+ Initialize the EngineAPI instance.
22
+
23
+ Args:
24
+ config (EngineRouteConfig): The configuration object containing routes and other settings.
25
+ *args (Any): Additional positional arguments.
26
+ **kwargs (Any): Additional keyword arguments.
27
+ """
28
+ super().__init__(*args, **kwargs)
29
+ self.dynamic_routes: Dict[str, Route] = {}
30
+ self.set_dynamic_routes(config)
31
+
32
+ def set_dynamic_routes(self, config: EngineRouteConfig) -> None:
33
+ """
34
+ Clears existing dynamic routes and sets new ones based on the provided configuration.
35
+
36
+ Args:
37
+ config (EngineRouteConfig): The configuration object containing routes and other settings.
38
+ """
39
+ self.dynamic_routes.clear()
40
+ for route in config.routes:
41
+ for provider in route.model_providers:
42
+ self._add_dynamic_route(route, provider)
43
+
44
+ def _add_dynamic_route(self, route: dict, provider: dict):
45
+ """
46
+ Internal method to add a dynamic route based on route and provider information.
47
+
48
+ Args:
49
+ route (dict): Dictionary containing information about the route.
50
+ provider (dict): Dictionary containing information about the provider.
51
+ """
52
+ provider_name = provider.provider
53
+ provider_config = provider.config or {}
54
+ route_type_name = f"{route.route_type.value}/{provider_name}"
55
+ path = f"{ENGINE_ROUTE_BASE}{route_type_name}"
56
+
57
+ self.add_api_route(
58
+ path=path,
59
+ endpoint=self._route_type_to_endpoint(
60
+ provider_name, provider_config, route.route_type
61
+ ),
62
+ methods=["POST"],
63
+ )
64
+ self.dynamic_routes[route_type_name] = route.to_route(provider_name, path)
65
+
66
+ def _route_type_to_endpoint(
67
+ self, provider_name: str, provider_config: dict, route_type: RouteType
68
+ ) -> Callable:
69
+ """
70
+ Maps a route type to its corresponding endpoint callable based on provider information.
71
+
72
+ Args:
73
+ provider_name (str): The name of the provider.
74
+ provider_config (dict): Configuration specific to the provider.
75
+ route_type (RouteType): The type of the route.
76
+
77
+ Returns:
78
+ Callable: The callable endpoint to be used for the route.
79
+
80
+ Raises:
81
+ HTTPException: If the route type is unexpected for the given provider.
82
+ """
83
+ provider_to_factory = {
84
+ RouteType.LLM_CHAT: "chat",
85
+ RouteType.LLM_VALIDATION: "test",
86
+ }
87
+
88
+ factory = provider_to_factory.get(route_type)
89
+ if factory:
90
+ return self._create_generic_endpoint(factory, provider_name, provider_config)
91
+
92
+ raise HTTPException(
93
+ status_code=404,
94
+ detail=f"Unexpected route type {route_type!r} for provider {provider_name!r}.",
95
+ )
96
+
97
+ def _create_generic_endpoint(
98
+ self, method_name: str, provider_name: str, provider_config: str
99
+ ) -> Callable:
100
+ """
101
+ Creates a generic endpoint for the given method name and provider.
102
+
103
+ Args:
104
+ method_name (str): The method name that should be invoked on the provider.
105
+ provider_name (str): The name of the provider.
106
+ provider_config (str): Configuration specific to the provider.
107
+
108
+ Returns:
109
+ Callable: A generic endpoint that invokes the specified method on the provider.
110
+
111
+ Raises:
112
+ HTTPException: If the specified method is not found for the given provider.
113
+ """
114
+
115
+ async def _generic_endpoint(request: Request):
116
+ payload = await request.json()
117
+ api_key = payload.get("api_key", None)
118
+ provider_instance = get_provider(provider_name)(provider_config, api_key)
119
+ method = getattr(provider_instance, method_name, None)
120
+
121
+ if not method:
122
+ raise HTTPException(
123
+ status_code=404,
124
+ detail=f"Method {method_name!r} not found for provider {provider_name!r}.",
125
+ )
126
+
127
+ return await method(payload)
128
+
129
+ return _generic_endpoint
130
+
131
+ def get_dynamic_route(self, route_name: str) -> Optional[Route]:
132
+ """
133
+ Retrieves the dynamic route by its name.
134
+
135
+ Args:
136
+ route_name (str): The name of the route to retrieve.
137
+
138
+ Returns:
139
+ Optional[Route]: The Route object if found, None otherwise.
140
+ """
141
+ return self.dynamic_routes.get(route_name)
142
+
143
+ def get_dynamic_routes(self):
144
+ """
145
+ Retrieves all dynamic routes.
146
+
147
+ Returns:
148
+ Dict[str, Route]: A dictionary of all dynamic routes, keyed by route name.
149
+ """
150
+ return self.dynamic_routes
151
+
152
+
153
+ def create_app_from_config(config: EngineRouteConfig) -> EngineAPI:
154
+ """
155
+ Initializes and returns an EngineAPI application based on the given configuration.
156
+
157
+ Parameters:
158
+ config (EngineRouteConfig): The configuration settings for initializing the EngineAPI.
159
+
160
+ Returns:
161
+ EngineAPI: An initialized EngineAPI application.
162
+ """
163
+ app = EngineAPI(
164
+ config=config,
165
+ title="engine API",
166
+ description="The core API for engine",
167
+ version=VERSION,
168
+ )
169
+
170
+ app.add_middleware(
171
+ CORSMiddleware,
172
+ allow_origins=["http://localhost:3000"],
173
+ allow_credentials=True,
174
+ allow_methods=["*"],
175
+ allow_headers=["*"],
176
+ )
177
+
178
+ @app.get(ENGINE_HEALTH_ENDPOINT)
179
+ async def health():
180
+ return {"status": "OK"}
181
+
182
+ @app.get(ENGINE_ROUTE_BASE + "{route_name}")
183
+ async def get_route(route_name: str) -> Route:
184
+ if matched := app.get_dynamic_route(route_name):
185
+ return matched
186
+
187
+ raise HTTPException(
188
+ status_code=404,
189
+ detail=f"The route '{route_name}' is not present or active on the server. Please "
190
+ "verify the route name.",
191
+ )
192
+
193
+ @app.get(ENGINE_ROUTE_BASE)
194
+ async def search_routes(page_token: Optional[str] = None):
195
+ # TODO: Implement better function
196
+ routes = app.get_dynamic_routes()
197
+ return routes
198
+
199
+ return app
@@ -0,0 +1,405 @@
1
+ import json
2
+ import os
3
+ import pathlib
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from typing import List, Optional, Union
7
+
8
+ import pydantic
9
+ import yaml
10
+ from packaging import version
11
+ from pydantic import BaseModel, ValidationError, validator
12
+ from pydantic.json import pydantic_encoder
13
+
14
+ from llmstudio.engine.utils import (
15
+ check_configuration_route_name_collisions,
16
+ is_valid_endpoint_name,
17
+ )
18
+
19
+ IS_PYDANTIC_V2 = version.parse(pydantic.version.VERSION) >= version.parse("2.0")
20
+
21
+
22
+ class EngineConfig:
23
+ def __init__(
24
+ self,
25
+ api_name="Engine",
26
+ host="localhost",
27
+ port=8000,
28
+ localhost=True,
29
+ config_path=os.path.join(os.path.dirname(__file__), "config.yaml"),
30
+ health_endpoint="health",
31
+ routes_endpoint="api/engine",
32
+ ):
33
+ self.api_name = api_name
34
+ self.host = host
35
+ self.port = port
36
+ self.config_path = config_path
37
+ self.localhost = localhost
38
+ self.update_url()
39
+ self.update_endpoints(health_endpoint, routes_endpoint)
40
+
41
+ def update_url(self):
42
+ """Update the URL based on the current host, port and localhost values."""
43
+ self.url = f"http://{self.host}:{self.port}" if self.localhost else self.host
44
+
45
+ def update_endpoints(self, health_endpoint, routes_endpoint):
46
+ """Update the health and routes endpoints based on the current url."""
47
+ self.health_endpoint = f"{self.url}/{health_endpoint}"
48
+ self.routes_endpoint = f"{self.url}/{routes_endpoint}"
49
+
50
+
51
+ class RouteType(str, Enum):
52
+ """
53
+ Used for specifying various types of routes in an API.
54
+ """
55
+
56
+ LLM_CHAT = "chat"
57
+ LLM_VALIDATION = "validation"
58
+
59
+
60
+ class Provider(str, Enum):
61
+ """
62
+ Enum class to represent different AI service providers.
63
+ """
64
+
65
+ OPENAI = "openai"
66
+ VERTEXAI = "vertexai"
67
+ BEDROCK = "bedrock"
68
+
69
+ @classmethod
70
+ def values(cls):
71
+ return {p.value for p in cls}
72
+
73
+
74
+ class OpenAIConfig(BaseModel):
75
+ """
76
+ OpenAIConfig is a class derived from BaseModel for handling the configuration needed for OpenAI API calls.
77
+
78
+ Attributes:
79
+ api_key (str): The API key for authentication.
80
+ openai_api_type (str, optional): Type of the OpenAI API to use, default is 'openai'.
81
+ openai_api_base (str, optional): Base URL for the OpenAI API, default is None.
82
+ openai_api_version (str, optional): API version, default is None.
83
+ openai_deployment_name (str, optional): Name of the deployment, default is None.
84
+ openai_organization (str, optional): Name of the organization, default is None.
85
+ """
86
+
87
+ api_key: str
88
+ openai_api_type: Optional[str] = "openai"
89
+ openai_api_base: Optional[str] = None
90
+ openai_api_version: Optional[str] = None
91
+ openai_deployment_name: Optional[str] = None
92
+ openai_organization: Optional[str] = None
93
+
94
+ @validator("api_key", pre=True)
95
+ def validate_api_key(cls, value):
96
+ return _resolve_api_key_from_input(value)
97
+
98
+
99
+ class VertexAIConfig(BaseModel):
100
+ """
101
+ VertexAIConfig is a class derived from BaseModel for handling the configuration needed for VertexAI API calls.
102
+
103
+ Attributes:
104
+ api_key (dict): The JSON API key for authentication.
105
+ """
106
+
107
+ api_key: dict
108
+
109
+ @validator("api_key", pre=True)
110
+ def validate_api_key(cls, value):
111
+ return _resolve_api_key_from_input(value)
112
+
113
+
114
+ class BedrockConfig(BaseModel):
115
+ """
116
+ BedrockConfig is a class derived from BaseModel for handling the configuration needed for Bedrock API calls.
117
+
118
+ Attributes:
119
+ api_key (dict): The JSON API key for authentication.
120
+ """
121
+
122
+ api_key: dict
123
+
124
+ @validator("api_key", pre=True)
125
+ def validate_api_key(cls, value):
126
+ return _resolve_api_key_from_input(value)
127
+
128
+
129
+ provider_configs = {
130
+ Provider.OPENAI: OpenAIConfig,
131
+ Provider.VERTEXAI: VertexAIConfig,
132
+ Provider.BEDROCK: BedrockConfig,
133
+ }
134
+
135
+
136
+ def _resolve_api_key_from_input(api_key_input):
137
+ """
138
+ Resolves the provided API key.
139
+
140
+ Input formats accepted:
141
+
142
+ - Path to a file as a string which will have the key loaded from it
143
+ - environment variable name that stores the api key
144
+ - the api key itself
145
+ """
146
+
147
+ if isinstance(api_key_input, dict):
148
+ api_key_input = api_key_input.get("api_key")
149
+
150
+ # try reading as an environment variable
151
+ if api_key_input.startswith("$"):
152
+ env_var_name = api_key_input[1:]
153
+ if env_var := os.getenv(env_var_name):
154
+ return env_var
155
+ else:
156
+ raise ValueError(f"Environment variable {env_var_name!r} is not set")
157
+
158
+ # try reading from a local path
159
+ file = pathlib.Path(api_key_input)
160
+ if file.is_file():
161
+ return file.read_text()
162
+
163
+ # if the key itself is passed, return
164
+ return api_key_input
165
+
166
+
167
+ class ModelProvider(BaseModel):
168
+ """
169
+ Represents a provider for machine learning models along with its configuration.
170
+
171
+ Attributes:
172
+ provider (Union[str, Provider]): The provider of the machine learning model.
173
+ config (Optional[Union[OpenAIConfig, ...]]): The configuration for the selected provider.
174
+ """
175
+
176
+ provider: Union[str, Provider]
177
+ config: Optional[
178
+ Union[
179
+ OpenAIConfig,
180
+ VertexAIConfig,
181
+ BedrockConfig,
182
+ ]
183
+ ] = None
184
+
185
+ @validator("provider", pre=True)
186
+ def validate_provider(cls, value):
187
+ """
188
+ Validates the 'provider' field.
189
+
190
+ Parameters:
191
+ value: The value to validate, can be either a string or an instance of the Provider enum.
192
+
193
+ Returns:
194
+ Provider: A valid Provider enum instance.
195
+
196
+ Raises:
197
+ ValueError: If the provided value is not a valid provider.
198
+ """
199
+ if isinstance(value, Provider):
200
+ return value
201
+ formatted_value = value.replace("-", "_").upper()
202
+ if formatted_value in Provider.__members__:
203
+ return Provider[formatted_value]
204
+ raise ValueError(f"The provider '{value}' is not supported.")
205
+
206
+ @classmethod
207
+ def _validate_config(cls, info, values):
208
+ """
209
+ Internal method to validate the 'config' field based on the provided 'provider'.
210
+
211
+ Parameters:
212
+ info: The configuration information to validate.
213
+ values: Dictionary containing other field values of the class, primarily used to fetch 'provider'.
214
+
215
+ Returns:
216
+ The validated configuration as an instance of the appropriate configuration class.
217
+
218
+ Raises:
219
+ ValueError: If a valid 'provider' is not provided.
220
+ """
221
+ if provider := values.get("provider"):
222
+ config_type = provider_configs[provider]
223
+ return config_type(**info)
224
+
225
+ raise ValueError("A provider must be provided for each gateway route.")
226
+
227
+ if IS_PYDANTIC_V2:
228
+
229
+ @validator("config", pre=True)
230
+ def validate_config(cls, info, values):
231
+ return cls._validate_config(info, values)
232
+
233
+ else:
234
+
235
+ @validator("config", pre=True)
236
+ def validate_config(cls, config, values):
237
+ return cls._validate_config(config, values)
238
+
239
+
240
+ class RouteConfig(BaseModel):
241
+ """
242
+ Represents the configuration for a single route.
243
+
244
+ Attributes:
245
+ name (str): The name of the route.
246
+ route_type (RouteType): The type of the route, as defined in the RouteType enum.
247
+ model_providers (List[ModelProvider]): A list of model providers for the route.
248
+ """
249
+
250
+ name: str
251
+ route_type: RouteType
252
+ model_providers: List[ModelProvider]
253
+
254
+ @validator("name", pre=True)
255
+ def validate_endpoint_name(cls, route_name):
256
+ """
257
+ Validates that the provided route name is a valid endpoint name.
258
+
259
+ Args:
260
+ route_name (str): The name of the route.
261
+
262
+ Returns:
263
+ str: The validated name.
264
+
265
+ Raises:
266
+ ValueError: If the name contains invalid characters.
267
+ """
268
+ if not is_valid_endpoint_name(route_name):
269
+ raise ValueError(
270
+ "The route name provided contains disallowed characters for a url endpoint. "
271
+ f"'{route_name}' is invalid. Names cannot contain spaces or any non "
272
+ "alphanumeric characters other than hyphen and underscore."
273
+ )
274
+ return route_name
275
+
276
+ @validator("model_providers", pre=True)
277
+ def validate_model(cls, model_providers):
278
+ """
279
+ Validates that the provided model providers list is not empty and contains valid entries.
280
+
281
+ Args:
282
+ model_providers (List[Dict]): A list of dictionaries containing model provider information.
283
+
284
+ Returns:
285
+ List[Dict]: The validated list of model providers.
286
+
287
+ Raises:
288
+ ValueError: If the list is empty or contains invalid providers.
289
+ """
290
+ if not model_providers:
291
+ raise ValueError(
292
+ "No model providers were provided for the route. Please provide at least one model provider."
293
+ )
294
+ for model_provider in model_providers:
295
+ if model_provider:
296
+ model_instance = ModelProvider(**model_provider)
297
+ if model_instance.provider not in Provider.values():
298
+ raise ValueError(
299
+ f"The provider entry for {model_instance.provider} is incorrect. Providers accepted are {Provider.values()}"
300
+ )
301
+ return model_providers
302
+
303
+ @validator("route_type", pre=True)
304
+ def validate_route_type(cls, value):
305
+ """
306
+ Validates that the provided route type is a valid RouteType enum value.
307
+
308
+ Args:
309
+ value (str): The type of the route.
310
+
311
+ Returns:
312
+ str: The validated route type.
313
+
314
+ Raises:
315
+ ValueError: If the route_type is not a valid RouteType enum value.
316
+ """
317
+ if value in RouteType._value2member_map_:
318
+ return value
319
+ raise ValueError(
320
+ f"The route_type '{value}' is not supported. Please use one of {RouteType._value2member_map_}"
321
+ )
322
+
323
+ def to_route(self, name, route_url) -> "Route":
324
+ """
325
+ Converts the configuration to a Route object.
326
+
327
+ Args:
328
+ name (str): The name of the route.
329
+ route_url (str): The URL for the route.
330
+
331
+ Returns:
332
+ Route: A Route object containing the route's configuration.
333
+ """
334
+ return Route(
335
+ name=name,
336
+ route_type=self.route_type,
337
+ route_url=route_url,
338
+ )
339
+
340
+
341
+ class Route(BaseModel):
342
+ """
343
+ A class to represent a routing information.
344
+
345
+ Attributes:
346
+ name (str): The name of the route.
347
+ route_type (str): The type of the route, usually represented as a string-based identifier.
348
+ route_url (str): The URL pattern for the route.
349
+
350
+ Config:
351
+ schema_extra: Provides an example instantiation of the Route class.
352
+ """
353
+
354
+ name: str
355
+ route_type: str
356
+ route_url: str
357
+
358
+ class Config:
359
+ schema_extra = {
360
+ "example": {
361
+ "name": "openai",
362
+ "route_type": "llm/v1/completions",
363
+ "route_url": "/engine/openai",
364
+ }
365
+ }
366
+
367
+
368
+ class EngineRouteConfig(BaseModel):
369
+ routes: List[RouteConfig]
370
+
371
+
372
+ def _load_route_config(path: Union[str, Path]) -> EngineRouteConfig:
373
+ """
374
+ Reads the gateway configuration yaml file from the storage location and returns an instance
375
+ of the configuration RouteConfig class
376
+ """
377
+ if isinstance(path, str):
378
+ path = Path(path)
379
+ try:
380
+ configuration = yaml.safe_load(path.read_text())
381
+ except Exception as e:
382
+ raise ValueError(f"The file at {path} is not a valid yaml file") from e
383
+ check_configuration_route_name_collisions(configuration)
384
+ try:
385
+ return EngineRouteConfig(**configuration)
386
+ except ValidationError as e:
387
+ raise ValueError(f"The gateway configuration is invalid: {e}") from e
388
+
389
+
390
+ def _save_route_config(config: EngineRouteConfig, path: Union[str, Path]) -> None:
391
+ if isinstance(path, str):
392
+ path = Path(path)
393
+ path.write_text(
394
+ yaml.safe_dump(json.loads(json.dumps(config.dict(), default=pydantic_encoder)))
395
+ )
396
+
397
+
398
+ def _validate_config(config_path: str) -> EngineRouteConfig:
399
+ if not os.path.exists(config_path):
400
+ raise ValueError(f"{config_path} does not exist")
401
+
402
+ try:
403
+ return _load_route_config(config_path)
404
+ except ValidationError as e:
405
+ raise ValueError(f"Invalid gateway configuration: {e}") from e
@@ -0,0 +1,28 @@
1
+ ENGINE_ROUTE_BASE = "/api/engine/"
2
+ ENGINE_HEALTH_ENDPOINT = "/health"
3
+
4
+
5
+ # Change to llmstudio version
6
+ VERSION = "0.1.0"
7
+
8
+
9
+ END_TOKEN = "<END_TOKEN>"
10
+
11
+ VERTEXAI_TOKEN_PRICE = 0.0000005
12
+
13
+
14
+ OPENAI_PRICING_DICT = {
15
+ "gpt-3.5-turbo": {"input_tokens": 0.0000015, "output_tokens": 0.000002},
16
+ "gpt-4": {"input_tokens": 0.00003, "output_tokens": 0.00006},
17
+ "gpt-3.5-turbo-16k": {"input_tokens": 0.00003, "output_tokens": 0.00004},
18
+ }
19
+
20
+
21
+ TITAN_MODELS = ["amazon.titan-tg1-large"]
22
+ CLAUDE_MODELS = [
23
+ "anthropic.claude-instant-v1",
24
+ "anthropic.claude-v1",
25
+ "anthropic.claude-v2",
26
+ ]
27
+
28
+ BEDROCK_MODELS = TITAN_MODELS + CLAUDE_MODELS
@@ -0,0 +1,59 @@
1
+ import re
2
+
3
+
4
+ def is_valid_endpoint_name(name: str) -> bool:
5
+ """
6
+ Check whether a string contains any URL reserved characters, spaces, or characters other
7
+ than alphanumeric, underscore, hyphen, and dot.
8
+
9
+ Returns True if the string doesn't contain any of these characters.
10
+ """
11
+ pattern = r"[^\w\.-]"
12
+ return re.search(pattern, name) is None
13
+
14
+
15
+ def validate_provider_config(config, api_key):
16
+ """
17
+ Validate and/or initialize a provider configuration based on input parameters.
18
+
19
+ Parameters:
20
+ - config (dict or None): Configuration dictionary for the provider. Can be None.
21
+ - api_key (str or None): API key for the provider. Can be None.
22
+
23
+ Returns:
24
+ dict: The modified or validated configuration dictionary.
25
+
26
+ Raises:
27
+ - ValueError: If both `config` and `api_key` are None.
28
+ """
29
+ if not (config or api_key):
30
+ raise ValueError(f"Config was not specified neither an api_key was provided.")
31
+ if config is None:
32
+ config = {}
33
+ if api_key is not None:
34
+ config.setdefault("api_key", api_key)
35
+ return config
36
+
37
+
38
+ def check_configuration_route_name_collisions(config):
39
+ """
40
+ Checks for duplicate route names in the given configuration.
41
+
42
+ Parameters:
43
+ config (dict): The configuration dictionary containing a list of routes.
44
+ Each route should be a dictionary with a 'name' key.
45
+
46
+ Returns:
47
+ None: Returns None if there are no duplicates.
48
+
49
+ Raises:
50
+ ValueError: If there are duplicate route names found in the configuration.
51
+ """
52
+ if len(config["routes"]) < 2:
53
+ return
54
+ names = [route["name"] for route in config["routes"]]
55
+ if len(names) != len(set(names)):
56
+ raise ValueError(
57
+ "Duplicate names found in route configurations. Please remove the duplicate route "
58
+ "name from the configuration to ensure that route endpoints are created properly."
59
+ )
@@ -0,0 +1,4 @@
1
+ from .bedrock import BedrockClient
2
+ from .models import LLMClient, LLMModel
3
+ from .openai import OpenAIClient
4
+ from .vertexai import VertexAIClient
@@ -1,7 +1,7 @@
1
- from llmstudoi.models import LLMClient, LLMModel
2
-
3
1
  from llmstudio.engine.config import EngineConfig
4
- from llmstudio.validators import ClaudeParameters, TitanParameters
2
+
3
+ from ..validators import ClaudeParameters, TitanParameters
4
+ from .models import LLMClient, LLMModel
5
5
 
6
6
 
7
7
  class BedrockClient(LLMClient):
@@ -1,8 +1,9 @@
1
1
  import os
2
2
 
3
3
  from llmstudio.engine.config import EngineConfig
4
- from llmstudio.models import LLMClient, LLMModel
5
- from llmstudio.validators import OpenAIParameters
4
+
5
+ from ..validators import OpenAIParameters
6
+ from .models import LLMClient, LLMModel
6
7
 
7
8
 
8
9
  class OpenAIClient(LLMClient):
@@ -1,6 +1,7 @@
1
1
  from llmstudio.engine.config import EngineConfig
2
- from llmstudio.models import LLMClient, LLMModel
3
- from llmstudio.validators import VertexAIParameters
2
+
3
+ from ..validators import VertexAIParameters
4
+ from .models import LLMClient, LLMModel
4
5
 
5
6
 
6
7
  class VertexAIClient(LLMClient):
@@ -0,0 +1,21 @@
1
+ import os
2
+
3
+ from fastapi import FastAPI
4
+ from fastapi.staticfiles import StaticFiles
5
+
6
+
7
+ def create_ui_app():
8
+ app = FastAPI()
9
+
10
+ app.mount(
11
+ "/static",
12
+ StaticFiles(directory=os.path.join(os.path.dirname(__file__), "build", "static")),
13
+ name="static",
14
+ )
15
+ app.mount(
16
+ "/",
17
+ StaticFiles(directory=os.path.join(os.path.dirname(__file__), "build"), html=True),
18
+ name="app",
19
+ )
20
+
21
+ return app
@@ -0,0 +1,53 @@
1
+ from threading import Thread
2
+
3
+ import requests
4
+ import uvicorn
5
+
6
+ from llmstudio.engine import create_app_from_config
7
+ from llmstudio.engine.config import EngineConfig, _load_route_config
8
+
9
+
10
+ def is_api_running(url, name) -> bool:
11
+ try:
12
+ response = requests.get(url, timeout=5)
13
+ if response.status_code == 200:
14
+ print(f"API {name} is already running")
15
+ return True
16
+ except requests.RequestException as e:
17
+ return False
18
+
19
+
20
+ def run_engine_app(engine_config=EngineConfig()):
21
+ config = _load_route_config(engine_config.config_path)
22
+ engine = create_app_from_config(config)
23
+ print(f"Running {engine_config.api_name} on {engine_config.host}:{engine_config.port}")
24
+ uvicorn.run(
25
+ engine,
26
+ host=engine_config.host,
27
+ port=engine_config.port,
28
+ log_level="critical",
29
+ )
30
+
31
+
32
+ def run_ui_app(ui_server_app):
33
+ uvicorn.run(
34
+ ui_server_app,
35
+ host="localhost",
36
+ port=3000,
37
+ log_level="critical",
38
+ )
39
+
40
+
41
+ def run_apis(engine_config=EngineConfig(), ui_server_app=None, serverless=False):
42
+ if engine_config.localhost and not is_api_running(
43
+ engine_config.health_endpoint, engine_config.api_name
44
+ ):
45
+ thread = Thread(target=run_engine_app, args=(engine_config,))
46
+ thread.daemon = True
47
+ thread.start()
48
+
49
+ if ui_server_app:
50
+ thread = Thread(target=run_ui_app, args=(ui_server_app,))
51
+ thread.daemon = True
52
+ thread.start()
53
+ thread.join()
@@ -0,0 +1,3 @@
1
+ from .bedrock import ClaudeParameters, TitanParameters
2
+ from .openai import OpenAIParameters
3
+ from .vertexai import VertexAIParameters
@@ -0,0 +1,35 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ClaudeParameters(BaseModel):
7
+ """
8
+ Model for validating and storing parameters specific to Claude model.
9
+
10
+ Attributes:
11
+ temperature (Optional[float]): Controls randomness in the model's output.
12
+ max_tokens (Optional[int]): The maximum number of tokens in the output.
13
+ top_p (Optional[float]): Influences the diversity of output by controlling token sampling.
14
+ top_k (Optional[float]): Sets the number of the most likely next tokens to filter for.
15
+ """
16
+
17
+ temperature: Optional[float] = Field(1, ge=0, le=1)
18
+ max_tokens: Optional[int] = Field(300, ge=1, le=2048)
19
+ top_p: Optional[float] = Field(0.999, ge=0, le=1)
20
+ top_k: Optional[int] = Field(250, ge=1, le=500)
21
+
22
+
23
+ class TitanParameters(BaseModel):
24
+ """
25
+ Model for validating and storing parameters specific to Titan model.
26
+
27
+ Attributes:
28
+ temperature (Optional[float]): Controls randomness in the model's output.
29
+ max_tokens (Optional[int]): The maximum number of tokens in the output.
30
+ top_p (Optional[float]): Influences the diversity of output by controlling token sampling.
31
+ """
32
+
33
+ temperature: Optional[float] = Field(0, ge=0, le=1)
34
+ max_tokens: Optional[int] = Field(512, ge=1, le=4096)
35
+ top_p: Optional[float] = Field(0.9, ge=0.1, le=1)
@@ -0,0 +1,22 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class OpenAIParameters(BaseModel):
7
+ """
8
+ A Pydantic model for encapsulating parameters used in OpenAI API requests.
9
+
10
+ Attributes:
11
+ temperature (Optional[float]): Controls randomness in the model's output.
12
+ max_tokens (Optional[int]): The maximum number of tokens in the output.
13
+ top_p (Optional[float]): Influences the diversity of output by controlling token sampling.
14
+ frequency_penalty (Optional[float]): Modifies the likelihood of tokens appearing based on their frequency.
15
+ presence_penalty (Optional[float]): Adjusts the likelihood of new tokens appearing.
16
+ """
17
+
18
+ temperature: Optional[float] = Field(default=1, ge=0, le=2)
19
+ max_tokens: Optional[int] = Field(default=256, ge=1, le=2048)
20
+ top_p: Optional[float] = Field(default=1, ge=0, le=1)
21
+ frequency_penalty: Optional[float] = Field(default=0, ge=0, le=1)
22
+ presence_penalty: Optional[float] = Field(default=0, ge=0, le=1)
@@ -0,0 +1,20 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class VertexAIParameters(BaseModel):
7
+ """
8
+ A Pydantic model that encapsulates parameters used for VertexAI API requests.
9
+
10
+ Attributes:
11
+ temperature (Optional[float]): Controls randomness in the model's output.
12
+ max_tokens (Optional[int]): The maximum number of tokens in the output.
13
+ top_p (Optional[float]): Influences the diversity of output by controlling token sampling.
14
+ top_k (Optional[float]): Sets the number of the most likely next tokens to filter for.
15
+ """
16
+
17
+ temperature: Optional[float] = Field(1, ge=0, le=1)
18
+ max_tokens: Optional[int] = Field(256, ge=1, le=1024)
19
+ top_p: Optional[float] = Field(1, ge=0, le=1)
20
+ top_k: Optional[float] = Field(40, ge=1, le=40)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: llmstudio
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Prompt Perfection at Your Fingertips
5
5
  Home-page: https://llmstudio.ai/
6
6
  Author: TensorOps
@@ -8,5 +8,6 @@ Author-email: contact@tensorops.ai
8
8
  Project-URL: Source Code, https://github.com/tensoropsai/llmstudio
9
9
  Project-URL: Bug Tracker, https://github.com/tensoropsai/llmstudio/issues
10
10
  Project-URL: Documentation, https://docs.llmstudio.ai
11
- Keywords: ml ai llm llmstudio tensorops
11
+ Keywords: ml ai llm llmops openai langchain chatgpt llmstudio tensorops
12
+ Requires-Python: ~=3.9
12
13
  License-File: LICENSE
@@ -10,8 +10,18 @@ llmstudio.egg-info/dependency_links.txt
10
10
  llmstudio.egg-info/entry_points.txt
11
11
  llmstudio.egg-info/requires.txt
12
12
  llmstudio.egg-info/top_level.txt
13
+ llmstudio/engine/__init__.py
14
+ llmstudio/engine/config.py
15
+ llmstudio/engine/constants.py
16
+ llmstudio/engine/utils.py
13
17
  llmstudio/models/__init__.py
14
18
  llmstudio/models/bedrock.py
15
19
  llmstudio/models/models.py
16
20
  llmstudio/models/openai.py
17
- llmstudio/models/vertexai.py
21
+ llmstudio/models/vertexai.py
22
+ llmstudio/ui/__init__.py
23
+ llmstudio/utils/rest_utils.py
24
+ llmstudio/validators/__init__.py
25
+ llmstudio/validators/bedrock.py
26
+ llmstudio/validators/openai.py
27
+ llmstudio/validators/vertexai.py
@@ -14,14 +14,26 @@ setup(
14
14
  },
15
15
  author_email="contact@tensorops.ai",
16
16
  description="Prompt Perfection at Your Fingertips",
17
- keywords="ml ai llm llmstudio tensorops",
17
+ keywords="ml ai llm llmops openai langchain chatgpt llmstudio tensorops",
18
18
  version=SDK_VERSION,
19
- packages=["llmstudio", "llmstudio.models"],
19
+ packages=[
20
+ "llmstudio",
21
+ "llmstudio.engine",
22
+ "llmstudio.models",
23
+ "llmstudio.ui",
24
+ "llmstudio.utils",
25
+ "llmstudio.validators",
26
+ ],
20
27
  package_dir={
21
28
  "llmstudio": "llmstudio",
29
+ "llmstudio.engine": "llmstudio/engine",
22
30
  "llmstudio.models": "llmstudio/models",
31
+ "llmstudio.ui": "llmstudio/ui",
32
+ "llmstudio.utils": "llmstudio/utils",
33
+ "llmstudio.validators": "llmstudio/validators",
23
34
  },
24
35
  install_requires=REQUIREMENTS,
25
36
  include_package_data=True,
26
37
  entry_points={"console_scripts": ["llmstudio = llmstudio.cli:main"]},
38
+ python_requires="~=3.9",
27
39
  )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes