Download splitbit_llm/agents/agent_base.py from hermescures1/splitbit-llm: direct link, hf CLI and curl.
- Browser
- Download file 7.11 kB
-
https://huggingface.co/hermescures1/splitbit-llm/resolve/main/splitbit_llm/agents/agent_base.py
- Command line
-
hf download hf://hermescures1/splitbit-llm/splitbit_llm/agents/agent_base.py
-
curl -L -o agent_base.py https://huggingface.co/hermescures1/splitbit-llm/resolve/main/splitbit_llm/agents/agent_base.py
7.11 kB
| """Base AI Agent — common functionality for all agents. | |
| Each agent: | |
| - Has a name, role, and specialization | |
| - Runs in a background thread (persistent) | |
| - Picks up goals from the goal memory | |
| - Uses the LLM (via harness) to generate responses | |
| - Reports progress and results | |
| - Can be paused/resumed | |
| - Tracks stats (goals completed, steps executed, errors) | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import threading | |
| import time | |
| from dataclasses import dataclass, field | |
| from typing import Any, Callable | |
| from ..memory.goal_memory import GoalMemory, Goal | |
| from ..memory.persistent import PersistentMemory | |
| logger = logging.getLogger(__name__) | |
| class AgentStats: | |
| """Stats for a single agent.""" | |
| goals_assigned: int = 0 | |
| goals_completed: int = 0 | |
| goals_failed: int = 0 | |
| steps_executed: int = 0 | |
| errors: int = 0 | |
| uptime_s: float = 0.0 | |
| last_active: float = 0.0 | |
| class BaseAgent: | |
| """Base class for all AI agents. | |
| Subclasses implement `process_goal()` which is called when a goal | |
| is assigned to this agent. | |
| """ | |
| def __init__(self, name: str, role: str, description: str, | |
| goal_memory: GoalMemory, | |
| persistent_memory: PersistentMemory | None = None, | |
| generate_fn: Callable[[str], str] | None = None, | |
| poll_interval_s: float = 5.0) -> None: | |
| self.name = name | |
| self.role = role | |
| self.description = description | |
| self.goal_memory = goal_memory | |
| self.persistent_memory = persistent_memory | |
| self._generate_fn = generate_fn | |
| self._poll_interval = poll_interval_s | |
| self._running = False | |
| self._paused = False | |
| self._thread: threading.Thread | None = None | |
| self._current_goal: Goal | None = None | |
| self._stats = AgentStats() | |
| self._start_time = 0.0 | |
| def set_generate_fn(self, fn: Callable[[str], str]) -> None: | |
| """Set the function used to generate LLM responses.""" | |
| self._generate_fn = fn | |
| def start(self) -> None: | |
| """Start the agent in a background thread.""" | |
| if self._running: | |
| return | |
| self._running = True | |
| self._paused = False | |
| self._start_time = time.time() | |
| self._thread = threading.Thread(target=self._run_loop, daemon=True, name=f"agent-{self.name}") | |
| self._thread.start() | |
| logger.info("Agent '%s' started (%s)", self.name, self.role) | |
| def stop(self) -> None: | |
| """Stop the agent.""" | |
| self._running = False | |
| if self._thread: | |
| self._thread.join(timeout=10) | |
| logger.info("Agent '%s' stopped", self.name) | |
| def pause(self) -> None: | |
| """Pause the agent (doesn't pick up new goals).""" | |
| self._paused = True | |
| logger.info("Agent '%s' paused", self.name) | |
| def resume(self) -> None: | |
| """Resume the agent.""" | |
| self._paused = False | |
| logger.info("Agent '%s' resumed", self.name) | |
| def _run_loop(self) -> None: | |
| """Main agent loop — continuously picks up and processes goals.""" | |
| while self._running: | |
| try: | |
| if self._paused: | |
| time.sleep(self._poll_interval) | |
| continue | |
| # Find a goal assigned to this agent | |
| goals = self.goal_memory.get_goals_for_agent(self.name) | |
| active = [g for g in goals if g.status in ("in_progress", "planning")] | |
| if not active: | |
| # Try to pick up an unassigned goal that matches our role | |
| pending = self.goal_memory.list_goals(status="pending") | |
| for g in pending: | |
| if self._can_handle(g): | |
| self.goal_memory.assign_agent(g.id, self.name) | |
| self._stats.goals_assigned += 1 | |
| active = [g] | |
| break | |
| if not active: | |
| time.sleep(self._poll_interval) | |
| continue | |
| # Process the first active goal | |
| goal = active[0] | |
| self._current_goal = goal | |
| self._stats.last_active = time.time() | |
| result = self.process_goal(goal) | |
| if result.get("success"): | |
| self._stats.steps_executed += 1 | |
| # Advance the goal | |
| self.goal_memory.execute_step( | |
| goal.id, result.get("output", ""), success=True | |
| ) | |
| else: | |
| self._stats.errors += 1 | |
| self.goal_memory.execute_step( | |
| goal.id, result.get("error", "Unknown error"), success=False | |
| ) | |
| # Check if goal is completed | |
| updated = self.goal_memory.get_goal(goal.id) | |
| if updated and updated.status == "completed": | |
| self._stats.goals_completed += 1 | |
| if self.persistent_memory: | |
| self.persistent_memory.add_episodic( | |
| "event", f"Agent {self.name} completed goal: {goal.title}", | |
| importance=0.8, tags=["goal", "completed", self.name] | |
| ) | |
| logger.info("Agent '%s' completed goal: %s", self.name, goal.title) | |
| self._current_goal = None | |
| except Exception as e: | |
| logger.error("Agent '%s' error: %s", self.name, e) | |
| self._stats.errors += 1 | |
| time.sleep(self._poll_interval) | |
| self._stats.uptime_s = time.time() - self._start_time | |
| def _can_handle(self, goal: Goal) -> bool: | |
| """Check if this agent can handle a goal. Override in subclasses.""" | |
| return True | |
| def process_goal(self, goal: Goal) -> dict[str, Any]: | |
| """Process a goal step. Must be implemented by subclasses. | |
| Returns: {"success": bool, "output": str, "error": str} | |
| """ | |
| raise NotImplementedError | |
| def _generate(self, prompt: str) -> str: | |
| """Generate a response using the LLM.""" | |
| if self._generate_fn: | |
| return self._generate_fn(prompt) | |
| return "(no generation function available)" | |
| def get_status(self) -> dict[str, Any]: | |
| """Get agent status.""" | |
| return { | |
| "name": self.name, | |
| "role": self.role, | |
| "description": self.description, | |
| "running": self._running, | |
| "paused": self._paused, | |
| "current_goal": self._current_goal.title if self._current_goal else None, | |
| "stats": { | |
| "goals_assigned": self._stats.goals_assigned, | |
| "goals_completed": self._stats.goals_completed, | |
| "goals_failed": self._stats.goals_failed, | |
| "steps_executed": self._stats.steps_executed, | |
| "errors": self._stats.errors, | |
| "uptime_s": round(self._stats.uptime_s, 1), | |
| "last_active": self._stats.last_active, | |
| }, | |
| } | |