beamlit 0.0.33rc50__py3-none-any.whl → 0.0.34__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.
Files changed (79) hide show
  1. beamlit/agents/__init__.py +2 -1
  2. beamlit/agents/chat.py +16 -4
  3. beamlit/agents/decorator.py +68 -155
  4. beamlit/agents/thread.py +14 -0
  5. beamlit/api/workspaces/workspace_quotas_request.py +97 -0
  6. beamlit/authentication/clientcredentials.py +5 -3
  7. beamlit/authentication/device_mode.py +4 -4
  8. beamlit/common/instrumentation.py +202 -34
  9. beamlit/common/settings.py +3 -1
  10. beamlit/deploy/deploy.py +64 -60
  11. beamlit/deploy/format.py +10 -0
  12. beamlit/functions/__init__.py +2 -2
  13. beamlit/functions/decorator.py +149 -1
  14. beamlit/functions/github/github.py +0 -1
  15. beamlit/models/__init__.py +51 -11
  16. beamlit/models/agent.py +27 -15
  17. beamlit/models/agent_metadata.py +1 -1
  18. beamlit/models/agent_render.py +45 -0
  19. beamlit/models/agent_spec.py +32 -5
  20. beamlit/models/core_event.py +88 -0
  21. beamlit/models/core_spec.py +14 -5
  22. beamlit/models/core_spec_configurations.py +1 -1
  23. beamlit/models/core_status.py +3 -20
  24. beamlit/models/environment.py +2 -2
  25. beamlit/models/environment_metadata.py +1 -1
  26. beamlit/models/function.py +27 -15
  27. beamlit/models/function_metadata.py +1 -1
  28. beamlit/models/function_render.py +45 -0
  29. beamlit/models/function_spec.py +14 -5
  30. beamlit/models/histogram_bucket.py +79 -0
  31. beamlit/models/histogram_stats.py +88 -0
  32. beamlit/models/increase_and_rate_metric.py +0 -9
  33. beamlit/models/integration_connection.py +2 -2
  34. beamlit/models/integration_connection_spec.py +11 -2
  35. beamlit/models/integration_repository.py +88 -0
  36. beamlit/models/last_n_requests_metric.py +88 -0
  37. beamlit/models/latency_metric.py +124 -0
  38. beamlit/models/metadata.py +1 -1
  39. beamlit/models/metric.py +18 -9
  40. beamlit/models/metrics.py +81 -46
  41. beamlit/models/metrics_models.py +45 -0
  42. beamlit/models/metrics_request_total_per_code.py +45 -0
  43. beamlit/models/metrics_rps_per_code.py +45 -0
  44. beamlit/models/model.py +27 -15
  45. beamlit/models/model_metadata.py +1 -1
  46. beamlit/models/model_provider.py +2 -2
  47. beamlit/models/model_render.py +45 -0
  48. beamlit/models/model_spec.py +14 -14
  49. beamlit/models/pending_invitation_accept.py +1 -1
  50. beamlit/models/pending_invitation_render.py +3 -3
  51. beamlit/models/policy.py +2 -2
  52. beamlit/models/provider_config.py +1 -1
  53. beamlit/models/repository.py +70 -0
  54. beamlit/models/repository_type_0.py +70 -0
  55. beamlit/models/request_duration_over_time_metric.py +97 -0
  56. beamlit/models/request_duration_over_time_metrics.py +74 -0
  57. beamlit/models/request_total_by_origin_metric.py +103 -0
  58. beamlit/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
  59. beamlit/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
  60. beamlit/models/request_total_metric.py +115 -0
  61. beamlit/models/request_total_metric_request_total_per_code.py +45 -0
  62. beamlit/models/request_total_metric_rps_per_code.py +45 -0
  63. beamlit/models/resource_deployment_metrics.py +6 -4
  64. beamlit/models/resource_deployment_metrics_query_per_second_per_region_per_code.py +1 -1
  65. beamlit/models/resource_environment_metrics.py +155 -75
  66. beamlit/models/resource_environment_metrics_request_total_per_code.py +45 -0
  67. beamlit/models/resource_environment_metrics_rps_per_code.py +45 -0
  68. beamlit/models/resource_metrics.py +1 -1
  69. beamlit/models/runtime.py +2 -2
  70. beamlit/models/store_agent.py +1 -1
  71. beamlit/models/store_function.py +1 -1
  72. beamlit/models/token_rate_metric.py +88 -0
  73. beamlit/models/token_rate_metrics.py +106 -0
  74. beamlit/models/token_total_metric.py +106 -0
  75. beamlit/models/workspace.py +17 -8
  76. beamlit/serve/app.py +9 -13
  77. {beamlit-0.0.33rc50.dist-info → beamlit-0.0.34.dist-info}/METADATA +21 -3
  78. {beamlit-0.0.33rc50.dist-info → beamlit-0.0.34.dist-info}/RECORD +79 -50
  79. {beamlit-0.0.33rc50.dist-info → beamlit-0.0.34.dist-info}/WHEEL +0 -0
