beamlit 0.0.23rc13__py3-none-any.whl → 0.0.23rc15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -65,6 +65,7 @@ class Settings(BaseSettings):
65
65
  name: str = Field(default="beamlit-agent")
66
66
  base_url: str = Field(default="https://api.beamlit.dev/v0")
67
67
  run_url: str = Field(default="https://run.beamlit.dev")
68
+ registry_url: str = Field(default="https://serverless-registry-production.beamlit.workers.dev")
68
69
  log_level: str = Field(default="INFO")
69
70
  agent: SettingsAgent = SettingsAgent()
70
71
  server: SettingsServer = SettingsServer()
@@ -0,0 +1,3 @@
1
+ from .deploy import generate_beamlit_deployment
2
+
3
+ __all__ = ["generate_beamlit_deployment"]
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: beamlit
3
- Version: 0.0.23rc13
3
+ Version: 0.0.23rc15
4
4
  Summary: Add your description here
5
5
  Author-email: cploujoux <ch.ploujoux@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -138,8 +138,10 @@ beamlit/common/__init__.py,sha256=yDoMJDKj-xjTGl7U1YI59KpWxiOV65HSiUulgO8xdTA,27
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=cL5HAg6atxnTJXL9Rxz5Xs4iApNCCYY5ijha8UM3PW4,5446
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
146
  beamlit/functions/decorator.py,sha256=uYZOVxD-7ZNHORTQfCIn0qdNKPIZupsr7_QdUmWEKu0,2996
145
147
  beamlit/functions/github/__init__.py,sha256=gYnUkeegukOfbymdabuuJkScvH-_ZJygX05BoqkPn0o,49
@@ -296,6 +298,6 @@ 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.23rc13.dist-info/METADATA,sha256=pgkuT087O9yvCPPpcE64DjtxW5PcTcqBPA5oToxGwFk,2027
300
- beamlit-0.0.23rc13.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
301
- beamlit-0.0.23rc13.dist-info/RECORD,,
301
+ beamlit-0.0.23rc15.dist-info/METADATA,sha256=piLvTAjRvfpbIyBmUvCHSZHa2jxLezNRyfGLWmtIKBg,2027
302
+ beamlit-0.0.23rc15.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
303
+ beamlit-0.0.23rc15.dist-info/RECORD,,