Skip to content

Commit be81e13

Browse files
authored
Merge pull request #103 from Fr4nc3/main
fix creating tasks
2 parents 03ab5d1 + 73f79c5 commit be81e13

File tree

3 files changed

+149
-31
lines changed

3 files changed

+149
-31
lines changed

src/backend/app_kernel.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,18 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
115115
if not input_task.session_id:
116116
input_task.session_id = str(uuid.uuid4())
117117

118-
# Fix 2: Don't try to set user_id on InputTask directly since it doesn't have that field
119-
# Instead, include it in the JSON we'll pass to the planner
120-
121118
try:
122-
# Create just the planner agent instead of all agents
119+
# Create all agents instead of just the planner agent
120+
# This ensures other agents are created first and the planner has access to them
123121
kernel, memory_store = await initialize_runtime_and_context(input_task.session_id, user_id)
124-
planner_agent = await AgentFactory.create_agent(
125-
agent_type=AgentType.PLANNER,
126-
session_id=input_task.session_id,
122+
agents = await AgentFactory.create_all_agents(
123+
session_id=input_task.session_id,
127124
user_id=user_id
128125
)
129126

127+
# Get the planner agent from the created agents
128+
planner_agent = agents[AgentType.PLANNER]
129+
130130
# Convert input task to JSON for the kernel function, add user_id here
131131
input_task_data = input_task.model_dump()
132132
input_task_data["user_id"] = user_id
@@ -137,8 +137,11 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
137137
KernelArguments(input_task_json=input_task_json)
138138
)
139139

140+
print(f"Result: {result}")
140141
# Get plan from memory store
141142
plan = await memory_store.get_plan_by_session(input_task.session_id)
143+
144+
print(f"Plan: {plan}")
142145

143146
if not plan or not plan.id:
144147
# 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):
190193
raise HTTPException(status_code=400, detail="Error creating plan")
191194

192195

193-
194196
@app.post("/human_feedback")
195197
async def human_feedback_endpoint(human_feedback: HumanFeedback, request: Request):
196198

