""" Base class for all reasoning agents in the forge. Each agent must implement analyze() and get_analysis_templates(). The base class provides keyword matching and template selection utilities. """ from abc import ABC, abstractmethod import random import re class ReasoningAgent(ABC): """Abstract base class for all reasoning agents.""" name: str = "BaseAgent" perspective: str = "general" def __init__(self): self._templates = self.get_analysis_templates() self._keyword_map = self.get_keyword_map() @abstractmethod def analyze(self, concept: str) -> str: """Analyze a concept from this agent's perspective. Args: concept: The concept text to analyze. Returns: A substantive analysis string from this agent's perspective. """ raise NotImplementedError @abstractmethod def get_analysis_templates(self) -> list[str]: """Return diverse analysis templates. Each template should contain {concept} placeholder and produce genuine expert-level reasoning, not placeholder text. Returns: List of template strings. """ raise NotImplementedError def get_keyword_map(self) -> dict[str, list[int]]: """Return a mapping of keywords to preferred template indices. Override in subclasses to steer template selection based on concept content. Keys are lowercase keywords/phrases, values are lists of template indices that work well for that keyword. Returns: Dictionary mapping keywords to template index lists. """ return {} def select_template(self, concept: str) -> str: """Select the best template for the given concept. Uses keyword matching to find relevant templates. Falls back to random selection if no keywords match. Args: concept: The concept text. Returns: A single template string. """ concept_lower = concept.lower() scored_indices: dict[int, int] = {} for keyword, indices in self._keyword_map.items(): if keyword in concept_lower: for idx in indices: if 0 <= idx < len(self._templates): scored_indices[idx] = scored_indices.get(idx, 0) + 1 if scored_indices: max_score = max(scored_indices.values()) best = [i for i, s in scored_indices.items() if s == max_score] chosen = random.choice(best) return self._templates[chosen] return random.choice(self._templates) def extract_key_terms(self, concept: str) -> list[str]: """Extract significant terms from the concept for template filling. Args: concept: The concept text. Returns: List of key terms found in the concept. """ stop_words = { "the", "a", "an", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", "may", "might", "can", "shall", "of", "in", "to", "for", "with", "on", "at", "from", "by", "about", "as", "into", "through", "during", "before", "after", "above", "below", "between", "and", "but", "or", "nor", "not", "so", "yet", "both", "either", "neither", "each", "every", "this", "that", "these", "those", "it", "its", "they", "them", "their", "we", "our", "you", "your", "he", "she", "his", "her", "how", "what", "when", "where", "which", "who", "why", } words = re.findall(r'\b[a-zA-Z]{3,}\b', concept.lower()) return [w for w in words if w not in stop_words] def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self.name!r}, perspective={self.perspective!r})"