Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/beeai-sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies = [
"tenacity>=9.1.2",
"janus>=2.0.0",
"uvloop>=0.21.0",
"httpx", # version determined by a2a-sdk
]

[dependency-groups]
Expand Down
8 changes: 8 additions & 0 deletions apps/beeai-sdk/src/beeai_sdk/platform/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
# SPDX-License-Identifier: Apache-2.0

from .context import *
from .file import *
from .provider import *
from .variables import *
from .vector_store import *
18 changes: 18 additions & 0 deletions apps/beeai-sdk/src/beeai_sdk/platform/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
# SPDX-License-Identifier: Apache-2.0

import os

import httpx

from beeai_sdk.util import resource_context

get_platform_client, use_platform_client = resource_context(
factory=httpx.AsyncClient,
default_factory=lambda: httpx.AsyncClient(base_url=os.environ.get("PLATFORM_URL", "http://127.0.0.1:8333")),
)

__all__ = [
"get_platform_client",
"use_platform_client",
]
144 changes: 144 additions & 0 deletions apps/beeai-sdk/src/beeai_sdk/platform/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import typing

import httpx
import pydantic

from beeai_sdk.platform.context import get_platform_client


class Extraction(pydantic.BaseModel):
id: str
file_id: str
extracted_file_id: str | None = None
status: typing.Literal["pending", "in_progress", "completed", "failed", "cancelled"] = "pending"
job_id: str | None = None
error_message: str | None = None
extraction_metadata: dict[str, typing.Any] | None = None
started_at: pydantic.AwareDatetime | None = None
finished_at: pydantic.AwareDatetime | None = None
created_at: pydantic.AwareDatetime


class File(pydantic.BaseModel):
id: str
filename: str
file_size_bytes: int
created_at: pydantic.AwareDatetime
created_by: str
file_type: typing.Literal["user_upload", "extracted_text"]
parent_file_id: str | None = None

@staticmethod
async def create(
*,
filename: str,
content: typing.BinaryIO | bytes,
content_type: str = "application/octet-stream",
client: httpx.AsyncClient | None = None,
) -> File:
return pydantic.TypeAdapter(File).validate_python(
(
await (client or get_platform_client()).post(
url="/api/v1/files",
files={"file": (filename, content, content_type)},
)
)
.raise_for_status()
.json()
)

async def get(
self: File | str,
*,
client: httpx.AsyncClient | None = None,
) -> File:
# `self` has a weird type so that you can call both `instance.get()` to update an instance, or `File.get("123")` to obtain a new instance
file_id = self if isinstance(self, str) else self.id
return pydantic.TypeAdapter(File).validate_python(
(await (client or get_platform_client()).get(url=f"/api/v1/files/{file_id}")).raise_for_status().json()
)

async def delete(
self: File | str,
*,
client: httpx.AsyncClient | None = None,
) -> None:
# `self` has a weird type so that you can call both `instance.delete()` or `File.delete("123")`
file_id = self if isinstance(self, str) else self.id
_ = (await (client or get_platform_client()).delete(url=f"/api/v1/files/{file_id}")).raise_for_status()

async def content(
self: File | str,
*,
client: httpx.AsyncClient | None = None,
) -> str:
# `self` has a weird type so that you can call both `instance.content()` to get content of an instance, or `File.content("123")`
file_id = self if isinstance(self, str) else self.id
return (
(await (client or get_platform_client()).get(url=f"/api/v1/files/{file_id}/content"))
.raise_for_status()
.text
)

async def text_content(
self: File | str,
*,
client: httpx.AsyncClient | None = None,
) -> str:
# `self` has a weird type so that you can call both `instance.text_content()` to get text content of an instance, or `File.text_content("123")`
file_id = self if isinstance(self, str) else self.id
return (
(await (client or get_platform_client()).get(url=f"/api/v1/files/{file_id}/text_content"))
.raise_for_status()
.text
)

async def create_extraction(
self: File | str,
*,
client: httpx.AsyncClient | None = None,
) -> Extraction:
# `self` has a weird type so that you can call both `instance.create_extraction()` to create an extraction for an instance, or `File.create_extraction("123")`
file_id = self if isinstance(self, str) else self.id
return pydantic.TypeAdapter(Extraction).validate_python(
(
await (client or get_platform_client()).post(
url=f"/api/v1/files/{file_id}/extraction",
)
)
.raise_for_status()
.json()
)

async def get_extraction(
self: File | str,
*,
client: httpx.AsyncClient | None = None,
) -> Extraction:
# `self` has a weird type so that you can call both `instance.get_extraction()` to get an extraction of an instance, or `File.get_extraction("123", "456")`
file_id = self if isinstance(self, str) else self.id
return pydantic.TypeAdapter(Extraction).validate_python(
(
await (client or get_platform_client()).get(
url=f"/api/v1/files/{file_id}/extraction",
)
)
.raise_for_status()
.json()
)