src/backend/kernel_agents/agent_factory.py

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -152,29 +152,37 @@ async def create_agent(
152152
agent_type_str = cls._agent_type_strings.get(agent_type, agent_type.value.lower())
153153
tools = await cls._load_tools_for_agent(kernel, agent_type_str)
154154

155-
# Build the agent definition (functions schema) if tools exist
155+
# Build the agent definition (functions schema)
156156
definition = None
157157
client = None
158+
158159
try:
159160
client = config.get_ai_project_client()
160161
except Exception as client_exc:
161162
logger.error(f"Error creating AIProjectClient: {client_exc}")
162-
raise
163+
if agent_type == AgentType.GROUP_CHAT_MANAGER:
164+
logger.info(f"Continuing with GroupChatManager creation despite AIProjectClient error")
165+
else:
166+
raise
167+
163168
try:
164-
if tools:
165-
# Create the agent definition using the AIProjectClient (project-based pattern)
169+
# Create the agent definition using the AIProjectClient (project-based pattern)
170+
# For GroupChatManager, create a definition with minimal configuration
171+
if client is not None:
166172
definition = await client.agents.create_agent(
167173
model=config.AZURE_OPENAI_DEPLOYMENT_NAME,
168174
name=agent_type_str,
169175
instructions=system_message,
170176
temperature=temperature,
171177
response_format=None # Add response_format if required
172178
)
179+
logger.info(f"Successfully created agent definition for {agent_type_str}")
173180
except Exception as agent_exc:
174-
logger.error(f"Error creating agent definition with AIProjectClient: {agent_exc}")
175-
raise
176-
if definition is None:
177-
raise RuntimeError("Failed to create agent definition from Azure AI Project. Check your Azure configuration, permissions, and network connectivity.")
181+
logger.error(f"Error creating agent definition with AIProjectClient for {agent_type_str}: {agent_exc}")
182+
if agent_type == AgentType.GROUP_CHAT_MANAGER:
183+
logger.info(f"Continuing with GroupChatManager creation despite definition error")
184+
else:
185+
raise
178186

179187
# Create the agent instance using the project-based pattern
180188
try:
@@ -215,7 +223,7 @@ async def create_agent(
215223
cls._agent_cache[session_id][agent_type] = agent
216224

217225
return agent
218-
226+
219227
@classmethod
220228
async def create_azure_ai_agent(
221229
cls,
@@ -272,7 +280,7 @@ async def _load_tools_for_agent(cls, kernel: Kernel, agent_type: str) -> List[Ke
272280
"""Load tools for an agent from the tools directory.
273281
274282
This tries to load tool configurations from JSON files. If that fails,
275-
it creates a simple helper function as a fallback.
283+
it returns an empty list for agents that don't need tools.
276284
277285
Args:
278286
kernel: The semantic kernel instance
@@ -286,9 +294,20 @@ async def _load_tools_for_agent(cls, kernel: Kernel, agent_type: str) -> List[Ke
286294
tools = BaseAgent.get_tools_from_config(kernel, agent_type)
287295
logger.info(f"Successfully loaded {len(tools)} tools for {agent_type}")
288296
return tools
297+
except FileNotFoundError:
298+
# No tool configuration file found - this is expected for some agents
299+
logger.info(f"No tools defined for agent type '{agent_type}'. Returning empty list.")
300+
return []
289301
except Exception as e:
290-
logger.warning(f"Failed to load tools for {agent_type}, using fallback: {e}")
302+
logger.warning(f"Error loading tools for {agent_type}: {e}")
291303

304+
# Return an empty list for agents without tools rather than attempting a fallback
305+
# Special handling for group_chat_manager which typically doesn't need tools
306+
if "group_chat_manager" in agent_type:
307+
logger.info(f"No tools needed for {agent_type}. Returning empty list.")
308+
return []
309+
310+
# For other agent types, try to create a simple fallback tool
292311
try:
293312
# Use PromptTemplateConfig to create a simple tool
294313
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
319338
return [function]
320339
except Exception as fallback_error:
321340
logger.error(f"Failed to create fallback tool for {agent_type}: {fallback_error}")
322-
# Return an empty list if everything fails
341+
# Return an empty list if everything fails - the agent can still function without tools
323342
return []
324343

325344
@classmethod
@@ -343,16 +362,56 @@ async def create_all_agents(
343362
if session_id in cls._agent_cache and len(cls._agent_cache[session_id]) == len(cls._agent_classes):
344363
return cls._agent_cache[session_id]
345364

346-
# Create each agent type
365+
# Create each agent type in two phases
366+
# First, create all agents except PlannerAgent and GroupChatManager
347367
agents = {}
348-
for agent_type in cls._agent_classes.keys():
368+
planner_agent_type = AgentType.PLANNER
369+
group_chat_manager_type = AgentType.GROUP_CHAT_MANAGER
370+
371+
# Initialize cache for this session if it doesn't exist
372+
if session_id not in cls._agent_cache:
373+
cls._agent_cache[session_id] = {}
374+
375+
# Phase 1: Create all agents except planner and group chat manager
376+
for agent_type in [at for at in cls._agent_classes.keys()
377+
if at != planner_agent_type and at != group_chat_manager_type]:
349378
agents[agent_type] = await cls.create_agent(
350379
agent_type=agent_type,
351380
session_id=session_id,
352381
user_id=user_id,
353382
temperature=temperature
354383
)
355-
384+
385+
# Create agent name to instance mapping for the planner
386+
agent_instances = {}
387+
for agent_type, agent in agents.items():
388+
agent_name = cls._agent_type_strings.get(agent_type).replace("_", "") + "Agent"
389+
agent_name = agent_name[0].upper() + agent_name[1:] # Capitalize first letter
390+
agent_instances[agent_name] = agent
391+
392+
# Log the agent instances for debugging
393+
logger.debug(f"Created {len(agent_instances)} agent instances for planner: {', '.join(agent_instances.keys())}")
394+
395+
# Phase 2: Create the planner agent with agent_instances
396+
planner_agent = await cls.create_agent(
397+
agent_type=planner_agent_type,
398+
session_id=session_id,
399+
user_id=user_id,
400+
temperature=temperature,
401+
agent_instances=agent_instances # Pass agent instances to the planner
402+
)
403+
agents[planner_agent_type] = planner_agent
404+
405+
# Phase 3: Create group chat manager with all agents including the planner
406+
group_chat_manager = await cls.create_agent(
407+
agent_type=group_chat_manager_type,
408+
session_id=session_id,
409+
user_id=user_id,
410+
temperature=temperature,
411+
available_agents=agent_instances # Pass all agents to group chat manager
412+
)
413+
agents[group_chat_manager_type] = group_chat_manager
414+
356415
return agents
357416

358417
@classmethod

src/backend/kernel_agents/planner_agent.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(
5454
config_path: Optional[str] = None,
5555
available_agents: List[str] = None,
5656
agent_tools_list: List[str] = None,
57+
agent_instances: Optional[Dict[str, BaseAgent]] = None,
5758
client=None,
5859
definition=None,
5960
) -> None:
@@ -70,6 +71,7 @@ def __init__(
7071
config_path: Optional path to the configuration file
7172
available_agents: List of available agent names for creating steps
7273
agent_tools_list: List of available tools across all agents
74+
agent_instances: Dictionary of agent instances available to the planner
7375
client: Optional client instance (passed to BaseAgent)
7476
definition: Optional definition instance (passed to BaseAgent)
7577
"""
@@ -96,6 +98,7 @@ def __init__(
9698
"ProductAgent", "ProcurementAgent",
9799
"TechSupportAgent", "GenericAgent"]
98100
self._agent_tools_list = agent_tools_list or []
101+
self._agent_instances = agent_instances or {}
99102

100103
# Create the Azure AI Agent for planning operations
101104
# This will be initialized in async_init
@@ -138,6 +141,11 @@ async def handle_input_task(self, kernel_arguments: KernelArguments) -> str:
138141

139142
# Generate a structured plan with steps
140143
plan, steps = await self._create_structured_plan(input_task)
144+
145+
print(f"Plan created: {plan}")
146+
147+
print(f"Steps created: {steps}")
148+
141149

142150
if steps:
143151
# Add a message about the created plan
@@ -280,26 +288,34 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li
280288
if self._azure_ai_agent is None:
281289
raise RuntimeError("Failed to initialize Azure AI Agent for planning")
282290

283-
# Get response from the Azure AI Agent
284-
# Based on the method signature, invoke takes only named arguments, not positional ones
291+
# Log detailed information about the instruction being sent
285292
logging.info(f"Invoking PlannerAgent with instruction length: {len(instruction)}")
286293

287-
# Create kernel arguments
294+
# Create kernel arguments - make sure we explicitly emphasize the task
288295
kernel_args = KernelArguments()
289-
kernel_args["input"] = instruction
296+
kernel_args["input"] = f"TASK: {input_task.description}\n\n{instruction}"
290297

298+
print(f"Kernel arguments: {kernel_args}")
299+
291300
# Call invoke with proper keyword arguments
292301
response_content = ""
293302

294303
# Use keyword arguments instead of positional arguments
295-
# Based on the method signature, we need to pass 'arguments' and possibly 'kernel'
296-
async_generator = self._azure_ai_agent.invoke(arguments=kernel_args)
304+
# Set a lower temperature to ensure consistent results
305+
async_generator = self._azure_ai_agent.invoke(
306+
arguments=kernel_args,
307+
settings={
308+
"temperature": 0.0
309+
}
310+
)
297311

298312
# Collect the response from the async generator
299313
async for chunk in async_generator:
300314
if chunk is not None:
301315
response_content += str(chunk)
302316

317+
print(f"Response content: {response_content}")
318+
303319
# Debug the response
304320
logging.info(f"Response content length: {len(response_content)}")
305321
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
358374
summary = parsed_result.summary_plan_and_steps
359375
human_clarification_request = parsed_result.human_clarification_request
360376

377+
# Log potential mismatches between task and plan for debugging
378+
if "onboard" in input_task.description.lower() and "marketing" in initial_goal.lower():
379+
logging.warning(f"Potential mismatch: Task was about onboarding but plan goal mentions marketing: {initial_goal}")
380+
361381
# Log the steps and agent assignments for debugging
362382
for i, step in enumerate(steps_data):
363383
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
469489

470490
await self._memory_store.add_plan(error_plan)
471491
return error_plan, []
472-
492+
473493
async def _create_fallback_plan_from_text(self, input_task: InputTask, text_content: str) -> Tuple[Plan, List[Step]]:
474494
"""Create a plan from unstructured text when JSON parsing fails.
475495
@@ -574,7 +594,44 @@ def _generate_instruction(self, objective: str) -> str:
574594
agents_str = ", ".join(self._available_agents)
575595

576596
# Create list of available tools
577-
tools_str = "\n".join(self._agent_tools_list) if self._agent_tools_list else "Various specialized tools"
597+
# If _agent_tools_list is empty but we have agent instances available elsewhere,
598+
# we should retrieve tools directly from agent instances
599+
tools_str = ""
600+
if hasattr(self, '_agent_instances') and self._agent_instances:
601+
# Extract tools from agent instances
602+
agent_tools_sections = []
603+
604+
# Process each agent to get their tools
605+
for agent_name, agent in self._agent_instances.items():
606+
if hasattr(agent, '_tools') and agent._tools:
607+
# Create a section header for this agent
608+
agent_tools_sections.append(f"### {agent_name} Tools ###")
609+
610+
# Add each tool from this agent
611+
for tool in agent._tools:
612+
if hasattr(tool, 'name') and hasattr(tool, 'description'):
613+
tool_desc = f"Agent: {agent_name} - Function: {tool.name} - {tool.description}"
614+
agent_tools_sections.append(tool_desc)
615+
616+
# Add a blank line after each agent's tools
617+
agent_tools_sections.append("")
618+
619+
# Join all sections
620+
if agent_tools_sections:
621+
tools_str = "\n".join(agent_tools_sections)
622+
# Log the tools for debugging
623+
logging.debug(f"Generated tools list from agent instances with {len(agent_tools_sections)} entries")
624+
else:
625+
tools_str = "Various specialized tools (No tool details available from agent instances)"
626+
logging.warning("No tools found in agent instances")
627+
elif self._agent_tools_list:
628+
# Fall back to the existing tools list if available
629+
tools_str = "\n".join(self._agent_tools_list)
630+
logging.debug(f"Using existing agent_tools_list with {len(self._agent_tools_list)} entries")
631+
else:
632+
# Default fallback
633+
tools_str = "Various specialized tools"
634+
logging.warning("No tools information available for planner instruction")
578635

579636
# Build the instruction, avoiding backslashes in f-string expressions
580637
objective_part = f"Your objective is:\n{objective}" if objective else "When given an objective, analyze it and create a plan to accomplish it."

0 commit comments

Comments
 (0)