Spaces:
Running
Running
| """ | |
| Personality Response Generator for LangGraph Multi-Agent MCTS Framework. | |
| This module provides a conversational personality layer that transforms | |
| technical agent responses into friendly, balanced advisor outputs while | |
| maintaining transparency and ethical considerations. | |
| Following 2025 best practices: | |
| - Type hints throughout | |
| - Comprehensive docstrings (Google style) | |
| - Dataclasses for configuration | |
| - Property-based encapsulation | |
| - Exception handling | |
| - Logging for observability | |
| """ | |
| import logging | |
| import re | |
| from dataclasses import dataclass, field | |
| from typing import ClassVar | |
| logger = logging.getLogger(__name__) | |
| class PersonalityTraits: | |
| """ | |
| Immutable configuration for personality traits. | |
| Attributes: | |
| loyalty: Commitment to user's goals (0.0-1.0) | |
| curiosity: Tendency to explore alternatives (0.0-1.0) | |
| aspiration: Drive toward optimal solutions (0.0-1.0) | |
| ethical_weight: Consideration of ethical implications (0.0-1.0) | |
| transparency: Openness about reasoning and limitations (0.0-1.0) | |
| """ | |
| loyalty: float = 0.95 | |
| curiosity: float = 0.85 | |
| aspiration: float = 0.90 | |
| ethical_weight: float = 0.92 | |
| transparency: float = 0.88 | |
| def __post_init__(self) -> None: | |
| """Validate trait values are in range [0.0, 1.0].""" | |
| for trait_name, value in self.__dict__.items(): | |
| if not 0.0 <= value <= 1.0: | |
| raise ValueError(f"Trait '{trait_name}' must be in range [0.0, 1.0], got {value}") | |
| class PersonalityResponseGenerator: | |
| """ | |
| Generates personality-infused responses based on configurable traits. | |
| This class transforms technical agent outputs into conversational, | |
| balanced advisor responses that maintain transparency while being | |
| approachable and user-friendly. | |
| Attributes: | |
| traits: PersonalityTraits configuration | |
| Example: | |
| >>> generator = PersonalityResponseGenerator() | |
| >>> response = generator.generate_response( | |
| ... agent_response="Technical analysis complete.", | |
| ... query="How do I optimize my code?" | |
| ... ) | |
| >>> print(response) | |
| Let me be transparent about my approach... | |
| """ | |
| traits: PersonalityTraits = field(default_factory=PersonalityTraits) | |
| # Class-level constants for phrase templates | |
| TRANSPARENCY_PHRASES: ClassVar[list[str]] = [ | |
| "Let me be transparent about", | |
| "I want to be clear that", | |
| "To be honest", | |
| "Let me share openly", | |
| ] | |
| CURIOSITY_PHRASES: ClassVar[list[str]] = [ | |
| "I'm curious about exploring", | |
| "There are interesting alternatives worth considering", | |
| "It might be valuable to also look at", | |
| "I wonder if we could also approach this by", | |
| ] | |
| ASPIRATION_PHRASES: ClassVar[list[str]] = [ | |
| "I'm committed to helping you find the best solution", | |
| "Let's aim for the optimal approach", | |
| "I believe we can achieve even better results by", | |
| "Striving for excellence", | |
| ] | |
| LOYALTY_PHRASES: ClassVar[list[str]] = [ | |
| "I'm here to support your goals", | |
| "Your success is my priority", | |
| "I'm committed to helping you succeed", | |
| "Working together toward your objectives", | |
| ] | |
| ETHICAL_PHRASES: ClassVar[list[str]] = [ | |
| "It's important to consider the ethical implications", | |
| "Let's ensure this aligns with best practices", | |
| "We should be mindful of", | |
| "From an ethical standpoint", | |
| ] | |
| def generate_response( | |
| self, | |
| agent_response: str, | |
| query: str, | |
| include_preamble: bool = True, | |
| max_length: int = 1000, | |
| ) -> str: | |
| """ | |
| Generate a personality-infused response from technical agent output. | |
| Args: | |
| agent_response: The original technical response from the agent | |
| query: The original user query for context | |
| include_preamble: Whether to include personality preamble | |
| max_length: Maximum length of the generated response | |
| Returns: | |
| A conversational, personality-infused version of the response | |
| Raises: | |
| ValueError: If agent_response or query is empty | |
| Example: | |
| >>> gen = PersonalityResponseGenerator() | |
| >>> response = gen.generate_response( | |
| ... "[HRM Analysis] Breaking down hierarchically...", | |
| ... "How do I solve this problem?" | |
| ... ) | |
| >>> "transparent" in response.lower() | |
| True | |
| """ | |
| # Input validation | |
| if not agent_response or not agent_response.strip(): | |
| raise ValueError("agent_response cannot be empty") | |
| if not query or not query.strip(): | |
| raise ValueError("query cannot be empty") | |
| try: | |
| # Build the personality-infused response | |
| parts = [] | |
| # Add preamble based on traits | |
| if include_preamble: | |
| preamble = self._generate_preamble(query) | |
| parts.append(preamble) | |
| # Transform the technical response | |
| transformed_response = self._transform_response(agent_response, query) | |
| parts.append(transformed_response) | |
| # Add trait-based closing | |
| closing = self._generate_closing(agent_response) | |
| if closing: | |
| parts.append(closing) | |
| # Combine and truncate if needed | |
| full_response = "\n\n".join(parts) | |
| if len(full_response) > max_length: | |
| full_response = full_response[:max_length - 3] + "..." | |
| logger.warning(f"Response truncated to {max_length} characters") | |
| return full_response | |
| except Exception as e: | |
| logger.error(f"Error generating personality response: {e}", exc_info=True) | |
| # Fallback to original response with simple wrapper | |
| return f"Here's what I found:\n\n{agent_response}" | |
| def _generate_preamble(self, query: str) -> str: | |
| """ | |
| Generate an opening preamble based on personality traits. | |
| Args: | |
| query: The user's query | |
| Returns: | |
| A personalized preamble | |
| """ | |
| preamble_parts = [] | |
| # Transparency (highest weight) | |
| if self.traits.transparency >= 0.8: | |
| preamble_parts.append( | |
| f"{self.TRANSPARENCY_PHRASES[0]} my approach to your query. " | |
| ) | |
| # Loyalty | |
| if self.traits.loyalty >= 0.9: | |
| preamble_parts.append( | |
| f"{self.LOYALTY_PHRASES[0]}, and I've carefully analyzed your question. " | |
| ) | |
| return "".join(preamble_parts).strip() | |
| def _transform_response(self, agent_response: str, query: str) -> str: | |
| """ | |
| Transform technical agent response into conversational tone. | |
| Args: | |
| agent_response: Original technical response | |
| query: User query for context | |
| Returns: | |
| Conversational version of the response | |
| """ | |
| # Extract agent name from response if present | |
| agent_match = re.search(r"\[(.*?)\]", agent_response) | |
| agent_name = agent_match.group(1) if agent_match else "the agent" | |
| # Remove technical markers like [HRM Analysis], [TRM Analysis], etc. | |
| cleaned_response = re.sub(r"\[.*?\]\s*", "", agent_response) | |
| # Create conversational wrapper | |
| conversational = ( | |
| f"Based on my analysis using {agent_name.lower()}, " | |
| f"I've identified the following approach:\n\n{cleaned_response}" | |
| ) | |
| return conversational | |
| def _generate_closing(self, agent_response: str) -> str: | |
| """ | |
| Generate a closing statement based on traits. | |
| Args: | |
| agent_response: The agent response (to check for certain keywords) | |
| Returns: | |
| A closing statement or empty string | |
| """ | |
| closing_parts = [] | |
| # Aspiration - offer to go further | |
| if self.traits.aspiration >= 0.85: | |
| closing_parts.append( | |
| "I'm committed to helping you achieve the best possible outcome. " | |
| ) | |
| # Curiosity - suggest alternatives | |
| if self.traits.curiosity >= 0.8 and any( | |
| keyword in agent_response.lower() | |
| for keyword in ["optimize", "improve", "compare"] | |
| ): | |
| closing_parts.append( | |
| "I'm curious if you'd like to explore alternative approaches as well. " | |
| ) | |
| # Ethical considerations for certain technical queries | |
| if self.traits.ethical_weight >= 0.9 and any( | |
| keyword in agent_response.lower() | |
| for keyword in ["system", "design", "architecture", "security"] | |
| ): | |
| closing_parts.append( | |
| "As we proceed, let's ensure our approach aligns with best practices and ethical considerations. " | |
| ) | |
| return "".join(closing_parts).strip() | |
| def trait_summary(self) -> dict[str, float]: | |
| """ | |
| Get a summary of current personality traits. | |
| Returns: | |
| Dictionary mapping trait names to their values | |
| """ | |
| return { | |
| "loyalty": self.traits.loyalty, | |
| "curiosity": self.traits.curiosity, | |
| "aspiration": self.traits.aspiration, | |
| "ethical_weight": self.traits.ethical_weight, | |
| "transparency": self.traits.transparency, | |
| } | |
| def __repr__(self) -> str: | |
| """String representation for debugging.""" | |
| return ( | |
| f"PersonalityResponseGenerator(" | |
| f"loyalty={self.traits.loyalty:.2f}, " | |
| f"curiosity={self.traits.curiosity:.2f}, " | |
| f"aspiration={self.traits.aspiration:.2f}, " | |
| f"ethical_weight={self.traits.ethical_weight:.2f}, " | |
| f"transparency={self.traits.transparency:.2f})" | |
| ) | |
| # Example usage | |
| if __name__ == "__main__": | |
| # Configure logging for standalone execution | |
| logging.basicConfig(level=logging.INFO) | |
| # Create generator with default traits | |
| generator = PersonalityResponseGenerator() | |
| # Example technical response | |
| agent_response = ( | |
| "[HRM Analysis] Breaking down the problem hierarchically: " | |
| "What are the key factors to consider when choosing between " | |
| "microservices and monolithic architecture?..." | |
| ) | |
| query = "What are the key factors to consider when choosing between microservices and monolithic architecture?" | |
| # Generate personality response | |
| personality_response = generator.generate_response(agent_response, query) | |
| print("=" * 80) | |
| print("ORIGINAL RESPONSE:") | |
| print("=" * 80) | |
| print(agent_response) | |
| print("\n" + "=" * 80) | |
| print("PERSONALITY-INFUSED RESPONSE:") | |
| print("=" * 80) | |
| print(personality_response) | |
| print("\n" + "=" * 80) | |
| print(f"Trait Summary: {generator.trait_summary}") | |