@@ -277,24 +277,74 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li
277277 raise RuntimeError ("Failed to initialize Azure AI Agent for planning" )
278278
279279 # Get response from the Azure AI Agent
280- response = await self ._azure_ai_agent .invoke_async (instruction )
281- response_content = str (response ).strip ()
280+ # Based on the method signature, invoke takes only named arguments, not positional ones
281+ logging .info (f"Invoking PlannerAgent with instruction length: { len (instruction )} " )
282+
283+ # Create kernel arguments
284+ kernel_args = KernelArguments ()
285+ kernel_args ["input" ] = instruction
286+
287+ # Call invoke with proper keyword arguments
288+ response_content = ""
289+
290+ # Use keyword arguments instead of positional arguments
291+ # Based on the method signature, we need to pass 'arguments' and possibly 'kernel'
292+ async_generator = self ._azure_ai_agent .invoke (arguments = kernel_args )
293+
294+ # Collect the response from the async generator
295+ async for chunk in async_generator :
296+ if chunk is not None :
297+ response_content += str (chunk )
298+
299+ # Debug the response
300+ logging .info (f"Response content length: { len (response_content )} " )
301+ logging .debug (f"Response content first 500 chars: { response_content [:500 ]} " )
302+
303+ # Check if response is empty or whitespace
304+ if not response_content or response_content .isspace ():
305+ raise ValueError ("Received empty response from Azure AI Agent" )
282306
283307 # Parse the JSON response using the structured output model
284308 try :
285309 # First try to parse using Pydantic model
286310 try :
287311 parsed_result = StructuredOutputPlan .parse_raw (response_content )
288- except Exception :
312+ except Exception as e1 :
313+ logging .warning (f"Failed to parse direct JSON with Pydantic: { str (e1 )} " )
314+
289315 # If direct parsing fails, try to extract JSON first
290316 json_match = re .search (r'```json\s*(.*?)\s*```' , response_content , re .DOTALL )
291317 if json_match :
292318 json_content = json_match .group (1 )
293- parsed_result = StructuredOutputPlan .parse_raw (json_content )
319+ logging .info (f"Found JSON content in markdown code block, length: { len (json_content )} " )
320+ try :
321+ parsed_result = StructuredOutputPlan .parse_raw (json_content )
322+ except Exception as e2 :
323+ logging .warning (f"Failed to parse extracted JSON with Pydantic: { str (e2 )} " )
324+ # Try conventional JSON parsing as fallback
325+ json_data = json .loads (json_content )
326+ parsed_result = StructuredOutputPlan .parse_obj (json_data )
294327 else :
295- # Try parsing as regular JSON then convert to Pydantic model
296- json_data = json .loads (response_content )
297- parsed_result = StructuredOutputPlan .parse_obj (json_data )
328+ # Try to extract JSON without code blocks - maybe it's embedded in text
329+ # Look for patterns like { ... } that contain "initial_goal" and "steps"
330+ json_pattern = r'\{.*?"initial_goal".*?"steps".*?\}'
331+ alt_match = re .search (json_pattern , response_content , re .DOTALL )
332+
333+ if alt_match :
334+ potential_json = alt_match .group (0 )
335+ logging .info (f"Found potential JSON in text, length: { len (potential_json )} " )
336+ try :
337+ json_data = json .loads (potential_json )
338+ parsed_result = StructuredOutputPlan .parse_obj (json_data )
339+ except Exception as e3 :
340+ logging .warning (f"Failed to parse potential JSON: { str (e3 )} " )
341+ # If all extraction attempts fail, try parsing the whole response as JSON
342+ json_data = json .loads (response_content )
343+ parsed_result = StructuredOutputPlan .parse_obj (json_data )
344+ else :
345+ # If we can't find JSON patterns, create a fallback plan from the text
346+ logging .info ("Using fallback plan creation from text response" )
347+ return await self ._create_fallback_plan_from_text (input_task , response_content )
298348
299349 # Extract plan details
300350 initial_goal = parsed_result .initial_goal
@@ -372,9 +422,11 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li
372422 return plan , steps
373423
374424 except Exception as e :
375- # If JSON parsing fails, use regex to extract steps
376- logging .warning (f"Failed to parse JSON response: { e } . Falling back to text parsing." )
377- return await self ._create_plan_from_text (input_task , response_content )
425+ # If JSON parsing fails, log error and create error plan
426+ logging .exception (f"Failed to parse JSON response: { e } " )
427+ logging .info (f"Raw response was: { response_content [:1000 ]} ..." )
428+ # Try a fallback approach
429+ return await self ._create_fallback_plan_from_text (input_task , response_content )
378430
379431 except Exception as e :
380432 logging .exception (f"Error creating structured plan: { e } " )
@@ -403,8 +455,8 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li
403455
404456 await self ._memory_store .add_plan (error_plan )
405457 return error_plan , []
406-
407- async def _create_plan_from_text (self , input_task : InputTask , text_content : str ) -> Tuple [Plan , List [Step ]]:
458+
459+ async def _create_fallback_plan_from_text (self , input_task : InputTask , text_content : str ) -> Tuple [Plan , List [Step ]]:
408460 """Create a plan from unstructured text when JSON parsing fails.
409461
410462 Args:
@@ -414,6 +466,8 @@ async def _create_plan_from_text(self, input_task: InputTask, text_content: str)
414466 Returns:
415467 Tuple containing the created plan and list of steps
416468 """
469+ logging .info ("Creating fallback plan from text content" )
470+
417471 # Extract goal from the text (first line or use input task description)
418472 goal_match = re .search (r"(?:Goal|Initial Goal|Plan):\s*(.+?)(?:\n|$)" , text_content )
419473 goal = goal_match .group (1 ).strip () if goal_match else input_task .description
@@ -424,7 +478,8 @@ async def _create_plan_from_text(self, input_task: InputTask, text_content: str)
424478 session_id = input_task .session_id ,
425479 user_id = self ._user_id ,
426480 initial_goal = goal ,
427- overall_status = PlanStatus .in_progress
481+ overall_status = PlanStatus .in_progress ,
482+ summary = f"Plan created from { input_task .description } "
428483 )
429484
430485 # Store the plan
@@ -439,32 +494,57 @@ async def _create_plan_from_text(self, input_task: InputTask, text_content: str)
439494 step_pattern = re .compile (r'(\d+)[.:\)]\s*([^:]*?):\s*(.*?)(?=\d+[.:\)]|$)' , re .DOTALL )
440495 matches = step_pattern .findall (text_content )
441496
497+ # If still no matches, look for bullet points or numbered lists
498+ if not matches :
499+ step_pattern = re .compile (r'[•\-*]\s*([^:]*?):\s*(.*?)(?=[•\-*]|$)' , re .DOTALL )
500+ bullet_matches = step_pattern .findall (text_content )
501+ if bullet_matches :
502+ # Convert bullet matches to our expected format (number, agent, action)
503+ matches = []
504+ for i , (agent_text , action ) in enumerate (bullet_matches , 1 ):
505+ matches .append ((str (i ), agent_text .strip (), action .strip ()))
506+
442507 steps = []
443- for match in matches :
444- number = match [0 ].strip ()
445- agent_text = match [1 ].strip ()
446- action = match [2 ].strip ()
447-
448- # Clean up agent name
449- agent = re .sub (r'\s+' , '' , agent_text )
450- if not agent or agent not in self ._available_agents :
451- agent = "GenericAgent" # Default to GenericAgent if not recognized
452-
453- # Create and store the step
454- step = Step (
508+ # If we found no steps at all, create at least one generic step
509+ if not matches :
510+ generic_step = Step (
455511 id = str (uuid .uuid4 ()),
456512 plan_id = plan .id ,
457513 session_id = input_task .session_id ,
458514 user_id = self ._user_id ,
459- action = action ,
460- agent = agent ,
515+ action = f"Process the request: { input_task . description } " ,
516+ agent = "GenericAgent" ,
461517 status = StepStatus .planned ,
462518 human_approval_status = HumanFeedbackStatus .requested
463519 )
464-
465- await self ._memory_store .add_step (step )
466- steps .append (step )
467-
520+ await self ._memory_store .add_step (generic_step )
521+ steps .append (generic_step )
522+ else :
523+ for match in matches :
524+ number = match [0 ].strip ()
525+ agent_text = match [1 ].strip ()
526+ action = match [2 ].strip ()
527+
528+ # Clean up agent name
529+ agent = re .sub (r'\s+' , '' , agent_text )
530+ if not agent or agent not in self ._available_agents :
531+ agent = "GenericAgent" # Default to GenericAgent if not recognized
532+
533+ # Create and store the step
534+ step = Step (
535+ id = str (uuid .uuid4 ()),
536+ plan_id = plan .id ,
537+ session_id = input_task .session_id ,
538+ user_id = self ._user_id ,
539+ action = action ,
540+ agent = agent ,
541+ status = StepStatus .planned ,
542+ human_approval_status = HumanFeedbackStatus .requested
543+ )
544+
545+ await self ._memory_store .add_step (step )
546+ steps .append (step )
547+
468548 return plan , steps
469549
470550 def _generate_instruction (self , objective : str ) -> str :
@@ -482,7 +562,9 @@ def _generate_instruction(self, objective: str) -> str:
482562 # Create list of available tools
483563 tools_str = "\n " .join (self ._agent_tools_list ) if self ._agent_tools_list else "Various specialized tools"
484564
485- # Use double curly braces to escape them in f-strings
565+ # Build the instruction, avoiding backslashes in f-string expressions
566+ objective_part = f"Your objective is:\n { objective } " if objective else "When given an objective, analyze it and create a plan to accomplish it."
567+
486568 return f"""
487569 You are the Planner, an AI orchestrator that manages a group of AI agents to accomplish tasks.
488570
@@ -492,7 +574,7 @@ def _generate_instruction(self, objective: str) -> str:
492574
493575 These actions are passed to the specific agent. Make sure the action contains all the information required for the agent to execute the task.
494576
495- { f"Your objective is: \\ n { objective } " if objective else "When given an objective, analyze it and create a plan to accomplish it." }
577+ { objective_part }
496578
497579 The agents you have access to are:
498580 { agents_str }
0 commit comments