"""Utility functions for the multi-agent system.""" def explain_supervisor_pattern(): """Explain how the LangGraph supervisor pattern works.""" print("šŸ—ļø MULTI-AGENT SUPERVISOR PATTERN EXPLANATION:") print("=" * 60) print("1. šŸŽÆ SUPERVISOR: Receives user query and decides which agent to delegate to") print("2. šŸ”„ TRANSFER: Uses transfer tools (e.g., transfer_to_system_info_worker)") print("3. šŸ¤– AGENT: Specialized agent executes its task with its own prompt/tools") print("4. šŸ”™ RETURN: Agent uses transfer_back_to_supervisor when done") print("5. 🧠 DECISION: Supervisor analyzes results and decides next agent or final response") print() print("šŸ“‹ WHAT 'Successfully transferred' MEANS:") print(" - It's the response from a transfer tool call") print(" - Indicates control handoff between supervisor and agent") print(" - Each agent gets the full conversation context") print(" - Agent's prompt guides how it processes that context") print() print("šŸ” SUPERVISOR PROMPT (from config.py):") print(" - Defines available agents and their specialties") print(" - Guides delegation strategy (start with system_info & service_inventory)") print(" - Agent prompts are in agents/*.py files") print("=" * 60) print() def print_step_info(step_count: int, chunk): """Print formatted step information during streaming.""" print(f"\nšŸ”„ STEP {step_count}:") print("-" * 30) try: # Extract agent information from chunk if isinstance(chunk, dict): # Look for agent names in the chunk keys agent_names = [key for key in chunk.keys() if key in [ 'system_info_worker', 'service_inventory_worker', 'mariadb_analyzer', 'nginx_analyzer', 'phpfpm_analyzer', 'network_diag', 'cert_checker', 'risk_scorer', 'remediation_worker', 'harmonizer_worker', 'supervisor' ]] if agent_names: current_agent = agent_names[0] print(f"šŸ¤– ACTIVE AGENT: {current_agent}") # Show the messages from this agent agent_data = chunk[current_agent] if 'messages' in agent_data: messages = agent_data['messages'] if messages: last_message = messages[-1] # Get message type from the class name message_type = type(last_message).__name__ print(f"šŸ’¬ MESSAGE TYPE: {message_type}") # Show content preview if available if hasattr(last_message, 'content') and last_message.content: content = last_message.content content_length = len(content) print(f"šŸ“ CONTENT LENGTH: {content_length} characters") # Show full content for final AI responses, abbreviated for others if message_type == 'AIMessage': print(f"šŸ“„ FULL CONTENT:") print(content) print() # Extra line for readability else: # Truncate other message types for brevity preview = content[:200] + "..." if len(content) > 200 else content print(f"šŸ“„ CONTENT PREVIEW:") print(preview) print() # Extra line for readability # Show tool calls if any if hasattr(last_message, 'tool_calls') and last_message.tool_calls: tool_calls = last_message.tool_calls print(f"šŸ”§ TOOL CALLS: {len(tool_calls)} tool(s)") for i, tool_call in enumerate(tool_calls): tool_name = getattr(tool_call, 'name', 'unknown') print(f" {i+1}. {tool_name}") # Show transfer details for supervisor delegation if tool_name.startswith('transfer_to_'): target_agent = tool_name.replace('transfer_to_', '') print(f" šŸŽÆ DELEGATING to: {target_agent}") # Show the arguments/context being passed if hasattr(tool_call, 'args') and tool_call.args: print(f" šŸ“‹ Context/Args: {tool_call.args}") # Show additional info for ToolMessage if message_type == 'ToolMessage': if hasattr(last_message, 'name'): tool_name = last_message.name print(f"šŸ”§ TOOL NAME: {tool_name}") # Explain what "Successfully transferred" means if "transfer" in tool_name and "Successfully transferred" in content: if tool_name.startswith('transfer_to_'): target_agent = tool_name.replace('transfer_to_', '') print(f" ā„¹ļø EXPLANATION: Supervisor delegated control to {target_agent}") print(f" ā„¹ļø The {target_agent} will now execute its specialized tasks") elif tool_name == 'transfer_back_to_supervisor': print(f" ā„¹ļø EXPLANATION: Agent completed its task and returned control to supervisor") print(f" ā„¹ļø Supervisor will decide the next step based on results") if hasattr(last_message, 'tool_call_id'): print(f"šŸ”§ TOOL CALL ID: {last_message.tool_call_id}") # Show conversation context for better understanding agent_data = chunk[current_agent] if 'messages' in agent_data and len(agent_data['messages']) > 1: print(f"\nšŸ“š CONVERSATION CONTEXT ({len(agent_data['messages'])} messages):") for i, msg in enumerate(agent_data['messages'][-3:], start=max(0, len(agent_data['messages'])-3)): msg_type = type(msg).__name__ if hasattr(msg, 'content') and msg.content: preview = msg.content[:100].replace('\n', ' ') if len(msg.content) > 100: preview += "..." print(f" {i+1}. {msg_type}: {preview}") elif hasattr(msg, 'tool_calls') and msg.tool_calls: tool_names = [getattr(tc, 'name', 'unknown') for tc in msg.tool_calls] print(f" {i+1}. {msg_type}: Tool calls: {tool_names}") else: print(f" {i+1}. {msg_type}: (no content)") print() # Extra spacing for readability else: print("šŸ“‹ CHUNK DATA:") # Show first few keys for debugging chunk_keys = list(chunk.keys())[:3] print(f" Keys: {chunk_keys}") else: print(f"šŸ“¦ CHUNK TYPE: {type(chunk)}") print(f"šŸ“„ CONTENT: {str(chunk)[:100]}...") except Exception as e: print(f"āŒ Error processing chunk: {e}") print(f"šŸ“¦ CHUNK TYPE: {type(chunk)}") if hasattr(chunk, '__dict__'): print(f"šŸ“„ CHUNK ATTRIBUTES: {list(chunk.__dict__.keys())}") print("-" * 30)