llama-deploy-appserver 0.2.7a1__py3-none-any.whl → 0.3.0__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.
- llama_deploy/appserver/app.py +274 -26
- llama_deploy/appserver/bootstrap.py +55 -25
- llama_deploy/appserver/configure_logging.py +189 -0
- llama_deploy/appserver/correlation_id.py +24 -0
- llama_deploy/appserver/deployment.py +70 -412
- llama_deploy/appserver/deployment_config_parser.py +12 -130
- llama_deploy/appserver/interrupts.py +55 -0
- llama_deploy/appserver/process_utils.py +214 -0
- llama_deploy/appserver/py.typed +0 -0
- llama_deploy/appserver/routers/__init__.py +4 -3
- llama_deploy/appserver/routers/deployments.py +163 -382
- llama_deploy/appserver/routers/status.py +4 -31
- llama_deploy/appserver/routers/ui_proxy.py +255 -0
- llama_deploy/appserver/settings.py +99 -49
- llama_deploy/appserver/types.py +0 -3
- llama_deploy/appserver/workflow_loader.py +431 -0
- llama_deploy/appserver/workflow_store/agent_data_store.py +100 -0
- llama_deploy/appserver/workflow_store/keyed_lock.py +32 -0
- llama_deploy/appserver/workflow_store/lru_cache.py +49 -0
- llama_deploy_appserver-0.3.0.dist-info/METADATA +25 -0
- llama_deploy_appserver-0.3.0.dist-info/RECORD +24 -0
- {llama_deploy_appserver-0.2.7a1.dist-info → llama_deploy_appserver-0.3.0.dist-info}/WHEEL +1 -1
- llama_deploy/appserver/__main__.py +0 -14
- llama_deploy/appserver/client/__init__.py +0 -3
- llama_deploy/appserver/client/base.py +0 -30
- llama_deploy/appserver/client/client.py +0 -49
- llama_deploy/appserver/client/models/__init__.py +0 -4
- llama_deploy/appserver/client/models/apiserver.py +0 -356
- llama_deploy/appserver/client/models/model.py +0 -82
- llama_deploy/appserver/run_autodeploy.py +0 -141
- llama_deploy/appserver/server.py +0 -60
- llama_deploy/appserver/source_managers/__init__.py +0 -5
- llama_deploy/appserver/source_managers/base.py +0 -33
- llama_deploy/appserver/source_managers/git.py +0 -48
- llama_deploy/appserver/source_managers/local.py +0 -51
- llama_deploy/appserver/tracing.py +0 -237
- llama_deploy_appserver-0.2.7a1.dist-info/METADATA +0 -23
- llama_deploy_appserver-0.2.7a1.dist-info/RECORD +0 -28
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import websockets
|
|
8
|
+
from fastapi import (
|
|
9
|
+
APIRouter,
|
|
10
|
+
FastAPI,
|
|
11
|
+
HTTPException,
|
|
12
|
+
Request,
|
|
13
|
+
WebSocket,
|
|
14
|
+
)
|
|
15
|
+
from fastapi.responses import StreamingResponse
|
|
16
|
+
from fastapi.staticfiles import StaticFiles
|
|
17
|
+
from llama_deploy.appserver.configure_logging import suppress_httpx_logs
|
|
18
|
+
from llama_deploy.appserver.interrupts import (
|
|
19
|
+
OperationAborted,
|
|
20
|
+
shutdown_event,
|
|
21
|
+
wait_or_abort,
|
|
22
|
+
)
|
|
23
|
+
from llama_deploy.appserver.settings import ApiserverSettings
|
|
24
|
+
from llama_deploy.core.deployment_config import DeploymentConfig
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def _ws_proxy(ws: WebSocket, upstream_url: str) -> None:
|
|
30
|
+
"""Proxy WebSocket connection to upstream server."""
|
|
31
|
+
if shutdown_event.is_set():
|
|
32
|
+
await ws.close()
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# Defer accept until after upstream connects so we can mirror the selected subprotocol
|
|
36
|
+
|
|
37
|
+
# Forward most headers except WebSocket-specific ones
|
|
38
|
+
header_prefix_blacklist = ["sec-websocket-"]
|
|
39
|
+
header_blacklist = {
|
|
40
|
+
"host",
|
|
41
|
+
"connection",
|
|
42
|
+
"upgrade",
|
|
43
|
+
}
|
|
44
|
+
hdrs = []
|
|
45
|
+
for k, v in ws.headers.items():
|
|
46
|
+
if k.lower() not in header_blacklist:
|
|
47
|
+
for prefix in header_prefix_blacklist:
|
|
48
|
+
if k.lower().startswith(prefix):
|
|
49
|
+
break
|
|
50
|
+
else:
|
|
51
|
+
hdrs.append((k, v))
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
# Parse subprotocols if present
|
|
55
|
+
subprotocols: List[str] | None = None
|
|
56
|
+
requested = ws.headers.get("sec-websocket-protocol")
|
|
57
|
+
if requested:
|
|
58
|
+
# Parse comma-separated subprotocols (as plain strings)
|
|
59
|
+
subprotocols = [p.strip() for p in requested.split(",")]
|
|
60
|
+
|
|
61
|
+
# Open upstream WebSocket connection, offering the same subprotocols
|
|
62
|
+
async with websockets.connect(
|
|
63
|
+
upstream_url,
|
|
64
|
+
additional_headers=hdrs,
|
|
65
|
+
subprotocols=subprotocols,
|
|
66
|
+
open_timeout=5,
|
|
67
|
+
) as upstream:
|
|
68
|
+
await ws.accept(subprotocol=upstream.subprotocol)
|
|
69
|
+
|
|
70
|
+
async def client_to_upstream() -> None:
|
|
71
|
+
try:
|
|
72
|
+
while True:
|
|
73
|
+
msg = await wait_or_abort(ws.receive(), shutdown_event)
|
|
74
|
+
if msg["type"] == "websocket.receive":
|
|
75
|
+
if "text" in msg:
|
|
76
|
+
await upstream.send(msg["text"])
|
|
77
|
+
elif "bytes" in msg:
|
|
78
|
+
await upstream.send(msg["bytes"])
|
|
79
|
+
elif msg["type"] == "websocket.disconnect":
|
|
80
|
+
break
|
|
81
|
+
except OperationAborted:
|
|
82
|
+
pass
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
async def upstream_to_client() -> None:
|
|
87
|
+
try:
|
|
88
|
+
while True:
|
|
89
|
+
message = await wait_or_abort(upstream.recv(), shutdown_event)
|
|
90
|
+
if isinstance(message, str):
|
|
91
|
+
await ws.send_text(message)
|
|
92
|
+
else:
|
|
93
|
+
await ws.send_bytes(message)
|
|
94
|
+
except OperationAborted:
|
|
95
|
+
pass
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
# Pump both directions concurrently, cancel the peer when one side closes
|
|
100
|
+
t1 = asyncio.create_task(client_to_upstream())
|
|
101
|
+
t2 = asyncio.create_task(upstream_to_client())
|
|
102
|
+
_, pending = await asyncio.wait(
|
|
103
|
+
{t1, t2}, return_when=asyncio.FIRST_COMPLETED
|
|
104
|
+
)
|
|
105
|
+
for task in pending:
|
|
106
|
+
task.cancel()
|
|
107
|
+
with suppress(asyncio.CancelledError):
|
|
108
|
+
await task
|
|
109
|
+
|
|
110
|
+
# On shutdown, proactively close both sides to break any remaining waits
|
|
111
|
+
if shutdown_event.is_set():
|
|
112
|
+
with suppress(Exception):
|
|
113
|
+
await ws.close()
|
|
114
|
+
with suppress(Exception):
|
|
115
|
+
await upstream.close()
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.error(f"WebSocket proxy error: {e}")
|
|
119
|
+
# Accept then close so clients (and TestClient) don't error on enter
|
|
120
|
+
with suppress(Exception):
|
|
121
|
+
await ws.accept()
|
|
122
|
+
with suppress(Exception):
|
|
123
|
+
await ws.close()
|
|
124
|
+
finally:
|
|
125
|
+
try:
|
|
126
|
+
await ws.close()
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.debug(f"Error closing client connection: {e}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def create_ui_proxy_router(name: str, port: int) -> APIRouter:
|
|
132
|
+
deployment_router = APIRouter(
|
|
133
|
+
prefix=f"/deployments/{name}",
|
|
134
|
+
tags=["deployments"],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@deployment_router.websocket("/ui/{path:path}")
|
|
138
|
+
@deployment_router.websocket("/ui")
|
|
139
|
+
async def websocket_proxy(
|
|
140
|
+
websocket: WebSocket,
|
|
141
|
+
path: str | None = None,
|
|
142
|
+
) -> None:
|
|
143
|
+
# Build the upstream WebSocket URL using FastAPI's extracted path parameter
|
|
144
|
+
slash_path = f"/{path}" if path is not None else ""
|
|
145
|
+
upstream_path = f"/deployments/{name}/ui{slash_path}"
|
|
146
|
+
|
|
147
|
+
# Convert to WebSocket URL
|
|
148
|
+
upstream_url = f"ws://localhost:{port}{upstream_path}"
|
|
149
|
+
if websocket.url.query:
|
|
150
|
+
upstream_url += f"?{websocket.url.query}"
|
|
151
|
+
|
|
152
|
+
await _ws_proxy(websocket, upstream_url)
|
|
153
|
+
|
|
154
|
+
@deployment_router.api_route(
|
|
155
|
+
"/ui/{path:path}",
|
|
156
|
+
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"],
|
|
157
|
+
include_in_schema=False,
|
|
158
|
+
)
|
|
159
|
+
@deployment_router.api_route(
|
|
160
|
+
"/ui",
|
|
161
|
+
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"],
|
|
162
|
+
include_in_schema=False,
|
|
163
|
+
)
|
|
164
|
+
async def proxy(
|
|
165
|
+
request: Request,
|
|
166
|
+
path: str | None = None,
|
|
167
|
+
) -> StreamingResponse:
|
|
168
|
+
# Build the upstream URL using FastAPI's extracted path parameter
|
|
169
|
+
slash_path = f"/{path}" if path else ""
|
|
170
|
+
upstream_path = f"/deployments/{name}/ui{slash_path}"
|
|
171
|
+
|
|
172
|
+
upstream_url = httpx.URL(f"http://localhost:{port}{upstream_path}").copy_with(
|
|
173
|
+
params=request.query_params
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Debug logging
|
|
177
|
+
logger.debug(f"Proxying {request.method} {request.url} -> {upstream_url}")
|
|
178
|
+
|
|
179
|
+
# Strip hop-by-hop headers + host
|
|
180
|
+
hop_by_hop = {
|
|
181
|
+
"connection",
|
|
182
|
+
"keep-alive",
|
|
183
|
+
"proxy-authenticate",
|
|
184
|
+
"proxy-authorization",
|
|
185
|
+
"te", # codespell:ignore
|
|
186
|
+
"trailers",
|
|
187
|
+
"transfer-encoding",
|
|
188
|
+
"upgrade",
|
|
189
|
+
"host",
|
|
190
|
+
}
|
|
191
|
+
headers = {
|
|
192
|
+
k: v for k, v in request.headers.items() if k.lower() not in hop_by_hop
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
client = httpx.AsyncClient(timeout=None)
|
|
197
|
+
|
|
198
|
+
req = client.build_request(
|
|
199
|
+
request.method,
|
|
200
|
+
upstream_url,
|
|
201
|
+
headers=headers,
|
|
202
|
+
content=request.stream(), # stream uploads
|
|
203
|
+
)
|
|
204
|
+
async with suppress_httpx_logs():
|
|
205
|
+
upstream = await client.send(req, stream=True)
|
|
206
|
+
|
|
207
|
+
resp_headers = {
|
|
208
|
+
k: v for k, v in upstream.headers.items() if k.lower() not in hop_by_hop
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# Stream downloads and ensure cleanup in the generator's finally block
|
|
212
|
+
async def upstream_body():
|
|
213
|
+
try:
|
|
214
|
+
async for chunk in upstream.aiter_raw():
|
|
215
|
+
yield chunk
|
|
216
|
+
finally:
|
|
217
|
+
try:
|
|
218
|
+
await upstream.aclose()
|
|
219
|
+
finally:
|
|
220
|
+
await client.aclose()
|
|
221
|
+
|
|
222
|
+
return StreamingResponse(
|
|
223
|
+
upstream_body(),
|
|
224
|
+
status_code=upstream.status_code,
|
|
225
|
+
headers=resp_headers,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
except httpx.ConnectError:
|
|
229
|
+
raise HTTPException(status_code=502, detail="Upstream server unavailable")
|
|
230
|
+
except httpx.TimeoutException:
|
|
231
|
+
raise HTTPException(status_code=504, detail="Upstream server timeout")
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"Proxy error: {e}")
|
|
234
|
+
raise HTTPException(status_code=502, detail="Proxy error")
|
|
235
|
+
|
|
236
|
+
return deployment_router
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def mount_static_files(
|
|
240
|
+
app: FastAPI, config: DeploymentConfig, settings: ApiserverSettings
|
|
241
|
+
) -> None:
|
|
242
|
+
path = settings.app_root / config.build_output_path()
|
|
243
|
+
if not path:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
if not path.exists():
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
# Serve index.html when accessing the directory path
|
|
250
|
+
app.mount(
|
|
251
|
+
f"/deployments/{config.name}/ui",
|
|
252
|
+
StaticFiles(directory=str(path), html=True),
|
|
253
|
+
name=f"ui-static-{config.name}",
|
|
254
|
+
)
|
|
255
|
+
return None
|
|
@@ -1,9 +1,43 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from pathlib import Path
|
|
3
|
+
from typing import Literal
|
|
2
4
|
|
|
5
|
+
from llama_deploy.core.config import DEFAULT_DEPLOYMENT_FILE_PATH
|
|
6
|
+
from llama_deploy.core.deployment_config import resolve_config_parent
|
|
3
7
|
from pydantic import Field
|
|
4
8
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
9
|
|
|
6
10
|
|
|
11
|
+
class BootstrapSettings(BaseSettings):
|
|
12
|
+
"""
|
|
13
|
+
Settings configurable via env vars for controlling how an application is
|
|
14
|
+
created from a git repository.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
model_config = SettingsConfigDict(env_prefix="LLAMA_DEPLOY_")
|
|
18
|
+
repo_url: str | None = Field(
|
|
19
|
+
default=None, description="The URL of the git repository to clone"
|
|
20
|
+
)
|
|
21
|
+
auth_token: str | None = Field(
|
|
22
|
+
default=None, description="The token to use to clone the git repository"
|
|
23
|
+
)
|
|
24
|
+
git_ref: str | None = Field(
|
|
25
|
+
default=None, description="The git reference to checkout"
|
|
26
|
+
)
|
|
27
|
+
git_sha: str | None = Field(default=None, description="The git SHA to checkout")
|
|
28
|
+
deployment_file_path: str = Field(
|
|
29
|
+
default=".",
|
|
30
|
+
description="The path to the deployment file, relative to the root of the repository",
|
|
31
|
+
)
|
|
32
|
+
deployment_name: str | None = Field(
|
|
33
|
+
default=None, description="The name of the deployment"
|
|
34
|
+
)
|
|
35
|
+
bootstrap_sdists: str | None = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
description="A directory containing tar.gz sdists to install instead of installing the appserver",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
7
41
|
class ApiserverSettings(BaseSettings):
|
|
8
42
|
model_config = SettingsConfigDict(env_prefix="LLAMA_DEPLOY_APISERVER_")
|
|
9
43
|
|
|
@@ -15,69 +49,85 @@ class ApiserverSettings(BaseSettings):
|
|
|
15
49
|
default=4501,
|
|
16
50
|
description="The TCP port where to bind the API Server",
|
|
17
51
|
)
|
|
18
|
-
rc_path: Path = Field(
|
|
19
|
-
default=Path("./.llama_deploy_rc"),
|
|
20
|
-
description="Path to the folder containing the deployment configs that will be loaded at startup",
|
|
21
|
-
)
|
|
22
|
-
deployments_path: Path | None = Field(
|
|
23
|
-
default=None,
|
|
24
|
-
description="Path to the folder where deployments will create their root path, defaults to a temp dir",
|
|
25
|
-
)
|
|
26
|
-
deployment_file_path: str | None = Field(
|
|
27
|
-
default=None,
|
|
28
|
-
description="Optional path, relative to the rc_path, where the deployment file is located. If not provided, will glob all .yml/.yaml files in the rc_path",
|
|
29
|
-
)
|
|
30
|
-
use_tls: bool = Field(
|
|
31
|
-
default=False,
|
|
32
|
-
description="Use TLS (HTTPS) to communicate with the API Server",
|
|
33
|
-
)
|
|
34
52
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
description="Whether to enable the Prometheus metrics exporter along with the API Server",
|
|
53
|
+
app_root: Path = Field(
|
|
54
|
+
default=Path("."),
|
|
55
|
+
description="The root of the application",
|
|
39
56
|
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
|
|
58
|
+
deployment_file_path: Path = Field(
|
|
59
|
+
default=Path(DEFAULT_DEPLOYMENT_FILE_PATH),
|
|
60
|
+
description="path, relative to the repository root, where the pyproject.toml file is located",
|
|
43
61
|
)
|
|
44
62
|
|
|
45
|
-
|
|
46
|
-
tracing_enabled: bool = Field(
|
|
63
|
+
proxy_ui: bool = Field(
|
|
47
64
|
default=False,
|
|
48
|
-
description="
|
|
65
|
+
description="If true, proxy a development UI server instead of serving built assets",
|
|
49
66
|
)
|
|
50
|
-
|
|
51
|
-
default=
|
|
52
|
-
description="
|
|
67
|
+
proxy_ui_port: int = Field(
|
|
68
|
+
default=4502,
|
|
69
|
+
description="The TCP port where to bind the UI proxy server",
|
|
53
70
|
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
|
|
72
|
+
reload: bool = Field(
|
|
73
|
+
default=False,
|
|
74
|
+
description="If true, reload the workflow modules, for use in a dev server environment",
|
|
57
75
|
)
|
|
58
|
-
|
|
76
|
+
|
|
77
|
+
persistence: Literal["memory", "local", "cloud"] | None = Field(
|
|
59
78
|
default=None,
|
|
60
|
-
description="
|
|
61
|
-
)
|
|
62
|
-
tracing_sample_rate: float = Field(
|
|
63
|
-
default=1.0,
|
|
64
|
-
description="Trace sampling rate (0.0 to 1.0). Defaults to 1.0 (100% sampling).",
|
|
79
|
+
description="The persistence mode to use for the workflow server",
|
|
65
80
|
)
|
|
66
|
-
|
|
67
|
-
default=
|
|
68
|
-
description="
|
|
81
|
+
local_persistence_path: str | None = Field(
|
|
82
|
+
default=None,
|
|
83
|
+
description="The path to the sqlite database to use for the workflow server",
|
|
69
84
|
)
|
|
70
|
-
|
|
71
|
-
default=
|
|
72
|
-
description="
|
|
85
|
+
cloud_persistence_name: str | None = Field(
|
|
86
|
+
default=None,
|
|
87
|
+
description="Agent Data deployment name to use for workflow persistence. May optionally include a `:` delimited collection name, e.g. 'my_agent:my_collection'. Leave none to use the current deployment name. Recommended to override with _public if running locally, and specify a collection name",
|
|
73
88
|
)
|
|
74
89
|
|
|
75
90
|
@property
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
if self.port == 80:
|
|
79
|
-
return f"{protocol}{self.host}"
|
|
80
|
-
return f"{protocol}{self.host}:{self.port}"
|
|
91
|
+
def resolved_config_parent(self) -> Path:
|
|
92
|
+
return resolve_config_parent(self.app_root, self.deployment_file_path)
|
|
81
93
|
|
|
82
94
|
|
|
83
95
|
settings = ApiserverSettings()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def configure_settings(
|
|
99
|
+
proxy_ui: bool | None = None,
|
|
100
|
+
deployment_file_path: Path | None = None,
|
|
101
|
+
app_root: Path | None = None,
|
|
102
|
+
reload: bool | None = None,
|
|
103
|
+
persistence: Literal["memory", "local", "cloud"] | None = None,
|
|
104
|
+
local_persistence_path: str | None = None,
|
|
105
|
+
cloud_persistence_name: str | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
if proxy_ui is not None:
|
|
108
|
+
settings.proxy_ui = proxy_ui
|
|
109
|
+
os.environ["LLAMA_DEPLOY_APISERVER_PROXY_UI"] = "true" if proxy_ui else "false"
|
|
110
|
+
if deployment_file_path is not None:
|
|
111
|
+
settings.deployment_file_path = deployment_file_path
|
|
112
|
+
os.environ["LLAMA_DEPLOY_APISERVER_DEPLOYMENT_FILE_PATH"] = str(
|
|
113
|
+
deployment_file_path
|
|
114
|
+
)
|
|
115
|
+
if app_root is not None:
|
|
116
|
+
settings.app_root = app_root
|
|
117
|
+
os.environ["LLAMA_DEPLOY_APISERVER_APP_ROOT"] = str(app_root)
|
|
118
|
+
if reload is not None:
|
|
119
|
+
settings.reload = reload
|
|
120
|
+
os.environ["LLAMA_DEPLOY_APISERVER_RELOAD"] = "true" if reload else "false"
|
|
121
|
+
if persistence is not None:
|
|
122
|
+
settings.persistence = persistence
|
|
123
|
+
os.environ["LLAMA_DEPLOY_APISERVER_PERSISTENCE"] = persistence
|
|
124
|
+
if local_persistence_path is not None:
|
|
125
|
+
settings.local_persistence_path = local_persistence_path
|
|
126
|
+
os.environ["LLAMA_DEPLOY_APISERVER_LOCAL_PERSISTENCE_PATH"] = (
|
|
127
|
+
local_persistence_path
|
|
128
|
+
)
|
|
129
|
+
if cloud_persistence_name is not None:
|
|
130
|
+
settings.cloud_persistence_name = cloud_persistence_name
|
|
131
|
+
os.environ["LLAMA_DEPLOY_APISERVER_CLOUD_PERSISTENCE_NAME"] = (
|
|
132
|
+
cloud_persistence_name
|
|
133
|
+
)
|
llama_deploy/appserver/types.py
CHANGED