@@ -1,17 +1,25 @@
1
- from typing import Any
1
+ import importlib
2
+ import logging
3
+ from typing import Any, Optional, Type
2
4
 
3
5
  from fastapi import FastAPI
4
- from opentelemetry import metrics, trace
5
- from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
6
+ from opentelemetry import _logs, metrics, trace
7
+ from opentelemetry._logs import set_logger_provider
8
+ from opentelemetry.exporter.otlp.proto.http._log_exporter import (
9
+ OTLPLogExporter,
10
+ )
11
+ from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
6
12
  OTLPMetricExporter,
7
13
  )
8
- from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
14
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
9
15
  OTLPSpanExporter,
10
16
  )
11
- from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
12
- from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
13
- from opentelemetry.instrumentation.logging import LoggingInstrumentor
17
+ from opentelemetry.instrumentation.fastapi import ( # type: ignore
18
+ FastAPIInstrumentor,
19
+ )
14
20
  from opentelemetry.metrics import NoOpMeterProvider
21
+ from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
22
+ from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
15
23
  from opentelemetry.sdk.metrics import MeterProvider
16
24
  from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
17
25
  from opentelemetry.sdk.resources import Resource
@@ -20,22 +28,30 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
20
28
  from opentelemetry.trace import NoOpTracerProvider
21
29
  from typing_extensions import Dict
22
30
 
31
+ from beamlit.authentication import get_authentication_headers
32
+
23
33
  from .settings import get_settings
24
34
 
25
35
  tracer: trace.Tracer | None = None
26
36
  meter: metrics.Meter | None = None
37
+ logger: LoggerProvider | None = None
27
38
 
39
+ log = logging.getLogger(__name__)
28
40
 
29
- def get_tracer() -> trace.Tracer:
30
- if tracer is None:
31
- raise Exception("Tracer is not initialized")
32
- return tracer
33
41
 
42
+ def auth_headers() -> Dict[str, str]:
43
+ settings = get_settings()
44
+ headers = get_authentication_headers(settings)
45
+ return {
46
+ "x-beamlit-authorization": headers.get("X-Beamlit-Authorization", ""),
47
+ "x-beamlit-workspace": headers.get("X-Beamlit-Workspace", ""),
48
+ }
34
49
 
35
- def get_meter() -> metrics.Meter:
36
- if meter is None:
37
- raise Exception("Meter is not initialized")
38
- return meter
50
+
51
+ def get_logger() -> LoggerProvider:
52
+ if logger is None:
53
+ raise Exception("Logger is not initialized")
54
+ return logger
39
55
 
40
56
 
41
57
  def get_resource_attributes() -> Dict[str, Any]:
@@ -44,8 +60,6 @@ def get_resource_attributes() -> Dict[str, Any]:
44
60
  for key in resources.attributes:
45
61
  resources_dict[key] = resources.attributes[key]
46
62
  settings = get_settings()
47
- if settings is None:
48
- raise Exception("Settings are not initialized")
49
63
  resources_dict["workspace"] = settings.workspace
50
64
  resources_dict["service.name"] = settings.name
51
65
  return resources_dict
@@ -53,28 +67,144 @@ def get_resource_attributes() -> Dict[str, Any]:
53
67
 
54
68
  def get_metrics_exporter() -> OTLPMetricExporter | None:
55
69
  settings = get_settings()
56
- if settings is None:
57
- raise Exception("Settings are not initialized")
58
70
  if not settings.enable_opentelemetry:
59
- # Return None or a NoOpExporter equivalent
60
71
  return None
61
- return OTLPMetricExporter()
72
+ return OTLPMetricExporter(headers=auth_headers())
62
73
 
63
74
 
