beamlit 0.0.22rc11__py3-none-any.whl → 0.0.23__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.
- beamlit/agents/chat.py +2 -1
- beamlit/agents/decorator.py +9 -5
- beamlit/authentication/authentication.py +0 -1
- beamlit/authentication/credentials.py +1 -1
- beamlit/client.py +0 -1
- beamlit/common/settings.py +13 -11
- beamlit/deploy/__init__.py +3 -0
- beamlit/deploy/deploy.py +503 -0
- beamlit/functions/decorator.py +5 -10
- beamlit/serve/app.py +0 -1
- {beamlit-0.0.22rc11.dist-info → beamlit-0.0.23.dist-info}/METADATA +2 -2
- {beamlit-0.0.22rc11.dist-info → beamlit-0.0.23.dist-info}/RECORD +13 -11
- {beamlit-0.0.22rc11.dist-info → beamlit-0.0.23.dist-info}/WHEEL +1 -1
beamlit/agents/chat.py
CHANGED
@@ -37,12 +37,13 @@ def get_chat_model(agent_model: AgentDeployment):
|
|
37
37
|
headers = get_authentication_headers(settings)
|
38
38
|
headers["X-Beamlit-Environment"] = agent_model.environment
|
39
39
|
|
40
|
-
jwt = headers.
|
40
|
+
jwt = headers.get("X-Beamlit-Authorization", "").replace("Bearer ", "")
|
41
41
|
params = {"environment": agent_model.environment}
|
42
42
|
chat_classes = {
|
43
43
|
"openai": {
|
44
44
|
"func": get_openai_chat_model,
|
45
45
|
"kwargs": {
|
46
|
+
"http_async_client": client.get_async_httpx_client(),
|
46
47
|
"http_client": client.get_httpx_client(),
|
47
48
|
},
|
48
49
|
},
|
beamlit/agents/decorator.py
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# Import necessary modules
|
2
2
|
import ast
|
3
|
+
import asyncio
|
3
4
|
import importlib
|
4
5
|
import os
|
5
6
|
from logging import getLogger
|
6
7
|
|
7
|
-
from langgraph.checkpoint.memory import MemorySaver
|
8
|
-
from langgraph.prebuilt import create_react_agent
|
9
|
-
|
10
8
|
from beamlit.api.models import get_model_deployment
|
11
9
|
from beamlit.authentication import new_client
|
12
10
|
from beamlit.common.settings import get_settings, init
|
13
11
|
from beamlit.errors import UnexpectedStatus
|
14
12
|
from beamlit.models import AgentDeployment
|
13
|
+
from langchain_core.tools import Tool
|
14
|
+
from langgraph.checkpoint.memory import MemorySaver
|
15
|
+
from langgraph.prebuilt import create_react_agent
|
15
16
|
|
16
17
|
from .chat import get_chat_model
|
17
18
|
|
@@ -35,7 +36,7 @@ def get_functions(dir="src/functions", from_decorator="function"):
|
|
35
36
|
# Look for function definitions with decorators
|
36
37
|
for node in ast.walk(tree):
|
37
38
|
if (
|
38
|
-
not isinstance(node, ast.FunctionDef)
|
39
|
+
(not isinstance(node, ast.FunctionDef) and not isinstance(node, ast.AsyncFunctionDef))
|
39
40
|
or len(node.decorator_list) == 0
|
40
41
|
):
|
41
42
|
continue
|
@@ -73,7 +74,10 @@ def get_functions(dir="src/functions", from_decorator="function"):
|
|
73
74
|
# Get the decorated function
|
74
75
|
if not is_kit and hasattr(module, func_name):
|
75
76
|
func = getattr(module, func_name)
|
76
|
-
|
77
|
+
if asyncio.iscoroutinefunction(func):
|
78
|
+
functions.append(Tool(name=func.__name__, description=func.__doc__, func=func, coroutine=func))
|
79
|
+
else:
|
80
|
+
functions.append(Tool(name=func.__name__, description=func.__doc__, func=func))
|
77
81
|
except Exception as e:
|
78
82
|
logger.warning(f"Error processing {file_path}: {e!s}")
|
79
83
|
return functions
|
@@ -130,7 +130,7 @@ def load_credentials(workspace_name: str) -> Credentials:
|
|
130
130
|
def load_credentials_from_settings(settings: Settings) -> Credentials:
|
131
131
|
return Credentials(
|
132
132
|
api_key=settings.authentication.api_key,
|
133
|
-
client_credentials=settings.authentication.
|
133
|
+
client_credentials=settings.authentication.client.credentials,
|
134
134
|
)
|
135
135
|
|
136
136
|
|
beamlit/client.py
CHANGED
@@ -248,7 +248,6 @@ class AuthenticatedClient:
|
|
248
248
|
def get_async_httpx_client(self) -> httpx.AsyncClient:
|
249
249
|
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
|
250
250
|
if self._async_client is None:
|
251
|
-
self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
|
252
251
|
self._async_client = httpx.AsyncClient(
|
253
252
|
base_url=self._base_url,
|
254
253
|
cookies=self._cookies,
|
beamlit/common/settings.py
CHANGED
@@ -2,16 +2,6 @@ import os
|
|
2
2
|
from logging import getLogger
|
3
3
|
from typing import List, Tuple, Type, Union
|
4
4
|
|
5
|
-
from langchain_core.language_models.chat_models import BaseChatModel
|
6
|
-
from langgraph.graph.graph import CompiledGraph
|
7
|
-
from pydantic import Field
|
8
|
-
from pydantic_settings import (
|
9
|
-
BaseSettings,
|
10
|
-
PydanticBaseSettingsSource,
|
11
|
-
SettingsConfigDict,
|
12
|
-
YamlConfigSettingsSource,
|
13
|
-
)
|
14
|
-
|
15
5
|
from beamlit.api.functions import get_function_deployment
|
16
6
|
from beamlit.api.models import get_model_deployment
|
17
7
|
from beamlit.client import AuthenticatedClient
|
@@ -20,6 +10,11 @@ from beamlit.models.agent_deployment import AgentDeployment
|
|
20
10
|
from beamlit.models.function_deployment import FunctionDeployment
|
21
11
|
from beamlit.models.model_deployment import ModelDeployment
|
22
12
|
from beamlit.types import UNSET, Unset
|
13
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
14
|
+
from langgraph.graph.graph import CompiledGraph
|
15
|
+
from pydantic import Field
|
16
|
+
from pydantic_settings import (BaseSettings, PydanticBaseSettingsSource,
|
17
|
+
SettingsConfigDict, YamlConfigSettingsSource)
|
23
18
|
|
24
19
|
global SETTINGS
|
25
20
|
SETTINGS = None
|
@@ -39,10 +34,14 @@ class SettingsAgent(BaseSettings):
|
|
39
34
|
module: str = Field(default="main.main")
|
40
35
|
|
41
36
|
|
37
|
+
class SettingsAuthenticationClient(BaseSettings):
|
38
|
+
credentials: Union[None, str] = None
|
39
|
+
|
40
|
+
|
42
41
|
class SettingsAuthentication(BaseSettings):
|
43
42
|
api_key: Union[None, str] = None
|
44
43
|
jwt: Union[None, str] = None
|
45
|
-
|
44
|
+
client: SettingsAuthenticationClient = SettingsAuthenticationClient()
|
46
45
|
|
47
46
|
|
48
47
|
class SettingsServer(BaseSettings):
|
@@ -56,14 +55,17 @@ class Settings(BaseSettings):
|
|
56
55
|
yaml_file="beamlit.yaml",
|
57
56
|
env_prefix="bl_",
|
58
57
|
env_nested_delimiter="_",
|
58
|
+
extra="ignore",
|
59
59
|
)
|
60
60
|
|
61
61
|
workspace: str
|
62
62
|
environment: str = Field(default="production")
|
63
|
+
remote: bool = Field(default=False)
|
63
64
|
type: str = Field(default="agent")
|
64
65
|
name: str = Field(default="beamlit-agent")
|
65
66
|
base_url: str = Field(default="https://api.beamlit.dev/v0")
|
66
67
|
run_url: str = Field(default="https://run.beamlit.dev")
|
68
|
+
registry_url: str = Field(default="https://serverless-registry-production.beamlit.workers.dev")
|
67
69
|
log_level: str = Field(default="INFO")
|
68
70
|
agent: SettingsAgent = SettingsAgent()
|
69
71
|
server: SettingsServer = SettingsServer()
|
beamlit/deploy/deploy.py
ADDED
@@ -0,0 +1,503 @@
|
|
1
|
+
import ast
|
2
|
+
import importlib
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from logging import getLogger
|
8
|
+
from typing import Callable, Literal
|
9
|
+
|
10
|
+
from beamlit.common.settings import Settings, get_settings, init
|
11
|
+
from beamlit.models import (AgentChain, AgentDeployment, Flavor,
|
12
|
+
FunctionDeployment, Runtime,
|
13
|
+
StoreFunctionParameter)
|
14
|
+
|
15
|
+
sys.path.insert(0, os.getcwd())
|
16
|
+
sys.path.insert(0, os.path.join(os.getcwd(), "src"))
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass
|
20
|
+
class Resource:
|
21
|
+
type: Literal["agent", "function"]
|
22
|
+
module: Callable
|
23
|
+
name: str
|
24
|
+
decorator: ast.Call
|
25
|
+
func: Callable
|
26
|
+
|
27
|
+
def get_resources(from_decorator, dir="src") -> list[Resource]:
|
28
|
+
"""
|
29
|
+
Scans through Python files in a directory to find functions decorated with a specific decorator.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
from_decorator (str): The name of the decorator to search for
|
33
|
+
dir (str): The directory to scan, defaults to "src"
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
list[Resource]: List of Resource objects containing information about decorated functions
|
37
|
+
"""
|
38
|
+
resources = []
|
39
|
+
logger = getLogger(__name__)
|
40
|
+
|
41
|
+
# Walk through all Python files in resources directory and subdirectories
|
42
|
+
for root, _, files in os.walk(dir):
|
43
|
+
for file in files:
|
44
|
+
if file.endswith(".py"):
|
45
|
+
file_path = os.path.join(root, file)
|
46
|
+
# Read and compile the file content
|
47
|
+
with open(file_path) as f:
|
48
|
+
try:
|
49
|
+
file_content = f.read()
|
50
|
+
# Parse the file content to find decorated resources
|
51
|
+
tree = ast.parse(file_content)
|
52
|
+
|
53
|
+
# Look for function definitions with decorators
|
54
|
+
for node in ast.walk(tree):
|
55
|
+
if (
|
56
|
+
not isinstance(node, ast.FunctionDef) and not isinstance(node, ast.AsyncFunctionDef)
|
57
|
+
) or len(node.decorator_list) == 0:
|
58
|
+
continue
|
59
|
+
decorator = node.decorator_list[0]
|
60
|
+
|
61
|
+
decorator_name = ""
|
62
|
+
if isinstance(decorator, ast.Call):
|
63
|
+
decorator_name = decorator.func.id
|
64
|
+
if isinstance(decorator, ast.Name):
|
65
|
+
decorator_name = decorator.id
|
66
|
+
if decorator_name == from_decorator:
|
67
|
+
# Get the function name and decorator name
|
68
|
+
func_name = node.name
|
69
|
+
|
70
|
+
# Import the module to get the actual function
|
71
|
+
spec = importlib.util.spec_from_file_location(func_name, file_path)
|
72
|
+
module = importlib.util.module_from_spec(spec)
|
73
|
+
spec.loader.exec_module(module)
|
74
|
+
# Check if kit=True in the decorator arguments
|
75
|
+
|
76
|
+
# Get the decorated function
|
77
|
+
if hasattr(module, func_name) and isinstance(decorator, ast.Call):
|
78
|
+
|
79
|
+
resources.append(
|
80
|
+
Resource(
|
81
|
+
type=decorator_name,
|
82
|
+
module=module,
|
83
|
+
name=func_name,
|
84
|
+
func=getattr(module, func_name),
|
85
|
+
decorator=decorator,
|
86
|
+
)
|
87
|
+
)
|
88
|
+
except Exception as e:
|
89
|
+
logger.warning(f"Error processing {file_path}: {e!s}")
|
90
|
+
return resources
|
91
|
+
|
92
|
+
|
93
|
+
def get_parameters(resource: Resource) -> list[StoreFunctionParameter]:
|
94
|
+
"""
|
95
|
+
Extracts parameter information from a function's signature and docstring.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
resource (Resource): The resource object containing the function to analyze
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
list[StoreFunctionParameter]: List of parameter objects with name, type, required status, and description
|
102
|
+
"""
|
103
|
+
parameters = []
|
104
|
+
# Get function signature
|
105
|
+
import inspect
|
106
|
+
sig = inspect.signature(resource.func)
|
107
|
+
# Get docstring for parameter descriptions
|
108
|
+
docstring = inspect.getdoc(resource.func)
|
109
|
+
param_descriptions = {}
|
110
|
+
if docstring:
|
111
|
+
# Parse docstring for parameter descriptions
|
112
|
+
lines = docstring.split('\n')
|
113
|
+
for line in lines:
|
114
|
+
line = line.strip().lower()
|
115
|
+
if line.startswith(':param '):
|
116
|
+
# Extract parameter name and description
|
117
|
+
param_line = line[7:].split(':', 1)
|
118
|
+
if len(param_line) == 2:
|
119
|
+
param_name = param_line[0].strip()
|
120
|
+
param_desc = param_line[1].strip()
|
121
|
+
param_descriptions[param_name] = param_desc
|
122
|
+
for name, param in sig.parameters.items():
|
123
|
+
# Skip *args and **kwargs parameters
|
124
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
125
|
+
|
126
|
+
continue
|
127
|
+
|
128
|
+
param_type = "string" # Default type
|
129
|
+
type_mapping = {
|
130
|
+
'str': 'string',
|
131
|
+
'int': 'integer',
|
132
|
+
'float': 'number',
|
133
|
+
'bool': 'boolean',
|
134
|
+
'list': 'array',
|
135
|
+
'dict': 'object',
|
136
|
+
'none': 'null'
|
137
|
+
}
|
138
|
+
if param.annotation != inspect.Parameter.empty:
|
139
|
+
# Map Python types to OpenAPI types
|
140
|
+
if hasattr(param.annotation, "__name__"):
|
141
|
+
param_type = param.annotation.__name__.lower()
|
142
|
+
else:
|
143
|
+
# Handle special types like Union, Optional etc
|
144
|
+
param_type = str(param.annotation).lower()
|
145
|
+
parameter = StoreFunctionParameter(
|
146
|
+
name=name,
|
147
|
+
type_=type_mapping.get(param_type, "string"),
|
148
|
+
required=param.default == inspect.Parameter.empty,
|
149
|
+
description=param_descriptions.get(name, f"Parameter {name}")
|
150
|
+
)
|
151
|
+
parameters.append(parameter)
|
152
|
+
|
153
|
+
return parameters
|
154
|
+
|
155
|
+
|
156
|
+
def get_description(description: str | None, resource: Resource) -> str:
|
157
|
+
"""
|
158
|
+
Gets the description of a function from either a provided description or the function's docstring.
|
159
|
+
|
160
|
+
Args:
|
161
|
+
description (str | None): Optional explicit description
|
162
|
+
resource (Resource): The resource object containing the function
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
str: The function description
|
166
|
+
"""
|
167
|
+
if description:
|
168
|
+
return description
|
169
|
+
doc = resource.func.__doc__
|
170
|
+
if doc:
|
171
|
+
# Split docstring into sections and get only the description part
|
172
|
+
doc_lines = doc.split('\n')
|
173
|
+
description_lines = []
|
174
|
+
for line in doc_lines:
|
175
|
+
line = line.strip()
|
176
|
+
# Stop when we hit param/return sections
|
177
|
+
if line.startswith(':param') or line.startswith(':return'):
|
178
|
+
break
|
179
|
+
if line:
|
180
|
+
description_lines.append(line)
|
181
|
+
return ' '.join(description_lines).strip()
|
182
|
+
return ""
|
183
|
+
|
184
|
+
def get_kwargs(arg: ast.Call) -> dict:
|
185
|
+
"""
|
186
|
+
Extracts keyword arguments from an AST Call node.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
arg (ast.Call): The AST Call node to process
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
dict: Dictionary of keyword arguments and their values
|
193
|
+
"""
|
194
|
+
kwargs = {}
|
195
|
+
for keyword in arg.keywords:
|
196
|
+
if isinstance(keyword.value, ast.Constant):
|
197
|
+
kwargs[keyword.arg] = keyword.value.value
|
198
|
+
elif isinstance(keyword.value, (ast.List, ast.Tuple)):
|
199
|
+
kwargs[keyword.arg] = [
|
200
|
+
AgentChain(**get_kwargs(elem)) if isinstance(elem, ast.Call) and isinstance(elem.func, ast.Name) and elem.func.id == "AgentChain"
|
201
|
+
else elem.value if isinstance(elem, ast.Constant) else elem
|
202
|
+
for elem in keyword.value.elts
|
203
|
+
]
|
204
|
+
elif isinstance(keyword.value, ast.Dict):
|
205
|
+
kwargs[keyword.arg] = {}
|
206
|
+
for k, v in zip(keyword.value.keys, keyword.value.values):
|
207
|
+
if isinstance(k, ast.Constant) and isinstance(v, ast.Constant):
|
208
|
+
kwargs[keyword.arg][k.value] = v.value
|
209
|
+
if isinstance(k, ast.Constant) and isinstance(v, ast.Call):
|
210
|
+
kwargs[keyword.arg][k.value] = get_kwargs(v)
|
211
|
+
return kwargs
|
212
|
+
|
213
|
+
def get_runtime(type: str, name: str) -> Runtime:
|
214
|
+
settings = get_settings()
|
215
|
+
registry_url = settings.registry_url.replace("https://", "").replace("http://", "")
|
216
|
+
image = f"{registry_url}/{settings.workspace}/{type}s/{name}"
|
217
|
+
return Runtime(image=image)
|
218
|
+
|
219
|
+
def get_beamlit_deployment_from_resource(resource: Resource) -> AgentDeployment | FunctionDeployment:
|
220
|
+
"""
|
221
|
+
Creates a deployment configuration from a resource.
|
222
|
+
|
223
|
+
Args:
|
224
|
+
resource (Resource): The resource to create a deployment for
|
225
|
+
|
226
|
+
Returns:
|
227
|
+
AgentDeployment | FunctionDeployment: The deployment configuration
|
228
|
+
"""
|
229
|
+
for arg in resource.decorator.args:
|
230
|
+
if isinstance(arg, ast.Call):
|
231
|
+
if isinstance(arg.func, ast.Name) and arg.func.id == "AgentDeployment":
|
232
|
+
kwargs = get_kwargs(arg)
|
233
|
+
description = kwargs.pop("description", None)
|
234
|
+
return AgentDeployment(
|
235
|
+
**kwargs,
|
236
|
+
description=get_description(description, resource),
|
237
|
+
runtime=get_runtime("agent", kwargs.get("agent", resource.name))
|
238
|
+
)
|
239
|
+
if isinstance(arg.func, ast.Name) and arg.func.id == "FunctionDeployment":
|
240
|
+
kwargs = get_kwargs(arg)
|
241
|
+
description = kwargs.pop("description", None)
|
242
|
+
return FunctionDeployment(
|
243
|
+
**kwargs,
|
244
|
+
parameters=get_parameters(resource),
|
245
|
+
description=get_description(description, resource),
|
246
|
+
runtime=get_runtime("function", kwargs.get("function", resource.name))
|
247
|
+
)
|
248
|
+
for arg in resource.decorator.keywords:
|
249
|
+
if isinstance(arg.value, ast.Call):
|
250
|
+
if isinstance(arg.value.func, ast.Name) and arg.value.func.id == "AgentDeployment":
|
251
|
+
kwargs = get_kwargs(arg.value)
|
252
|
+
description = kwargs.pop("description", None)
|
253
|
+
return AgentDeployment(
|
254
|
+
**kwargs,
|
255
|
+
description=get_description(description, resource),
|
256
|
+
runtime=get_runtime("agent", kwargs.get("agent", resource.name))
|
257
|
+
)
|
258
|
+
if isinstance(arg.value.func, ast.Name) and arg.value.func.id == "FunctionDeployment":
|
259
|
+
kwargs = get_kwargs(arg.value)
|
260
|
+
description = kwargs.pop("description", None)
|
261
|
+
return FunctionDeployment(
|
262
|
+
**kwargs,
|
263
|
+
parameters=get_parameters(resource),
|
264
|
+
description=get_description(description, resource),
|
265
|
+
runtime=get_runtime("function", kwargs.get("function", resource.name))
|
266
|
+
)
|
267
|
+
if resource.type == "agent":
|
268
|
+
return AgentDeployment(
|
269
|
+
agent=resource.name,
|
270
|
+
description=get_description(None,resource),
|
271
|
+
runtime=get_runtime("agent", resource.name)
|
272
|
+
)
|
273
|
+
if resource.type == "function":
|
274
|
+
return FunctionDeployment(
|
275
|
+
function=resource.name,
|
276
|
+
parameters=get_parameters(resource),
|
277
|
+
description=get_description(None,resource),
|
278
|
+
runtime=get_runtime("function", resource.name)
|
279
|
+
)
|
280
|
+
return None
|
281
|
+
|
282
|
+
|
283
|
+
def get_flavors(flavors: list[Flavor]) -> str:
|
284
|
+
"""
|
285
|
+
Converts a list of Flavor objects to JSON string.
|
286
|
+
|
287
|
+
Args:
|
288
|
+
flavors (list[Flavor]): List of Flavor objects
|
289
|
+
|
290
|
+
Returns:
|
291
|
+
str: JSON string representation of flavors
|
292
|
+
"""
|
293
|
+
if not flavors:
|
294
|
+
return "[]"
|
295
|
+
return json.dumps([flavor.to_dict() for flavor in flavors])
|
296
|
+
|
297
|
+
def format_parameters(parameters: list[StoreFunctionParameter]) -> str:
|
298
|
+
"""
|
299
|
+
Formats function parameters into YAML-compatible string.
|
300
|
+
|
301
|
+
Args:
|
302
|
+
parameters (list[StoreFunctionParameter]): List of parameter objects
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
str: YAML-formatted string of parameters
|
306
|
+
"""
|
307
|
+
if not parameters:
|
308
|
+
return "[]"
|
309
|
+
|
310
|
+
formatted = []
|
311
|
+
for param in parameters:
|
312
|
+
formatted.append(f"""
|
313
|
+
- name: {param.name}
|
314
|
+
type: {param.type_}
|
315
|
+
required: {str(param.required).lower()}
|
316
|
+
description: {param.description}""")
|
317
|
+
|
318
|
+
return "\n".join(formatted)
|
319
|
+
|
320
|
+
def format_agent_chain(agent_chain: list[AgentChain]) -> str:
|
321
|
+
"""
|
322
|
+
Formats agent chain configuration into YAML-compatible string.
|
323
|
+
|
324
|
+
Args:
|
325
|
+
agent_chain (list[AgentChain]): List of agent chain configurations
|
326
|
+
|
327
|
+
Returns:
|
328
|
+
str: YAML-formatted string of agent chain
|
329
|
+
"""
|
330
|
+
if not agent_chain:
|
331
|
+
return "[]"
|
332
|
+
formatted = []
|
333
|
+
|
334
|
+
for agent in agent_chain:
|
335
|
+
formatted.append(f"""
|
336
|
+
- agent: {agent.name}
|
337
|
+
enabled: {agent.enabled}""")
|
338
|
+
if agent.description:
|
339
|
+
formatted.append(f" description: {agent.description}")
|
340
|
+
return "\n".join(formatted)
|
341
|
+
|
342
|
+
def get_agent_yaml(agent: AgentDeployment, functions: list[tuple[Resource, FunctionDeployment]], settings: Settings) -> str:
|
343
|
+
"""
|
344
|
+
Generates YAML configuration for an agent deployment.
|
345
|
+
|
346
|
+
Args:
|
347
|
+
agent (AgentDeployment): Agent deployment configuration
|
348
|
+
functions (list[tuple[Resource, FunctionDeployment]]): List of associated functions
|
349
|
+
settings (Settings): Application settings
|
350
|
+
|
351
|
+
Returns:
|
352
|
+
str: YAML configuration string
|
353
|
+
"""
|
354
|
+
template = f"""
|
355
|
+
apiVersion: beamlit.com/v1alpha1
|
356
|
+
kind: Agent
|
357
|
+
metadata:
|
358
|
+
name: {agent.agent}
|
359
|
+
spec:
|
360
|
+
display_name: {agent.agent}
|
361
|
+
deployments:
|
362
|
+
- environment: {settings.environment}
|
363
|
+
enabled: true
|
364
|
+
policies: [{", ".join(agent.policies or [])}]
|
365
|
+
functions: [{", ".join([f"{function.function}" for (_, function) in functions])}]
|
366
|
+
agent_chain: {format_agent_chain(agent.agent_chain)}
|
367
|
+
model: {agent.model}
|
368
|
+
runtime:
|
369
|
+
image: {agent.runtime.image}
|
370
|
+
"""
|
371
|
+
if agent.description:
|
372
|
+
template += f""" description: |
|
373
|
+
{agent.description}"""
|
374
|
+
return template
|
375
|
+
|
376
|
+
def get_function_yaml(function: FunctionDeployment, settings: Settings) -> str:
|
377
|
+
"""
|
378
|
+
Generates YAML configuration for a function deployment.
|
379
|
+
|
380
|
+
Args:
|
381
|
+
function (FunctionDeployment): Function deployment configuration
|
382
|
+
settings (Settings): Application settings
|
383
|
+
|
384
|
+
Returns:
|
385
|
+
str: YAML configuration string
|
386
|
+
"""
|
387
|
+
return f"""
|
388
|
+
apiVersion: beamlit.com/v1alpha1
|
389
|
+
kind: Function
|
390
|
+
metadata:
|
391
|
+
name: {function.function}
|
392
|
+
spec:
|
393
|
+
display_name: {function.function}
|
394
|
+
deployments:
|
395
|
+
- environment: {settings.environment}
|
396
|
+
enabled: true
|
397
|
+
policies: [{", ".join(function.policies or [])}]
|
398
|
+
description: |
|
399
|
+
{function.description}
|
400
|
+
parameters: {format_parameters(function.parameters)}
|
401
|
+
runtime:
|
402
|
+
image: {function.runtime.image}
|
403
|
+
"""
|
404
|
+
|
405
|
+
def dockerfile(type: Literal["agent", "function"], resource: Resource, deployment: AgentDeployment | FunctionDeployment) -> str:
|
406
|
+
"""
|
407
|
+
Generates Dockerfile content for agent or function deployment.
|
408
|
+
|
409
|
+
Args:
|
410
|
+
type (Literal["agent", "function"]): Type of deployment
|
411
|
+
resource (Resource): Resource to be deployed
|
412
|
+
deployment (AgentDeployment | FunctionDeployment): Deployment configuration
|
413
|
+
|
414
|
+
Returns:
|
415
|
+
str: Dockerfile content
|
416
|
+
"""
|
417
|
+
if type == "agent":
|
418
|
+
module = f"{resource.module.__file__.split('/')[-1].replace('.py', '')}.{resource.module.__name__}"
|
419
|
+
else:
|
420
|
+
module = f"functions.{resource.module.__name__}.{resource.func.__name__}"
|
421
|
+
cmd = ["bl", "serve", "--port", "80", "--module", module]
|
422
|
+
if type == "agent":
|
423
|
+
cmd.append("--remote")
|
424
|
+
cmd_str = ','.join([f'"{c}"' for c in cmd])
|
425
|
+
|
426
|
+
return f"""
|
427
|
+
FROM python:3.12-slim
|
428
|
+
|
429
|
+
ARG UV_VERSION="latest"
|
430
|
+
RUN apt update && apt install -y curl
|
431
|
+
|
432
|
+
# Install uv.
|
433
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
434
|
+
RUN curl -fsSL https://raw.githubusercontent.com/beamlit/toolkit/main/install.sh | BINDIR=/bin sh
|
435
|
+
WORKDIR /beamlit
|
436
|
+
|
437
|
+
# Install the application dependencies.
|
438
|
+
COPY pyproject.toml /beamlit/pyproject.toml
|
439
|
+
COPY uv.lock /beamlit/uv.lock
|
440
|
+
RUN uv sync --no-cache
|
441
|
+
|
442
|
+
COPY README.md /beamlit/README.md
|
443
|
+
COPY LICENSE /beamlit/LICENSE
|
444
|
+
COPY src /beamlit/src
|
445
|
+
|
446
|
+
ENV PATH="/beamlit/.venv/bin:$PATH"
|
447
|
+
|
448
|
+
ENTRYPOINT [{cmd_str}]
|
449
|
+
"""
|
450
|
+
|
451
|
+
def generate_beamlit_deployment(directory: str):
|
452
|
+
"""
|
453
|
+
Generates all necessary deployment files for Beamlit agents and functions.
|
454
|
+
|
455
|
+
Args:
|
456
|
+
directory (str): Target directory for generated files
|
457
|
+
|
458
|
+
Creates:
|
459
|
+
- Agent and function YAML configurations
|
460
|
+
- Dockerfiles for each deployment
|
461
|
+
- Directory structure for agents and functions
|
462
|
+
"""
|
463
|
+
settings = init()
|
464
|
+
logger = getLogger(__name__)
|
465
|
+
logger.info(f"Importing server module: {settings.server.module}")
|
466
|
+
functions: list[tuple[Resource, FunctionDeployment]] = []
|
467
|
+
agents: list[tuple[Resource, AgentDeployment]] = []
|
468
|
+
for agent in get_resources("agent"):
|
469
|
+
agent_deployment = get_beamlit_deployment_from_resource(agent)
|
470
|
+
if agent_deployment:
|
471
|
+
agents.append((agent, agent_deployment))
|
472
|
+
for function in get_resources("function"):
|
473
|
+
function_deployment = get_beamlit_deployment_from_resource(function)
|
474
|
+
if function_deployment:
|
475
|
+
functions.append((function, function_deployment))
|
476
|
+
|
477
|
+
agents_dir = os.path.join(directory, "agents")
|
478
|
+
functions_dir = os.path.join(directory, "functions")
|
479
|
+
# Create directory if it doesn't exist
|
480
|
+
os.makedirs(agents_dir, exist_ok=True)
|
481
|
+
os.makedirs(functions_dir, exist_ok=True)
|
482
|
+
for (resource, agent) in agents:
|
483
|
+
# write deployment file
|
484
|
+
agent_dir = os.path.join(agents_dir, agent.agent)
|
485
|
+
os.makedirs(agent_dir, exist_ok=True)
|
486
|
+
with open(os.path.join(agent_dir, f"agent.yaml"), "w") as f:
|
487
|
+
content = get_agent_yaml(agent, functions, settings)
|
488
|
+
f.write(content)
|
489
|
+
# write dockerfile for build
|
490
|
+
with open(os.path.join(agent_dir, f"Dockerfile"), "w") as f:
|
491
|
+
content = dockerfile("agent", resource, agent)
|
492
|
+
f.write(content)
|
493
|
+
for (resource, function) in functions:
|
494
|
+
# write deployment file
|
495
|
+
function_dir = os.path.join(functions_dir, function.function)
|
496
|
+
os.makedirs(function_dir, exist_ok=True)
|
497
|
+
with open(os.path.join(function_dir, f"function.yaml"), "w") as f:
|
498
|
+
content = get_function_yaml(function, settings)
|
499
|
+
f.write(content)
|
500
|
+
# write dockerfile for build
|
501
|
+
with open(os.path.join(function_dir, f"Dockerfile"), "w") as f:
|
502
|
+
content = dockerfile("function", resource, function)
|
503
|
+
f.write(content)
|
beamlit/functions/decorator.py
CHANGED
@@ -3,12 +3,11 @@
|
|
3
3
|
from collections.abc import Callable
|
4
4
|
from logging import getLogger
|
5
5
|
|
6
|
-
from langchain_core.tools import create_schema_from_function, tool
|
7
|
-
|
8
6
|
from beamlit.authentication import new_client
|
9
7
|
from beamlit.common.settings import get_settings
|
10
8
|
from beamlit.models import FunctionDeployment, FunctionKit
|
11
9
|
from beamlit.run import RunClient
|
10
|
+
from langchain_core.tools import create_schema_from_function
|
12
11
|
|
13
12
|
logger = getLogger(__name__)
|
14
13
|
|
@@ -58,7 +57,7 @@ def kit(bl_kit: FunctionKit = None, **kwargs: dict) -> Callable:
|
|
58
57
|
def wrapper(func: Callable) -> Callable:
|
59
58
|
if bl_kit and not func.__doc__ and bl_kit.description:
|
60
59
|
func.__doc__ = bl_kit.description
|
61
|
-
return
|
60
|
+
return func
|
62
61
|
|
63
62
|
return wrapper
|
64
63
|
|
@@ -72,10 +71,7 @@ def function(
|
|
72
71
|
def wrapper(func: Callable) -> Callable:
|
73
72
|
if bl_function and not func.__doc__ and bl_function.description:
|
74
73
|
func.__doc__ = bl_function.description
|
75
|
-
if
|
76
|
-
settings.environment == "development"
|
77
|
-
or settings.environment == "production"
|
78
|
-
):
|
74
|
+
if settings.remote:
|
79
75
|
remote_func = get_remote_function(func, bl_function)
|
80
76
|
if not kwargs.get("args_schema"):
|
81
77
|
kwargs["args_schema"] = create_schema_from_function(
|
@@ -83,8 +79,7 @@ def function(
|
|
83
79
|
func,
|
84
80
|
parse_docstring=func.__doc__,
|
85
81
|
)
|
86
|
-
return
|
87
|
-
return
|
82
|
+
return remote_func
|
83
|
+
return func
|
88
84
|
|
89
85
|
return wrapper
|
90
|
-
return wrapper
|
beamlit/serve/app.py
CHANGED
@@ -16,7 +16,6 @@ from .middlewares import AccessLogMiddleware, AddProcessTimeHeader
|
|
16
16
|
sys.path.insert(0, os.getcwd())
|
17
17
|
sys.path.insert(0, os.path.join(os.getcwd(), "src"))
|
18
18
|
|
19
|
-
|
20
19
|
def import_module():
|
21
20
|
settings = get_settings()
|
22
21
|
main_module = importlib.import_module(".".join(settings.server.module.split(".")[0:-1]))
|
@@ -1,12 +1,12 @@
|
|
1
1
|
beamlit/__init__.py,sha256=545gFC-wLLwUktWcOAjUWe_Glha40tBetRTOYSfHnbI,164
|
2
|
-
beamlit/client.py,sha256=
|
2
|
+
beamlit/client.py,sha256=OdRbs5VVHF32HUd5RMcnkhe8YxamnAmgGlxO6pm1Xac,12431
|
3
3
|
beamlit/errors.py,sha256=gO8GBmKqmSNgAg-E5oT-oOyxztvp7V_6XG7OUTT15q0,546
|
4
4
|
beamlit/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
|
5
5
|
beamlit/run.py,sha256=y61iDBaR0917ihj5q-cJ_r3BFW1Rn5K_kDAISw5O6aU,1339
|
6
6
|
beamlit/types.py,sha256=E1hhDh_zXfsSQ0NCt9-uw90_Mr5iIlsdfnfvxv5HarU,1005
|
7
7
|
beamlit/agents/__init__.py,sha256=nf1iwQwGtCG6nDqyVhxfWoqR6dv6X3bvSpCeqkTCFaM,101
|
8
|
-
beamlit/agents/chat.py,sha256=
|
9
|
-
beamlit/agents/decorator.py,sha256=
|
8
|
+
beamlit/agents/chat.py,sha256=aI7pObyywRyg3dBpubzHAUWTbTk1nwtxvpY7iIP1RLY,2704
|
9
|
+
beamlit/agents/decorator.py,sha256=XDrWN-Xx4czSy1sIkfQY4t9-UnsM21vMll58CP1TRLw,6468
|
10
10
|
beamlit/api/__init__.py,sha256=zTSiG_ujSjAqWPyc435YXaX9XTlpMjiJWBbV-f-YtdA,45
|
11
11
|
beamlit/api/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
12
|
beamlit/api/agents/create_agent.py,sha256=HFExosu02JZqZz7I6U6WjN81TERz6p2i8CzQCyiRYXo,4112
|
@@ -130,18 +130,20 @@ beamlit/api/workspaces/update_workspace.py,sha256=qa5DV2UJSUYuB_ibALb4E9ghKpT1Ha
|
|
130
130
|
beamlit/api/workspaces/update_workspace_user_role.py,sha256=Yn9iuJ4tKtauzBiJyU4-wYUMS9g98X2Om8zs7UkzrY8,4917
|
131
131
|
beamlit/authentication/__init__.py,sha256=wiXqRbc7E-ulrH_ueA9duOGFvXeo7-RvhSD1XbFogMo,1020
|
132
132
|
beamlit/authentication/apikey.py,sha256=jnz1FMRauI5qAInqeeDER8aCONx4O8ZPZGedvi3Ap_o,659
|
133
|
-
beamlit/authentication/authentication.py,sha256=
|
133
|
+
beamlit/authentication/authentication.py,sha256=tZu8GoVueKDuq1RLXMvtHcV95XLikmQ19PCxLWBn2Ek,3120
|
134
134
|
beamlit/authentication/clientcredentials.py,sha256=6kbfTjwUkXUArJX8XZLe9ZzbEicQc19tSXBvsTpiXMk,3954
|
135
|
-
beamlit/authentication/credentials.py,sha256=
|
135
|
+
beamlit/authentication/credentials.py,sha256=DlfiF_FfOosPVsRoa39JSR3XoICmhBqTlHXc6b4zWtE,5349
|
136
136
|
beamlit/authentication/device_mode.py,sha256=oQVBCDsq-pdeXF31WSTAAEdaX6eACV7SYcOSyf3ea_Q,3728
|
137
137
|
beamlit/common/__init__.py,sha256=yDoMJDKj-xjTGl7U1YI59KpWxiOV65HSiUulgO8xdTA,277
|
138
138
|
beamlit/common/generate.py,sha256=VJ_MiRDulXdQdnlKdM4_Bod6CO6DOGlFiosGXOLuLGs,7227
|
139
139
|
beamlit/common/logger.py,sha256=ayabnsoHS8ncXm8EpBS01FkvSe0XRcaNdQjKVuPI5z4,1025
|
140
140
|
beamlit/common/secrets.py,sha256=sid81bOe3LflkMKDHwBsBs9nIju8bp5-v9qU9gkyNMc,212
|
141
|
-
beamlit/common/settings.py,sha256=
|
141
|
+
beamlit/common/settings.py,sha256=N0VzveNQ3vAE6ZJWMnWrIVi2hXidLtkRZkAXQTGo40A,5546
|
142
142
|
beamlit/common/utils.py,sha256=jouz5igBvT37Xn_e94-foCHyQczVim-UzVcoIF6RWJ4,657
|
143
|
+
beamlit/deploy/__init__.py,sha256=GS7l7Jtm2yKs7iNLKcfjYO-rAhUzggQ3xiYSf3oxLBY,91
|
144
|
+
beamlit/deploy/deploy.py,sha256=bWUXG_5b_XhFcG5t75vel2EbEfZPIdg_YLXtzkfgXUs,18799
|
143
145
|
beamlit/functions/__init__.py,sha256=_RPG1Bfg54JGdIPnViAU6n9zD7E1cDNsdXi8oYGskzE,138
|
144
|
-
beamlit/functions/decorator.py,sha256
|
146
|
+
beamlit/functions/decorator.py,sha256=uYZOVxD-7ZNHORTQfCIn0qdNKPIZupsr7_QdUmWEKu0,2996
|
145
147
|
beamlit/functions/github/__init__.py,sha256=gYnUkeegukOfbymdabuuJkScvH-_ZJygX05BoqkPn0o,49
|
146
148
|
beamlit/functions/github/github.py,sha256=FajzLCNkpXcwfgnC0l9rOGT2eSPLCz8-qrMzK9N_ZNc,598
|
147
149
|
beamlit/functions/github/kit/__init__.py,sha256=jBwPqZv6C23_utukohxqXZwrlicNlI7PYPUj0Den7Cw,136
|
@@ -292,10 +294,10 @@ beamlit/models/websocket_channel.py,sha256=tyNtsVR0cOwd6BK--ehBCH8bIjxtyPhiAkrxY
|
|
292
294
|
beamlit/models/workspace.py,sha256=s7wS6ibswosB0FdUb3ry3BnlLa325axBdYPLI3ipe0Q,3986
|
293
295
|
beamlit/models/workspace_labels.py,sha256=WbnUY6eCTkUNdY7hhhSF-KQCl8fWFfkCf7hzCTiNp4A,1246
|
294
296
|
beamlit/models/workspace_user.py,sha256=70CcifQWYbeWG7TDui4pblTzUe5sVK0AS19vNCzKE8g,3423
|
295
|
-
beamlit/serve/app.py,sha256
|
297
|
+
beamlit/serve/app.py,sha256=_0ZesKcczd1sYm8vs3ulbXO1M1boO_5DhFf3jSmjM4g,2398
|
296
298
|
beamlit/serve/middlewares/__init__.py,sha256=1dVmnOmhAQWvWktqHkKSIX-YoF6fmMU8xkUQuhg_rJU,148
|
297
299
|
beamlit/serve/middlewares/accesslog.py,sha256=wM52-hcwtO-_hdM1pnsEJzerzJf1MzEyN5m85BdDccE,609
|
298
300
|
beamlit/serve/middlewares/processtime.py,sha256=lDAaIasZ4bwvN-HKHvZpaD9r-yrkVNZYx4abvbjbrCg,411
|
299
|
-
beamlit-0.0.
|
300
|
-
beamlit-0.0.
|
301
|
-
beamlit-0.0.
|
301
|
+
beamlit-0.0.23.dist-info/METADATA,sha256=3o33Ckt9YG7lMNqHL9niFXXNSeQCLZUWX7BavDh7ZjI,2023
|
302
|
+
beamlit-0.0.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
303
|
+
beamlit-0.0.23.dist-info/RECORD,,
|