From 1e8b9a2e31196e9029be5ea2f6ef98187a510748 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:49:41 +0000 Subject: [PATCH] Fix: Correct indentation in chat method try-finally block - Fixed syntax error at line 1197 where code was outside try block - Properly indented all main logic to be inside try block - Ensures telemetry cleanup finally block executes correctly - Maintains backward compatibility - No breaking changes Co-authored-by: Mervin Praison --- .../praisonaiagents/agent/agent.py | 609 +++++++++--------- 1 file changed, 304 insertions(+), 305 deletions(-) diff --git a/src/praisonai-agents/praisonaiagents/agent/agent.py b/src/praisonai-agents/praisonaiagents/agent/agent.py index 94ada7b7a..d81304fde 100644 --- a/src/praisonai-agents/praisonaiagents/agent/agent.py +++ b/src/praisonai-agents/praisonaiagents/agent/agent.py @@ -1193,69 +1193,140 @@ def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pyd # Reset the final display flag for each new conversation self._final_display_shown = False - # Log all parameter values when in debug mode - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - param_info = { - "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt), - "temperature": temperature, - "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None, - "output_json": str(output_json.__class__.__name__) if output_json else None, - "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None, - "reasoning_steps": reasoning_steps, - "agent_name": self.name, - "agent_role": self.role, - "agent_goal": self.goal - } - logging.debug(f"Agent.chat parameters: {json.dumps(param_info, indent=2, default=str)}") - - start_time = time.time() - reasoning_steps = reasoning_steps or self.reasoning_steps - # Search for existing knowledge if any knowledge is provided - if self.knowledge: - search_results = self.knowledge.search(prompt, agent_id=self.agent_id) - if search_results: - # Check if search_results is a list of dictionaries or strings - if isinstance(search_results, dict) and 'results' in search_results: - # Extract memory content from the results - knowledge_content = "\n".join([result['memory'] for result in search_results['results']]) - else: - # If search_results is a list of strings, join them directly - knowledge_content = "\n".join(search_results) - - # Append found knowledge to the prompt - prompt = f"{prompt}\n\nKnowledge: {knowledge_content}" + # Log all parameter values when in debug mode + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + param_info = { + "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt), + "temperature": temperature, + "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None, + "output_json": str(output_json.__class__.__name__) if output_json else None, + "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None, + "reasoning_steps": reasoning_steps, + "agent_name": self.name, + "agent_role": self.role, + "agent_goal": self.goal + } + logging.debug(f"Agent.chat parameters: {json.dumps(param_info, indent=2, default=str)}") + + start_time = time.time() + reasoning_steps = reasoning_steps or self.reasoning_steps + # Search for existing knowledge if any knowledge is provided + if self.knowledge: + search_results = self.knowledge.search(prompt, agent_id=self.agent_id) + if search_results: + # Check if search_results is a list of dictionaries or strings + if isinstance(search_results, dict) and 'results' in search_results: + # Extract memory content from the results + knowledge_content = "\n".join([result['memory'] for result in search_results['results']]) + else: + # If search_results is a list of strings, join them directly + knowledge_content = "\n".join(search_results) + + # Append found knowledge to the prompt + prompt = f"{prompt}\n\nKnowledge: {knowledge_content}" - if self._using_custom_llm: - try: - # Special handling for MCP tools when using provider/model format - # Fix: Handle empty tools list properly - use self.tools if tools is None or empty - if tools is None or (isinstance(tools, list) and len(tools) == 0): - tool_param = self.tools - else: - tool_param = tools - - # Convert MCP tool objects to OpenAI format if needed - if tool_param is not None: - from ..mcp.mcp import MCP - if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'): - logging.debug("Converting MCP tool to OpenAI format") - openai_tool = tool_param.to_openai_tool() - if openai_tool: - # Handle both single tool and list of tools - if isinstance(openai_tool, list): - tool_param = openai_tool - else: - tool_param = [openai_tool] - logging.debug(f"Converted MCP tool: {tool_param}") + if self._using_custom_llm: + try: + # Special handling for MCP tools when using provider/model format + # Fix: Handle empty tools list properly - use self.tools if tools is None or empty + if tools is None or (isinstance(tools, list) and len(tools) == 0): + tool_param = self.tools + else: + tool_param = tools + + # Convert MCP tool objects to OpenAI format if needed + if tool_param is not None: + from ..mcp.mcp import MCP + if isinstance(tool_param, MCP) and hasattr(tool_param, 'to_openai_tool'): + logging.debug("Converting MCP tool to OpenAI format") + openai_tool = tool_param.to_openai_tool() + if openai_tool: + # Handle both single tool and list of tools + if isinstance(openai_tool, list): + tool_param = openai_tool + else: + tool_param = [openai_tool] + logging.debug(f"Converted MCP tool: {tool_param}") + + # Store chat history length for potential rollback + chat_history_length = len(self.chat_history) + + # Normalize prompt content for consistent chat history storage + normalized_content = prompt + if isinstance(prompt, list): + # Extract text from multimodal prompts + normalized_content = next((item["text"] for item in prompt if item.get("type") == "text"), "") + + # Prevent duplicate messages + if not (self.chat_history and + self.chat_history[-1].get("role") == "user" and + self.chat_history[-1].get("content") == normalized_content): + # Add user message to chat history BEFORE LLM call so handoffs can access it + self.chat_history.append({"role": "user", "content": normalized_content}) + + try: + # Pass everything to LLM class + response_text = self.llm_instance.get_response( + prompt=prompt, + system_prompt=self._build_system_prompt(tools), + chat_history=self.chat_history, + temperature=temperature, + tools=tool_param, + output_json=output_json, + output_pydantic=output_pydantic, + verbose=self.verbose, + markdown=self.markdown, + self_reflect=self.self_reflect, + max_reflect=self.max_reflect, + min_reflect=self.min_reflect, + console=self.console, + agent_name=self.name, + agent_role=self.role, + agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in (tools if tools is not None else self.tools)], + task_name=task_name, + task_description=task_description, + task_id=task_id, + execute_tool_fn=self.execute_tool, # Pass tool execution function + reasoning_steps=reasoning_steps, + stream=stream # Pass the stream parameter from chat method + ) + + self.chat_history.append({"role": "assistant", "content": response_text}) + + # Log completion time if in debug mode + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + total_time = time.time() - start_time + logging.debug(f"Agent.chat completed in {total_time:.2f} seconds") + + # Apply guardrail validation for custom LLM response + try: + validated_response = self._apply_guardrail_with_retry(response_text, prompt, temperature, tools, task_name, task_description, task_id) + return validated_response + except Exception as e: + logging.error(f"Agent {self.name}: Guardrail validation failed for custom LLM: {e}") + # Rollback chat history on guardrail failure + self.chat_history = self.chat_history[:chat_history_length] + return None + except Exception as e: + # Rollback chat history if LLM call fails + self.chat_history = self.chat_history[:chat_history_length] + display_error(f"Error in LLM chat: {e}") + return None + except Exception as e: + display_error(f"Error in LLM chat: {e}") + return None + else: + # Use the new _build_messages helper method + messages, original_prompt = self._build_messages(prompt, temperature, output_json, output_pydantic) # Store chat history length for potential rollback chat_history_length = len(self.chat_history) - # Normalize prompt content for consistent chat history storage - normalized_content = prompt - if isinstance(prompt, list): + # Normalize original_prompt for consistent chat history storage + normalized_content = original_prompt + if isinstance(original_prompt, list): # Extract text from multimodal prompts - normalized_content = next((item["text"] for item in prompt if item.get("type") == "text"), "") + normalized_content = next((item["text"] for item in original_prompt if item.get("type") == "text"), "") # Prevent duplicate messages if not (self.chat_history and @@ -1263,269 +1334,198 @@ def chat(self, prompt, temperature=0.2, tools=None, output_json=None, output_pyd self.chat_history[-1].get("content") == normalized_content): # Add user message to chat history BEFORE LLM call so handoffs can access it self.chat_history.append({"role": "user", "content": normalized_content}) + + reflection_count = 0 + start_time = time.time() + # Wrap entire while loop in try-except for rollback on any failure try: - # Pass everything to LLM class - response_text = self.llm_instance.get_response( - prompt=prompt, - system_prompt=self._build_system_prompt(tools), - chat_history=self.chat_history, - temperature=temperature, - tools=tool_param, - output_json=output_json, - output_pydantic=output_pydantic, - verbose=self.verbose, - markdown=self.markdown, - self_reflect=self.self_reflect, - max_reflect=self.max_reflect, - min_reflect=self.min_reflect, - console=self.console, - agent_name=self.name, - agent_role=self.role, - agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in (tools if tools is not None else self.tools)], - task_name=task_name, - task_description=task_description, - task_id=task_id, - execute_tool_fn=self.execute_tool, # Pass tool execution function - reasoning_steps=reasoning_steps, - stream=stream # Pass the stream parameter from chat method - ) - - self.chat_history.append({"role": "assistant", "content": response_text}) - - # Log completion time if in debug mode - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - total_time = time.time() - start_time - logging.debug(f"Agent.chat completed in {total_time:.2f} seconds") - - # Apply guardrail validation for custom LLM response - try: - validated_response = self._apply_guardrail_with_retry(response_text, prompt, temperature, tools, task_name, task_description, task_id) - return validated_response - except Exception as e: - logging.error(f"Agent {self.name}: Guardrail validation failed for custom LLM: {e}") - # Rollback chat history on guardrail failure - self.chat_history = self.chat_history[:chat_history_length] - return None - except Exception as e: - # Rollback chat history if LLM call fails - self.chat_history = self.chat_history[:chat_history_length] - display_error(f"Error in LLM chat: {e}") - return None - except Exception as e: - display_error(f"Error in LLM chat: {e}") - return None - else: - # Use the new _build_messages helper method - messages, original_prompt = self._build_messages(prompt, temperature, output_json, output_pydantic) - - # Store chat history length for potential rollback - chat_history_length = len(self.chat_history) - - # Normalize original_prompt for consistent chat history storage - normalized_content = original_prompt - if isinstance(original_prompt, list): - # Extract text from multimodal prompts - normalized_content = next((item["text"] for item in original_prompt if item.get("type") == "text"), "") - - # Prevent duplicate messages - if not (self.chat_history and - self.chat_history[-1].get("role") == "user" and - self.chat_history[-1].get("content") == normalized_content): - # Add user message to chat history BEFORE LLM call so handoffs can access it - self.chat_history.append({"role": "user", "content": normalized_content}) - - reflection_count = 0 - start_time = time.time() - - # Wrap entire while loop in try-except for rollback on any failure - try: - while True: - try: - if self.verbose: - # Handle both string and list prompts for instruction display - display_text = prompt - if isinstance(prompt, list): - # Extract text content from multimodal prompt - display_text = next((item["text"] for item in prompt if item["type"] == "text"), "") - - if display_text and str(display_text).strip(): - # Pass agent information to display_instruction - agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools] - display_instruction( - f"Agent {self.name} is processing prompt: {display_text}", - console=self.console, - agent_name=self.name, - agent_role=self.role, - agent_tools=agent_tools - ) - - response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None, reasoning_steps=reasoning_steps, stream=self.stream, task_name=task_name, task_description=task_description, task_id=task_id) - if not response: - # Rollback chat history on response failure - self.chat_history = self.chat_history[:chat_history_length] - return None - - response_text = response.choices[0].message.content.strip() - - # Handle output_json or output_pydantic if specified - if output_json or output_pydantic: - # Add to chat history and return raw response - # User message already added before LLM call via _build_messages - self.chat_history.append({"role": "assistant", "content": response_text}) - # Apply guardrail validation even for JSON output - try: - validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id) - # Execute callback after validation - self._execute_callback_and_display(original_prompt, validated_response, time.time() - start_time, task_name, task_description, task_id) - return validated_response - except Exception as e: - logging.error(f"Agent {self.name}: Guardrail validation failed for JSON output: {e}") - # Rollback chat history on guardrail failure - self.chat_history = self.chat_history[:chat_history_length] - return None - - if not self.self_reflect: - # User message already added before LLM call via _build_messages - self.chat_history.append({"role": "assistant", "content": response_text}) - if self.verbose: - logging.debug(f"Agent {self.name} final response: {response_text}") - # Return only reasoning content if reasoning_steps is True - if reasoning_steps and hasattr(response.choices[0].message, 'reasoning_content'): - # Apply guardrail to reasoning content - try: - validated_reasoning = self._apply_guardrail_with_retry(response.choices[0].message.reasoning_content, original_prompt, temperature, tools, task_name, task_description, task_id) - # Execute callback after validation - self._execute_callback_and_display(original_prompt, validated_reasoning, time.time() - start_time, task_name, task_description, task_id) - return validated_reasoning - except Exception as e: - logging.error(f"Agent {self.name}: Guardrail validation failed for reasoning content: {e}") - # Rollback chat history on guardrail failure - self.chat_history = self.chat_history[:chat_history_length] - return None - # Apply guardrail to regular response - try: - validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id) - # Execute callback after validation - self._execute_callback_and_display(original_prompt, validated_response, time.time() - start_time, task_name, task_description, task_id) - return validated_response - except Exception as e: - logging.error(f"Agent {self.name}: Guardrail validation failed: {e}") - # Rollback chat history on guardrail failure - self.chat_history = self.chat_history[:chat_history_length] - return None - - reflection_prompt = f""" -Reflect on your previous response: '{response_text}'. -{self.reflect_prompt if self.reflect_prompt else "Identify any flaws, improvements, or actions."} -Provide a "satisfactory" status ('yes' or 'no'). -Output MUST be JSON with 'reflection' and 'satisfactory'. - """ - logging.debug(f"{self.name} reflection attempt {reflection_count+1}, sending prompt: {reflection_prompt}") - messages.append({"role": "user", "content": reflection_prompt}) - + while True: try: - # Check if we're using a custom LLM (like Gemini) - if self._using_custom_llm or self._openai_client is None: - # For custom LLMs, we need to handle reflection differently - # Use non-streaming to get complete JSON response - reflection_response = self._chat_completion(messages, temperature=temperature, tools=None, stream=False, reasoning_steps=False, task_name=task_name, task_description=task_description, task_id=task_id) - - if not reflection_response or not reflection_response.choices: - raise Exception("No response from reflection request") - - reflection_text = reflection_response.choices[0].message.content.strip() - - # Clean the JSON output - cleaned_json = self.clean_json_output(reflection_text) - - # Parse the JSON manually - reflection_data = json.loads(cleaned_json) - - # Create a reflection output object manually - class CustomReflectionOutput: - def __init__(self, data): - self.reflection = data.get('reflection', '') - self.satisfactory = data.get('satisfactory', 'no').lower() + if self.verbose: + # Handle both string and list prompts for instruction display + display_text = prompt + if isinstance(prompt, list): + # Extract text content from multimodal prompt + display_text = next((item["text"] for item in prompt if item["type"] == "text"), "") - reflection_output = CustomReflectionOutput(reflection_data) - else: - # Use OpenAI's structured output for OpenAI models - reflection_response = self._openai_client.sync_client.beta.chat.completions.parse( - model=self.reflect_llm if self.reflect_llm else self.llm, - messages=messages, - temperature=temperature, - response_format=ReflectionOutput - ) - - reflection_output = reflection_response.choices[0].message.parsed + if display_text and str(display_text).strip(): + # Pass agent information to display_instruction + agent_tools = [t.__name__ if hasattr(t, '__name__') else str(t) for t in self.tools] + display_instruction( + f"Agent {self.name} is processing prompt: {display_text}", + console=self.console, + agent_name=self.name, + agent_role=self.role, + agent_tools=agent_tools + ) - if self.verbose: - display_self_reflection(f"Agent {self.name} self reflection (using {self.reflect_llm if self.reflect_llm else self.llm}): reflection='{reflection_output.reflection}' satisfactory='{reflection_output.satisfactory}'", console=self.console) + response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None, reasoning_steps=reasoning_steps, stream=self.stream, task_name=task_name, task_description=task_description, task_id=task_id) + if not response: + # Rollback chat history on response failure + self.chat_history = self.chat_history[:chat_history_length] + return None - messages.append({"role": "assistant", "content": f"Self Reflection: {reflection_output.reflection} Satisfactory?: {reflection_output.satisfactory}"}) + response_text = response.choices[0].message.content.strip() - # Only consider satisfactory after minimum reflections - if reflection_output.satisfactory == "yes" and reflection_count >= self.min_reflect - 1: - if self.verbose: - display_self_reflection("Agent marked the response as satisfactory after meeting minimum reflections", console=self.console) + # Handle output_json or output_pydantic if specified + if output_json or output_pydantic: + # Add to chat history and return raw response # User message already added before LLM call via _build_messages self.chat_history.append({"role": "assistant", "content": response_text}) - # Apply guardrail validation after satisfactory reflection + # Apply guardrail validation even for JSON output try: validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id) # Execute callback after validation self._execute_callback_and_display(original_prompt, validated_response, time.time() - start_time, task_name, task_description, task_id) return validated_response except Exception as e: - logging.error(f"Agent {self.name}: Guardrail validation failed after reflection: {e}") + logging.error(f"Agent {self.name}: Guardrail validation failed for JSON output: {e}") # Rollback chat history on guardrail failure self.chat_history = self.chat_history[:chat_history_length] return None - # Check if we've hit max reflections - if reflection_count >= self.max_reflect - 1: - if self.verbose: - display_self_reflection("Maximum reflection count reached, returning current response", console=self.console) + if not self.self_reflect: # User message already added before LLM call via _build_messages self.chat_history.append({"role": "assistant", "content": response_text}) - # Apply guardrail validation after max reflections + if self.verbose: + logging.debug(f"Agent {self.name} final response: {response_text}") + # Return only reasoning content if reasoning_steps is True + if reasoning_steps and hasattr(response.choices[0].message, 'reasoning_content'): + # Apply guardrail to reasoning content + try: + validated_reasoning = self._apply_guardrail_with_retry(response.choices[0].message.reasoning_content, original_prompt, temperature, tools, task_name, task_description, task_id) + # Execute callback after validation + self._execute_callback_and_display(original_prompt, validated_reasoning, time.time() - start_time, task_name, task_description, task_id) + return validated_reasoning + except Exception as e: + logging.error(f"Agent {self.name}: Guardrail validation failed for reasoning content: {e}") + # Rollback chat history on guardrail failure + self.chat_history = self.chat_history[:chat_history_length] + return None + # Apply guardrail to regular response try: validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id) # Execute callback after validation self._execute_callback_and_display(original_prompt, validated_response, time.time() - start_time, task_name, task_description, task_id) return validated_response except Exception as e: - logging.error(f"Agent {self.name}: Guardrail validation failed after max reflections: {e}") + logging.error(f"Agent {self.name}: Guardrail validation failed: {e}") # Rollback chat history on guardrail failure self.chat_history = self.chat_history[:chat_history_length] return None - - # If not satisfactory and not at max reflections, continue with regeneration - logging.debug(f"{self.name} reflection count {reflection_count + 1}, continuing reflection process") - messages.append({"role": "user", "content": "Now regenerate your response using the reflection you made"}) - # For custom LLMs during reflection, always use non-streaming to ensure complete responses - use_stream = self.stream if not self._using_custom_llm else False - response = self._chat_completion(messages, temperature=temperature, tools=None, stream=use_stream, task_name=task_name, task_description=task_description, task_id=task_id) - response_text = response.choices[0].message.content.strip() - reflection_count += 1 - continue # Continue the loop for more reflections - except Exception as e: - display_error(f"Error in parsing self-reflection json {e}. Retrying", console=self.console) - logging.error("Reflection parsing failed.", exc_info=True) - messages.append({"role": "assistant", "content": "Self Reflection failed."}) + reflection_prompt = f""" +Reflect on your previous response: '{response_text}'. +{self.reflect_prompt if self.reflect_prompt else "Identify any flaws, improvements, or actions."} +Provide a "satisfactory" status ('yes' or 'no'). +Output MUST be JSON with 'reflection' and 'satisfactory'. + """ + logging.debug(f"{self.name} reflection attempt {reflection_count+1}, sending prompt: {reflection_prompt}") + messages.append({"role": "user", "content": reflection_prompt}) + + try: + # Check if we're using a custom LLM (like Gemini) + if self._using_custom_llm or self._openai_client is None: + # For custom LLMs, we need to handle reflection differently + # Use non-streaming to get complete JSON response + reflection_response = self._chat_completion(messages, temperature=temperature, tools=None, stream=False, reasoning_steps=False, task_name=task_name, task_description=task_description, task_id=task_id) + + if not reflection_response or not reflection_response.choices: + raise Exception("No response from reflection request") + + reflection_text = reflection_response.choices[0].message.content.strip() + + # Clean the JSON output + cleaned_json = self.clean_json_output(reflection_text) + + # Parse the JSON manually + reflection_data = json.loads(cleaned_json) + + # Create a reflection output object manually + class CustomReflectionOutput: + def __init__(self, data): + self.reflection = data.get('reflection', '') + self.satisfactory = data.get('satisfactory', 'no').lower() + + reflection_output = CustomReflectionOutput(reflection_data) + else: + # Use OpenAI's structured output for OpenAI models + reflection_response = self._openai_client.sync_client.beta.chat.completions.parse( + model=self.reflect_llm if self.reflect_llm else self.llm, + messages=messages, + temperature=temperature, + response_format=ReflectionOutput + ) + + reflection_output = reflection_response.choices[0].message.parsed + + if self.verbose: + display_self_reflection(f"Agent {self.name} self reflection (using {self.reflect_llm if self.reflect_llm else self.llm}): reflection='{reflection_output.reflection}' satisfactory='{reflection_output.satisfactory}'", console=self.console) + + messages.append({"role": "assistant", "content": f"Self Reflection: {reflection_output.reflection} Satisfactory?: {reflection_output.satisfactory}"}) + + # Only consider satisfactory after minimum reflections + if reflection_output.satisfactory == "yes" and reflection_count >= self.min_reflect - 1: + if self.verbose: + display_self_reflection("Agent marked the response as satisfactory after meeting minimum reflections", console=self.console) + # User message already added before LLM call via _build_messages + self.chat_history.append({"role": "assistant", "content": response_text}) + # Apply guardrail validation after satisfactory reflection + try: + validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id) + # Execute callback after validation + self._execute_callback_and_display(original_prompt, validated_response, time.time() - start_time, task_name, task_description, task_id) + return validated_response + except Exception as e: + logging.error(f"Agent {self.name}: Guardrail validation failed after reflection: {e}") + # Rollback chat history on guardrail failure + self.chat_history = self.chat_history[:chat_history_length] + return None + + # Check if we've hit max reflections + if reflection_count >= self.max_reflect - 1: + if self.verbose: + display_self_reflection("Maximum reflection count reached, returning current response", console=self.console) + # User message already added before LLM call via _build_messages + self.chat_history.append({"role": "assistant", "content": response_text}) + # Apply guardrail validation after max reflections + try: + validated_response = self._apply_guardrail_with_retry(response_text, original_prompt, temperature, tools, task_name, task_description, task_id) + # Execute callback after validation + self._execute_callback_and_display(original_prompt, validated_response, time.time() - start_time, task_name, task_description, task_id) + return validated_response + except Exception as e: + logging.error(f"Agent {self.name}: Guardrail validation failed after max reflections: {e}") + # Rollback chat history on guardrail failure + self.chat_history = self.chat_history[:chat_history_length] + return None + + # If not satisfactory and not at max reflections, continue with regeneration + logging.debug(f"{self.name} reflection count {reflection_count + 1}, continuing reflection process") + messages.append({"role": "user", "content": "Now regenerate your response using the reflection you made"}) + # For custom LLMs during reflection, always use non-streaming to ensure complete responses + use_stream = self.stream if not self._using_custom_llm else False + response = self._chat_completion(messages, temperature=temperature, tools=None, stream=use_stream, task_name=task_name, task_description=task_description, task_id=task_id) + response_text = response.choices[0].message.content.strip() reflection_count += 1 - continue # Continue even after error to try again - except Exception: - # Catch any exception from the inner try block and re-raise to outer handler - raise - except Exception as e: - # Catch any exceptions that escape the while loop - display_error(f"Unexpected error in chat: {e}", console=self.console) - # Rollback chat history - self.chat_history = self.chat_history[:chat_history_length] - return None + continue # Continue the loop for more reflections + + except Exception as e: + display_error(f"Error in parsing self-reflection json {e}. Retrying", console=self.console) + logging.error("Reflection parsing failed.", exc_info=True) + messages.append({"role": "assistant", "content": "Self Reflection failed."}) + reflection_count += 1 + continue # Continue even after error to try again + except Exception: + # Catch any exception from the inner try block and re-raise to outer handler + raise + except Exception as e: + # Catch any exceptions that escape the while loop + display_error(f"Unexpected error in chat: {e}", console=self.console) + # Rollback chat history + self.chat_history = self.chat_history[:chat_history_length] + return None finally: # Ensure proper cleanup of telemetry system to prevent hanging self._cleanup_telemetry() @@ -1548,24 +1548,23 @@ async def achat(self, prompt: str, temperature=0.2, tools=None, output_json=None # Reset the final display flag for each new conversation self._final_display_shown = False - # Log all parameter values when in debug mode - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - param_info = { - "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt), - "temperature": temperature, - "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None, - "output_json": str(output_json.__class__.__name__) if output_json else None, - "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None, - "reasoning_steps": reasoning_steps, - "agent_name": self.name, - "agent_role": self.role, - "agent_goal": self.goal - } - logging.debug(f"Agent.achat parameters: {json.dumps(param_info, indent=2, default=str)}") + # Log all parameter values when in debug mode + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + param_info = { + "prompt": str(prompt)[:100] + "..." if isinstance(prompt, str) and len(str(prompt)) > 100 else str(prompt), + "temperature": temperature, + "tools": [t.__name__ if hasattr(t, "__name__") else str(t) for t in tools] if tools else None, + "output_json": str(output_json.__class__.__name__) if output_json else None, + "output_pydantic": str(output_pydantic.__class__.__name__) if output_pydantic else None, + "reasoning_steps": reasoning_steps, + "agent_name": self.name, + "agent_role": self.role, + "agent_goal": self.goal + } + logging.debug(f"Agent.achat parameters: {json.dumps(param_info, indent=2, default=str)}") - start_time = time.time() - reasoning_steps = reasoning_steps or self.reasoning_steps - try: + start_time = time.time() + reasoning_steps = reasoning_steps or self.reasoning_steps # Default to self.tools if tools argument is None if tools is None: tools = self.tools