fix(studio): tighten partial capability fallback

parent 3aa1cfcc
......@@ -256,6 +256,16 @@ async def dashboard_studio(request: Request):
if 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(
request=request,
name="dashboard/studio.html",
......@@ -263,7 +273,7 @@ async def dashboard_studio(request: Request):
"request": request,
"session": request.session,
"__version__": __version__,
"studio_bootstrap_json": json.dumps({}),
"studio_bootstrap_json": json.dumps(catalog),
"studio_body_mode": "wide",
},
)
......
......@@ -207,10 +207,13 @@ def merge_capabilities(
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]
if not normalized_sets:
return StudioCapabilityMergeResult(capabilities=[], partial_capabilities=[])
has_unknown_member = any(not normalize_capabilities(capabilities) for capabilities in capability_sets)
aggregate = list(normalized_sets[0])
partial: List[str] = []
for capability_set in normalized_sets[1:]:
......@@ -218,6 +221,10 @@ def derive_aggregate_capabilities(capability_sets: Iterable[Optional[Iterable[st
aggregate = merged.capabilities
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]
return StudioCapabilityMergeResult(capabilities=aggregate, partial_capabilities=partial)
......
......@@ -80,7 +80,35 @@ def test_dashboard_studio_renders_empty_diagnostics_contract_for_shell_boot():
assert response.status_code == 200
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 '<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):
......
......@@ -3,6 +3,7 @@ import pytest
from aisbf.studio import (
StudioCapabilityResult,
build_catalog_entry,
derive_aggregate_capabilities,
infer_model_capabilities,
merge_capabilities,
stamp_inferred_capabilities,
......@@ -234,3 +235,13 @@ def test_merge_capabilities_reports_partial_support_for_rotation_intersection():
assert merged.capabilities == ["chat", "vision"]
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