llama-deploy-appserver 0.2.7a1__tar.gz
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-0.2.7a1/PKG-INFO +23 -0
- llama_deploy_appserver-0.2.7a1/README.md +2 -0
- llama_deploy_appserver-0.2.7a1/pyproject.toml +34 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/__init__.py +0 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/__main__.py +14 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/app.py +49 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/bootstrap.py +43 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/__init__.py +3 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/base.py +30 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/client.py +49 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/models/__init__.py +4 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/models/apiserver.py +356 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/client/models/model.py +82 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/deployment.py +495 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/deployment_config_parser.py +133 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/routers/__init__.py +4 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/routers/deployments.py +433 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/routers/status.py +40 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/run_autodeploy.py +141 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/server.py +60 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/settings.py +83 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/__init__.py +5 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/base.py +33 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/git.py +48 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/source_managers/local.py +51 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/stats.py +36 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/tracing.py +237 -0
- llama_deploy_appserver-0.2.7a1/src/llama_deploy/appserver/types.py +100 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: llama-deploy-appserver
|
3
|
+
Version: 0.2.7a1
|
4
|
+
Summary: Application server components for LlamaDeploy
|
5
|
+
Author: Massimiliano Pippi
|
6
|
+
Author-email: Massimiliano Pippi <mpippi@gmail.com>
|
7
|
+
License: MIT
|
8
|
+
Requires-Dist: asgiref>=3.9.1
|
9
|
+
Requires-Dist: llama-index-workflows>=1.1.0
|
10
|
+
Requires-Dist: pydantic-settings>=2.10.1
|
11
|
+
Requires-Dist: uvicorn>=0.24.0
|
12
|
+
Requires-Dist: prometheus-client>=0.20.0
|
13
|
+
Requires-Dist: python-multipart>=0.0.18,<0.0.19
|
14
|
+
Requires-Dist: fastapi>=0.100.0
|
15
|
+
Requires-Dist: websockets>=12.0
|
16
|
+
Requires-Dist: gitpython>=3.1.40,<4
|
17
|
+
Requires-Dist: llama-deploy-core>=0.2.7a1,<0.3.0
|
18
|
+
Requires-Dist: httpx>=0.28.1
|
19
|
+
Requires-Python: >=3.12, <4
|
20
|
+
Description-Content-Type: text/markdown
|
21
|
+
|
22
|
+
> [!WARNING]
|
23
|
+
> This repository contains pre-release software. It is unstable, incomplete, and subject to breaking changes. Not recommended for use.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
[project]
|
2
|
+
name = "llama-deploy-appserver"
|
3
|
+
version = "0.2.7a1"
|
4
|
+
description = "Application server components for LlamaDeploy"
|
5
|
+
readme = "README.md"
|
6
|
+
license = { text = "MIT" }
|
7
|
+
authors = [
|
8
|
+
{ name = "Massimiliano Pippi", email = "mpippi@gmail.com" }
|
9
|
+
]
|
10
|
+
requires-python = ">=3.12, <4"
|
11
|
+
dependencies = [
|
12
|
+
"asgiref>=3.9.1",
|
13
|
+
"llama-index-workflows>=1.1.0",
|
14
|
+
"pydantic-settings>=2.10.1",
|
15
|
+
"uvicorn>=0.24.0",
|
16
|
+
"prometheus-client>=0.20.0",
|
17
|
+
"python-multipart>=0.0.18,<0.0.19",
|
18
|
+
"fastapi>=0.100.0",
|
19
|
+
"websockets>=12.0",
|
20
|
+
"gitpython>=3.1.40,<4",
|
21
|
+
"llama-deploy-core>=0.2.7a1,<0.3.0",
|
22
|
+
"httpx>=0.28.1",
|
23
|
+
]
|
24
|
+
|
25
|
+
[build-system]
|
26
|
+
requires = ["uv_build>=0.7.20,<0.8.0"]
|
27
|
+
build-backend = "uv_build"
|
28
|
+
|
29
|
+
[tool.uv.build-backend]
|
30
|
+
module-name = "llama_deploy.appserver"
|
31
|
+
|
32
|
+
|
33
|
+
[tool.uv.sources]
|
34
|
+
llama-deploy-core = { workspace = true }
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import uvicorn
|
2
|
+
from prometheus_client import start_http_server
|
3
|
+
|
4
|
+
from .settings import settings
|
5
|
+
|
6
|
+
if __name__ == "__main__":
|
7
|
+
if settings.prometheus_enabled:
|
8
|
+
start_http_server(settings.prometheus_port)
|
9
|
+
|
10
|
+
uvicorn.run(
|
11
|
+
"llama_deploy.appserver.app:app",
|
12
|
+
host=settings.host,
|
13
|
+
port=settings.port,
|
14
|
+
)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
|
4
|
+
from fastapi import FastAPI
|
5
|
+
from fastapi.middleware.cors import CORSMiddleware
|
6
|
+
from fastapi.requests import Request
|
7
|
+
from fastapi.responses import JSONResponse, RedirectResponse
|
8
|
+
|
9
|
+
from .routers import deployments_router, status_router
|
10
|
+
from .server import lifespan, manager
|
11
|
+
from .settings import settings
|
12
|
+
from .tracing import configure_tracing
|
13
|
+
|
14
|
+
|
15
|
+
logger = logging.getLogger("uvicorn.info")
|
16
|
+
|
17
|
+
|
18
|
+
app = FastAPI(lifespan=lifespan)
|
19
|
+
|
20
|
+
# Setup tracing
|
21
|
+
configure_tracing(settings)
|
22
|
+
|
23
|
+
# Configure CORS middleware if the environment variable is set
|
24
|
+
if not os.environ.get("DISABLE_CORS", False):
|
25
|
+
app.add_middleware(
|
26
|
+
CORSMiddleware,
|
27
|
+
allow_origins=["*"], # Allows all origins
|
28
|
+
allow_credentials=True,
|
29
|
+
allow_methods=["GET", "POST"],
|
30
|
+
allow_headers=["Content-Type", "Authorization"],
|
31
|
+
)
|
32
|
+
|
33
|
+
app.include_router(deployments_router)
|
34
|
+
app.include_router(status_router)
|
35
|
+
|
36
|
+
|
37
|
+
@app.get("/", response_model=None)
|
38
|
+
async def root(request: Request) -> JSONResponse | RedirectResponse:
|
39
|
+
# for local dev, just redirect to the one UI if we have one
|
40
|
+
if len(manager.deployment_names) == 1:
|
41
|
+
deployment = manager.get_deployment(manager.deployment_names[0])
|
42
|
+
if deployment is not None and deployment._ui_server_process is not None:
|
43
|
+
return RedirectResponse(f"deployments/{deployment.name}/ui")
|
44
|
+
return JSONResponse(
|
45
|
+
{
|
46
|
+
"swagger_docs": f"{request.base_url}docs",
|
47
|
+
"status": f"{request.base_url}status",
|
48
|
+
}
|
49
|
+
)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
"""
|
2
|
+
Bootstraps an application from a remote github repository given environment variables.
|
3
|
+
|
4
|
+
This just sets up the files from the repository. It's more of a build process, does not start an application.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import asyncio
|
8
|
+
from llama_deploy.core.git.git_util import (
|
9
|
+
clone_repo,
|
10
|
+
)
|
11
|
+
from pydantic import Field
|
12
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
13
|
+
|
14
|
+
|
15
|
+
class BootstrapSettings(BaseSettings):
|
16
|
+
model_config = SettingsConfigDict(env_prefix="LLAMA_DEPLOY_")
|
17
|
+
git_url: str = Field(..., description="The URL of the git repository to clone")
|
18
|
+
git_token: str | None = Field(
|
19
|
+
default=None, description="The token to use to clone the git repository"
|
20
|
+
)
|
21
|
+
git_ref: str | None = Field(
|
22
|
+
default=None, description="The git reference to checkout"
|
23
|
+
)
|
24
|
+
git_sha: str | None = Field(default=None, description="The git SHA to checkout")
|
25
|
+
deployment_file_path: str = Field(
|
26
|
+
default="llama_deploy.yaml", description="The path to the deployment file"
|
27
|
+
)
|
28
|
+
deployment_name: str | None = Field(
|
29
|
+
default=None, description="The name of the deployment"
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
async def main():
|
34
|
+
settings = BootstrapSettings()
|
35
|
+
# Needs the github url+auth, and the deployment file path
|
36
|
+
# clones the repo to a standard directory
|
37
|
+
# (eventually) runs the UI build process and moves that to a standard directory for a file server
|
38
|
+
clone_repo(settings.git_url, "/app/", settings.git_token)
|
39
|
+
pass
|
40
|
+
|
41
|
+
|
42
|
+
if __name__ == "__main__":
|
43
|
+
asyncio.run(main())
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
5
|
+
|
6
|
+
|
7
|
+
class _BaseClient(BaseSettings):
|
8
|
+
"""Base type for clients, to be used in Pydantic models to avoid circular imports.
|
9
|
+
|
10
|
+
Settings can be passed to the Client constructor when creating an instance, or defined with environment variables
|
11
|
+
having names prefixed with the string `LLAMA_DEPLOY_`, e.g. `LLAMA_DEPLOY_DISABLE_SSL`.
|
12
|
+
"""
|
13
|
+
|
14
|
+
model_config = SettingsConfigDict(env_prefix="LLAMA_DEPLOY_")
|
15
|
+
|
16
|
+
api_server_url: str = "http://localhost:4501"
|
17
|
+
disable_ssl: bool = False
|
18
|
+
timeout: float | None = 120.0
|
19
|
+
poll_interval: float = 0.5
|
20
|
+
|
21
|
+
async def request(
|
22
|
+
self, method: str, url: str | httpx.URL, **kwargs: Any
|
23
|
+
) -> httpx.Response:
|
24
|
+
"""Performs an async HTTP request using httpx."""
|
25
|
+
verify = kwargs.pop("verify", True)
|
26
|
+
timeout = kwargs.pop("timeout", self.timeout)
|
27
|
+
async with httpx.AsyncClient(verify=verify) as client:
|
28
|
+
response = await client.request(method, url, timeout=timeout, **kwargs)
|
29
|
+
response.raise_for_status()
|
30
|
+
return response
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from .base import _BaseClient
|
5
|
+
from .models import ApiServer, make_sync
|
6
|
+
|
7
|
+
|
8
|
+
class Client(_BaseClient):
|
9
|
+
"""The LlamaDeploy Python client.
|
10
|
+
|
11
|
+
The client is gives access to both the asyncio and non-asyncio APIs. To access the sync
|
12
|
+
API just use methods of `client.sync`.
|
13
|
+
|
14
|
+
Example usage:
|
15
|
+
```py
|
16
|
+
from llama_deploy.client import Client
|
17
|
+
|
18
|
+
# Use the same client instance
|
19
|
+
c = Client()
|
20
|
+
|
21
|
+
async def an_async_function():
|
22
|
+
status = await client.apiserver.status()
|
23
|
+
|
24
|
+
def normal_function():
|
25
|
+
status = client.sync.apiserver.status()
|
26
|
+
```
|
27
|
+
"""
|
28
|
+
|
29
|
+
@property
|
30
|
+
def sync(self) -> "_SyncClient":
|
31
|
+
"""Returns the sync version of the client API."""
|
32
|
+
try:
|
33
|
+
asyncio.get_running_loop()
|
34
|
+
except RuntimeError:
|
35
|
+
return _SyncClient(**self.model_dump())
|
36
|
+
|
37
|
+
msg = "You cannot use the sync client within an async event loop - just await the async methods directly."
|
38
|
+
raise RuntimeError(msg)
|
39
|
+
|
40
|
+
@property
|
41
|
+
def apiserver(self) -> ApiServer:
|
42
|
+
"""Access the API Server functionalities."""
|
43
|
+
return ApiServer(client=self, id="apiserver")
|
44
|
+
|
45
|
+
|
46
|
+
class _SyncClient(_BaseClient):
|
47
|
+
@property
|
48
|
+
def apiserver(self) -> Any:
|
49
|
+
return make_sync(ApiServer)(client=self, id="apiserver")
|
@@ -0,0 +1,356 @@
|
|
1
|
+
"""Client functionalities to operate on the API Server.
|
2
|
+
|
3
|
+
This module allows the client to use all the functionalities
|
4
|
+
from the LlamaDeploy API Server. For this to work, the API
|
5
|
+
Server must be up and its URL (by default `http://localhost:4501`)
|
6
|
+
reachable by the host executing the client code.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import asyncio
|
10
|
+
import json
|
11
|
+
from typing import Any, AsyncGenerator, TextIO
|
12
|
+
|
13
|
+
import httpx
|
14
|
+
from llama_deploy.appserver.types import (
|
15
|
+
EventDefinition,
|
16
|
+
SessionDefinition,
|
17
|
+
Status,
|
18
|
+
StatusEnum,
|
19
|
+
TaskDefinition,
|
20
|
+
TaskResult,
|
21
|
+
)
|
22
|
+
from pydantic import Field
|
23
|
+
from workflows.context import JsonSerializer
|
24
|
+
from workflows.events import Event
|
25
|
+
|
26
|
+
from .model import Collection, Model
|
27
|
+
|
28
|
+
|
29
|
+
class SessionCollection(Collection):
|
30
|
+
"""A model representing a collection of session for a given deployment."""
|
31
|
+
|
32
|
+
deployment_id: str = Field(
|
33
|
+
description="The ID of the deployment containing the sessions."
|
34
|
+
)
|
35
|
+
|
36
|
+
async def delete(self, session_id: str) -> None:
|
37
|
+
"""Deletes the session with the provided `session_id`.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
session_id: The id of the session that will be removed
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
HTTPException: If the session couldn't be found with the id provided.
|
44
|
+
"""
|
45
|
+
delete_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions/delete"
|
46
|
+
|
47
|
+
await self.client.request(
|
48
|
+
"POST",
|
49
|
+
delete_url,
|
50
|
+
params={"session_id": session_id},
|
51
|
+
verify=not self.client.disable_ssl,
|
52
|
+
timeout=self.client.timeout,
|
53
|
+
)
|
54
|
+
|
55
|
+
async def create(self) -> SessionDefinition:
|
56
|
+
"""Create a new session."""
|
57
|
+
create_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions/create"
|
58
|
+
|
59
|
+
r = await self.client.request(
|
60
|
+
"POST",
|
61
|
+
create_url,
|
62
|
+
verify=not self.client.disable_ssl,
|
63
|
+
timeout=self.client.timeout,
|
64
|
+
)
|
65
|
+
|
66
|
+
return SessionDefinition(**r.json())
|
67
|
+
|
68
|
+
async def list(self) -> list[SessionDefinition]:
|
69
|
+
"""Returns a collection of all the sessions in the given deployment."""
|
70
|
+
sessions_url = (
|
71
|
+
f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions"
|
72
|
+
)
|
73
|
+
r = await self.client.request(
|
74
|
+
"GET",
|
75
|
+
sessions_url,
|
76
|
+
verify=not self.client.disable_ssl,
|
77
|
+
timeout=self.client.timeout,
|
78
|
+
)
|
79
|
+
|
80
|
+
return r.json()
|
81
|
+
|
82
|
+
async def get(self, id: str) -> SessionDefinition:
|
83
|
+
"""Gets a deployment by id."""
|
84
|
+
get_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/sessions/{id}"
|
85
|
+
await self.client.request(
|
86
|
+
"GET",
|
87
|
+
get_url,
|
88
|
+
verify=not self.client.disable_ssl,
|
89
|
+
timeout=self.client.timeout,
|
90
|
+
)
|
91
|
+
model_class = self._prepare(SessionDefinition)
|
92
|
+
return model_class(client=self.client, id=id)
|
93
|
+
|
94
|
+
|
95
|
+
class Task(Model):
|
96
|
+
"""A model representing a task belonging to a given session in the given deployment."""
|
97
|
+
|
98
|
+
deployment_id: str = Field(
|
99
|
+
description="The ID of the deployment this task belongs to."
|
100
|
+
)
|
101
|
+
session_id: str = Field(description="The ID of the session this task belongs to.")
|
102
|
+
|
103
|
+
async def results(self) -> TaskResult | None:
|
104
|
+
"""Returns the result of a given task."""
|
105
|
+
results_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/{self.id}/results"
|
106
|
+
|
107
|
+
r = await self.client.request(
|
108
|
+
"GET",
|
109
|
+
results_url,
|
110
|
+
verify=not self.client.disable_ssl,
|
111
|
+
params={"session_id": self.session_id},
|
112
|
+
timeout=self.client.timeout,
|
113
|
+
)
|
114
|
+
if r.json():
|
115
|
+
return TaskResult.model_validate(r.json())
|
116
|
+
return None
|
117
|
+
|
118
|
+
async def send_event(self, ev: Event, service_name: str) -> EventDefinition:
|
119
|
+
"""Sends a human response event."""
|
120
|
+
url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/{self.id}/events"
|
121
|
+
|
122
|
+
serializer = JsonSerializer()
|
123
|
+
event_def = EventDefinition(
|
124
|
+
event_obj_str=serializer.serialize(ev), service_id=service_name
|
125
|
+
)
|
126
|
+
|
127
|
+
r = await self.client.request(
|
128
|
+
"POST",
|
129
|
+
url,
|
130
|
+
verify=not self.client.disable_ssl,
|
131
|
+
params={"session_id": self.session_id},
|
132
|
+
json=event_def.model_dump(),
|
133
|
+
timeout=self.client.timeout,
|
134
|
+
)
|
135
|
+
return EventDefinition.model_validate(r.json())
|
136
|
+
|
137
|
+
async def events(self) -> AsyncGenerator[dict[str, Any], None]: # pragma: no cover
|
138
|
+
"""Returns a generator object to consume the events streamed from a service."""
|
139
|
+
events_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/{self.id}/events"
|
140
|
+
|
141
|
+
while True:
|
142
|
+
try:
|
143
|
+
async with httpx.AsyncClient(
|
144
|
+
verify=not self.client.disable_ssl
|
145
|
+
) as client:
|
146
|
+
async with client.stream(
|
147
|
+
"GET", events_url, params={"session_id": self.session_id}
|
148
|
+
) as response:
|
149
|
+
response.raise_for_status()
|
150
|
+
async for line in response.aiter_lines():
|
151
|
+
json_line = json.loads(line)
|
152
|
+
yield json_line
|
153
|
+
break # Exit the function if successful
|
154
|
+
except httpx.HTTPStatusError as e:
|
155
|
+
if e.response.status_code != 404:
|
156
|
+
raise # Re-raise if it's not a 404 error
|
157
|
+
await asyncio.sleep(self.client.poll_interval)
|
158
|
+
|
159
|
+
|
160
|
+
class TaskCollection(Collection):
|
161
|
+
"""A model representing a collection of tasks for a given deployment."""
|
162
|
+
|
163
|
+
deployment_id: str = Field(
|
164
|
+
description="The ID of the deployment these tasks belong to."
|
165
|
+
)
|
166
|
+
|
167
|
+
async def run(self, task: TaskDefinition) -> Any:
|
168
|
+
"""Runs a task and returns the results once it's done.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
task: The definition of the task we want to run.
|
172
|
+
"""
|
173
|
+
run_url = (
|
174
|
+
f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/run"
|
175
|
+
)
|
176
|
+
if task.session_id:
|
177
|
+
run_url += f"?session_id={task.session_id}"
|
178
|
+
|
179
|
+
r = await self.client.request(
|
180
|
+
"POST",
|
181
|
+
run_url,
|
182
|
+
verify=not self.client.disable_ssl,
|
183
|
+
json=task.model_dump(),
|
184
|
+
timeout=self.client.timeout,
|
185
|
+
)
|
186
|
+
|
187
|
+
return r.json()
|
188
|
+
|
189
|
+
async def create(self, task: TaskDefinition) -> Task:
|
190
|
+
"""Runs a task returns it immediately, without waiting for the results."""
|
191
|
+
create_url = f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks/create"
|
192
|
+
|
193
|
+
r = await self.client.request(
|
194
|
+
"POST",
|
195
|
+
create_url,
|
196
|
+
verify=not self.client.disable_ssl,
|
197
|
+
json=task.model_dump(),
|
198
|
+
timeout=self.client.timeout,
|
199
|
+
)
|
200
|
+
response_fields = r.json()
|
201
|
+
|
202
|
+
model_class = self._prepare(Task)
|
203
|
+
return model_class(
|
204
|
+
client=self.client,
|
205
|
+
deployment_id=self.deployment_id,
|
206
|
+
id=response_fields["task_id"],
|
207
|
+
session_id=response_fields["session_id"],
|
208
|
+
)
|
209
|
+
|
210
|
+
async def list(self) -> list[Task]:
|
211
|
+
"""Returns the list of tasks from this collection."""
|
212
|
+
tasks_url = (
|
213
|
+
f"{self.client.api_server_url}/deployments/{self.deployment_id}/tasks"
|
214
|
+
)
|
215
|
+
r = await self.client.request(
|
216
|
+
"GET",
|
217
|
+
tasks_url,
|
218
|
+
verify=not self.client.disable_ssl,
|
219
|
+
timeout=self.client.timeout,
|
220
|
+
)
|
221
|
+
task_model_class = self._prepare(Task)
|
222
|
+
items = {
|
223
|
+
"id": task_model_class(
|
224
|
+
client=self.client,
|
225
|
+
id=task_def.task_id,
|
226
|
+
session_id=task_def.session_id,
|
227
|
+
deployment_id=self.deployment_id,
|
228
|
+
)
|
229
|
+
for task_def in r.json()
|
230
|
+
}
|
231
|
+
model_class = self._prepare(TaskCollection)
|
232
|
+
return model_class(
|
233
|
+
client=self.client, deployment_id=self.deployment_id, items=items
|
234
|
+
)
|
235
|
+
|
236
|
+
|
237
|
+
class Deployment(Model):
|
238
|
+
"""A model representing a deployment."""
|
239
|
+
|
240
|
+
@property
|
241
|
+
def tasks(self) -> TaskCollection:
|
242
|
+
"""Returns a collection of tasks from all the sessions in the given deployment."""
|
243
|
+
|
244
|
+
model_class = self._prepare(TaskCollection)
|
245
|
+
return model_class(client=self.client, deployment_id=self.id, items={})
|
246
|
+
|
247
|
+
@property
|
248
|
+
def sessions(self) -> SessionCollection:
|
249
|
+
"""Returns a collection of all the sessions in the given deployment."""
|
250
|
+
|
251
|
+
coll_model_class = self._prepare(SessionCollection)
|
252
|
+
return coll_model_class(client=self.client, deployment_id=self.id, items={})
|
253
|
+
|
254
|
+
|
255
|
+
class DeploymentCollection(Collection):
|
256
|
+
"""A model representing a collection of deployments currently active."""
|
257
|
+
|
258
|
+
async def create(
|
259
|
+
self, config: TextIO, base_path: str, reload: bool = False, local: bool = False
|
260
|
+
) -> Deployment:
|
261
|
+
"""Creates a new deployment from a deployment file.
|
262
|
+
|
263
|
+
If `reload` is true, an existing deployment will be reloaded, otherwise
|
264
|
+
an error will be raised.
|
265
|
+
|
266
|
+
If `local` is true, the sync managers won't attempt at syncing data.
|
267
|
+
This is mostly for supporting local development.
|
268
|
+
|
269
|
+
Example:
|
270
|
+
```
|
271
|
+
with open("deployment.yml") as f:
|
272
|
+
await client.apiserver.deployments.create(f)
|
273
|
+
```
|
274
|
+
"""
|
275
|
+
create_url = f"{self.client.api_server_url}/deployments/create"
|
276
|
+
|
277
|
+
files = {"config_file": config.read()}
|
278
|
+
r = await self.client.request(
|
279
|
+
"POST",
|
280
|
+
create_url,
|
281
|
+
files=files,
|
282
|
+
params={"reload": reload, "local": local, "base_path": base_path},
|
283
|
+
verify=not self.client.disable_ssl,
|
284
|
+
timeout=self.client.timeout,
|
285
|
+
)
|
286
|
+
|
287
|
+
model_class = self._prepare(Deployment)
|
288
|
+
return model_class(client=self.client, id=r.json().get("name"))
|
289
|
+
|
290
|
+
async def get(self, id: str) -> Deployment:
|
291
|
+
"""Gets a deployment by id."""
|
292
|
+
get_url = f"{self.client.api_server_url}/deployments/{id}"
|
293
|
+
# Current version of apiserver doesn't returns anything useful in this endpoint, let's just ignore it
|
294
|
+
await self.client.request(
|
295
|
+
"GET",
|
296
|
+
get_url,
|
297
|
+
verify=not self.client.disable_ssl,
|
298
|
+
timeout=self.client.timeout,
|
299
|
+
)
|
300
|
+
model_class = self._prepare(Deployment)
|
301
|
+
return model_class(client=self.client, id=id)
|
302
|
+
|
303
|
+
async def list(self) -> list[Deployment]:
|
304
|
+
"""Return a list of Deployment instances for this collection."""
|
305
|
+
deployments_url = f"{self.client.api_server_url}/deployments/"
|
306
|
+
r = await self.client.request("GET", deployments_url)
|
307
|
+
model_class = self._prepare(Deployment)
|
308
|
+
deployments = [model_class(client=self.client, id=name) for name in r.json()]
|
309
|
+
return deployments
|
310
|
+
|
311
|
+
|
312
|
+
class ApiServer(Model):
|
313
|
+
"""A model representing the API Server instance."""
|
314
|
+
|
315
|
+
async def status(self) -> Status:
|
316
|
+
"""Returns the status of the API Server."""
|
317
|
+
status_url = f"{self.client.api_server_url}/status/"
|
318
|
+
|
319
|
+
try:
|
320
|
+
r = await self.client.request(
|
321
|
+
"GET",
|
322
|
+
status_url,
|
323
|
+
verify=not self.client.disable_ssl,
|
324
|
+
timeout=self.client.timeout,
|
325
|
+
)
|
326
|
+
except httpx.ConnectError:
|
327
|
+
return Status(
|
328
|
+
status=StatusEnum.DOWN,
|
329
|
+
status_message="API Server is down",
|
330
|
+
)
|
331
|
+
|
332
|
+
if r.status_code >= 400:
|
333
|
+
body = r.json()
|
334
|
+
return Status(status=StatusEnum.UNHEALTHY, status_message=r.text)
|
335
|
+
|
336
|
+
description = "LlamaDeploy is up and running."
|
337
|
+
body = r.json()
|
338
|
+
deployments = body.get("deployments") or []
|
339
|
+
if deployments:
|
340
|
+
description += "\nActive deployments:"
|
341
|
+
for d in deployments:
|
342
|
+
description += f"\n- {d}"
|
343
|
+
else:
|
344
|
+
description += "\nCurrently there are no active deployments"
|
345
|
+
|
346
|
+
return Status(
|
347
|
+
status=StatusEnum.HEALTHY,
|
348
|
+
status_message=description,
|
349
|
+
deployments=deployments,
|
350
|
+
)
|
351
|
+
|
352
|
+
@property
|
353
|
+
def deployments(self) -> DeploymentCollection:
|
354
|
+
"""Returns a collection of deployments currently active in the API Server."""
|
355
|
+
model_class = self._prepare(DeploymentCollection)
|
356
|
+
return model_class(client=self.client, items={})
|