64
75
  def get_span_exporter() -> OTLPSpanExporter | None:
65
76
  settings = get_settings()
66
77
  if not settings.enable_opentelemetry:
67
78
  return None
68
- return OTLPSpanExporter()
79
+ return OTLPSpanExporter(headers=auth_headers())
80
+
81
+
82
+ def get_log_exporter() -> OTLPLogExporter | None:
83
+ settings = get_settings()
84
+ if not settings.enable_opentelemetry:
85
+ return None
86
+ return OTLPLogExporter(headers=auth_headers())
87
+
88
+
89
+ def _import_class(module_path: str, class_name: str) -> Optional[Type]: # type: ignore
90
+ """Dynamically import a class from a module path."""
91
+ try:
92
+ module = importlib.import_module(module_path)
93
+ return getattr(module, class_name)
94
+ except (ImportError, AttributeError) as e:
95
+ log.error(f"Could not import {class_name} from {module_path}: {str(e)}")
96
+ return None
97
+
98
+
99
+ # Define mapping of instrumentor info: (module path, class name, required package)
100
+ INSTRUMENTOR_CONFIGS = {
101
+ "httpx": (
102
+ "opentelemetry.instrumentation.httpx",
103
+ "HTTPXClientInstrumentor",
104
+ "httpx",
105
+ ),
106
+ "anthropic": (
107
+ "opentelemetry.instrumentation.anthropic",
108
+ "AnthropicInstrumentor",
109
+ "anthropic",
110
+ ),
111
+ "chroma": (
112
+ "opentelemetry.instrumentation.chroma",
113
+ "ChromaInstrumentor",
114
+ "chromadb",
115
+ ),
116
+ "cohere": (
117
+ "opentelemetry.instrumentation.cohere",
118
+ "CohereInstrumentor",
119
+ "cohere",
120
+ ),
121
+ "groq": ("opentelemetry.instrumentation.groq", "GroqInstrumentor", "groq"),
122
+ "lance": (
123
+ "opentelemetry.instrumentation.lance",
124
+ "LanceInstrumentor",
125
+ "pylance",
126
+ ),
127
+ "langchain": (
128
+ "opentelemetry.instrumentation.langchain",
129
+ "LangchainInstrumentor",
130
+ "langchain",
131
+ ),
132
+ "llama_index": (
133
+ "opentelemetry.instrumentation.llama_index",
134
+ "LlamaIndexInstrumentor",
135
+ "llama_index",
136
+ ),
137
+ "marqo": (
138
+ "opentelemetry.instrumentation.marqo",
139
+ "MarqoInstrumentor",
140
+ "marqo",
141
+ ),
142
+ "milvus": (
143
+ "opentelemetry.instrumentation.milvus",
144
+ "MilvusInstrumentor",
145
+ "pymilvus",
146
+ ),
147
+ "mistralai": (
148
+ "opentelemetry.instrumentation.mistralai",
149
+ "MistralAiInstrumentor",
150
+ "mistralai",
151
+ ),
152
+ "ollama": (
153
+ "opentelemetry.instrumentation.ollama",
154
+ "OllamaInstrumentor",
155
+ "ollama",
156
+ ),
157
+ "openai": (
158
+ "opentelemetry.instrumentation.openai",
159
+ "OpenAIInstrumentor",
160
+ "openai",
161
+ ),
162
+ "pinecone": (
163
+ "opentelemetry.instrumentation.pinecone",
164
+ "PineconeInstrumentor",
165
+ "pinecone",
166
+ ),
167
+ "qdrant": (
168
+ "opentelemetry.instrumentation.qdrant",
169
+ "QdrantInstrumentor",
170
+ "qdrant_client",
171
+ ),
172
+ "replicate": (
173
+ "opentelemetry.instrumentation.replicate",
174
+ "ReplicateInstrumentor",
175
+ "replicate",
176
+ ),
177
+ "together": (
178
+ "opentelemetry.instrumentation.together",
179
+ "TogetherAiInstrumentor",
180
+ "together",
181
+ ),
182
+ "watsonx": (
183
+ "opentelemetry.instrumentation.watsonx",
184
+ "WatsonxInstrumentor",
185
+ "ibm_watson_machine_learning",
186
+ ),
187
+ "weaviate": (
188
+ "opentelemetry.instrumentation.weaviate",
189
+ "WeaviateInstrumentor",
190
+ "weaviate",
191
+ ),
192
+ }
193
+
194
+
195
+ def _is_package_installed(package_name: str) -> bool:
196
+ """Check if a package is installed."""
197
+ try:
198
+ importlib.import_module(package_name)
199
+ return True
200
+ except (ImportError, ModuleNotFoundError):
201
+ return False
69
202
 
