| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- """
- agentpaas.services.learning — Adaptive learning service
- Records tool call patterns (success/failure), extracts strategy rules,
- and auto-applies learned strategies in future similar scenarios.
- """
- from __future__ import annotations
- import json
- import os
- import time
- from dataclasses import dataclass, field
- from typing import Any, Dict, List
- @dataclass
- class LearningEntry:
- tool_name: str
- input_preview: str
- success: bool
- error: str = ""
- strategy_used: str = "" # e.g. "bash_python3_fallback"
- timestamp: float = 0
- def __post_init__(self):
- if self.timestamp == 0:
- self.timestamp = time.time()
- @dataclass
- class StrategyRule:
- pattern: str # e.g. "WriteFile with markdown code blocks"
- strategy: str # e.g. "Use Bash+python3 instead of WriteFile"
- confidence: float = 0.0 # 0.0 - 1.0
- evidence_count: int = 0
- last_applied: float = 0
- def to_dict(self):
- return {"pattern": self.pattern, "strategy": self.strategy,
- "confidence": self.confidence, "evidence_count": self.evidence_count}
- LEARNING_PATH = os.path.expanduser("~/.lambdagent/learning.json")
- class LearningService:
- """Track tool patterns and extract strategies."""
- def __init__(self, path: str = ""):
- self.path = path or LEARNING_PATH
- self._entries: List[LearningEntry] = []
- self._strategies: List[StrategyRule] = []
- self._load()
- def record(self, tool_name: str, input_preview: str, success: bool,
- error: str = "", strategy: str = ""):
- """Record a tool call outcome."""
- self._entries.append(LearningEntry(
- tool_name=tool_name, input_preview=input_preview[:200],
- success=success, error=error[:200], strategy_used=strategy,
- ))
- # Keep last 500 entries
- if len(self._entries) > 500:
- self._entries = self._entries[-500:]
- self._extract_rules()
- self._save()
- def get_strategy(self, tool_name: str, input_preview: str) -> str:
- """Get recommended strategy for a tool call."""
- for rule in sorted(self._strategies, key=lambda r: -r.confidence):
- if rule.confidence >= 0.6 and (
- tool_name.lower() in rule.pattern.lower() or
- any(w in input_preview.lower() for w in rule.pattern.lower().split())
- ):
- rule.last_applied = time.time()
- return rule.strategy
- return ""
- def list_strategies(self) -> List[StrategyRule]:
- return [r for r in self._strategies if r.confidence >= 0.5]
- def _extract_rules(self):
- """Auto-extract strategy rules from patterns."""
- # Count failures by tool
- fail_patterns: Dict[str, int] = {}
- success_patterns: Dict[str, int] = {}
- for e in self._entries:
- key = e.tool_name
- if e.success:
- success_patterns[key] = success_patterns.get(key, 0) + 1
- else:
- fail_patterns[key] = fail_patterns.get(key, 0) + 1
- # Strategy: if a tool fails >3 times and an alternative succeeds
- for tool, fails in fail_patterns.items():
- if fails >= 3:
- # Check if there's a successful alternative
- successes = success_patterns.get(tool, 0)
- total = fails + successes
- fail_rate = fails / max(1, total)
- if fail_rate > 0.5:
- # Find what strategy succeeded
- successful_strategies = [
- e.strategy_used for e in self._entries
- if e.tool_name == tool and e.success and e.strategy_used
- ]
- if successful_strategies:
- strategy = max(set(successful_strategies), key=successful_strategies.count)
- self._update_rule(
- f"{tool} frequently fails",
- f"Use alternative: {strategy}",
- fail_rate,
- fails,
- )
- def _update_rule(self, pattern: str, strategy: str, confidence: float, evidence: int):
- for rule in self._strategies:
- if rule.pattern == pattern:
- rule.strategy = strategy
- rule.confidence = confidence
- rule.evidence_count = evidence
- return
- self._strategies.append(StrategyRule(
- pattern=pattern, strategy=strategy,
- confidence=confidence, evidence_count=evidence,
- ))
- def _save(self):
- os.makedirs(os.path.dirname(self.path), exist_ok=True)
- data = {
- "strategies": [r.to_dict() for r in self._strategies],
- "recent_entries": [
- {"tool": e.tool_name, "success": e.success, "error": e.error[:100],
- "timestamp": e.timestamp}
- for e in self._entries[-50:]
- ],
- }
- with open(self.path, "w") as f:
- json.dump(data, f, indent=2, ensure_ascii=False)
- def _load(self):
- if not os.path.exists(self.path):
- return
- try:
- with open(self.path) as f:
- data = json.load(f)
- for r in data.get("strategies", []):
- self._strategies.append(StrategyRule(**r))
- except Exception:
- pass
- # Agent-side tool functions
- _default_service = None
- def _get_service() -> LearningService:
- global _default_service
- if _default_service is None:
- _default_service = LearningService()
- return _default_service
- def learning_feedback(input_val: Any) -> str:
- params = _parse(input_val)
- _get_service().record(
- tool_name=params.get("tool", ""),
- input_preview=params.get("input", ""),
- success=params.get("success", True),
- error=params.get("error", ""),
- strategy=params.get("strategy", ""),
- )
- return "[OK] Feedback recorded"
- def learning_strategies(input_val: Any) -> str:
- rules = _get_service().list_strategies()
- if not rules:
- return "[EMPTY] No strategies learned yet"
- lines = ["Learned strategies:"]
- for r in rules:
- lines.append(f" [{r.confidence:.0%}] {r.pattern} → {r.strategy} ({r.evidence_count} evidence)")
- return "\n".join(lines)
- def _parse(input_val: Any) -> dict:
- if isinstance(input_val, dict):
- return input_val
- if isinstance(input_val, str):
- try:
- return json.loads(input_val)
- except Exception:
- return {}
- return {}
|