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

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ from .deploy import generate_beamlit_deployment
2
+
3
+ __all__ = ["generate_beamlit_deployment"]
@@ -0,0 +1,345 @@
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, init
11
+ from beamlit.models import (AgentChain, AgentDeployment, Flavor,
12
+ FunctionDeployment, StoreFunctionParameter)
13
+
14
+ sys.path.insert(0, os.getcwd())
15
+ sys.path.insert(0, os.path.join(os.getcwd(), "src"))
16
+
17
+
18
+ @dataclass
19
+ class Resource:
20
+ type: Literal["agent", "function"]
21
+ module: Callable
22
+ name: str
23
+ decorator: ast.Call
24
+ func: Callable
25
+
26
+ def get_resources(from_decorator, dir="src") -> list[Resource]:
27
+ resources = []
28
+ logger = getLogger(__name__)
29
+
30
+ # Walk through all Python files in resources directory and subdirectories
31
+ for root, _, files in os.walk(dir):
32
+ for file in files:
33
+ if file.endswith(".py"):
34
+ file_path = os.path.join(root, file)
35
+ # Read and compile the file content
36
+ with open(file_path) as f:
37
+ try:
38
+ file_content = f.read()
39
+ # Parse the file content to find decorated resources
40
+ tree = ast.parse(file_content)
41
+
42
+ # Look for function definitions with decorators
43
+ for node in ast.walk(tree):
44
+ if (
45
+ not isinstance(node, ast.FunctionDef) and not isinstance(node, ast.AsyncFunctionDef)
46
+ ) or len(node.decorator_list) == 0:
47
+ continue
48
+ decorator = node.decorator_list[0]
49
+
50
+ decorator_name = ""
51
+ if isinstance(decorator, ast.Call):
52
+ decorator_name = decorator.func.id
53
+ if isinstance(decorator, ast.Name):
54
+ decorator_name = decorator.id
55
+ if decorator_name == from_decorator:
56
+ # Get the function name and decorator name
57
+ func_name = node.name
58
+
59
+ # Import the module to get the actual function
60
+ spec = importlib.util.spec_from_file_location(func_name, file_path)
61
+ module = importlib.util.module_from_spec(spec)
62
+ spec.loader.exec_module(module)
63
+ # Check if kit=True in the decorator arguments
64
+
65
+ # Get the decorated function
66
+ if hasattr(module, func_name) and isinstance(decorator, ast.Call):
67
+
68
+ resources.append(
69
+ Resource(
70
+ type=decorator_name,
71
+ module=module,
72
+ name=func_name,
73
+ func=getattr(module, func_name),
74
+ decorator=decorator,
75
+ )
76
+ )
77
+ except Exception as e:
78
+ logger.warning(f"Error processing {file_path}: {e!s}")
79
+ return resources
80
+
81
+
82
+ def get_parameters(resource: Resource) -> list[StoreFunctionParameter]:
83
+ parameters = []
84
+ # Get function signature
85
+ import inspect
86
+ sig = inspect.signature(resource.func)
87
+ # Get docstring for parameter descriptions
88
+ docstring = inspect.getdoc(resource.func)
89
+ param_descriptions = {}
90
+ if docstring:
91
+ # Parse docstring for parameter descriptions
92
+ lines = docstring.split('\n')
93
+ for line in lines:
94
+ line = line.strip().lower()
95
+ if line.startswith(':param '):
96
+ # Extract parameter name and description
97
+ param_line = line[7:].split(':', 1)
98
+ if len(param_line) == 2:
99
+ param_name = param_line[0].strip()
100
+ param_desc = param_line[1].strip()
101
+ param_descriptions[param_name] = param_desc
102
+ for name, param in sig.parameters.items():
103
+ # Skip *args and **kwargs parameters
104
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
105
+
106
+ continue
107
+
108
+ param_type = "string" # Default type
109
+ type_mapping = {
110
+ 'str': 'string',
111
+ 'int': 'integer',
112
+ 'float': 'number',
113
+ 'bool': 'boolean',
114
+ 'list': 'array',
115
+ 'dict': 'object',
116
+ 'none': 'null'
117
+ }
118
+ if param.annotation != inspect.Parameter.empty:
119
+ # Map Python types to OpenAPI types
120
+ if hasattr(param.annotation, "__name__"):
121
+ param_type = param.annotation.__name__.lower()
122
+ else:
123
+ # Handle special types like Union, Optional etc
124
+ param_type = str(param.annotation).lower()
125
+ parameter = StoreFunctionParameter(
126
+ name=name,
127
+ type_=type_mapping.get(param_type, "string"),
128
+ required=param.default == inspect.Parameter.empty,
129
+ description=param_descriptions.get(name, f"Parameter {name}")
130
+ )
131
+ parameters.append(parameter)
132
+
133
+ return parameters
134
+
135
+
136
+ def get_description(description: str | None, resource: Resource) -> str:
137
+ if description:
138
+ return description
139
+ doc = resource.func.__doc__
140
+ if doc:
141
+ # Split docstring into sections and get only the description part
142
+ doc_lines = doc.split('\n')
143
+ description_lines = []
144
+ for line in doc_lines:
145
+ line = line.strip()
146
+ # Stop when we hit param/return sections
147
+ if line.startswith(':param') or line.startswith(':return'):
148
+ break
149
+ if line:
150
+ description_lines.append(line)
151
+ return ' '.join(description_lines).strip()
152
+ return ""
153
+
154
+ def get_kwargs(arg: ast.Call) -> dict:
155
+ kwargs = {}
156
+ for keyword in arg.keywords:
157
+ if isinstance(keyword.value, ast.Constant):
158
+ kwargs[keyword.arg] = keyword.value.value
159
+ elif isinstance(keyword.value, (ast.List, ast.Tuple)):
160
+ kwargs[keyword.arg] = [
161
+ AgentChain(**get_kwargs(elem)) if isinstance(elem, ast.Call) and isinstance(elem.func, ast.Name) and elem.func.id == "AgentChain"
162
+ else elem.value if isinstance(elem, ast.Constant) else elem
163
+ for elem in keyword.value.elts
164
+ ]
165
+ elif isinstance(keyword.value, ast.Dict):
166
+ kwargs[keyword.arg] = {}
167
+ for k, v in zip(keyword.value.keys, keyword.value.values):
168
+ if isinstance(k, ast.Constant) and isinstance(v, ast.Constant):
169
+ kwargs[keyword.arg][k.value] = v.value
170
+ if isinstance(k, ast.Constant) and isinstance(v, ast.Call):
171
+ kwargs[keyword.arg][k.value] = get_kwargs(v)
172
+ return kwargs
173
+
174
+ def get_beamlit_deployment_from_resource(resource: Resource) -> AgentDeployment | FunctionDeployment:
175
+ for arg in resource.decorator.args:
176
+ if isinstance(arg, ast.Call):
177
+ if isinstance(arg.func, ast.Name) and arg.func.id == "AgentDeployment":
178
+ kwargs = get_kwargs(arg)
179
+ description = kwargs.pop("description", None)
180
+ return AgentDeployment(**kwargs, description=get_description(description, resource))
181
+ if isinstance(arg.func, ast.Name) and arg.func.id == "FunctionDeployment":
182
+ kwargs = get_kwargs(arg)
183
+ description = kwargs.pop("description", None)
184
+ return FunctionDeployment(**kwargs, parameters=get_parameters(resource), description=get_description(description, resource))
185
+ if resource.type == "agent":
186
+ return AgentDeployment(
187
+ agent=resource.name,
188
+ description=get_description(None,resource)
189
+ )
190
+ if resource.type == "function":
191
+ return FunctionDeployment(
192
+ function=resource.name,
193
+ parameters=get_parameters(resource),
194
+ description=get_description(None,resource)
195
+ )
196
+ return None
197
+
198
+
199
+ def get_flavors(flavors: list[Flavor]) -> str:
200
+ if not flavors:
201
+ return "[]"
202
+ return json.dumps([flavor.to_dict() for flavor in flavors])
203
+
204
+ def format_parameters(parameters: list[StoreFunctionParameter]) -> str:
205
+ if not parameters:
206
+ return "[]"
207
+
208
+ formatted = []
209
+ for param in parameters:
210
+ formatted.append(f"""
211
+ - name: {param.name}
212
+ type: {param.type_}
213
+ required: {str(param.required).lower()}
214
+ description: {param.description}""")
215
+
216
+ return "\n".join(formatted)
217
+
218
+ def format_agent_chain(agent_chain: list[AgentChain]) -> str:
219
+ if not agent_chain:
220
+ return "[]"
221
+ formatted = []
222
+
223
+ for agent in agent_chain:
224
+ formatted.append(f"""
225
+ - agent: {agent.name}
226
+ enabled: {agent.enabled}""")
227
+ if agent.description:
228
+ formatted.append(f" description: {agent.description}")
229
+ return "\n".join(formatted)
230
+
231
+ def get_agent_yaml(agent: AgentDeployment, functions: list[tuple[Resource, FunctionDeployment]], settings: Settings) -> str:
232
+ template = f"""
233
+ apiVersion: beamlit.com/v1alpha1
234
+ kind: Agent
235
+ metadata:
236
+ name: {agent.agent}
237
+ spec:
238
+ display_name: Agent - {agent.agent}
239
+ deployments:
240
+ - environment: production
241
+ enabled: true
242
+ policies: [{", ".join(agent.policies or [])}]
243
+ functions: [{", ".join([f"{function.function}" for (_, function) in functions])}]
244
+ agent_chain: {format_agent_chain(agent.agent_chain)}
245
+ model: {agent.model}
246
+ """
247
+ if agent.description:
248
+ template += f""" description: |
249
+ {agent.description}"""
250
+ return template
251
+
252
+ def get_function_yaml(function: FunctionDeployment, settings: Settings) -> str:
253
+ return f"""
254
+ apiVersion: beamlit.com/v1alpha1
255
+ kind: Function
256
+ metadata:
257
+ name: {function.function}
258
+ spec:
259
+ display_name: {function.function}
260
+ deployments:
261
+ - environment: {settings.environment}
262
+ enabled: true
263
+ policies: [{", ".join(function.policies or [])}]
264
+ description: |
265
+ {function.description}
266
+ parameters: {format_parameters(function.parameters)}
267
+ """
268
+
269
+ def dockerfile(type: Literal["agent", "function"], resource: Resource, deployment: AgentDeployment | FunctionDeployment):
270
+ if type == "agent":
271
+ module = f"{resource.module.__file__.split('/')[-1].replace('.py', '')}.{resource.module.__name__}"
272
+ else:
273
+ module = f"functions.{resource.module.__name__}.{resource.func.__name__}"
274
+ cmd = ["bl", "serve", "--port", "80", "--module", module]
275
+ if type == "agent":
276
+ cmd.append("--remote")
277
+ cmd_str = ','.join([f'"{c}"' for c in cmd])
278
+
279
+ return f"""
280
+ FROM python:3.12-slim
281
+
282
+ ARG UV_VERSION="latest"
283
+ RUN apt update && apt install -y curl
284
+
285
+ # Install uv.
286
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
287
+ RUN curl -fsSL https://raw.githubusercontent.com/beamlit/toolkit/main/install.sh | BINDIR=/bin sh
288
+ WORKDIR /beamlit
289
+
290
+ # Install the application dependencies.
291
+ COPY pyproject.toml /beamlit/pyproject.toml
292
+ COPY uv.lock /beamlit/uv.lock
293
+ RUN uv sync --no-cache
294
+
295
+ COPY README.md /beamlit/README.md
296
+ COPY LICENSE /beamlit/LICENSE
297
+ COPY src /beamlit/src
298
+
299
+ ENV PATH="/beamlit/.venv/bin:$PATH"
300
+
301
+ ENTRYPOINT [{cmd_str}]
302
+ """
303
+
304
+ def generate_beamlit_deployment(directory: str):
305
+ settings = init()
306
+ logger = getLogger(__name__)
307
+ logger.info(f"Importing server module: {settings.server.module}")
308
+ functions: list[tuple[Resource, FunctionDeployment]] = []
309
+ agents: list[tuple[Resource, AgentDeployment]] = []
310
+ for agent in get_resources("agent"):
311
+ agent_deployment = get_beamlit_deployment_from_resource(agent)
312
+ if agent_deployment:
313
+ agents.append((agent, agent_deployment))
314
+ for function in get_resources("function"):
315
+ function_deployment = get_beamlit_deployment_from_resource(function)
316
+ if function_deployment:
317
+ functions.append((function, function_deployment))
318
+
319
+ agents_dir = os.path.join(directory, "agents")
320
+ functions_dir = os.path.join(directory, "functions")
321
+ # Create directory if it doesn't exist
322
+ os.makedirs(agents_dir, exist_ok=True)
323
+ os.makedirs(functions_dir, exist_ok=True)
324
+ for (resource, agent) in agents:
325
+ # write deployment file
326
+ agent_dir = os.path.join(agents_dir, agent.agent)
327
+ os.makedirs(agent_dir, exist_ok=True)
328
+ with open(os.path.join(agent_dir, f"agent.yaml"), "w") as f:
329
+ content = get_agent_yaml(agent, functions, settings)
330
+ f.write(content)
331
+ # write dockerfile for build
332
+ with open(os.path.join(agent_dir, f"Dockerfile"), "w") as f:
333
+ content = dockerfile("agent", resource, agent)
334
+ f.write(content)
335
+ for (resource, function) in functions:
336
+ # write deployment file
337
+ function_dir = os.path.join(functions_dir, function.function)
338
+ os.makedirs(function_dir, exist_ok=True)
339
+ with open(os.path.join(function_dir, f"function.yaml"), "w") as f:
340
+ content = get_function_yaml(function, settings)
341
+ f.write(content)
342
+ # write dockerfile for build
343
+ with open(os.path.join(function_dir, f"Dockerfile"), "w") as f:
344
+ content = dockerfile("function", resource, function)
345
+ 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.23rc14
4
4
  Summary: Add your description here
5
5
  Author-email: cploujoux <ch.ploujoux@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -140,6 +140,8 @@ beamlit/common/logger.py,sha256=ayabnsoHS8ncXm8EpBS01FkvSe0XRcaNdQjKVuPI5z4,1025
140
140
  beamlit/common/secrets.py,sha256=sid81bOe3LflkMKDHwBsBs9nIju8bp5-v9qU9gkyNMc,212
141
141
  beamlit/common/settings.py,sha256=cL5HAg6atxnTJXL9Rxz5Xs4iApNCCYY5ijha8UM3PW4,5446
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=F79JRGhZV-5wIvux66UC6ATSnqQkTfL2MvD8ayVy6-o,13487
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.23rc14.dist-info/METADATA,sha256=l7YaLMfPwUhKPfEiD2-Ve1OtVt6U3dkdR9rEh243w5Y,2027
302
+ beamlit-0.0.23rc14.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
303
+ beamlit-0.0.23rc14.dist-info/RECORD,,