Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions src/backend/app_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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):

Expand Down
89 changes: 74 additions & 15 deletions src/backend/kernel_agents/agent_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,29 +152,37 @@ 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,
instructions=system_message,
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:
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
73 changes: 65 additions & 8 deletions src/backend/kernel_agents/planner_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
"""
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]}")
Expand Down Expand Up @@ -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}")
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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."
Expand Down
Loading