70
203
 
71
204
  def instrument_app(app: FastAPI):
72
205
  global tracer
73
206
  global meter
74
207
  settings = get_settings()
75
- if settings is None:
76
- raise Exception("Settings are not initialized")
77
-
78
208
  if not settings.enable_opentelemetry:
79
209
  # Use NoOp implementations to stub tracing and metrics
80
210
  trace.set_tracer_provider(NoOpTracerProvider())
@@ -95,7 +225,7 @@ def instrument_app(app: FastAPI):
95
225
  # Set up the TracerProvider if not already set
96
226
  if not isinstance(trace.get_tracer_provider(), TracerProvider):
97
227
  trace_provider = TracerProvider(resource=resource)
98
- span_processor = BatchSpanProcessor(get_span_exporter())
228
+ span_processor = BatchSpanProcessor(get_span_exporter()) # type: ignore
99
229
  trace_provider.add_span_processor(span_processor)
100
230
  trace.set_tracer_provider(trace_provider)
101
231
  tracer = trace_provider.get_tracer(__name__)
@@ -104,7 +234,7 @@ def instrument_app(app: FastAPI):
104
234
 
105
235
  # Set up the MeterProvider if not already set
106
236
  if not isinstance(metrics.get_meter_provider(), MeterProvider):
107
- metrics_exporter = PeriodicExportingMetricReader(get_metrics_exporter())
237
+ metrics_exporter = PeriodicExportingMetricReader(get_metrics_exporter()) # type: ignore
108
238
  meter_provider = MeterProvider(
109
239
  resource=resource, metric_readers=[metrics_exporter]
110
240
  )
@@ -113,11 +243,49 @@ def instrument_app(app: FastAPI):
113
243
  else:
114
244
  meter = metrics.get_meter(__name__)
115
245
 
246
+ if not isinstance(_logs.get_logger_provider(), LoggerProvider):
247
+ logger_provider = LoggerProvider(resource=resource)
248
+ set_logger_provider(logger_provider)
249
+ logger_provider.add_log_record_processor(
250
+ BatchLogRecordProcessor(get_log_exporter()) # type: ignore
251
+ )
252
+ handler = LoggingHandler(
253
+ level=logging.NOTSET, logger_provider=logger_provider
254
+ )
255
+ logging.getLogger().addHandler(handler)
256
+ else:
257
+ logger_provider = _logs.get_logger_provider()
258
+
116
259
  # Only instrument the app when OpenTelemetry is enabled
117
- FastAPIInstrumentor.instrument_app(
118
- app=app, tracer_provider=trace.get_tracer_provider(), meter_provider=metrics.get_meter_provider()
119
- )
120
- HTTPXClientInstrumentor().instrument(meter_provider=metrics.get_meter_provider())
121
- LoggingInstrumentor(tracer_provider=trace.get_tracer_provider()).instrument(
122
- set_logging_format=True
123
- )
260
+ FastAPIInstrumentor.instrument_app(app) # type: ignore
261
+
262
+ for name, (
263
+ module_path,
264
+ class_name,
265
+ required_package,
266
+ ) in INSTRUMENTOR_CONFIGS.items():
267
+ if _is_package_installed(required_package):
268
+ instrumentor_class = _import_class(module_path, class_name) # type: ignore
269
+ if instrumentor_class:
270
+ try:
271
+ instrumentor_class().instrument()
272
+ log.info(f"Successfully instrumented {name}")
273
+ except Exception as e:
274
+ log.error(f"Failed to instrument {name}: {str(e)}")
275
+ else:
276
+ log.error(f"Could not load instrumentor for {name}")
277
+ else:
278
+ log.debug(
279
+ f"Skipping {name} instrumentation - required package '{required_package}' not installed"
280
+ )
281
+
282
+
283
+ def shutdown_instrumentation():
284
+ if tracer is not None:
285
+ trace_provider = trace.get_tracer_provider()
286
+ if isinstance(trace_provider, TracerProvider):
287
+ trace_provider.shutdown()
288
+ if meter is not None:
289
+ meter_provider = metrics.get_meter_provider()
290
+ if isinstance(meter_provider, MeterProvider):
291
+ meter_provider.shutdown()
@@ -57,6 +57,7 @@ class Settings(BaseSettings):
57
57
  type: str = Field(default="agent")
