diff --git a/multi-agent-supervisor/main-multi-agent.py b/multi-agent-supervisor/main-multi-agent.py index 0aeb53c..cf4f5f5 100644 --- a/multi-agent-supervisor/main-multi-agent.py +++ b/multi-agent-supervisor/main-multi-agent.py @@ -14,6 +14,9 @@ if __name__ == "__main__": messages = [] print("Welcome to the multi-agent sysadmin assistant!") print("Type your sysadmin question below. Type 'exit' to quit.") + print("\nšŸ’” Note: When agents execute shell commands, you may see command output") + print(" appear between the structured step logs. This is normal behavior.") + print(" The output belongs to the agent that was most recently active.") while True: user_input = input("\nšŸ“ User: ") if user_input.strip().lower() == 'exit': @@ -22,27 +25,10 @@ if __name__ == "__main__": messages.append({"role": "user", "content": user_input}) query = {"messages": messages} - print("\n=== Using invoke() method ===") - result = supervisor.invoke(query) - - print("\nšŸ“Š FINAL RESULT:") - print("-" * 40) - print(result["messages"][-1].content) - print("-" * 40) - print(f"\nšŸ“ˆ Total messages exchanged: {len(result['messages'])}") - - # Add the assistant's reply to the conversation history - messages.append({"role": "assistant", "content": result["messages"][-1].content}) - - # Ask if the user wants to continue - cont = input("\nWould you like to continue the conversation? (y/n): ") - if cont.strip().lower() not in ('y', 'yes'): - print("Session ended.") - break - - print("\n=== Using stream() method for detailed step-by-step analysis ===") + print("\n=== Processing with detailed step-by-step analysis ===") step_count = 0 max_steps = 20 # Prevent infinite loops + final_result = None try: chunks_processed = [] @@ -50,22 +36,51 @@ if __name__ == "__main__": step_count += 1 chunks_processed.append(chunk) print_step_info(step_count, chunk) + + # Store the final result for conversation history + if isinstance(chunk, dict): + for agent_name, agent_data in chunk.items(): + if 'messages' in agent_data and agent_data['messages']: + last_msg = agent_data['messages'][-1] + if hasattr(last_msg, 'content') and last_msg.content: + final_result = last_msg.content # Safety check to prevent infinite loops if step_count >= max_steps: print(f"\nāš ļø Reached maximum steps ({max_steps}), stopping stream...") break - print(f"\nāœ… Streaming completed successfully with {step_count} steps") - print(f"šŸ“Š Total chunks processed: {len(chunks_processed)}") - - # Check if the last chunk contains a complete final response - if chunks_processed: - last_chunk = chunks_processed[-1] - print(f"šŸ” Last chunk keys: {list(last_chunk.keys()) if isinstance(last_chunk, dict) else type(last_chunk)}") + print(f"\nāœ… Analysis completed with {step_count} steps") + + # Add the assistant's reply to the conversation history + if final_result: + messages.append({"role": "assistant", "content": final_result}) + + print(f"\nšŸ“Š FINAL SUMMARY:") + print("-" * 60) + if final_result: + print(final_result) + else: + print("Analysis completed - check the detailed steps above for results") + print("-" * 60) except Exception as e: print(f"\nāŒ Streaming error after {step_count} steps: {e}") - print("šŸ’” The invoke() method worked fine, so the supervisor itself is functional.") - import traceback - traceback.print_exc() + print("šŸ’” Falling back to basic invoke method...") + try: + result = supervisor.invoke(query) + final_result = result["messages"][-1].content + messages.append({"role": "assistant", "content": final_result}) + print(f"\nšŸ“Š FINAL RESULT:") + print("-" * 40) + print(final_result) + print("-" * 40) + except Exception as fallback_error: + print(f"āŒ Fallback also failed: {fallback_error}") + continue + + # Ask if the user wants to continue + cont = input("\nWould you like to continue the conversation? (y/n): ") + if cont.strip().lower() not in ('y', 'yes'): + print("Session ended.") + break diff --git a/multi-agent-supervisor/utils.py b/multi-agent-supervisor/utils.py index cee8d5e..371c8ca 100644 --- a/multi-agent-supervisor/utils.py +++ b/multi-agent-supervisor/utils.py @@ -26,12 +26,12 @@ def explain_supervisor_pattern(): def print_step_info(step_count: int, chunk): - """Print formatted step information during streaming.""" - print(f"\nšŸ”„ STEP {step_count}:") - print("-" * 30) + """Print formatted step information during streaming with clear agent actions.""" + print(f"\n{'='*60}") + print(f"STEP {step_count}") + print(f"{'='*60}") 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 [ @@ -41,102 +41,115 @@ def print_step_info(step_count: int, chunk): ]] if agent_names: - current_agent = agent_names[0] - print(f"šŸ¤– ACTIVE AGENT: {current_agent}") + current_agent = agent_names[0].upper() + agent_data = chunk[agent_names[0]] - # 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}") + if 'messages' in agent_data and agent_data['messages']: + last_message = agent_data['messages'][-1] + message_type = type(last_message).__name__ + + # Handle different message types with clear formatting + if message_type == 'HumanMessage': + # This is typically the user query or supervisor instruction + content = getattr(last_message, 'content', '') + if current_agent == 'SUPERVISOR': + print(f"[ SUPERVISOR ] received user query: {content[:100]}{'...' if len(content) > 100 else ''}") else: - print(f" {i+1}. {msg_type}: (no content)") - - print() # Extra spacing for readability + print(f"[ {current_agent} ] received prompt from supervisor: {content[:100]}{'...' if len(content) > 100 else ''}") + + elif message_type == 'ToolMessage': + # Result of tool execution + tool_name = getattr(last_message, 'name', 'unknown') + content = getattr(last_message, 'content', '') + + if "Successfully transferred" in content: + if tool_name.startswith('transfer_to_'): + target_agent = tool_name.replace('transfer_to_', '').upper() + print(f"[ SUPERVISOR ] successfully transferred control to {target_agent}") + print(f"[ SUPERVISOR ] {target_agent} will now analyze the situation and execute necessary commands") + print(f"[ SUPERVISOR ] (Any shell command output below belongs to {target_agent})") + elif tool_name == 'transfer_back_to_supervisor': + print(f"[ {current_agent} ] completed analysis and transferred control back to supervisor") + print(f"[ {current_agent} ] (Any shell command output above was from {current_agent})") + + # Show the result being sent back to supervisor + # Look for the last AIMessage before this transfer to get the result + if 'messages' in agent_data and len(agent_data['messages']) > 1: + # Look for the most recent AIMessage with content + for msg in reversed(agent_data['messages'][:-1]): # Exclude current ToolMessage + if type(msg).__name__ == 'AIMessage' and hasattr(msg, 'content') and msg.content: + result_content = msg.content + if len(result_content) > 300: + preview = result_content[:300] + "..." + print(f"[ {current_agent} ] sending result to supervisor (preview): {preview}") + print(f"[ {current_agent} ] (full result length: {len(result_content)} characters)") + else: + print(f"[ {current_agent} ] sending result to supervisor: {result_content}") + break + else: + print(f"[ {current_agent} ] sending analysis results to supervisor") + else: + print(f"[ {current_agent} ] sending analysis results to supervisor") + else: + # Other tool execution result + if len(content) > 200: + preview = content[:200] + "..." + print(f"[ {current_agent} ] tool result preview: {preview}") + print(f"[ {current_agent} ] (full result length: {len(content)} characters)") + else: + print(f"[ {current_agent} ] tool result: {content}") + + elif message_type == 'AIMessage': + # Agent is responding or making tool calls + content = getattr(last_message, 'content', '') + tool_calls = getattr(last_message, 'tool_calls', []) + + if tool_calls: + for tool_call in tool_calls: + tool_name = getattr(tool_call, 'name', 'unknown') + + if tool_name.startswith('transfer_to_'): + target_agent = tool_name.replace('transfer_to_', '').upper() + args = getattr(tool_call, 'args', {}) + context = str(args)[:150] + "..." if len(str(args)) > 150 else str(args) + print(f"[ SUPERVISOR ] calling {target_agent} with context: {context}") + + elif tool_name == 'transfer_back_to_supervisor': + print(f"[ {current_agent} ] completed task, transferring back to supervisor") + + else: + print(f"[ {current_agent} ] using tool: {tool_name}") + args = getattr(tool_call, 'args', {}) + if args: + args_preview = str(args)[:100] + "..." if len(str(args)) > 100 else str(args) + print(f"[ {current_agent} ] tool arguments: {args_preview}") + + elif content: + # Final response from agent + if len(content) > 200: + preview = content[:200] + "..." + print(f"[ {current_agent} ] response preview: {preview}") + print(f"[ {current_agent} ] (full response length: {len(content)} characters)") + else: + print(f"[ {current_agent} ] response: {content}") + + else: + print(f"[ {current_agent} ] {message_type}: {getattr(last_message, 'content', 'No content')[:100]}") + + else: + print(f"[ {current_agent} ] no message data available") + else: - print("šŸ“‹ CHUNK DATA:") - # Show first few keys for debugging - chunk_keys = list(chunk.keys())[:3] - print(f" Keys: {chunk_keys}") + print("[ SYSTEM ] processing chunk with keys:", list(chunk.keys())[:3]) + else: - print(f"šŸ“¦ CHUNK TYPE: {type(chunk)}") - print(f"šŸ“„ CONTENT: {str(chunk)[:100]}...") + print(f"[ SYSTEM ] received {type(chunk).__name__}: {str(chunk)[:100]}{'...' if len(str(chunk)) > 100 else ''}") except Exception as e: - print(f"āŒ Error processing chunk: {e}") - print(f"šŸ“¦ CHUNK TYPE: {type(chunk)}") + print(f"[ ERROR ] processing step {step_count}: {e}") + print(f"[ DEBUG ] chunk type: {type(chunk)}") if hasattr(chunk, '__dict__'): - print(f"šŸ“„ CHUNK ATTRIBUTES: {list(chunk.__dict__.keys())}") + print(f"[ DEBUG ] chunk attributes: {list(chunk.__dict__.keys())}") - print("-" * 30) + print(f"{'='*60}") + print(f"NOTE: Shell command output may appear below before the next step")