async def delete_extraction(
self: File | str,
*,
client: httpx.AsyncClient | None = None,
) -> None:
# `self` has a weird type so that you can call both `instance.delete_extraction()` or `File.delete_extraction("123", "456")`
file_id = self if isinstance(self, str) else self.id
_ = (
await (client or get_platform_client()).delete(url=f"/api/v1/files/{file_id}/extraction")
).raise_for_status()
103 changes: 103 additions & 0 deletions apps/beeai-sdk/src/beeai_sdk/platform/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
# SPDX-License-Identifier: Apache-2.0

import typing
from datetime import timedelta

import httpx
import pydantic
from a2a.types import AgentCard

from beeai_sdk.platform.context import get_platform_client


class ProviderErrorMessage(pydantic.BaseModel):
message: str


class EnvVar(pydantic.BaseModel):
name: str
description: str | None = None
required: bool = False


class Provider(pydantic.BaseModel):
id: str
auto_stop_timeout: timedelta
source: str
registry: str | None = None
auto_remove: bool = False
created_at: pydantic.AwareDatetime
last_active_at: pydantic.AwareDatetime
agent_card: AgentCard
state: typing.Literal["missing", "starting", "ready", "running", "error"] = "missing"
last_error: ProviderErrorMessage | None = None
missing_configuration: list[EnvVar] = pydantic.Field(default_factory=list)

@staticmethod
async def create(
*,
location: str,
agent_card: AgentCard | None = None,
auto_remove: bool = False,
client: httpx.AsyncClient | None = None,
) -> "Provider":
return pydantic.TypeAdapter(Provider).validate_python(
(
await (client or get_platform_client()).post(
url="/api/v1/providers",
json={
"location": location,
"agent_card": agent_card.model_dump(mode="json") if agent_card else None,
},
params={"auto_remove": auto_remove},
)
)
.raise_for_status()
.json()
)

@staticmethod
async def preview(
*,
location: str,
agent_card: AgentCard | None = None,
client: httpx.AsyncClient | None = None,
) -> "Provider":
return pydantic.TypeAdapter(Provider).validate_python(
(
await (client or get_platform_client()).post(
url="/api/v1/providers/preview",
json={
"location": location,
"agent_card": agent_card.model_dump(mode="json") if agent_card else None,
},
)
)
.raise_for_status()
.json()
)

async def get(self: "Provider | str", /, *, client: httpx.AsyncClient | None = None) -> "Provider":
# `self` has a weird type so that you can call both `instance.get()` to update an instance, or `Provider.get("123")` to obtain a new instance
provider_id = self if isinstance(self, str) else self.id
result = pydantic.TypeAdapter(Provider).validate_json(
(await (client or get_platform_client()).get(url=f"/api/v1/providers/{provider_id}"))
.raise_for_status()
.content
)
if isinstance(self, Provider):
self.__dict__.update(result.__dict__)
return self
return result

async def delete(self: "Provider | str", /, *, client: httpx.AsyncClient | None = None) -> None:
# `self` has a weird type so that you can call both `instance.delete()` or `Provider.delete("123")`
provider_id = self if isinstance(self, str) else self.id
_ = (await (client or get_platform_client()).delete(f"/api/v1/providers/{provider_id}")).raise_for_status()

@staticmethod
async def list(*, client: httpx.AsyncClient | None = None) -> list["Provider"]:
return pydantic.TypeAdapter(list[Provider]).validate_python(
(await (client or get_platform_client()).get(url="/api/v1/providers")).raise_for_status().json()["items"]
)
44 changes: 44 additions & 0 deletions apps/beeai-sdk/src/beeai_sdk/platform/variables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import httpx

from beeai_sdk.platform.context import get_platform_client


class Variables(dict[str, str]):
async def save(
self: Variables | dict[str, str],
*,
client: httpx.AsyncClient | None = None,
) -> None:
"""
Save variables to the BeeAI platform. Does not delete keys unless explicitly set to None.

Can be used as a class method: Variables.save({"key": "value", ...})
...or as an instance method: variables.save()
"""
_ = (
await (client or get_platform_client()).put(
url="/api/v1/variables",
json={"env": self},
)
).raise_for_status()

async def load(self: Variables | None = None, *, client: httpx.AsyncClient | None = None) -> Variables:
"""
Load variables from the BeeAI platform.

Can be used as a class method: variables = Variables.load()
...or as an instance method to update the instance: variables.load()
"""
new_variables: dict[str, str] = (
(await (client or get_platform_client()).get(url="/api/v1/variables")).raise_for_status().json()
)
if isinstance(self, Variables):
self.clear()
self.update(new_variables)
return self
return Variables(new_variables)
Loading