Skip to content

Commit 8fbc212

Browse files
committed
feat(platform): add feature to build provider from github in k8s
Signed-off-by: Radek Ježek <[email protected]>
1 parent 898bce9 commit 8fbc212

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1944
-246
lines changed

apps/beeai-cli/src/beeai_cli/async_typer.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,19 @@ def wrapped_f(*args, **kwargs):
8080
else:
8181
return f(*args, **kwargs)
8282
except* Exception as ex:
83+
is_connect_error = False
8384
for exc_type, message in extract_messages(ex):
8485
err_console.print(format_error(exc_type, message))
85-
if exc_type in ["ConnectionError", "ConnectError"]:
86-
err_console.hint(
87-
"Start the BeeAI platform using: [green]beeai platform start[/green]. If that does not help, run [green]beeai platform delete[/green] to clean up, then [green]beeai platform start[/green] again."
88-
)
89-
else:
90-
err_console.hint(
91-
"Are you having consistent problems? If so, try these troubleshooting steps: [green]beeai platform delete[/green] to remove the platform, and [green]beeai platform start[/green] to recreate it."
92-
)
86+
is_connect_error = is_connect_error or exc_type in ["ConnectionError", "ConnectError"]
87+
err_console.print()
88+
if is_connect_error:
89+
err_console.hint(
90+
"Start the BeeAI platform using: [green]beeai platform start[/green]. If that does not help, run [green]beeai platform delete[/green] to clean up, then [green]beeai platform start[/green] again."
91+
)
92+
else:
93+
err_console.hint(
94+
"Are you having consistent problems? If so, try these troubleshooting steps: [green]beeai platform delete[/green] to remove the platform, and [green]beeai platform start[/green] to recreate it."
95+
)
9396
if DEBUG:
9497
raise
9598

apps/beeai-cli/src/beeai_cli/commands/agent.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@
9292
from rich.markdown import Markdown
9393
from rich.table import Column
9494

