7235d46ade
CI / quality (pull_request) Successful in 19s
CI / lint (pull_request) Successful in 21s
CI / benchmark-publish (pull_request) Has been skipped
CI / security (pull_request) Successful in 50s
CI / typecheck (pull_request) Successful in 58s
CI / build (pull_request) Successful in 29s
CI / integration_tests (pull_request) Successful in 4m16s
CI / unit_tests (pull_request) Successful in 12m18s
CI / docker (pull_request) Successful in 1m30s
CI / benchmark-regression (pull_request) Successful in 25m15s
CI / coverage (pull_request) Successful in 1h21m51s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 17s
CI / lint (push) Successful in 21s
CI / security (push) Successful in 28s
CI / typecheck (push) Successful in 31s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 2m47s
CI / unit_tests (push) Successful in 11m0s
CI / docker (push) Successful in 40s
CI / benchmark-publish (push) Successful in 12m22s
CI / coverage (push) Successful in 44m42s
Add Alembic migration m4_002_skill_flattened_tools to extend the skills table with five new columns: flattened_tools_json, includes_json, capability_summary_json, yaml_text, and flattening_hash (SHA-256). A defence-in-depth uniqueness constraint (uq_skills_name) is also added. Update SkillModel with the new column definitions and extend SkillRepository with update_flattened_tools(), get_flattened_tools(), needs_refresh(), recompute_flattening_hash(), and invalidate_cached_summaries() methods. The existing update() method now nulls all cached fields on mutation (hash-based invalidation). All new repository methods follow the session-factory pattern with @database_retry and flush-but-don-t-commit semantics. Structured logging via structlog records cache updates and invalidations. Database schema docs updated with the new skills table columns and a persistence-field-to-domain-model mapping table. Tests: - 6 Behave scenarios covering create, invalidation, hash staleness, refresh recomputation, uniqueness constraint, and namespace filtering - 2 Robot Framework smoke tests (round-trip and invalidation) - 3 ASV benchmarks (persist, refresh check, namespace list) ISSUES CLOSED: #166
131 lines
4.2 KiB
Python
131 lines
4.2 KiB
Python
"""ASV benchmarks for Skill Registry flattened tool persistence.
|
|
|
|
Measures performance of:
|
|
- Persisting flattened tools with ``update_flattened_tools``
|
|
- Refresh-check via ``needs_refresh``
|
|
- Namespace-filtered listing via ``list_all``
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from cleveragents.domain.models.core.skill import Skill
|
|
from cleveragents.infrastructure.database.models import Base
|
|
from cleveragents.infrastructure.database.repositories import (
|
|
SkillRepository,
|
|
)
|
|
except ModuleNotFoundError:
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from cleveragents.domain.models.core.skill import Skill
|
|
from cleveragents.infrastructure.database.models import Base
|
|
from cleveragents.infrastructure.database.repositories import (
|
|
SkillRepository,
|
|
)
|
|
|
|
|
|
def _make_skill(name: str) -> Skill:
|
|
return Skill(name=name, description=f"Benchmark skill {name}")
|
|
|
|
|
|
class SkillPersistWithTools:
|
|
"""Benchmark persisting flattened tools on an existing skill."""
|
|
|
|
timeout = 60.0
|
|
|
|
def setup(self) -> None:
|
|
self.engine = create_engine("sqlite:///:memory:", echo=False)
|
|
Base.metadata.create_all(self.engine)
|
|
self.factory: sessionmaker[Session] = sessionmaker(bind=self.engine)
|
|
self.repo = SkillRepository(session_factory=self.factory)
|
|
|
|
self.skill = _make_skill("bench/persist-tools")
|
|
self.repo.create(self.skill)
|
|
self.factory().commit()
|
|
|
|
self.tools_json = json.dumps([f"tool-{i}" for i in range(20)])
|
|
self.yaml_text = "name: bench/persist-tools\ntools: [...]"
|
|
self.hash_val = hashlib.sha256(self.yaml_text.encode()).hexdigest()
|
|
|
|
def teardown(self) -> None:
|
|
self.engine.dispose()
|
|
|
|
def time_skill_persist_with_tools(self) -> None:
|
|
self.repo.update_flattened_tools(
|
|
name="bench/persist-tools",
|
|
flattened_tools_json=self.tools_json,
|
|
includes_json="[]",
|
|
capability_summary_json="{}",
|
|
yaml_text=self.yaml_text,
|
|
flattening_hash=self.hash_val,
|
|
)
|
|
self.factory().commit()
|
|
|
|
|
|
class SkillRefreshCheck:
|
|
"""Benchmark refresh-check (hash comparison)."""
|
|
|
|
timeout = 60.0
|
|
|
|
def setup(self) -> None:
|
|
self.engine = create_engine("sqlite:///:memory:", echo=False)
|
|
Base.metadata.create_all(self.engine)
|
|
self.factory: sessionmaker[Session] = sessionmaker(bind=self.engine)
|
|
self.repo = SkillRepository(session_factory=self.factory)
|
|
|
|
self.skill = _make_skill("bench/refresh-check")
|
|
self.repo.create(self.skill)
|
|
self.factory().commit()
|
|
|
|
self.repo.update_flattened_tools(
|
|
name="bench/refresh-check",
|
|
flattened_tools_json="[]",
|
|
includes_json="[]",
|
|
capability_summary_json="{}",
|
|
yaml_text="name: bench/refresh-check",
|
|
flattening_hash="match_hash",
|
|
)
|
|
self.factory().commit()
|
|
|
|
def teardown(self) -> None:
|
|
self.engine.dispose()
|
|
|
|
def time_skill_refresh_check(self) -> None:
|
|
self.repo.needs_refresh("bench/refresh-check", "match_hash")
|
|
|
|
|
|
class SkillListByNamespace:
|
|
"""Benchmark listing skills filtered by namespace."""
|
|
|
|
timeout = 60.0
|
|
|
|
def setup(self) -> None:
|
|
self.engine = create_engine("sqlite:///:memory:", echo=False)
|
|
Base.metadata.create_all(self.engine)
|
|
self.factory: sessionmaker[Session] = sessionmaker(bind=self.engine)
|
|
self.repo = SkillRepository(session_factory=self.factory)
|
|
|
|
# Seed 50 skills across 5 namespaces
|
|
for ns in ("local", "devops", "data", "ml", "infra"):
|
|
for i in range(10):
|
|
uid = uuid.uuid4().hex[:8]
|
|
self.repo.create(_make_skill(f"{ns}/tool-{i}-{uid}"))
|
|
self.factory().commit()
|
|
|
|
def teardown(self) -> None:
|
|
self.engine.dispose()
|
|
|
|
def time_skill_list_by_namespace(self) -> None:
|
|
self.repo.list_all(namespace="local")
|