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.
- beamlit/agents/__init__.py +2 -1
- beamlit/agents/chat.py +16 -4
- beamlit/agents/decorator.py +68 -155
- beamlit/agents/thread.py +14 -0
- beamlit/api/workspaces/workspace_quotas_request.py +97 -0
- beamlit/authentication/clientcredentials.py +5 -3
- beamlit/authentication/device_mode.py +4 -4
- beamlit/common/instrumentation.py +202 -34
- beamlit/common/settings.py +3 -1
- beamlit/deploy/deploy.py +64 -60
- beamlit/deploy/format.py +10 -0
- beamlit/functions/__init__.py +2 -2
- beamlit/functions/decorator.py +149 -1
- beamlit/functions/github/github.py +0 -1
- beamlit/models/__init__.py +51 -11
- beamlit/models/agent.py +27 -15
- beamlit/models/agent_metadata.py +1 -1
- beamlit/models/agent_render.py +45 -0
- beamlit/models/agent_spec.py +32 -5
- beamlit/models/core_event.py +88 -0
- beamlit/models/core_spec.py +14 -5
- beamlit/models/core_spec_configurations.py +1 -1
- beamlit/models/core_status.py +3 -20
- beamlit/models/environment.py +2 -2
- beamlit/models/environment_metadata.py +1 -1
- beamlit/models/function.py +27 -15
- beamlit/models/function_metadata.py +1 -1
- beamlit/models/function_render.py +45 -0
- beamlit/models/function_spec.py +14 -5
- beamlit/models/histogram_bucket.py +79 -0
- beamlit/models/histogram_stats.py +88 -0
- beamlit/models/increase_and_rate_metric.py +0 -9
- beamlit/models/integration_connection.py +2 -2
- beamlit/models/integration_connection_spec.py +11 -2
- beamlit/models/integration_repository.py +88 -0
- beamlit/models/last_n_requests_metric.py +88 -0
- beamlit/models/latency_metric.py +124 -0
- beamlit/models/metadata.py +1 -1
- beamlit/models/metric.py +18 -9
- beamlit/models/metrics.py +81 -46
- beamlit/models/metrics_models.py +45 -0
- beamlit/models/metrics_request_total_per_code.py +45 -0
- beamlit/models/metrics_rps_per_code.py +45 -0
- beamlit/models/model.py +27 -15
- beamlit/models/model_metadata.py +1 -1
- beamlit/models/model_provider.py +2 -2
- beamlit/models/model_render.py +45 -0
- beamlit/models/model_spec.py +14 -14
- beamlit/models/pending_invitation_accept.py +1 -1
- beamlit/models/pending_invitation_render.py +3 -3
- beamlit/models/policy.py +2 -2
- beamlit/models/provider_config.py +1 -1
- beamlit/models/repository.py +70 -0
- beamlit/models/repository_type_0.py +70 -0
- beamlit/models/request_duration_over_time_metric.py +97 -0
- beamlit/models/request_duration_over_time_metrics.py +74 -0
- beamlit/models/request_total_by_origin_metric.py +103 -0
- beamlit/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
- beamlit/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
- beamlit/models/request_total_metric.py +115 -0
- beamlit/models/request_total_metric_request_total_per_code.py +45 -0
- beamlit/models/request_total_metric_rps_per_code.py +45 -0
- beamlit/models/resource_deployment_metrics.py +6 -4
- beamlit/models/resource_deployment_metrics_query_per_second_per_region_per_code.py +1 -1
- beamlit/models/resource_environment_metrics.py +155 -75
- beamlit/models/resource_environment_metrics_request_total_per_code.py +45 -0
- beamlit/models/resource_environment_metrics_rps_per_code.py +45 -0
- beamlit/models/resource_metrics.py +1 -1
- beamlit/models/runtime.py +2 -2
- beamlit/models/store_agent.py +1 -1
- beamlit/models/store_function.py +1 -1
- beamlit/models/token_rate_metric.py +88 -0
- beamlit/models/token_rate_metrics.py +106 -0
- beamlit/models/token_total_metric.py +106 -0
- beamlit/models/workspace.py +17 -8
- beamlit/serve/app.py +9 -13
- {beamlit-0.0.33rc50.dist-info → beamlit-0.0.34.dist-info}/METADATA +21 -3
- {beamlit-0.0.33rc50.dist-info → beamlit-0.0.34.dist-info}/RECORD +79 -50
- {beamlit-0.0.33rc50.dist-info → beamlit-0.0.34.dist-info}/WHEEL +0 -0
@@ -1,17 +1,25 @@
|
|
1
|
-
|
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.
|
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.
|
14
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
9
15
|
OTLPSpanExporter,
|
10
16
|
)
|
11
|
-
from opentelemetry.instrumentation.fastapi import
|
12
|
-
|
13
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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()
|
beamlit/common/settings.py
CHANGED
@@ -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
|
-
|
24
|
+
MetadataLabels,
|
19
25
|
)
|
20
26
|
|
21
|
-
from .format import arg_to_dict
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
282
|
-
|
283
|
-
|
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
|
"""
|
beamlit/functions/__init__.py
CHANGED