58
58
  name: str = Field(default="beamlit-agent")
59
59
  base_url: str = Field(default="https://api.beamlit.com/v0")
60
+ app_url: str = Field(default="https://app.beamlit.com")
60
61
  run_url: str = Field(default="https://run.beamlit.com")
61
62
  mcp_hub_url: str = Field(default="https://mcp-hub-server.beamlit.workers.com")
62
63
  registry_url: str = Field(default="https://us.registry.beamlit.com")
@@ -73,7 +74,8 @@ class Settings(BaseSettings):
73
74
  self.run_url = os.getenv('BL_RUN_URL') or "https://run.beamlit.dev"
74
75
  self.mcp_hub_url = os.getenv('BL_MCP_HUB_URL') or "https://mcp-hub-server.beamlit.workers.dev"
75
76
  self.registry_url = os.getenv('BL_REGISTRY_URL') or "https://eu.registry.beamlit.dev"
76
-
77
+ self.app_url = os.getenv('BL_APP_URL') or "https://app.beamlit.dev"
78
+
77
79
  @classmethod
78
80
  def settings_customise_sources(
79
81
  cls,
beamlit/deploy/deploy.py CHANGED
@@ -1,11 +1,17 @@
1
1
  import ast
2
2
  import json
3
3
  import os
4
+ import shutil
4
5
  import sys
5
- import uuid
6
6
  from logging import getLogger
7
+ from pathlib import Path
7
8
  from typing import Literal
8
9
 
10
+ import yaml
11
+
12
+ from beamlit.api.agents import get_agent
13
+ from beamlit.authentication import new_client
14
+ from beamlit.client import AuthenticatedClient
9
15
  from beamlit.common import slugify
10
16
  from beamlit.common.settings import Settings, get_settings, init
11
17
  from beamlit.models import (
@@ -15,40 +21,25 @@ from beamlit.models import (
15
21
  Flavor,
16
22
  Function,
17
23
  FunctionSpec,
18
- Runtime,
24
+ MetadataLabels,
19
25
  )
20
26
 
21
- from .format import arg_to_dict, format_agent_chain, format_parameters
27
+ from .format import arg_to_dict
22
28
  from .parser import Resource, get_description, get_parameters, get_resources
23
29
 
24
30
  sys.path.insert(0, os.getcwd())
25
31
  sys.path.insert(0, os.path.join(os.getcwd(), "src"))
26
32
 
27
- random_id = str(uuid.uuid4())[:8]
28
-
29
- def get_runtime_image(type: str, name: str) -> str:
30
- settings = get_settings()
31
- registry_url = settings.registry_url.replace("https://", "").replace("http://", "")
32
- image = f"{registry_url}/{settings.workspace}/{type}s/{name}"
33
- # Generate a random ID to ensure unique image tags
34
- image = f"{image}:{random_id}"
35
- return image
36
-
37
-
38
33
  def set_default_values(resource: Resource, deployment: Agent | Function):
39
34
  settings = get_settings()
40
35
  deployment.metadata.workspace = settings.workspace
41
36
  deployment.metadata.environment = settings.environment
42
37
  if not deployment.metadata.name:
43
- deployment.metadata.name = resource.name
38
+ deployment.metadata.name = slugify(resource.name)
44
39
  if not deployment.metadata.display_name:
45
40
  deployment.metadata.display_name = deployment.metadata.name
46
41
  if not deployment.spec.description:
47
42
  deployment.spec.description = get_description(None, resource)
48
- if not deployment.spec.runtime:
49
- deployment.spec.runtime = Runtime()
50
- if not deployment.spec.runtime.image:
51
- deployment.spec.runtime.image = get_runtime_image(resource.type, deployment.metadata.name)
52
43
  return deployment
53
44
 
54
45
  def get_beamlit_deployment_from_resource(
@@ -105,7 +96,7 @@ def get_flavors(flavors: list[Flavor]) -> str:
105
96
  return json.dumps([flavor.to_dict() for flavor in flavors])
106
97
 
107
98
  def get_agent_yaml(
108
- agent: Agent, functions: list[tuple[Resource, Function]], settings: Settings
99
+ agent: Agent, functions: list[tuple[Resource, Function]], settings: Settings, client: AuthenticatedClient
109
100
  ) -> str:
110
101
  """
111
102
  Generates YAML configuration for an agent deployment.
@@ -118,30 +109,24 @@ def get_agent_yaml(
118
109
  Returns:
119
110
  str: YAML configuration string
120
111
  """
112
+ try:
113
+ agent_response = get_agent.sync(agent.metadata.name, client=client)
114
+ agent.spec.repository = agent_response.spec.repository
115
+ except Exception:
116
+ pass
117
+ agent.spec.functions = [slugify(function.metadata.name) for (_, function) in functions]
118
+ agent.metadata.labels = agent.metadata.labels and MetadataLabels.from_dict(agent.metadata.labels) or MetadataLabels()
119
+ agent.metadata.labels["x-beamlit-auto-generated"] = "true"
120
+ agent_yaml = yaml.dump(agent.to_dict())
121
121
  template = f"""
122
122
  apiVersion: beamlit.com/v1alpha1
123
123
  kind: Agent
124
- metadata:
125
- name: {slugify(agent.metadata.name)}
126
- displayName: {agent.metadata.display_name or agent.metadata.name}
127
- environment: {settings.environment}
128
- workspace: {settings.workspace}
129
- spec:
130
- enabled: true
131
- policies: [{", ".join(agent.spec.policies or [])}]
132
- functions: [{", ".join([f"{slugify(function.metadata.name)}" for (_, function) in functions])}]
133
- agentChain: {format_agent_chain(agent.spec.agent_chain)}
134
- model: {agent.spec.model}
135
- runtime:
136
- image: {agent.spec.runtime.image}
124
+ {agent_yaml}
137
125
  """
138
- if agent.spec.description:
139
- template += f""" description: |
140
- {agent.spec.description}"""
141
126
  return template
142
127
 
143
128
 
144
- def get_function_yaml(function: Function, settings: Settings) -> str:
129
+ def get_function_yaml(function: Function, settings: Settings, client: AuthenticatedClient) -> str:
145
130
  """
146
131
  Generates YAML configuration for a function deployment.
147
132
 
@@ -152,21 +137,13 @@ def get_function_yaml(function: Function, settings: Settings) -> str:
152
137
  Returns:
153
138
  str: YAML configuration string
154
139
  """
140
+ function.metadata.labels = function.metadata.labels and MetadataLabels.from_dict(function.metadata.labels) or MetadataLabels()
141
+ function.metadata.labels["x-beamlit-auto-generated"] = "true"
142
+ function_yaml = yaml.dump(function.to_dict())
155
143
  return f"""
156
144
  apiVersion: beamlit.com/v1alpha1
157
145
  kind: Function
158
- metadata:
159
- name: {slugify(function.metadata.name)}
160
- displayName: {function.metadata.display_name or function.metadata.name}
161
- environment: {settings.environment}
162
- spec:
163
- enabled: true
164
- policies: [{", ".join(function.spec.policies or [])}]
165
- description: |
166
- {function.spec.description}
167
- parameters: {format_parameters(function.spec.parameters)}
168
- runtime:
169
- image: {function.spec.runtime.image}
146
+ {function_yaml}
170
147
  """
171
148
 
172
149
 
@@ -220,8 +197,37 @@ ENV PATH="/beamlit/.venv/bin:$PATH"
220
197
  ENTRYPOINT [{cmd_str}]
221
198
  """
222
199
 
200
+ def clean_auto_generated(
201
+ directory: str,
202
+ type: Literal["agent", "function"],
203
+ deployments: list[tuple[Resource, Agent | Function]]
204
+ ):
205
+ """
206
+ Cleans up auto-generated deployments of a specific type.
223
207
 
224
- def generate_beamlit_deployment(directory: str):
208
+ Args:
209
+ directory (str): Base directory containing deployments
210
+ type (str): Type of deployment ("agent" or "function")
211
+ deployments (list[tuple[Resource, Agent | Function]]): List of deployment resources and configurations
212
+ """
213
+
214
+ deploy_dir = Path(directory) / f"{type}s"
215
+ deploy_names = [d.metadata.name for (_, d) in deployments]
216
+
217
+ if deploy_dir.exists():
218
+ for item_dir in deploy_dir.iterdir():
219
+ if item_dir.is_dir() and item_dir.name not in deploy_names:
220
+ yaml_file = item_dir / f"{type}.yaml"
221
+ if yaml_file.exists():
222
+ with open(yaml_file) as f:
223
+ try:
224
+ content = yaml.safe_load(f)
225
+ if content.get("metadata", {}).get("labels", {}).get("x-beamlit-auto-generated") == "true":
226
+ shutil.rmtree(item_dir)
227
+ except yaml.YAMLError:
228
+ continue
229
+
230
+ def generate_beamlit_deployment(directory: str, name: str):
225
231
  """
226
232
  Generates all necessary deployment files for Beamlit agents and functions.
227
233
 
@@ -234,12 +240,15 @@ def generate_beamlit_deployment(directory: str):
234
240
  - Directory structure for agents and functions
235
241
  """
236
242
  settings = init()
243
+ client = new_client()
237
244
  logger = getLogger(__name__)
238
245
  logger.info(f"Importing server module: {settings.server.module}")
239
246
  functions: list[tuple[Resource, Function]] = []
240
247
  agents: list[tuple[Resource, Agent]] = []
241
248
  for resource in get_resources("agent", settings.server.directory):
242
249
  agent = get_beamlit_deployment_from_resource(resource)
250
+ if name and agent.metadata.name != name:
251
+ agent.metadata.name = slugify(name)
243
252
  if agent:
244
253
  agents.append((resource, agent))
245
254
  for resource in get_resources("function", settings.server.directory):
@@ -257,28 +266,23 @@ def generate_beamlit_deployment(directory: str):
257
266
  agent_dir = os.path.join(agents_dir, agent.metadata.name)
258
267
  os.makedirs(agent_dir, exist_ok=True)
259
268
  with open(os.path.join(agent_dir, "agent.yaml"), "w") as f:
260
- content = get_agent_yaml(agent, functions, settings)
269
+ content = get_agent_yaml(agent, functions, settings, client)
261
270
  f.write(content)
262
271
  # write dockerfile for build
263
272
  with open(os.path.join(agent_dir, "Dockerfile"), "w") as f:
264
273
  content = dockerfile("agent", resource, agent)
265
274
  f.write(content)
266
- # write destination docker
267
- with open(os.path.join(agent_dir, "destination.txt"), "w") as f:
268
- content = agent.spec.runtime.image
269
- f.write(content)
270
275
  for resource, function in functions:
271
276
  # write deployment file
272
277
  function_dir = os.path.join(functions_dir, function.metadata.name)
273
278
  os.makedirs(function_dir, exist_ok=True)
274
279
  with open(os.path.join(function_dir, "function.yaml"), "w") as f:
275
- content = get_function_yaml(function, settings)
280
+ content = get_function_yaml(function, settings, client)
276
281
  f.write(content)
277
282
  # write dockerfile for build
278
283
  with open(os.path.join(function_dir, "Dockerfile"), "w") as f:
279
284
  content = dockerfile("function", resource, function)
280
285
  f.write(content)
281
- # write destination docker
282
- with open(os.path.join(function_dir, "destination.txt"), "w") as f:
283
- content = function.spec.runtime.image
284
- f.write(content)
286
+
287
+ clean_auto_generated(directory, "agent", agents)
288
+ clean_auto_generated(directory, "function", functions)
beamlit/deploy/format.py CHANGED
@@ -48,6 +48,16 @@ def format_parameters(parameters: list[StoreFunctionParameter]) -> str:
48
48
 
49
49
  return "\n".join(formatted)
50
50
 
51
+ def format_dict(obj: dict) -> str:
52
+ if not obj:
53
+ return "null"
54
+ ret = ""
55
+ for k, v in obj.items():
56
+ if not v:
57
+ ret += f"{k}: null\n"
58
+ else:
59
+ ret += f"{k}: {v}\n"
60
+ return ret
51
61
 
52
62
  def format_agent_chain(agentChain: list[AgentChain]) -> str:
53
63
  """
@@ -1,5 +1,5 @@
1
1
  """Functions package providing function decorators and utilities."""
2
2
 
3
- from .decorator import function, kit
3
+ from .decorator import function, get_functions, kit
4
4
 
5
- __all__ = ["function", "kit"]
5
+ __all__ = ["function", "kit", "get_functions"]