@@ -263,9 +263,16 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li
263263 """
264264 try :
265265 # Generate the instruction for the LLM
266+ logging .info ("Generating instruction for the LLM" )
267+ logging .debug (f"Input: { input_task } " )
268+ logging .debug (f"Available agents: { self ._available_agents } " )
266269
267270 instruction = self ._generate_instruction (input_task .description )
268271
272+ logging .info (f"Generated instruction: { instruction } " )
273+ # Log the input task for debugging
274+ logging .info (f"Creating plan for task: '{ input_task .description } '" )
275+ logging .info (f"Using available agents: { self ._available_agents } " )
269276
270277 # Use the Azure AI Agent instead of direct function invocation
271278 if self ._azure_ai_agent is None :
@@ -293,6 +300,7 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li
293300 arguments = kernel_args ,
294301 settings = {
295302 "temperature" : 0.0 , # Keep temperature low for consistent planning
303+ "max_tokens" : 4096 # Ensure we have enough tokens for the full plan
296304 }
297305 )
298306
@@ -301,267 +309,123 @@ async def _create_structured_plan(self, input_task: InputTask) -> Tuple[Plan, Li
301309 if chunk is not None :
302310 response_content += str (chunk )
303311
304-
305312 logging .info (f"Response content: { response_content } " )
306313
307314 # Check if response is empty or whitespace
308315 if not response_content or response_content .isspace ():
309316 raise ValueError ("Received empty response from Azure AI Agent" )
310317
311- # Parse the JSON response using the structured output model
318+ # Parse the JSON response directly to PlannerResponsePlan
319+ parsed_result = None
320+
321+ # Try to parse the raw response first
312322 try :
313- # First try to parse using Pydantic model
314- try :
315- parsed_result = PlannerResponsePlan .parse_raw (response_content )
316- except Exception as e1 :
317- logging .warning (f"Failed to parse direct JSON with Pydantic: { str (e1 )} " )
318-
319- # If direct parsing fails, try to extract JSON first
320- json_match = re .search (r'```json\s*(.*?)\s*```' , response_content , re .DOTALL )
321- if json_match :
322- json_content = json_match .group (1 )
323- logging .info (f"Found JSON content in markdown code block, length: { len (json_content )} " )
324- try :
325- parsed_result = PlannerResponsePlan .parse_raw (json_content )
326- except Exception as e2 :
327- logging .warning (f"Failed to parse extracted JSON with Pydantic: { str (e2 )} " )
328- # Try conventional JSON parsing as fallback
329- json_data = json .loads (json_content )
330- parsed_result = PlannerResponsePlan .parse_obj (json_data )
331- else :
332- # Try to extract JSON without code blocks - maybe it's embedded in text
333- # Look for patterns like { ... } that contain "initial_goal" and "steps"
334- json_pattern = r'\{.*?"initial_goal".*?"steps".*?\}'
335- alt_match = re .search (json_pattern , response_content , re .DOTALL )
336-
337- if alt_match :
338- potential_json = alt_match .group (0 )
339- logging .info (f"Found potential JSON in text, length: { len (potential_json )} " )
340- try :
341- json_data = json .loads (potential_json )
342- parsed_result = PlannerResponsePlan .parse_obj (json_data )
343- except Exception as e3 :
344- logging .warning (f"Failed to parse potential JSON: { str (e3 )} " )
345- # If all extraction attempts fail, try parsing the whole response as JSON
346- json_data = json .loads (response_content )
347- parsed_result = PlannerResponsePlan .parse_obj (json_data )
348- else :
349- # If we can't find JSON patterns, create a fallback plan from the text
350- logging .info ("Using fallback plan creation from text response" )
351- return await self ._create_fallback_plan_from_text (input_task , response_content )
323+ parsed_result = PlannerResponsePlan .parse_raw (response_content )
324+ except Exception as e :
325+ logging .warning (f"Failed to parse raw response: { e } " )
352326
353- # Extract plan details and log for debugging
354- initial_goal = parsed_result .initial_goal
355- steps_data = parsed_result .steps
356- summary = parsed_result .summary_plan_and_steps
357- human_clarification_request = parsed_result .human_clarification_request
327+ # Try to extract JSON from markdown code blocks
328+ json_match = re .search (r'```(?:json)?\s*(.*?)\s*```' , response_content , re .DOTALL )
329+ if json_match :
330+ json_content = json_match .group (1 )
331+ logging .info (f"Found JSON in code block, attempting to parse" )
332+ parsed_result = PlannerResponsePlan .parse_raw (json_content )
333+ else :
334+ # If still not parsed, raise the error to be handled by outer exception
335+ raise ValueError (f"Failed to parse response as PlannerResponsePlan: { e } " )
336+
337+ # At this point, we have a valid parsed_result or an exception was raised
338+
339+ # Extract plan details
340+ initial_goal = parsed_result .initial_goal
341+ steps_data = parsed_result .steps
342+ summary = parsed_result .summary_plan_and_steps
343+ human_clarification_request = parsed_result .human_clarification_request
344+
345+ # Create the Plan instance
346+ plan = Plan (
347+ id = str (uuid .uuid4 ()),
348+ session_id = input_task .session_id ,
349+ user_id = self ._user_id ,
350+ initial_goal = initial_goal ,
351+ overall_status = PlanStatus .in_progress ,
352+ summary = summary ,
353+ human_clarification_request = human_clarification_request
354+ )
355+
356+ # Store the plan
357+ await self ._memory_store .add_plan (plan )
358+
359+ track_event_if_configured (
360+ "Planner - Initial plan and added into the cosmos" ,
361+ {
362+ "session_id" : input_task .session_id ,
363+ "user_id" : self ._user_id ,
364+ "initial_goal" : initial_goal ,
365+ "overall_status" : PlanStatus .in_progress ,
366+ "source" : "PlannerAgent" ,
367+ "summary" : summary ,
368+ "human_clarification_request" : human_clarification_request ,
369+ },
370+ )
371+
372+ # Create steps from the parsed data
373+ steps = []
374+ for step_data in steps_data :
375+ action = step_data .action
376+ agent_name = step_data .agent
358377
359- # Log potential mismatches between task and plan for debugging
360- if "onboard" in input_task .description .lower () and "marketing" in initial_goal .lower ():
361- logging .warning (f"Potential mismatch: Task was about onboarding but plan goal mentions marketing: { initial_goal } " )
362-
363- # Log the steps and agent assignments for debugging
364- for i , step in enumerate (steps_data ):
365- logging .info (f"Step { i + 1 } - Agent: { step .agent } , Action: { step .action } " )
378+ # Validate agent name
379+ if agent_name not in self ._available_agents :
380+ logging .warning (f"Invalid agent name: { agent_name } , defaulting to GenericAgent" )
381+ agent_name = "GenericAgent"
366382
367- # Create the Plan instance
368- plan = Plan (
383+ # Create the step
384+ step = Step (
369385 id = str (uuid .uuid4 ()),
386+ plan_id = plan .id ,
370387 session_id = input_task .session_id ,
371388 user_id = self ._user_id ,
372- initial_goal = initial_goal ,
373- overall_status = PlanStatus . in_progress ,
374- summary = summary ,
375- human_clarification_request = human_clarification_request
389+ action = action ,
390+ agent = agent_name ,
391+ status = StepStatus . planned ,
392+ human_approval_status = HumanFeedbackStatus . requested
376393 )
377394
378- # Store the plan
379- await self ._memory_store .add_plan (plan )
395+ # Store the step
396+ await self ._memory_store .add_step (step )
397+ steps .append (step )
380398
381399 track_event_if_configured (
382- "Planner - Initial plan and added into the cosmos" ,
400+ "Planner - Added planned individual step into the cosmos" ,
383401 {
402+ "plan_id" : plan .id ,
403+ "action" : action ,
404+ "agent" : agent_name ,
405+ "status" : StepStatus .planned ,
384406 "session_id" : input_task .session_id ,
385407 "user_id" : self ._user_id ,
386- "initial_goal" : initial_goal ,
387- "overall_status" : PlanStatus .in_progress ,
388- "source" : "PlannerAgent" ,
389- "summary" : summary ,
390- "human_clarification_request" : human_clarification_request ,
408+ "human_approval_status" : HumanFeedbackStatus .requested ,
391409 },
392410 )
393-
394- # Create steps from the parsed data
395- steps = []
396- for step_data in steps_data :
397- action = step_data .action
398- agent_name = step_data .agent
399-
400- # Log any unusual agent assignments for debugging
401- if "onboard" in input_task .description .lower () and agent_name != "HrAgent" :
402- logging .warning (f"UNUSUAL AGENT ASSIGNMENT: Task contains 'onboard' but assigned to { agent_name } instead of HrAgent" )
403-
404- # Validate agent name
405- if agent_name not in self ._available_agents :
406- logging .warning (f"Invalid agent name: { agent_name } , defaulting to GenericAgent" )
407- agent_name = "GenericAgent"
408-
409- # Create the step
410- step = Step (
411- id = str (uuid .uuid4 ()),
412- plan_id = plan .id ,
413- session_id = input_task .session_id ,
414- user_id = self ._user_id ,
415- action = action ,
416- agent = agent_name ,
417- status = StepStatus .planned ,
418- human_approval_status = HumanFeedbackStatus .requested
419- )
420-
421- # Store the step
422- await self ._memory_store .add_step (step )
423- steps .append (step )
424-
425- track_event_if_configured (
426- "Planner - Added planned individual step into the cosmos" ,
427- {
428- "plan_id" : plan .id ,
429- "action" : action ,
430- "agent" : agent_name ,
431- "status" : StepStatus .planned ,
432- "session_id" : input_task .session_id ,
433- "user_id" : self ._user_id ,
434- "human_approval_status" : HumanFeedbackStatus .requested ,
435- },
436- )
437-
438- return plan , steps
439-
440- except Exception as e :
441- # If JSON parsing fails, log error and create error plan
442- logging .exception (f"Failed to parse JSON response: { e } " )
443- logging .info (f"Raw response was: { response_content [:1000 ]} ..." )
444- # Try a fallback approach
445- return await self ._create_fallback_plan_from_text (input_task , response_content )
411+
412+ return plan , steps
446413
447414 except Exception as e :
448415 logging .exception (f"Error creating structured plan: { e } " )
449416
450417 track_event_if_configured (
451- f"Planner - Error in create_structured_plan: { e } into the cosmos " ,
418+ f"Planner - Error in create_structured_plan: { e } " ,
452419 {
453420 "session_id" : input_task .session_id ,
454421 "user_id" : self ._user_id ,
455- "initial_goal" : "Error generating plan" ,
456- "overall_status" : PlanStatus .failed ,
422+ "error" : str (e ),
457423 "source" : "PlannerAgent" ,
458- "summary" : f"Error generating plan: { e } " ,
459424 },
460425 )
461426
462- # Create an error plan
463- error_plan = Plan (
464- id = str (uuid .uuid4 ()),
465- session_id = input_task .session_id ,
466- user_id = self ._user_id ,
467- initial_goal = "Error generating plan" ,
468- overall_status = PlanStatus .failed ,
469- summary = f"Error generating plan: { str (e )} "
470- )
471-
472- await self ._memory_store .add_plan (error_plan )
473- return error_plan , []
474-
475- async def _create_fallback_plan_from_text (self , input_task : InputTask , text_content : str ) -> Tuple [Plan , List [Step ]]:
476- """Create a plan from unstructured text when JSON parsing fails.
477-
478- Args:
479- input_task: The input task
480- text_content: The text content from the LLM
481-
482- Returns:
483- Tuple containing the created plan and list of steps
484- """
485- logging .info ("Creating fallback plan from text content" )
486-
487- # Extract goal from the text (first line or use input task description)
488- goal_match = re .search (r"(?:Goal|Initial Goal|Plan):\s*(.+?)(?:\n|$)" , text_content )
489- goal = goal_match .group (1 ).strip () if goal_match else input_task .description
490-
491- # Create the plan
492- plan = Plan (
493- id = str (uuid .uuid4 ()),
494- session_id = input_task .session_id ,
495- user_id = self ._user_id ,
496- initial_goal = goal ,
497- overall_status = PlanStatus .in_progress ,
498- summary = f"Plan created from { input_task .description } "
499- )
500-
501- # Store the plan
502- await self ._memory_store .add_plan (plan )
503-
504- # Parse steps using regex
505- step_pattern = re .compile (r'(?:Step|)\s*(\d+)[:.]\s*\*?\*?(?:Agent|):\s*\*?([^:*\n]+)\*?[:\s]*(.+?)(?=(?:Step|)\s*\d+[:.]\s*|$)' , re .DOTALL )
506- matches = step_pattern .findall (text_content )
507-
508- if not matches :
509- # Fallback to simpler pattern
510- step_pattern = re .compile (r'(\d+)[.:\)]\s*([^:]*?):\s*(.*?)(?=\d+[.:\)]|$)' , re .DOTALL )
511- matches = step_pattern .findall (text_content )
512-
513- # If still no matches, look for bullet points or numbered lists
514- if not matches :
515- step_pattern = re .compile (r'[•\-*]\s*([^:]*?):\s*(.*?)(?=[•\-*]|$)' , re .DOTALL )
516- bullet_matches = step_pattern .findall (text_content )
517- if bullet_matches :
518- # Convert bullet matches to our expected format (number, agent, action)
519- matches = []
520- for i , (agent_text , action ) in enumerate (bullet_matches , 1 ):
521- matches .append ((str (i ), agent_text .strip (), action .strip ()))
522-
523- steps = []
524- # If we found no steps at all, create at least one generic step
525- if not matches :
526- generic_step = Step (
527- id = str (uuid .uuid4 ()),
528- plan_id = plan .id ,
529- session_id = input_task .session_id ,
530- user_id = self ._user_id ,
531- action = f"Process the request: { input_task .description } " ,
532- agent = "GenericAgent" ,
533- status = StepStatus .planned ,
534- human_approval_status = HumanFeedbackStatus .requested
535- )
536- await self ._memory_store .add_step (generic_step )
537- steps .append (generic_step )
538- else :
539- for match in matches :
540- number = match [0 ].strip ()
541- agent_text = match [1 ].strip ()
542- action = match [2 ].strip ()
543-
544- # Clean up agent name
545- agent = re .sub (r'\s+' , '' , agent_text )
546- if not agent or agent not in self ._available_agents :
547- agent = "GenericAgent" # Default to GenericAgent if not recognized
548-
549- # Create and store the step
550- step = Step (
551- id = str (uuid .uuid4 ()),
552- plan_id = plan .id ,
553- session_id = input_task .session_id ,
554- user_id = self ._user_id ,
555- action = action ,
556- agent = agent ,
557- status = StepStatus .planned ,
558- human_approval_status = HumanFeedbackStatus .requested
559- )
560-
561- await self ._memory_store .add_step (step )
562- steps .append (step )
563-
564- return plan , steps
427+ # Re-raise the exception to be handled by the calling method
428+ raise
565429
566430 def _generate_instruction (self , objective : str ) -> str :
567431 """Generate instruction for the LLM to create a plan.
0 commit comments