hermescures1's picture
Upload folder using huggingface_hub
0e3d4b8 verified
Raw
History Blame Contribute Delete
11.6 kB
"""Skill Creation System — automatic skill abstraction from conversations.
Adapted from inc_llm_v1's SkillManager + SkillFactory pattern.
After every conversation (voice or text):
1. Extract the pattern (what was asked → what worked)
2. Create a reusable skill with trigger conditions
3. Store in the skill library for future use
Skill types: conversation, code, speed, voice, tool
Bayesian effectiveness scoring: skills that work well get higher priority.
Meta-skills: after 10+ interactions in a category, summarize best practices.
"""
from __future__ import annotations
import hashlib
import logging
import time
from collections import defaultdict, deque
from dataclasses import dataclass, field
from typing import Any
import numpy as np
logger = logging.getLogger(__name__)
@dataclass
class Skill:
"""A reusable skill extracted from conversations."""
id: str
name: str
description: str
content: str
category: str # conversation, code, speed, voice, tool
trigger_conditions: list[str] = field(default_factory=list)
created_at: float = field(default_factory=time.time)
last_used: float = field(default_factory=time.time)
use_count: int = 0
success_count: int = 0
failure_count: int = 0
effectiveness_score: float = 0.5 # Bayesian prior
confidence: float = 0.0
version: int = 1
class SkillFactory:
"""Creates skills from conversation patterns.
Analyzes conversations and extracts reusable patterns.
"""
def __init__(self) -> None:
self._interaction_buffer: deque[dict[str, Any]] = deque(maxlen=500)
self._category_counts: dict[str, int] = defaultdict(int)
def record_interaction(
self,
user_message: str,
assistant_response: str,
channel: str = "cli",
success: bool = True,
) -> None:
"""Record a conversation interaction for skill extraction."""
self._interaction_buffer.append({
"user_message": user_message,
"assistant_response": assistant_response,
"channel": channel,
"success": success,
"timestamp": time.time(),
})
# Categorize
category = self._categorize(user_message, channel)
self._category_counts[category] += 1
def _categorize(self, message: str, channel: str) -> str:
"""Categorize an interaction."""
msg_lower = message.lower()
if channel == "voice" or channel == "jarvis":
return "voice"
if any(kw in msg_lower for kw in ["code", "function", "bug", "error", "debug", "python", "javascript"]):
return "code"
if any(kw in msg_lower for kw in ["tool", "search", "run", "execute", "file"]):
return "tool"
if len(message) < 20:
return "speed"
return "conversation"
def extract_skill(self) -> Skill | None:
"""Extract a skill from recent interactions.
Looks for patterns in recent interactions and creates a skill
if a clear pattern emerges.
"""
if len(self._interaction_buffer) < 3:
return None
# Group recent interactions by category
recent = list(self._interaction_buffer)[-20:]
categories: dict[str, list[dict[str, Any]]] = defaultdict(list)
for interaction in recent:
cat = self._categorize(interaction["user_message"], interaction["channel"])
categories[cat].append(interaction)
# Find the category with most interactions
best_cat = max(categories.items(), key=lambda x: len(x[1]))
if len(best_cat[1]) < 3:
return None
category, interactions = best_cat
# Extract pattern
success_rate = sum(1 for i in interactions if i["success"]) / len(interactions)
if success_rate < 0.5:
return None
# Create skill content
skill_id = hashlib.sha256(
f"{category}:{time.time()}".encode()
).hexdigest()[:16]
examples = "\n".join(
f" Q: {i['user_message'][:80]}\n A: {i['assistant_response'][:80]}"
for i in interactions[:5]
)
content = (
f"Skill: {category.title()} Interaction Pattern\n"
f"Success rate: {success_rate:.0%}\n"
f"Examples:\n{examples}\n"
f"Best practice: Be concise and direct for {category} interactions."
)
triggers = self._extract_triggers(interactions, category)
skill = Skill(
id=skill_id,
name=f"{category}-pattern-{skill_id[:8]}",
description=f"Learned {category} interaction pattern ({success_rate:.0%} success)",
content=content,
category=category,
trigger_conditions=triggers,
effectiveness_score=success_rate,
confidence=min(1.0, len(interactions) / 10.0),
)
return skill
def _extract_triggers(self, interactions: list[dict[str, Any]], category: str) -> list[str]:
"""Extract trigger conditions from interactions."""
triggers = [category]
# Find common keywords
word_freq: dict[str, int] = defaultdict(int)
for i in interactions:
for word in i["user_message"].lower().split():
if len(word) > 3:
word_freq[word] += 1
# Top 5 common words as triggers
top_words = sorted(word_freq.items(), key=lambda x: -x[1])[:5]
triggers.extend(w for w, _ in top_words)
return triggers
def maybe_create_meta_skill(self, skill_manager: "SkillManager") -> Skill | None:
"""Create a meta-skill after enough interactions in a category."""
for category, count in self._category_counts.items():
if count >= 10:
existing = skill_manager.read(f"{category}-meta")
if not existing:
meta_id = hashlib.sha256(
f"meta:{category}:{time.time()}".encode()
).hexdigest()[:16]
skill = Skill(
id=meta_id,
name=f"{category}-meta",
description=f"Meta-skill for {category} — learned patterns across all {category} interactions",
content=(
f"Meta-Skill: {category.title()}\n"
f"Total interactions: {count}\n"
f"Best practices:\n"
f"- Be concise and direct\n"
f"- Match the user's tone\n"
f"- Provide actionable responses\n"
),
category=f"{category}_meta",
trigger_conditions=[category, "meta"],
effectiveness_score=0.7,
confidence=min(1.0, count / 20.0),
)
return skill
return None
def get_stats(self) -> dict[str, Any]:
return {
"interactions_buffered": len(self._interaction_buffer),
"category_counts": dict(self._category_counts),
}
class SkillManager:
"""Manages skills — storage, retrieval, scoring, and lifecycle.
Uses Bayesian effectiveness scoring. Skills that work well get
higher priority. Skills that fail get deprecated.
"""
def __init__(self, storage: Any = None) -> None:
self.storage = storage
self._skills: dict[str, Skill] = {}
self._trigger_index: dict[str, set[str]] = defaultdict(set)
self._stats = {
"skills_created": 0,
"skills_used": 0,
"skills_deprecated": 0,
"meta_skills_created": 0,
}
def create(self, skill: Skill) -> bool:
"""Create a new skill."""
if skill.id in self._skills:
return False
self._skills[skill.id] = skill
for trigger in skill.trigger_conditions:
self._trigger_index[trigger.lower()].add(skill.id)
self._stats["skills_created"] += 1
if "meta" in skill.category:
self._stats["meta_skills_created"] += 1
if self.storage:
self.storage.save_skill(skill)
logger.info("Created skill: %s (category=%s, score=%.2f)",
skill.name, skill.category, skill.effectiveness_score)
return True
def read(self, name: str) -> Skill | None:
"""Read a skill by name."""
for skill in self._skills.values():
if skill.name == name:
return skill
return None
def find_by_triggers(self, message: str, max_results: int = 3) -> list[Skill]:
"""Find skills that match trigger conditions in the message."""
msg_lower = message.lower()
matched: dict[str, float] = defaultdict(float)
for trigger, skill_ids in self._trigger_index.items():
if trigger in msg_lower:
for sid in skill_ids:
skill = self._skills.get(sid)
if skill:
matched[sid] += skill.effectiveness_score * skill.confidence
sorted_ids = sorted(matched.items(), key=lambda x: -x[1])[:max_results]
return [self._skills[sid] for sid, _ in sorted_ids if sid in self._skills]
def record_use(self, skill_id: str, success: bool) -> None:
"""Record a skill use and update effectiveness score."""
skill = self._skills.get(skill_id)
if not skill:
return
skill.use_count += 1
skill.last_used = time.time()
if success:
skill.success_count += 1
else:
skill.failure_count += 1
# Bayesian update
total = skill.success_count + skill.failure_count
if total > 0:
success_rate = skill.success_count / total
# Bayesian: posterior = (prior * w + observed * n) / (w + n)
w = 2.0 # prior weight
skill.effectiveness_score = (0.5 * w + success_rate * total) / (w + total)
skill.confidence = min(1.0, total / 10.0)
# Deprecate low-scoring skills
if skill.effectiveness_score < 0.2 and skill.use_count > 5:
self._stats["skills_deprecated"] += 1
logger.info("Deprecated skill: %s (score=%.2f)", skill.name, skill.effectiveness_score)
self._stats["skills_used"] += 1
def get_relevant_skills(self, message: str, channel: str = "cli") -> list[Skill]:
"""Get skills relevant to a message and channel."""
skills = self.find_by_triggers(message, max_results=5)
# Filter by channel
if channel in ("voice", "jarvis"):
voice_skills = [s for s in skills if s.category in ("voice", "speed")]
if voice_skills:
return voice_skills
return skills
def get_skill_context(self, message: str, channel: str = "cli") -> str:
"""Get skill context to inject into the prompt."""
skills = self.get_relevant_skills(message, channel)
if not skills:
return ""
parts = [s.content[:200] for s in skills[:3]]
return " | ".join(parts)
def get_stats(self) -> dict[str, Any]:
return {
**self._stats,
"total_skills": len(self._skills),
"active_skills": sum(1 for s in self._skills.values() if s.effectiveness_score > 0.2),
"trigger_index_size": len(self._trigger_index),
}