95-
from beeai_cli.api import a2a_client, api_stream
95+
from beeai_cli.api import a2a_client
9696
from beeai_cli.async_typer import AsyncTyper, console, create_table, err_console
9797
from beeai_cli.utils import (
9898
generate_schema_example,
9999
parse_env_var,
100+
print_log,
100101
prompt_user,
101102
remove_nullable,
102103
run_command,
@@ -145,24 +146,6 @@ def short_location(provider: Provider) -> str:
145146
configuration = Configuration()
146147

147148

148-
def _print_log(line, ansi_mode=False):
149-
if "error" in line:
150-
151-
class CustomError(Exception): ...
152-
153-
CustomError.__name__ = line["error"]["type"]
154-
155-
raise CustomError(line["error"]["detail"])
156-
157-
def decode(text: str):
158-
return Text.from_ansi(text) if ansi_mode else text
159-
160-
if line["stream"] == "stderr":
161-
err_console.print(decode(line["message"]))
162-
elif line["stream"] == "stdout":
163-
console.print(decode(line["message"]))
164-
165-
166149
@app.command("add")
167150
async def add_agent(
168151
location: typing.Annotated[
@@ -243,8 +226,8 @@ async def stream_logs(
243226
"""Stream agent provider logs"""
244227
async with configuration.use_platform_client():
245228
provider = select_provider(search_path, await Provider.list()).id
246-
async for message in api_stream("get", f"providers/{provider}/logs"):
247-
_print_log(message)
229+
async for message in Provider.stream_logs(provider):
230+
print_log(message, ansi_mode=True)
248231

249232

250233
async def _ask_form_questions(form_render: FormRender) -> FormResponse:

apps/beeai-cli/src/beeai_cli/commands/build.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
import typer
1717
from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH
1818
from anyio import open_process
19+
from beeai_sdk.platform.provider_build import BuildState, ProviderBuild
1920
from httpx import AsyncClient, HTTPError
2021
from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_delay, wait_fixed
2122

2223
from beeai_cli.async_typer import AsyncTyper
2324
from beeai_cli.console import console
24-
from beeai_cli.utils import capture_output, extract_messages, run_command, status, verbosity
25+
from beeai_cli.utils import capture_output, extract_messages, print_log, run_command, status, verbosity
2526

2627

2728
async def find_free_port():
@@ -128,3 +129,25 @@ async def build(
128129
await driver.import_image(tag)
129130

130131
return tag, agent_card
132+
133+
134+
@app.command("server-side-build")
135+
async def server_side_build_experimental(
136+
github_url: typing.Annotated[
137+
str, typer.Argument(..., help="Github repository URL (public or private if supported by the platform instance)")
138+
],
139+
):
140+
"""EXPERIMENTAL: Build agent from github repository in the platform."""
141+
from beeai_cli.configuration import Configuration
142+
143+
async with Configuration().use_platform_client():
144+
build = await ProviderBuild.create(location=github_url)
145+
async for message in build.stream_logs():
146+
print_log(message, ansi_mode=True)
147+
build = await build.get()
148+
if build.status == BuildState.COMPLETED:
149+
console.success(
150+
f"Agent built successfully, add it to the platform using: [bold]beeai add {build.destination}[/bold]"
151+
)
152+
else:
153+
console.error("Agent build failed, see logs above for details.")

apps/beeai-cli/src/beeai_cli/commands/platform/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import functools
66
import importlib.resources
77
import os
8+
import pathlib
89
import platform
910
import shutil
1011
import sys
@@ -67,17 +68,26 @@ async def start(
6768
"--import", help="Import an image from a local Docker CLI into BeeAI platform", default_factory=list
6869
),
6970
],
71+
values_file: typing.Annotated[
72+
pathlib.Path | None, typer.Option("-f", help="Set Helm chart values using yaml values file")
73+
] = None,
7074
vm_name: typing.Annotated[str, typer.Option(hidden=True)] = "beeai-platform",
7175
verbose: typing.Annotated[bool, typer.Option("-v", help="Show verbose output")] = False,
7276
):
7377
"""Start BeeAI platform."""
7478
import beeai_cli.commands.server
7579

80+
values_file_path = None
81+
if values_file:
82+
values_file_path = pathlib.Path(values_file)
83+
if not values_file_path.is_file():
84+
raise FileNotFoundError(f"Values file {values_file} not found.")
85+
7686
with verbosity(verbose):
7787
driver = get_driver(vm_name=vm_name)
7888
await driver.create_vm()
7989
await driver.install_tools()
80-
await driver.deploy(set_values_list=set_values_list, import_images=import_images)
90+
await driver.deploy(set_values_list=set_values_list, values_file=values_file_path, import_images=import_images)
8191

8292
with console.status("Waiting for BeeAI platform to be ready...", spinner="dots"):
8393
timeout = datetime.timedelta(minutes=20)

apps/beeai-cli/src/beeai_cli/commands/platform/base_driver.py

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
import abc
55
import importlib.resources
6+
import pathlib
67
import shlex
78
import typing
89
from subprocess import CompletedProcess
10+
from textwrap import dedent
911

1012
import anyio
1113
import pydantic
@@ -50,6 +52,33 @@ async def import_image(self, tag: str) -> None: ...
5052
async def exec(self, command: list[str]) -> None: ...
5153

5254
async def install_tools(self) -> None:
55+
# Configure k3s registry for local registry access
56+
registry_config = dedent(
57+
"""\
58+
mirrors:
59+
"beeai-platform-registry-svc.default:5001":
60+
endpoint:
61+
- "http://localhost:30501"
62+
configs:
63+
"beeai-platform-registry-svc.default:5001":
64+
tls:
65+
insecure_skip_verify: true
66+
"""
67+
)
68+
69+
await self.run_in_vm(
70+
[
71+
"sh",
72+
"-c",
73+
(
74+
f"sudo mkdir -p /etc/rancher/k3s /registry-data && "
75+
f"echo '{registry_config}' | "
76+
"sudo tee /etc/rancher/k3s/registries.yaml > /dev/null"
77+
),
78+
],
79+
"Configuring k3s registry",
80+
)
81+
5382
await self.run_in_vm(
5483
[
5584
"sh",
@@ -67,29 +96,35 @@ async def install_tools(self) -> None:
6796
"Installing Helm",
6897
)
6998

70-
async def deploy(self, set_values_list: list[str], import_images: list[str] | None = None) -> None:
99+
async def deploy(
100+
self,
101+
set_values_list: list[str],
102+
values_file: pathlib.Path | None = None,
103+
import_images: list[str] | None = None,
104+
) -> None:
71105
await self.run_in_vm(
72106
["sh", "-c", "mkdir -p /tmp/beeai && cat >/tmp/beeai/chart.tgz"],
73107
"Preparing Helm chart",
74108
input=(importlib.resources.files("beeai_cli") / "data" / "helm-chart.tgz").read_bytes(),
75109
)
110+
values = {
111+
**{svc: {"service": {"type": "LoadBalancer"}} for svc in ["collector", "docling", "ui", "phoenix"]},
112+
"hostNetwork": True,
113+
"externalRegistries": {"public_github": str(Configuration().agent_registry)},
114+
"encryptionKey": "Ovx8qImylfooq4-HNwOzKKDcXLZCB3c_m0JlB9eJBxc=",
115+
"features": {
116+
"uiNavigation": True,
117+
"selfRegistration": True,
118+
"generateConversationTitle": False, # TODO: enable when UI implementation is ready
119+
},
120+
"auth": {"enabled": False},
121+
}
122+
if values_file:
123+
values.update(yaml.safe_load(values_file.read_text()))
76124
await self.run_in_vm(
77125
["sh", "-c", "cat >/tmp/beeai/values.yaml"],
78126
"Preparing Helm values",
79-
input=yaml.dump(
80-
{
81-
**{svc: {"service": {"type": "LoadBalancer"}} for svc in ["collector", "docling", "ui", "phoenix"]},
82-
"hostNetwork": True,
83-
"externalRegistries": {"public_github": str(Configuration().agent_registry)},
84-
"encryptionKey": "Ovx8qImylfooq4-HNwOzKKDcXLZCB3c_m0JlB9eJBxc=",
85-
"features": {
86-
"uiNavigation": True,
87-
"selfRegistration": True,
88-
"generateConversationTitle": False, # TODO: enable when UI implementation is ready
89-
},
90-
"auth": {"enabled": False},
91-
}
92-
).encode("utf-8"),
127+
input=yaml.dump(values).encode("utf-8"),
93128
)
94129

95130
images_str = (

apps/beeai-cli/src/beeai_cli/commands/platform/wsl_driver.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,12 @@ async def create_vm(self):
140140
await self.run_in_vm(["dbus-launch", "true"], "Ensuring persistence of BeeAI VM")
141141

142142
@typing.override
143-
async def deploy(self, set_values_list: list[str], import_images: list[str] | None = None) -> None:
143+
async def deploy(
144+
self,
145+
set_values_list: list[str],
146+
values_file: pathlib.Path | None = None,
147+
import_images: list[str] | None = None,
148+
) -> None:
144149
await self.run_in_vm(
145150
["k3s", "kubectl", "apply", "-f", "-"],
146151
"Setting up internal networking",
@@ -155,7 +160,7 @@ async def deploy(self, set_values_list: list[str], import_images: list[str] | No
155160
}
156161
).encode(),
157162
)
158-
await super().deploy(set_values_list=set_values_list, import_images=import_images)
163+
await super().deploy(set_values_list=set_values_list, values_file=values_file, import_images=import_images)
159164
await self.run_in_vm(
160165
["sh", "-c", "cat >/etc/systemd/system/[email protected]"],
161166
"Installing systemd unit for port-forwarding",

apps/beeai-cli/src/beeai_cli/utils.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import anyio
2121
import anyio.abc
22+
import httpx
2223
import typer
2324
import yaml
2425
from anyio import create_task_group
@@ -53,7 +54,13 @@ def extract_messages(exc):
5354
if isinstance(exc, BaseExceptionGroup):
5455
return [(exc_type, msg) for e in exc.exceptions for exc_type, msg in extract_messages(e)]
5556
else:
56-
return [(type(exc).__name__, str(exc))]
57+
message = str(exc)
58+
if isinstance(exc, httpx.HTTPStatusError):
59+
with contextlib.suppress(Exception):
60+
message = str(exc).split(" for url", maxsplit=1)[0]
61+
message = f"{message}: {exc.response.json()['detail']}"
62+
63+
return [(type(exc).__name__, message)]
5764

5865

5966
def parse_env_var(env_var: str) -> tuple[str, str]:
@@ -280,3 +287,21 @@ async def get_verify_option(server_url: str):
280287
raise RuntimeError(f"No certificate received from {server_url}")
281288
ca_cert_file.write_text(ssl.DER_cert_to_PEM_cert(der_cert))
282289
return str(ca_cert_file)
290+
291+
292+
def print_log(line, ansi_mode=False):
293+
if "error" in line:
294+
295+
class CustomError(Exception): ...
296+
297+
CustomError.__name__ = line["error"]["type"]
298+
299+
raise CustomError(line["error"]["detail"])
300+
301+
def decode(text: str):
302+
return Text.from_ansi(text) if ansi_mode else text
303+
304+
if line["stream"] == "stderr":
305+
err_console.print(decode(line["message"]))
306+
elif line["stream"] == "stdout":
307+
console.print(decode(line["message"]))

apps/beeai-cli/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/beeai-sdk/src/beeai_sdk/platform/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
from .file import *
77
from .model_provider import *
88
from .provider import *
9+
from .provider_build import *
910
from .vector_store import *

apps/beeai-sdk/src/beeai_sdk/platform/common.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
22
# SPDX-License-Identifier: Apache-2.0
3-
3+
from enum import StrEnum
44
from typing import Generic, TypeVar
55

66
from pydantic import BaseModel
@@ -13,3 +13,18 @@ class PaginatedResult(BaseModel, Generic[T]):
1313
total_count: int
1414
has_more: bool = False
1515
next_page_token: str | None = None
16+
17+
18+
class GithubVersionType(StrEnum):
19+
HEAD = "head"
20+
TAG = "tag"
21+
22+
23+
class ResolvedGithubUrl(BaseModel):
24+
host: str = "github.com"
25+
org: str
26+
repo: str
27+
version: str
28+
version_type: GithubVersionType
29+
commit_hash: str
30+
path: str | None = None

0 commit comments

Comments
 (0)