diff --git a/src/backend/app_kernel.py b/src/backend/app_kernel.py index 01fb21256..043135075 100644 --- a/src/backend/app_kernel.py +++ b/src/backend/app_kernel.py @@ -115,18 +115,18 @@ async def input_task_endpoint(input_task: InputTask, request: Request): if not input_task.session_id: input_task.session_id = str(uuid.uuid4()) - # Fix 2: Don't try to set user_id on InputTask directly since it doesn't have that field - # Instead, include it in the JSON we'll pass to the planner - try: - # Create just the planner agent instead of all agents + # Create all agents instead of just the planner agent + # This ensures other agents are created first and the planner has access to them kernel, memory_store = await initialize_runtime_and_context(input_task.session_id, user_id) - planner_agent = await AgentFactory.create_agent( - agent_type=AgentType.PLANNER, - session_id=input_task.session_id, + agents = await AgentFactory.create_all_agents( + session_id=input_task.session_id, user_id=user_id ) + # Get the planner agent from the created agents + planner_agent = agents[AgentType.PLANNER] + # Convert input task to JSON for the kernel function, add user_id here input_task_data = input_task.model_dump() input_task_data["user_id"] = user_id @@ -137,8 +137,11 @@ async def input_task_endpoint(input_task: InputTask, request: Request): KernelArguments(input_task_json=input_task_json) ) + print(f"Result: {result}") # Get plan from memory store plan = await memory_store.get_plan_by_session(input_task.session_id) + + print(f"Plan: {plan}") if not plan or not plan.id: # If plan not found by session, try to extract plan ID from result @@ -190,7 +193,6 @@ async def input_task_endpoint(input_task: InputTask, request: Request): raise HTTPException(status_code=400, detail="Error creating plan") - @app.post("/human_feedback") async def human_feedback_endpoint(human_feedback: HumanFeedback, request: Request): diff --git a/src/backend/kernel_agents/agent_factory.py b/src/backend/kernel_agents/agent_factory.py index 47b654589..574385c8c 100644 --- a/src/backend/kernel_agents/agent_factory.py +++ b/src/backend/kernel_agents/agent_factory.py @@ -152,17 +152,23 @@ async def create_agent( agent_type_str = cls._agent_type_strings.get(agent_type, agent_type.value.lower()) tools = await cls._load_tools_for_agent(kernel, agent_type_str) - # Build the agent definition (functions schema) if tools exist + # Build the agent definition (functions schema) definition = None client = None + try: client = config.get_ai_project_client() except Exception as client_exc: logger.error(f"Error creating AIProjectClient: {client_exc}") - raise + if agent_type == AgentType.GROUP_CHAT_MANAGER: + logger.info(f"Continuing with GroupChatManager creation despite AIProjectClient error") + else: + raise + try: - if tools: - # Create the agent definition using the AIProjectClient (project-based pattern) + # Create the agent definition using the AIProjectClient (project-based pattern) + # For GroupChatManager, create a definition with minimal configuration + if client is not None: definition = await client.agents.create_agent( model=config.AZURE_OPENAI_DEPLOYMENT_NAME, name=agent_type_str, @@ -170,11 +176,13 @@ async def create_agent( temperature=temperature, response_format=None # Add response_format if required ) + logger.info(f"Successfully created agent definition for {agent_type_str}") except Exception as agent_exc: - logger.error(f"Error creating agent definition with AIProjectClient: {agent_exc}") - raise - if definition is None: - raise RuntimeError("Failed to create agent definition from Azure AI Project. Check your Azure configuration, permissions, and network connectivity.") + logger.error(f"Error creating agent definition with AIProjectClient for {agent_type_str}: {agent_exc}") + if agent_type == AgentType.GROUP_CHAT_MANAGER: + logger.info(f"Continuing with GroupChatManager creation despite definition error") + else: + raise # Create the agent instance using the project-based pattern try: @@ -215,7 +223,7 @@ async def create_agent( cls._agent_cache[session_id][agent_type] = agent return agent - + @classmethod async def create_azure_ai_agent( cls, @@ -272,7 +280,7 @@ async def _load_tools_for_agent(cls, kernel: Kernel, agent_type: str) -> List[Ke """Load tools for an agent from the tools directory. This tries to load tool configurations from JSON files. If that fails, - it creates a simple helper function as a fallback. + it returns an empty list for agents that don't need tools. Args: kernel: The semantic kernel instance @@ -286,9 +294,20 @@ async def _load_tools_for_agent(cls, kernel: Kernel, agent_type: str) -> List[Ke tools = BaseAgent.get_tools_from_config(kernel, agent_type) logger.info(f"Successfully loaded {len(tools)} tools for {agent_type}") return tools + except FileNotFoundError: + # No tool configuration file found - this is expected for some agents + logger.info(f"No tools defined for agent type '{agent_type}'. Returning empty list.") + return [] except Exception as e: - logger.warning(f"Failed to load tools for {agent_type}, using fallback: {e}") + logger.warning(f"Error loading tools for {agent_type}: {e}") + # Return an empty list for agents without tools rather than attempting a fallback + # Special handling for group_chat_manager which typically doesn't need tools + if "group_chat_manager" in agent_type: + logger.info(f"No tools needed for {agent_type}. Returning empty list.") + return [] + + # For other agent types, try to create a simple fallback tool try: # Use PromptTemplateConfig to create a simple tool from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig @@ -319,7 +338,7 @@ async def _load_tools_for_agent(cls, kernel: Kernel, agent_type: str) -> List[Ke return [function] except Exception as fallback_error: logger.error(f"Failed to create fallback tool for {agent_type}: {fallback_error}") - # Return an empty list if everything fails + # Return an empty list if everything fails - the agent can still function without tools return [] @classmethod @@ -343,16 +362,56 @@ async def create_all_agents( if session_id in cls._agent_cache and len(cls._agent_cache[session_id]) == len(cls._agent_classes): return cls._agent_cache[session_id] - # Create each agent type + # Create each agent type in two phases + # First, create all agents except PlannerAgent and GroupChatManager agents = {} - for agent_type in cls._agent_classes.keys(): + planner_agent_type = AgentType.PLANNER + group_chat_manager_type = AgentType.GROUP_CHAT_MANAGER + + # Initialize cache for this session if it doesn't exist + if session_id not in cls._agent_cache: + cls._agent_cache[session_id] = {} + + # Phase 1: Create all agents except planner and group chat manager + for agent_type in [at for at in cls._agent_classes.keys() + if at != planner_agent_type and at != group_chat_manager_type]: agents[agent_type] = await cls.create_agent( agent_type=agent_type, session_id=session_id, user_id=user_id, temperature=temperature ) - + + # Create agent name to instance mapping for the planner + agent_instances = {} + for agent_type, agent in agents.items(): + agent_name = cls._agent_type_strings.get(agent_type).replace("_", "") + "Agent" + agent_name = agent_name[0].upper() + agent_name[1:] # Capitalize first letter + agent_instances[agent_name] = agent + + # Log the agent instances for debugging + logger.debug(f"Created {len(agent_instances)} agent instances for planner: {', '.join(agent_instances.keys())}") + + # Phase 2: Create the planner agent with agent_instances + planner_agent = await cls.create_agent( + agent_type=planner_agent_type, + session_id=session_id, + user_id=user_id, + temperature=temperature, + agent_instances=agent_instances # Pass agent instances to the planner + ) + agents[planner_agent_type] = planner_agent + + # Phase 3: Create group chat manager with all agents including the planner + group_chat_manager = await cls.create_agent( + agent_type=group_chat_manager_type, + session_id=session_id, + user_id=user_id, + temperature=temperature, + available_agents=agent_instances # Pass all agents to group chat manager + ) + agents[group_chat_manager_type] = group_chat_manager + return agents @classmethod diff --git a/src/backend/kernel_agents/planner_agent.py b/src/backend/kernel_agents/planner_agent.py index 46fe508c9..12d76016e 100644 --- a/src/backend/kernel_agents/planner_agent.py +++ b/src/backend/kernel_agents/planner_agent.py @@ -54,6 +54,7 @@ def __init__( config_path: Optional[str] = None, available_agents: List[str] = None, agent_tools_list: List[str] = None, + agent_instances: Optional[Dict[str, BaseAgent]] = None, client=None, definition=None, ) -> None: @@ -70,6 +71,7 @@ def __init__( config_path: Optional path to the configuration file available_agents: List of available agent names for creating steps agent_tools_list: List of available tools across all agents + agent_instances: Dictionary of agent instances available to the planner client: Optional client instance (passed to BaseAgent) definition: Optional definition instance (passed to BaseAgent) """ @@ -96,6 +98,7 @@ def __init__( "ProductAgent", "ProcurementAgent", "TechSupportAgent", "GenericAgent"] self._agent_tools_list = agent_tools_list or [] + self._agent_instances = agent_instances or {} # Create the Azure AI Agent for planning operations # This will be initialized in async_init @@ -138,6 +141,11 @@ async def handle_input_task(self, kernel_arguments: KernelArguments) -> str: # Generate a structured plan with steps plan, steps = await self._create_structured_plan(input_task) + + print(f"Plan created: {plan}") + + print(f"Steps created: {steps}") + if steps: # Add a message about the created plan @@ -280,26 +288,34 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li if self._azure_ai_agent is None: raise RuntimeError("Failed to initialize Azure AI Agent for planning") - # Get response from the Azure AI Agent - # Based on the method signature, invoke takes only named arguments, not positional ones + # Log detailed information about the instruction being sent logging.info(f"Invoking PlannerAgent with instruction length: {len(instruction)}") - # Create kernel arguments + # Create kernel arguments - make sure we explicitly emphasize the task kernel_args = KernelArguments() - kernel_args["input"] = instruction + kernel_args["input"] = f"TASK: {input_task.description}\n\n{instruction}" + print(f"Kernel arguments: {kernel_args}") + # Call invoke with proper keyword arguments response_content = "" # Use keyword arguments instead of positional arguments - # Based on the method signature, we need to pass 'arguments' and possibly 'kernel' - async_generator = self._azure_ai_agent.invoke(arguments=kernel_args) + # Set a lower temperature to ensure consistent results + async_generator = self._azure_ai_agent.invoke( + arguments=kernel_args, + settings={ + "temperature": 0.0 + } + ) # Collect the response from the async generator async for chunk in async_generator: if chunk is not None: response_content += str(chunk) + print(f"Response content: {response_content}") + # Debug the response logging.info(f"Response content length: {len(response_content)}") logging.debug(f"Response content first 500 chars: {response_content[:500]}") @@ -358,6 +374,10 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li summary = parsed_result.summary_plan_and_steps human_clarification_request = parsed_result.human_clarification_request + # Log potential mismatches between task and plan for debugging + if "onboard" in input_task.description.lower() and "marketing" in initial_goal.lower(): + logging.warning(f"Potential mismatch: Task was about onboarding but plan goal mentions marketing: {initial_goal}") + # Log the steps and agent assignments for debugging for i, step in enumerate(steps_data): logging.info(f"Step {i+1} - Agent: {step.agent}, Action: {step.action}") @@ -469,7 +489,7 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li await self._memory_store.add_plan(error_plan) return error_plan, [] - + async def _create_fallback_plan_from_text(self, input_task: InputTask, text_content: str) -> Tuple[Plan, List[Step]]: """Create a plan from unstructured text when JSON parsing fails. @@ -574,7 +594,44 @@ def _generate_instruction(self, objective: str) -> str: agents_str = ", ".join(self._available_agents) # Create list of available tools - tools_str = "\n".join(self._agent_tools_list) if self._agent_tools_list else "Various specialized tools" + # If _agent_tools_list is empty but we have agent instances available elsewhere, + # we should retrieve tools directly from agent instances + tools_str = "" + if hasattr(self, '_agent_instances') and self._agent_instances: + # Extract tools from agent instances + agent_tools_sections = [] + + # Process each agent to get their tools + for agent_name, agent in self._agent_instances.items(): + if hasattr(agent, '_tools') and agent._tools: + # Create a section header for this agent + agent_tools_sections.append(f"### {agent_name} Tools ###") + + # Add each tool from this agent + for tool in agent._tools: + if hasattr(tool, 'name') and hasattr(tool, 'description'): + tool_desc = f"Agent: {agent_name} - Function: {tool.name} - {tool.description}" + agent_tools_sections.append(tool_desc) + + # Add a blank line after each agent's tools + agent_tools_sections.append("") + + # Join all sections + if agent_tools_sections: + tools_str = "\n".join(agent_tools_sections) + # Log the tools for debugging + logging.debug(f"Generated tools list from agent instances with {len(agent_tools_sections)} entries") + else: + tools_str = "Various specialized tools (No tool details available from agent instances)" + logging.warning("No tools found in agent instances") + elif self._agent_tools_list: + # Fall back to the existing tools list if available + tools_str = "\n".join(self._agent_tools_list) + logging.debug(f"Using existing agent_tools_list with {len(self._agent_tools_list)} entries") + else: + # Default fallback + tools_str = "Various specialized tools" + logging.warning("No tools information available for planner instruction") # Build the instruction, avoiding backslashes in f-string expressions objective_part = f"Your objective is:\n{objective}" if objective else "When given an objective, analyze it and create a plan to accomplish it."