Skip to content

Commit 7fb9799

Browse files
authored
feat(variables): add user-scoped variables (#1203)
Signed-off-by: Radek Ježek <[email protected]>
1 parent c436f76 commit 7fb9799

File tree

24 files changed

+298
-19
lines changed

24 files changed

+298
-19
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import pydantic
1111
from a2a.types import Artifact, Message
12-
from pydantic import AwareDatetime, BaseModel
12+
from pydantic import AwareDatetime, BaseModel, SerializeAsAny
1313

1414
from beeai_sdk.platform.client import PlatformClient, get_platform_client
1515
from beeai_sdk.platform.common import PaginatedResult
@@ -45,6 +45,7 @@ class Permissions(ContextPermissions):
4545
embeddings: set[Literal["*"] | ResourceIdPermission] = set()
4646
a2a_proxy: set[Literal["*"]] = set()
4747
model_providers: set[Literal["read", "write", "*"]] = set()
48+
variables: SerializeAsAny[set[Literal["read", "write", "*"]]] = set()
4849

4950
providers: set[Literal["read", "write", "*"]] = set() # write includes "show logs" permission
5051
provider_variables: set[Literal["read", "write", "*"]] = set()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from __future__ import annotations
5+
6+
from beeai_sdk.platform.client import PlatformClient, get_platform_client
7+
8+
9+
class Variables(dict[str, str]):
10+
async def save(
11+
self: Variables | dict[str, str | None] | dict[str, str],
12+
*,
13+
client: PlatformClient | None = None,
14+
) -> None:
15+
"""
16+
Save variables to the BeeAI platform. Does not delete keys unless explicitly set to None.
17+
18+
Can be used as a class method: Variables.save({"key": "value", ...})
19+
...or as an instance method: variables.save()
20+
"""
21+
async with client or get_platform_client() as client:
22+
_ = (
23+
await client.put(
24+
url="/api/v1/variables",
25+
json={"variables": self},
26+
)
27+
).raise_for_status()
28+
29+
async def load(self: Variables | None = None, *, client: PlatformClient | None = None) -> Variables:
30+
"""
31+
Load variables from the BeeAI platform.
32+
33+
Can be used as a class method: variables = Variables.load()
34+
...or as an instance method to update the instance: variables.load()
35+
"""
36+
async with client or get_platform_client() as client:
37+
new_variables: dict[str, str] = (
38+
(await client.get(url="/api/v1/variables")).raise_for_status().json()["variables"]
39+
)
40+
if isinstance(self, Variables):
41+
self.clear()
42+
self.update(new_variables)
43+
return self
44+
return Variables(new_variables)

apps/beeai-server/src/beeai_server/api/auth/auth.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
a2a_proxy={"*"},
4545
feedback={"write"},
4646
providers={"read"},
47+
variables={"read", "write"},
4748
model_providers={"read"},
4849
contexts={"*"},
4950
context_data={"*"},

apps/beeai-server/src/beeai_server/api/dependencies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
from beeai_server.service_layer.services.contexts import ContextService
2828
from beeai_server.service_layer.services.files import FileService
2929
from beeai_server.service_layer.services.mcp import McpService
30-
from beeai_server.service_layer.services.model_provider import ModelProviderService
31-
from beeai_server.service_layer.services.provider import ProviderService
30+
from beeai_server.service_layer.services.model_providers import ModelProviderService
3231
from beeai_server.service_layer.services.provider_build import ProviderBuildService
32+
from beeai_server.service_layer.services.providers import ProviderService
3333
from beeai_server.service_layer.services.user_feedback import UserFeedbackService
3434
from beeai_server.service_layer.services.users import UserService
3535
from beeai_server.service_layer.services.vector_stores import VectorStoreService

apps/beeai-server/src/beeai_server/api/routes/provider.py renamed to apps/beeai-server/src/beeai_server/api/routes/providers.py

File renamed without changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import logging
5+
from typing import Annotated
6+
7+
import fastapi
8+
from fastapi import APIRouter, Depends
9+
10+
from beeai_server.api.dependencies import RequiresPermissions, UserServiceDependency
11+
from beeai_server.api.schema.env import ListVariablesSchema, UpdateVariablesRequest
12+
from beeai_server.domain.models.permissions import AuthorizedUser
13+
14+
logger = logging.getLogger(__name__)
15+
16+
router = APIRouter()
17+
18+
19+
@router.put("", status_code=fastapi.status.HTTP_201_CREATED)
20+
async def update_user_variables(
21+
request: UpdateVariablesRequest,
22+
user_service: UserServiceDependency,
23+
user: Annotated[AuthorizedUser, Depends(RequiresPermissions(variables={"write"}))],
24+
) -> None:
25+
await user_service.update_user_env(user=user.user, env=request.variables)
26+
27+
28+
@router.get("")
29+
async def list_user_variables(
30+
user_service: UserServiceDependency,
31+
user: Annotated[AuthorizedUser, Depends(RequiresPermissions(variables={"read"}))],
32+
) -> ListVariablesSchema:
33+
return ListVariablesSchema(variables=await user_service.list_user_env(user=user.user))

apps/beeai-server/src/beeai_server/api/schema/env.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
22
# SPDX-License-Identifier: Apache-2.0
33

4-
from pydantic import BaseModel
4+
from pydantic import BaseModel, Field
55

66

77
class UpdateVariablesRequest(BaseModel):
8-
variables: dict[str, str | None]
8+
variables: dict[str, str | None] = Field(max_length=100)
99

1010

1111
class ListVariablesSchema(BaseModel):

apps/beeai-server/src/beeai_server/application.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
from beeai_server.api.routes.mcp import router as mcp_router
2323
from beeai_server.api.routes.model_providers import router as model_providers_router
2424
from beeai_server.api.routes.openai import router as openai_router
25-
from beeai_server.api.routes.provider import router as provider_router
2625
from beeai_server.api.routes.provider_builds import router as provider_builds_router
26+
from beeai_server.api.routes.providers import router as provider_router
2727
from beeai_server.api.routes.user_feedback import router as user_feedback_router
28+
from beeai_server.api.routes.variables import router as variables_router
2829
from beeai_server.api.routes.vector_stores import router as vector_stores_router
2930
from beeai_server.bootstrap import bootstrap_dependencies_sync
3031
from beeai_server.configuration import Configuration
@@ -92,6 +93,7 @@ def mount_routes(app: FastAPI):
9293
server_router.include_router(model_providers_router, prefix="/model_providers", tags=["model_providers"])
9394
server_router.include_router(configuration_router, prefix="/configurations", tags=["configurations"])
9495
server_router.include_router(files_router, prefix="/files", tags=["files"])
96+
server_router.include_router(variables_router, prefix="/variables", tags=["variables"])
9597
server_router.include_router(contexts_router, prefix="/contexts", tags=["contexts"])
9698
server_router.include_router(openai_router, prefix="/openai", tags=["openai"])
9799
server_router.include_router(vector_stores_router, prefix="/vector_stores", tags=["vector_stores"])

apps/beeai-server/src/beeai_server/configuration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ class PersistenceConfiguration(BaseModel):
153153
stale_requests_remove_after_sec: int = int(timedelta(hours=1).total_seconds())
154154
vector_db_schema: str = Field(default="vector_db", pattern=r"^[a-zA-Z0-9_]+$")
155155
procrastinate_schema: str = Field(default="procrastinate", pattern=r"^[a-zA-Z0-9_]+$")
156+
variable_store_limit_per_users: int = 100
156157

157158

158159
class VectorStoresConfiguration(BaseModel):

apps/beeai-server/src/beeai_server/domain/models/permissions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Permissions(BaseModel):
2828
files: SerializeAsAny[set[Literal["read", "write", "extract", "*"]]] = set()
2929
feedback: SerializeAsAny[set[Literal["write"]]] = set()
3030
vector_stores: SerializeAsAny[set[Literal["read", "write", "extract", "*"]]] = set()
31+
variables: SerializeAsAny[set[Literal["read", "write", "*"]]] = set()
3132

3233
# openai proxy
3334
model_providers: SerializeAsAny[set[Literal["read", "write", "*"]]] = set()

0 commit comments

Comments
 (0)