fix(studio): tighten partial capability fallback

parent 3aa1cfcc
...@@ -256,6 +256,16 @@ async def dashboard_studio(request: Request): ...@@ -256,6 +256,16 @@ async def dashboard_studio(request: Request):
if auth_check: if auth_check:
return auth_check return auth_check
current_user_id = request.session.get("user_id")
scope = "admin" if request.session.get("role") == "admin" else "user"
db = None if scope == "admin" else DatabaseRegistry.get_config_database()
catalog = build_studio_catalog(
scope=scope,
owner_id=current_user_id,
config=_config,
db=db,
)
return _templates.TemplateResponse( return _templates.TemplateResponse(
request=request, request=request,
name="dashboard/studio.html", name="dashboard/studio.html",
...@@ -263,7 +273,7 @@ async def dashboard_studio(request: Request): ...@@ -263,7 +273,7 @@ async def dashboard_studio(request: Request):
"request": request, "request": request,
"session": request.session, "session": request.session,
"__version__": __version__, "__version__": __version__,
"studio_bootstrap_json": json.dumps({}), "studio_bootstrap_json": json.dumps(catalog),
"studio_body_mode": "wide", "studio_body_mode": "wide",
}, },
) )
......
...@@ -207,10 +207,13 @@ def merge_capabilities( ...@@ -207,10 +207,13 @@ def merge_capabilities(
def derive_aggregate_capabilities(capability_sets: Iterable[Optional[Iterable[str]]]) -> StudioCapabilityMergeResult: def derive_aggregate_capabilities(capability_sets: Iterable[Optional[Iterable[str]]]) -> StudioCapabilityMergeResult:
capability_sets = list(capability_sets)
normalized_sets = [normalize_capabilities(capabilities) for capabilities in capability_sets if capabilities] normalized_sets = [normalize_capabilities(capabilities) for capabilities in capability_sets if capabilities]
if not normalized_sets: if not normalized_sets:
return StudioCapabilityMergeResult(capabilities=[], partial_capabilities=[]) return StudioCapabilityMergeResult(capabilities=[], partial_capabilities=[])
has_unknown_member = any(not normalize_capabilities(capabilities) for capabilities in capability_sets)
aggregate = list(normalized_sets[0]) aggregate = list(normalized_sets[0])
partial: List[str] = [] partial: List[str] = []
for capability_set in normalized_sets[1:]: for capability_set in normalized_sets[1:]:
...@@ -218,6 +221,10 @@ def derive_aggregate_capabilities(capability_sets: Iterable[Optional[Iterable[st ...@@ -218,6 +221,10 @@ def derive_aggregate_capabilities(capability_sets: Iterable[Optional[Iterable[st
aggregate = merged.capabilities aggregate = merged.capabilities
partial = _dedupe([*partial, *merged.partial_capabilities, *[capability for capability in capability_set if capability not in aggregate]]) partial = _dedupe([*partial, *merged.partial_capabilities, *[capability for capability in capability_set if capability not in aggregate]])
if has_unknown_member:
partial = _dedupe([*partial, *aggregate])
aggregate = []
partial = [capability for capability in partial if capability not in aggregate] partial = [capability for capability in partial if capability not in aggregate]
return StudioCapabilityMergeResult(capabilities=aggregate, partial_capabilities=partial) return StudioCapabilityMergeResult(capabilities=aggregate, partial_capabilities=partial)
......
...@@ -80,7 +80,35 @@ def test_dashboard_studio_renders_empty_diagnostics_contract_for_shell_boot(): ...@@ -80,7 +80,35 @@ def test_dashboard_studio_renders_empty_diagnostics_contract_for_shell_boot():
assert response.status_code == 200 assert response.status_code == 200
assert 'id="studio-diagnostics" data-empty-message="No diagnostics yet."' in response.text assert 'id="studio-diagnostics" data-empty-message="No diagnostics yet."' in response.text
assert '<span data-i18n="studio.diagnostics_empty">No diagnostics yet.</span>' in response.text assert '<span data-i18n="studio.diagnostics_empty">No diagnostics yet.</span>' in response.text
assert '<script id="studio-bootstrap" type="application/json">{}</script>' in response.text
def test_dashboard_studio_bootstraps_initial_catalog_data(monkeypatch):
client = TestClient(app)
_login_as_admin(client)
monkeypatch.setattr(
dashboard_providers,
"build_studio_catalog",
lambda **kwargs: {
"scope": kwargs["scope"],
"owner_id": kwargs["owner_id"],
"entries": [
{
"id": "rotation/creative-rotation",
"kind": "rotation",
"label": "Creative Rotation",
"capabilities": ["chat"],
"partial_capabilities": ["vision"],
}
],
},
)
response = client.get("/dashboard/studio")
assert response.status_code == 200
assert '"id": "rotation/creative-rotation"' in response.text
assert '"partial_capabilities": ["vision"]' in response.text
def test_dashboard_studio_catalog_returns_global_resources_for_admin(monkeypatch): def test_dashboard_studio_catalog_returns_global_resources_for_admin(monkeypatch):
......
...@@ -3,6 +3,7 @@ import pytest ...@@ -3,6 +3,7 @@ import pytest
from aisbf.studio import ( from aisbf.studio import (
StudioCapabilityResult, StudioCapabilityResult,
build_catalog_entry, build_catalog_entry,
derive_aggregate_capabilities,
infer_model_capabilities, infer_model_capabilities,
merge_capabilities, merge_capabilities,
stamp_inferred_capabilities, stamp_inferred_capabilities,
...@@ -234,3 +235,13 @@ def test_merge_capabilities_reports_partial_support_for_rotation_intersection(): ...@@ -234,3 +235,13 @@ def test_merge_capabilities_reports_partial_support_for_rotation_intersection():
assert merged.capabilities == ["chat", "vision"] assert merged.capabilities == ["chat", "vision"]
assert merged.partial_capabilities == ["image_generation"] assert merged.partial_capabilities == ["image_generation"]
def test_derive_aggregate_capabilities_marks_missing_member_capabilities_as_uncertain():
derived = derive_aggregate_capabilities([
["chat", "vision"],
None,
])
assert derived.capabilities == []
assert derived.partial_capabilities == ["chat", "vision"]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment