Spaces:
Sleeping
Sleeping
| """Skill, Skillbook, and update operations for the ACE framework.""" | |
| from __future__ import annotations | |
| import json | |
| import re | |
| import threading | |
| from dataclasses import asdict, dataclass, field, fields as dataclass_fields | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from typing import Any, Dict, FrozenSet, Iterable, List, Literal, Optional, Union, cast | |
| from .insight_source import InsightSource, coerce_insight_source, coerce_insight_sources | |
| # --------------------------------------------------------------------------- | |
| # Constants / helpers | |
| # --------------------------------------------------------------------------- | |
| OperationType = Literal["ADD", "UPDATE", "TAG", "REMOVE"] | |
| SkillSection = Literal["context", "harness"] | |
| SCHEMA_VERSION = "2" | |
| VALID_SECTIONS: frozenset[str] = frozenset({"context", "harness"}) | |
| DEFAULT_LEGACY_SECTION: SkillSection = "context" | |
| InsightSourceInput = Union[ | |
| InsightSource, | |
| Dict[str, Any], | |
| List[Union[InsightSource, Dict[str, Any]]], | |
| ] | |
| _UNSET = object() | |
| def _now_iso() -> str: | |
| return datetime.now(timezone.utc).isoformat() | |
| def _normalize_optional_text(value: Any) -> str | None: | |
| if value is None: | |
| return None | |
| text = str(value).strip() | |
| return text or None | |
| def _normalize_required_text(value: Any, field_name: str) -> str: | |
| text = _normalize_optional_text(value) | |
| if text is None: | |
| raise ValueError(f"{field_name} is required and must be non-empty") | |
| return text | |
| def _normalize_keyword(value: Any) -> str | None: | |
| text = _normalize_optional_text(value) | |
| if text is None: | |
| return None | |
| text = re.sub(r"\s+", "_", text.lower()) | |
| return text or None | |
| def _normalize_keywords(keywords: Iterable[Any] | None) -> list[str]: | |
| normalized: list[str] = [] | |
| seen: set[str] = set() | |
| if keywords is None: | |
| return normalized | |
| for value in keywords: | |
| keyword = _normalize_keyword(value) | |
| if keyword is None or keyword in seen: | |
| continue | |
| normalized.append(keyword) | |
| seen.add(keyword) | |
| return normalized | |
| def _coerce_section_and_keywords( | |
| section: str, | |
| keywords: Iterable[Any] | None, | |
| ) -> tuple[SkillSection, list[str]]: | |
| normalized_section = _normalize_required_text(section, "section").lower() | |
| normalized_keywords = _normalize_keywords(keywords) | |
| if normalized_section in VALID_SECTIONS: | |
| return cast(SkillSection, normalized_section), normalized_keywords | |
| # Backward-compatible coercion for legacy free-form sections. | |
| legacy_keywords = _normalize_keywords([normalized_section, *normalized_keywords]) | |
| return DEFAULT_LEGACY_SECTION, legacy_keywords | |
| def _embedding_sidecar_path(file_path: Path) -> Path: | |
| if file_path.suffix: | |
| stem = file_path.with_suffix("") | |
| else: | |
| stem = file_path | |
| return stem.parent / f"{stem.name}.embeddings.npz" | |
| def _insight_source_signature(source: InsightSource) -> str: | |
| return json.dumps(source.to_dict(), ensure_ascii=False, sort_keys=True, default=str) | |
| def _append_unique_sources( | |
| existing: List[InsightSource], | |
| incoming: Iterable[InsightSource], | |
| ) -> None: | |
| seen = {_insight_source_signature(source) for source in existing} | |
| for source in incoming: | |
| signature = _insight_source_signature(source) | |
| if signature in seen: | |
| continue | |
| existing.append(source) | |
| seen.add(signature) | |
| def _serialize_sources(sources: Iterable[InsightSource]) -> list[dict[str, Any]]: | |
| return [source.to_dict() for source in sources] | |
| def _deserialize_sources(raw_sources: Any) -> list[InsightSource]: | |
| if not isinstance(raw_sources, list): | |
| return [] | |
| deduped_sources: list[InsightSource] = [] | |
| _append_unique_sources( | |
| deduped_sources, | |
| [ | |
| coerce_insight_source(item) | |
| for item in raw_sources | |
| if isinstance(item, (InsightSource, dict)) | |
| ], | |
| ) | |
| return deduped_sources | |
| # --------------------------------------------------------------------------- | |
| # Update operations | |
| # --------------------------------------------------------------------------- | |
| class UpdateOperation: | |
| """Single mutation to apply to the skillbook.""" | |
| type: OperationType | |
| section: str | |
| issue: Optional[str] = None | |
| keywords: List[str] = field(default_factory=list) | |
| insight: Optional[str] = None | |
| skill_id: Optional[str] = None | |
| metadata: Dict[str, int] = field(default_factory=dict) | |
| reason: Optional[str] = None | |
| insight_source: Optional[InsightSourceInput] = None | |
| learning_index: Optional[int] = None | |
| reflection_index: Optional[int] = None | |
| reflection_indices: List[int] = field(default_factory=list) | |
| def from_json(cls, payload: Dict[str, object]) -> "UpdateOperation": | |
| metadata_raw = payload.get("metadata") or {} | |
| metadata: Dict[str, Any] = ( | |
| cast(Dict[str, Any], metadata_raw) if isinstance(metadata_raw, dict) else {} | |
| ) | |
| op_type = str(payload["type"]).upper() | |
| if op_type not in ("ADD", "UPDATE", "TAG", "REMOVE"): | |
| raise ValueError(f"Invalid operation type: {op_type}") | |
| raw_source = payload.get("insight_source") | |
| insight_source: Optional[InsightSourceInput] = None | |
| if isinstance(raw_source, dict): | |
| insight_source = InsightSource.from_dict(cast(Dict[str, Any], raw_source)) | |
| elif isinstance(raw_source, Iterable) and not isinstance( | |
| raw_source, (str, bytes) | |
| ): | |
| insight_source = [ | |
| InsightSource.from_dict(cast(Dict[str, Any], item)) | |
| for item in raw_source | |
| if isinstance(item, dict) | |
| ] | |
| raw_learning_index = payload.get("learning_index") | |
| learning_index: Optional[int] = None | |
| if raw_learning_index is not None: | |
| try: | |
| learning_index = int(cast(int, raw_learning_index)) | |
| except (TypeError, ValueError): | |
| pass | |
| raw_reflection_index = payload.get("reflection_index") | |
| reflection_index: Optional[int] = None | |
| if raw_reflection_index is not None: | |
| try: | |
| reflection_index = int(cast(int, raw_reflection_index)) | |
| except (TypeError, ValueError): | |
| pass | |
| reflection_indices: List[int] = [] | |
| raw_reflection_indices = payload.get("reflection_indices") | |
| if isinstance(raw_reflection_indices, Iterable) and not isinstance( | |
| raw_reflection_indices, (str, bytes) | |
| ): | |
| for value in raw_reflection_indices: | |
| try: | |
| reflection_indices.append(int(cast(int, value))) | |
| except (TypeError, ValueError): | |
| continue | |
| raw_keywords = payload.get("keywords") | |
| keywords: list[str] = [] | |
| if isinstance(raw_keywords, Iterable) and not isinstance( | |
| raw_keywords, (str, bytes) | |
| ): | |
| keywords = _normalize_keywords(raw_keywords) | |
| issue = _normalize_optional_text(payload.get("issue")) | |
| insight = _normalize_optional_text(payload.get("insight")) | |
| reason = _normalize_optional_text(payload.get("reason")) | |
| return cls( | |
| type=cast(OperationType, op_type), | |
| section=str(payload.get("section", "")), | |
| issue=issue, | |
| keywords=keywords, | |
| insight=insight, | |
| skill_id=( | |
| str(payload["skill_id"]) | |
| if payload.get("skill_id") is not None | |
| else None | |
| ), | |
| metadata={str(k): int(v) for k, v in metadata.items()}, | |
| reason=reason, | |
| insight_source=insight_source, | |
| learning_index=learning_index, | |
| reflection_index=reflection_index, | |
| reflection_indices=reflection_indices, | |
| ) | |
| def to_json(self) -> Dict[str, object]: | |
| data: Dict[str, object] = {"type": self.type, "section": self.section} | |
| if self.issue is not None: | |
| data["issue"] = self.issue | |
| if self.keywords: | |
| data["keywords"] = list(self.keywords) | |
| if self.insight is not None: | |
| data["insight"] = self.insight | |
| if self.skill_id is not None: | |
| data["skill_id"] = self.skill_id | |
| if self.metadata: | |
| data["metadata"] = self.metadata | |
| if self.reason is not None: | |
| data["reason"] = self.reason | |
| if self.insight_source is not None: | |
| sources = coerce_insight_sources(self.insight_source) | |
| if len(sources) == 1: | |
| data["insight_source"] = sources[0].to_dict() | |
| elif sources: | |
| data["insight_source"] = [source.to_dict() for source in sources] | |
| if self.learning_index is not None: | |
| data["learning_index"] = self.learning_index | |
| if self.reflection_index is not None: | |
| data["reflection_index"] = self.reflection_index | |
| if self.reflection_indices: | |
| data["reflection_indices"] = list(self.reflection_indices) | |
| return data | |
| class UpdateBatch: | |
| """Bundle of skill manager reasoning and operations.""" | |
| reasoning: str | |
| operations: List[UpdateOperation] = field(default_factory=list) | |
| def from_json(cls, payload: Dict[str, object]) -> "UpdateBatch": | |
| ops_payload = payload.get("operations") | |
| operations = [] | |
| if isinstance(ops_payload, Iterable): | |
| for item in ops_payload: | |
| if isinstance(item, dict): | |
| operations.append(UpdateOperation.from_json(item)) | |
| return cls(reasoning=str(payload.get("reasoning", "")), operations=operations) | |
| def to_json(self) -> Dict[str, object]: | |
| return { | |
| "reasoning": self.reasoning, | |
| "operations": [op.to_json() for op in self.operations], | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Skill types | |
| # --------------------------------------------------------------------------- | |
| class SimilarityDecision: | |
| """Record of a SkillManager decision to KEEP two skills separate.""" | |
| decision: Literal["KEEP"] | |
| reasoning: str | |
| decided_at: str | |
| similarity_at_decision: float | |
| class Skill: | |
| """Single skillbook entry.""" | |
| id: str | |
| section: SkillSection | |
| keywords: list[str] | |
| issue: str | |
| insight: str | None = None | |
| occurrences: List[InsightSource] = field(default_factory=list) | |
| active: bool = True | |
| used_count: int = 0 | |
| helpful_count: int = 0 | |
| harmful_count: int = 0 | |
| neutral_count: int = 0 | |
| embedding: Optional[List[float]] = None | |
| created_at: str = field(default_factory=_now_iso) | |
| updated_at: str = field(default_factory=_now_iso) | |
| def to_llm_dict(self) -> Dict[str, Any]: | |
| return { | |
| "id": self.id, | |
| "section": self.section, | |
| "keywords": list(self.keywords), | |
| "issue": self.issue, | |
| "insight": self.insight, | |
| "active": self.active, | |
| "used_count": self.used_count, | |
| "helpful_count": self.helpful_count, | |
| "harmful_count": self.harmful_count, | |
| "neutral_count": self.neutral_count, | |
| } | |
| def embedding_text(self) -> str: | |
| parts = [self.issue] | |
| if self.insight: | |
| parts.append(self.insight) | |
| if self.keywords: | |
| parts.append(f"Keywords: {', '.join(self.keywords)}") | |
| return "\n\n".join(parts) | |
| # --------------------------------------------------------------------------- | |
| # Skillbook | |
| # --------------------------------------------------------------------------- | |
| class Skillbook: | |
| """Structured context store as defined by ACE.""" | |
| def __init__(self) -> None: | |
| self._skills: Dict[str, Skill] = {} | |
| self._sections: Dict[str, List[str]] = {} | |
| self._next_id = 0 | |
| self._similarity_decisions: Dict[FrozenSet[str], SimilarityDecision] = {} | |
| self._lock = threading.RLock() | |
| def __repr__(self) -> str: | |
| return f"Skillbook(skills={len(self._skills)}, sections={list(self._sections.keys())})" | |
| def __str__(self) -> str: | |
| if not self._skills: | |
| return "Skillbook(empty)" | |
| return self.as_prompt() | |
| # ------------------------------------------------------------------ # | |
| # CRUD | |
| # ------------------------------------------------------------------ # | |
| def add_skill( | |
| self, | |
| section: str, | |
| issue: str | None = None, | |
| *, | |
| keywords: Iterable[Any] | None = None, | |
| insight: str | None = None, | |
| skill_id: Optional[str] = None, | |
| insight_source: Optional[InsightSourceInput] = None, | |
| ) -> Skill: | |
| with self._lock: | |
| raw_section = _normalize_required_text(section, "section").lower() | |
| normalized_section, normalized_keywords = _coerce_section_and_keywords( | |
| section, keywords | |
| ) | |
| issue_text = _normalize_required_text(issue, "issue") | |
| insight_text = _normalize_optional_text(insight) | |
| if not normalized_keywords: | |
| normalized_keywords = _normalize_keywords([raw_section]) | |
| if normalized_section == "context" and insight_text is None: | |
| if raw_section not in VALID_SECTIONS: | |
| insight_text = issue_text | |
| else: | |
| raise ValueError("context skills require a non-empty insight") | |
| skill_id = skill_id or self._generate_id(normalized_section) | |
| skill = Skill( | |
| id=skill_id, | |
| section=normalized_section, | |
| keywords=normalized_keywords, | |
| issue=issue_text, | |
| insight=insight_text, | |
| ) | |
| _append_unique_sources( | |
| skill.occurrences, coerce_insight_sources(insight_source) | |
| ) | |
| self._skills[skill_id] = skill | |
| self._sections.setdefault(normalized_section, []).append(skill_id) | |
| return skill | |
| def update_skill( | |
| self, | |
| skill_id: str, | |
| *, | |
| issue: object = _UNSET, | |
| keywords: object = _UNSET, | |
| insight: object = _UNSET, | |
| insight_source: Optional[InsightSourceInput] = None, | |
| ) -> Optional[Skill]: | |
| with self._lock: | |
| skill = self._skills.get(skill_id) | |
| if skill is None: | |
| return None | |
| if issue is not _UNSET and issue is not None: | |
| skill.issue = _normalize_required_text(issue, "issue") | |
| if keywords is not _UNSET and keywords is not None: | |
| normalized_keywords = _normalize_keywords(cast(Iterable[Any], keywords)) | |
| if not normalized_keywords: | |
| raise ValueError("keywords are required and must be non-empty") | |
| skill.keywords = normalized_keywords | |
| if insight is not _UNSET and insight is not None: | |
| skill.insight = _normalize_optional_text(insight) | |
| if skill.section == "context" and not skill.insight: | |
| raise ValueError("context skills require a non-empty insight") | |
| if insight_source is not None: | |
| _append_unique_sources( | |
| skill.occurrences, | |
| coerce_insight_sources(insight_source), | |
| ) | |
| skill.embedding = None | |
| skill.updated_at = _now_iso() | |
| return skill | |
| def tag_skill( | |
| self, | |
| skill_id: str, | |
| delta: Literal[1, -1, 0], | |
| *, | |
| insight_source: Optional[InsightSourceInput] = None, | |
| ) -> Optional[Skill]: | |
| """Record an effectiveness observation for a skill.""" | |
| with self._lock: | |
| skill = self._skills.get(skill_id) | |
| if skill is None: | |
| return None | |
| if delta == 1: | |
| skill.helpful_count += 1 | |
| elif delta == -1: | |
| skill.harmful_count += 1 | |
| else: | |
| skill.neutral_count += 1 | |
| if insight_source is not None: | |
| _append_unique_sources( | |
| skill.occurrences, | |
| coerce_insight_sources(insight_source), | |
| ) | |
| skill.updated_at = _now_iso() | |
| return skill | |
| def mark_used(self, skill_ids: Iterable[str]) -> None: | |
| """Bump ``used_count`` for each active skill ID.""" | |
| with self._lock: | |
| for sid in skill_ids: | |
| skill = self._skills.get(sid) | |
| if skill is not None and skill.active: | |
| skill.used_count += 1 | |
| skill.updated_at = _now_iso() | |
| def remove_skill( | |
| self, | |
| skill_id: str, | |
| soft: bool = True, | |
| *, | |
| insight_source: Optional[InsightSourceInput] = None, | |
| ) -> None: | |
| with self._lock: | |
| skill = self._skills.get(skill_id) | |
| if skill is None: | |
| return | |
| if soft: | |
| skill.active = False | |
| if insight_source is not None: | |
| _append_unique_sources( | |
| skill.occurrences, | |
| coerce_insight_sources(insight_source), | |
| ) | |
| skill.updated_at = _now_iso() | |
| else: | |
| self.purge(skill_id) | |
| def purge(self, skill_id: str) -> None: | |
| with self._lock: | |
| skill = self._skills.pop(skill_id, None) | |
| if skill is None: | |
| return | |
| section_list = self._sections.get(skill.section) | |
| if section_list: | |
| self._sections[skill.section] = [ | |
| sid for sid in section_list if sid != skill_id | |
| ] | |
| if not self._sections[skill.section]: | |
| del self._sections[skill.section] | |
| def get_skill(self, skill_id: str) -> Optional[Skill]: | |
| return self._skills.get(skill_id) | |
| def skills(self, include_invalid: bool = False) -> List[Skill]: | |
| if include_invalid: | |
| return list(self._skills.values()) | |
| return [s for s in self._skills.values() if s.active] | |
| # ------------------------------------------------------------------ # | |
| # Similarity decisions | |
| # ------------------------------------------------------------------ # | |
| def get_similarity_decision( | |
| self, skill_id_a: str, skill_id_b: str | |
| ) -> Optional[SimilarityDecision]: | |
| pair_key = frozenset([skill_id_a, skill_id_b]) | |
| return self._similarity_decisions.get(pair_key) | |
| def set_similarity_decision( | |
| self, | |
| skill_id_a: str, | |
| skill_id_b: str, | |
| decision: SimilarityDecision, | |
| ) -> None: | |
| with self._lock: | |
| pair_key = frozenset([skill_id_a, skill_id_b]) | |
| self._similarity_decisions[pair_key] = decision | |
| def has_keep_decision(self, skill_id_a: str, skill_id_b: str) -> bool: | |
| decision = self.get_similarity_decision(skill_id_a, skill_id_b) | |
| return decision is not None and decision.decision == "KEEP" | |
| # ------------------------------------------------------------------ # | |
| # Serialization | |
| # ------------------------------------------------------------------ # | |
| def to_dict(self, exclude_embeddings: bool = False) -> Dict[str, object]: | |
| del exclude_embeddings # JSON never carries embeddings in v2. | |
| similarity_decisions_serialized = { | |
| ",".join(sorted(pair_ids)): asdict(decision) | |
| for pair_ids, decision in self._similarity_decisions.items() | |
| } | |
| skills_serialized = {} | |
| for skill_id, skill in self._skills.items(): | |
| skill_dict = asdict(skill) | |
| skill_dict.pop("embedding", None) | |
| skill_dict["occurrences"] = _serialize_sources(skill.occurrences) | |
| skills_serialized[skill_id] = skill_dict | |
| return { | |
| "schema_version": SCHEMA_VERSION, | |
| "skills": skills_serialized, | |
| "sections": self._sections, | |
| "next_id": self._next_id, | |
| "similarity_decisions": similarity_decisions_serialized, | |
| } | |
| def from_dict(cls, payload: Dict[str, object]) -> "Skillbook": | |
| schema_version = str(payload.get("schema_version", "")) | |
| if schema_version != SCHEMA_VERSION: | |
| raise ValueError("Skillbook format v2 required — regenerate") | |
| instance = cls() | |
| skills_payload = payload.get("skills", {}) | |
| if isinstance(skills_payload, dict): | |
| for skill_id, skill_value in skills_payload.items(): | |
| if not isinstance(skill_value, dict): | |
| continue | |
| skill_data = dict(skill_value) | |
| skill_data["embedding"] = None | |
| skill_data["active"] = bool(skill_data.get("active", True)) | |
| raw_keywords = skill_data.get("keywords") | |
| skill_data["keywords"] = _normalize_keywords( | |
| raw_keywords if isinstance(raw_keywords, list) else [] | |
| ) | |
| if not skill_data["keywords"]: | |
| legacy_section = skill_data.get("section", DEFAULT_LEGACY_SECTION) | |
| skill_data["keywords"] = _normalize_keywords([legacy_section]) | |
| section_value = str(skill_data.get("section", DEFAULT_LEGACY_SECTION)) | |
| normalized_section, normalized_keywords = _coerce_section_and_keywords( | |
| section_value, | |
| skill_data["keywords"], | |
| ) | |
| skill_data["section"] = normalized_section | |
| skill_data["keywords"] = normalized_keywords | |
| skill_data["issue"] = _normalize_required_text( | |
| skill_data.get("issue"), "issue" | |
| ) | |
| skill_data["insight"] = _normalize_optional_text( | |
| skill_data.get("insight") | |
| ) | |
| skill_data["occurrences"] = _deserialize_sources( | |
| skill_data.get("occurrences") | |
| ) | |
| valid_fields = {f.name for f in dataclass_fields(Skill)} | |
| skill_data = {k: v for k, v in skill_data.items() if k in valid_fields} | |
| instance._skills[str(skill_id)] = Skill(**skill_data) | |
| sections_payload = payload.get("sections", {}) | |
| if isinstance(sections_payload, dict): | |
| normalized_sections: dict[str, list[str]] = {} | |
| for section, ids in sections_payload.items(): | |
| if not isinstance(ids, Iterable) or isinstance(ids, (str, bytes)): | |
| continue | |
| normalized_sections[str(section)] = [str(item) for item in ids] | |
| instance._sections = normalized_sections | |
| next_id_value = payload.get("next_id", 0) | |
| instance._next_id = ( | |
| int(cast(Union[int, str], next_id_value)) | |
| if next_id_value is not None | |
| else 0 | |
| ) | |
| similarity_decisions_payload = payload.get("similarity_decisions", {}) | |
| if isinstance(similarity_decisions_payload, dict): | |
| for pair_key_str, decision_value in similarity_decisions_payload.items(): | |
| if isinstance(decision_value, dict): | |
| pair_ids = frozenset(str(pair_key_str).split(",")) | |
| instance._similarity_decisions[pair_ids] = SimilarityDecision( | |
| **decision_value | |
| ) | |
| # Prefer the explicit serialized section ordering, but rebuild if needed. | |
| if not instance._sections: | |
| for skill in instance._skills.values(): | |
| instance._sections.setdefault(skill.section, []).append(skill.id) | |
| return instance | |
| def dumps(self, exclude_embeddings: bool = False) -> str: | |
| return json.dumps( | |
| self.to_dict(exclude_embeddings=exclude_embeddings), | |
| ensure_ascii=False, | |
| indent=2, | |
| ) | |
| def loads(cls, data: str) -> "Skillbook": | |
| payload = json.loads(data) | |
| if not isinstance(payload, dict): | |
| raise ValueError("Skillbook serialization must be a JSON object.") | |
| return cls.from_dict(payload) | |
| def save_to_file(self, path: str, exclude_embeddings: bool = False) -> None: | |
| file_path = Path(path) | |
| sidecar_path = _embedding_sidecar_path(file_path) | |
| file_path.parent.mkdir(parents=True, exist_ok=True) | |
| with file_path.open("w", encoding="utf-8") as f: | |
| f.write(self.dumps(exclude_embeddings=True)) | |
| if exclude_embeddings: | |
| return | |
| embeddings = { | |
| skill.id: skill.embedding | |
| for skill in self._skills.values() | |
| if skill.embedding is not None | |
| } | |
| if not embeddings: | |
| if sidecar_path.exists(): | |
| sidecar_path.unlink() | |
| return | |
| import numpy as np | |
| sidecar_path.parent.mkdir(parents=True, exist_ok=True) | |
| arrays = { | |
| skill_id: np.asarray(embedding, dtype="float32") | |
| for skill_id, embedding in embeddings.items() | |
| } | |
| np.savez_compressed(sidecar_path, **arrays) # type: ignore[arg-type] | |
| def load_from_file(cls, path: str) -> "Skillbook": | |
| file_path = Path(path) | |
| if not file_path.exists(): | |
| raise FileNotFoundError(f"Skillbook file not found: {path}") | |
| with file_path.open("r", encoding="utf-8") as f: | |
| skillbook = cls.loads(f.read()) | |
| sidecar_path = _embedding_sidecar_path(file_path) | |
| if not sidecar_path.exists(): | |
| return skillbook | |
| import numpy as np | |
| with np.load(sidecar_path) as embeddings: | |
| for skill_id in embeddings.files: | |
| skill = skillbook.get_skill(skill_id) | |
| if skill is not None: | |
| skill.embedding = embeddings[skill_id].astype("float32").tolist() | |
| return skillbook | |
| # ------------------------------------------------------------------ # | |
| # Update application | |
| # ------------------------------------------------------------------ # | |
| def apply_update(self, update: UpdateBatch) -> None: | |
| with self._lock: | |
| for operation in update.operations: | |
| self._apply_operation(operation) | |
| def _apply_operation(self, operation: UpdateOperation) -> None: | |
| op_type = operation.type.upper() | |
| if op_type == "ADD": | |
| self.add_skill( | |
| section=operation.section, | |
| issue=operation.issue or "", | |
| keywords=operation.keywords, | |
| insight=operation.insight, | |
| skill_id=operation.skill_id, | |
| insight_source=operation.insight_source, | |
| ) | |
| elif op_type == "UPDATE": | |
| if operation.skill_id is None: | |
| return | |
| self.update_skill( | |
| operation.skill_id, | |
| issue=operation.issue if operation.issue is not None else _UNSET, | |
| keywords=operation.keywords if operation.keywords else _UNSET, | |
| insight=operation.insight if operation.insight is not None else _UNSET, | |
| insight_source=operation.insight_source, | |
| ) | |
| elif op_type == "TAG": | |
| if operation.skill_id is None: | |
| return | |
| delta = int(operation.metadata.get("delta", 0)) | |
| if delta > 0: | |
| delta = 1 | |
| elif delta < 0: | |
| delta = -1 | |
| self.tag_skill( | |
| operation.skill_id, | |
| cast(Literal[1, -1, 0], delta), | |
| insight_source=operation.insight_source, | |
| ) | |
| elif op_type == "REMOVE": | |
| if operation.skill_id is None: | |
| return | |
| self.remove_skill( | |
| operation.skill_id, insight_source=operation.insight_source | |
| ) | |
| # ------------------------------------------------------------------ # | |
| # Presentation | |
| # ------------------------------------------------------------------ # | |
| def as_prompt(self) -> str: | |
| parts: List[str] = [] | |
| for section in ("context", "harness"): | |
| skill_ids = self._sections.get(section, []) | |
| section_skills = [ | |
| self._skills[sid] for sid in skill_ids if self._skills[sid].active | |
| ] | |
| if not section_skills: | |
| continue | |
| parts.append(f"## {section}") | |
| for skill in section_skills: | |
| parts.append(f"- [{skill.id}]") | |
| parts.append(f" Keywords: {', '.join(skill.keywords)}") | |
| parts.append(f" Issue: {skill.issue}") | |
| if skill.insight: | |
| parts.append(f" Insight: {skill.insight}") | |
| parts.append("") | |
| return "\n".join(parts).rstrip() | |
| def stats(self) -> Dict[str, object]: | |
| active_skills = [skill for skill in self._skills.values() if skill.active] | |
| by_section: dict[str, int] = {section: 0 for section in VALID_SECTIONS} | |
| for skill in active_skills: | |
| by_section[skill.section] = by_section.get(skill.section, 0) + 1 | |
| return { | |
| "sections": len( | |
| [section for section, count in by_section.items() if count] | |
| ), | |
| "skills": len(self._skills), | |
| "active_skills": len(active_skills), | |
| "by_section": by_section, | |
| } | |
| # ------------------------------------------------------------------ # | |
| # Insight source analysis | |
| # ------------------------------------------------------------------ # | |
| def source_map(self) -> Dict[str, List[Dict[str, Any]]]: | |
| with self._lock: | |
| result: Dict[str, List[Dict[str, Any]]] = {} | |
| for skill_id, skill in self._skills.items(): | |
| if skill.occurrences: | |
| result[skill_id] = _serialize_sources(skill.occurrences) | |
| return result | |
| def source_summary(self) -> Dict[str, Any]: | |
| with self._lock: | |
| epochs: Dict[Optional[int], int] = {} | |
| source_systems: Dict[str, int] = {} | |
| trace_uids: Dict[str, int] = {} | |
| sample_questions: Dict[str, int] = {} | |
| total = 0 | |
| for skill in self._skills.values(): | |
| for src in skill.occurrences: | |
| total += 1 | |
| epochs[src.epoch] = epochs.get(src.epoch, 0) + 1 | |
| source_systems[src.source_system] = ( | |
| source_systems.get(src.source_system, 0) + 1 | |
| ) | |
| trace_uids[src.trace_uid] = trace_uids.get(src.trace_uid, 0) + 1 | |
| sq = src.sample_question or "" | |
| if sq: | |
| sample_questions[sq] = sample_questions.get(sq, 0) + 1 | |
| return { | |
| "total_sources": total, | |
| "epochs": epochs, | |
| "source_systems": source_systems, | |
| "trace_uids": trace_uids, | |
| "sample_questions": sample_questions, | |
| } | |
| def source_filter( | |
| self, | |
| *, | |
| epoch: Optional[int] = None, | |
| sample_question: Optional[str] = None, | |
| trace_uid: Optional[str] = None, | |
| trace_id: Optional[str] = None, | |
| source_system: Optional[str] = None, | |
| ) -> Dict[str, List[Dict[str, Any]]]: | |
| with self._lock: | |
| result: Dict[str, List[Dict[str, Any]]] = {} | |
| for skill_id, skill in self._skills.items(): | |
| matches = [] | |
| for src in skill.occurrences: | |
| if epoch is not None and src.epoch != epoch: | |
| continue | |
| if trace_uid is not None and src.trace_uid != trace_uid: | |
| continue | |
| if trace_id is not None and src.trace_id != trace_id: | |
| continue | |
| if source_system is not None and src.source_system != source_system: | |
| continue | |
| if sample_question is not None: | |
| sq = src.sample_question or "" | |
| if sample_question.lower() not in sq.lower(): | |
| continue | |
| matches.append(src.to_dict()) | |
| if matches: | |
| result[skill_id] = matches | |
| return result | |
| # ------------------------------------------------------------------ # | |
| # Internal | |
| # ------------------------------------------------------------------ # | |
| def _generate_id(self, section: SkillSection) -> str: | |
| self._next_id += 1 | |
| section_prefix = section.split()[0].lower() | |
| return f"{section_prefix}-{self._next_id:05d}" | |