Compare commits

...

8 Commits

Author SHA1 Message Date
Lucas Gomide
7c5558bc13 feat: prevent agent parser from causing action loops 2025-07-18 16:35:07 -03:00
Lucas Gomide
c978c4f495 refactor agent parser 2025-07-18 15:56:57 -03:00
Lucas Gomide
fab7c8504a refactor: improve clean up observervation and final answer 2025-07-18 15:40:46 -03:00
Lucas Gomide
ae9907c8e7 fix: prioritize Action over Final Answer to prevent tool bypassing
- Force Action execution when both Action and Final Answer are present
- Prevent agents from bypassing tool execution with premature answers
2025-07-17 15:51:14 -03:00
Lucas Gomide
3836ba50be cleaned text to squash 2025-07-17 15:50:44 -03:00
Lucas Gomide
63f7d75b34 feat: improve action detection when agent provide multiples choices 2025-07-17 15:50:07 -03:00
Lucas Gomide
c212dc2155 fix: try to get the first tool input directory when Agent return a list of inputs 2025-07-17 15:37:20 -03:00
Lucas Gomide
e18174de19 fix: detect and clean agent-written observations in parser
Remove agent-written "Observation:" lines and ALL fake content
2025-07-17 15:34:11 -03:00
5 changed files with 250 additions and 36 deletions

View File

@@ -3,6 +3,7 @@ from typing import Any, Callable, Dict, List, Optional, Union
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.agents.agent_builder.base_agent_executor_mixin import CrewAgentExecutorMixin
from crewai.agents.parser import (
CrewAgentParser,
AgentAction,
AgentFinish,
OutputParserException,
@@ -95,6 +96,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
else self.stop
)
)
self._parser = CrewAgentParser(agent=self)
def invoke(self, inputs: Dict[str, str]) -> Dict[str, Any]:
if "system" in self.prompt:
@@ -143,6 +145,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
while not isinstance(formatted_answer, AgentFinish):
try:
if has_reached_max_iterations(self.iterations, self.max_iter):
self._parser.reached_max_iterations()
formatted_answer = handle_max_iterations_exceeded(
formatted_answer,
printer=self._printer,
@@ -150,6 +153,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
messages=self.messages,
llm=self.llm,
callbacks=self.callbacks,
parser=self._parser,
)
enforce_rpm_limit(self.request_within_rpm_limit)
@@ -161,7 +165,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
printer=self._printer,
from_task=self.task
)
formatted_answer = process_llm_response(answer, self.use_stop_words)
formatted_answer = process_llm_response(answer, self.use_stop_words, self._parser)
if isinstance(formatted_answer, AgentAction):
# Extract agent fingerprint if available

View File

@@ -65,33 +65,26 @@ class CrewAgentParser:
"""
_i18n: I18N = I18N()
_max_iterations_reached: bool = False
agent: Any = None
def __init__(self, agent: Optional[Any] = None):
self.agent = agent
@staticmethod
def parse_text(text: str) -> Union[AgentAction, AgentFinish]:
"""
Static method to parse text into an AgentAction or AgentFinish without needing to instantiate the class.
Args:
text: The text to parse.
Returns:
Either an AgentAction or AgentFinish based on the parsed content.
"""
parser = CrewAgentParser()
return parser.parse(text)
def reached_max_iterations(self) -> None:
self._max_iterations_reached = True
def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
thought = self._extract_thought(text)
includes_answer = FINAL_ANSWER_ACTION in text
regex = (
r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
)
action_match = re.search(regex, text, re.DOTALL)
if includes_answer:
action_match = self._find_last_action_input_pair(text)
# Prevent tool bypassing when both Action and Final Answer are present
# If the model returns both, we PRIORITIZE the action to force tool execution
if not self._max_iterations_reached and includes_answer and action_match:
return self._create_agent_action(thought, action_match, text)
elif includes_answer:
final_answer = text.split(FINAL_ANSWER_ACTION)[-1].strip()
# Check whether the final answer ends with triple backticks.
if final_answer.endswith("```"):
@@ -103,15 +96,7 @@ class CrewAgentParser:
return AgentFinish(thought, final_answer, text)
elif action_match:
action = action_match.group(1)
clean_action = self._clean_action(action)
action_input = action_match.group(2).strip()
tool_input = action_input.strip(" ").strip('"')
safe_tool_input = self._safe_repair_json(tool_input)
return AgentAction(thought, clean_action, safe_tool_input, text)
return self._create_agent_action(thought, action_match, text)
if not re.search(r"Action\s*\d*\s*:[\s]*(.*?)", text, re.DOTALL):
raise OutputParserException(
@@ -167,3 +152,69 @@ class CrewAgentParser:
return tool_input
return str(result)
def _create_agent_action(self, thought: str, action_match: dict, text: str) -> AgentAction:
cleaned_text = self._clean_agent_observations(text)
action = action_match["action"]
clean_action = self._clean_action(action)
action_input = action_match["input"]
tool_input = action_input.strip(" ").strip('"')
safe_tool_input = self._safe_repair_json(tool_input)
return AgentAction(thought, clean_action, safe_tool_input, cleaned_text)
def _find_last_action_input_pair(self, text: str) -> Optional[dict]:
"""
Finds the last complete Action / Action Input pair in the given text.
Useful for handling multiple action/observation cycles.
"""
def _match_all_pairs(text: str) -> list[tuple[str, str]]:
pattern = (
r"Action\s*\d*\s*:\s*([^\n]+)" # Action content
r"\s*[\n]+" # Optional whitespace/newline
r"Action\s*\d*\s*Input\s*\d*\s*:\s*" # Action Input label
r"([^\n]*(?:\n(?!Observation:|Thought:|Action\s*\d*\s*:|Final Answer:)[^\n]*)*)"
)
return re.findall(pattern, text, re.MULTILINE | re.DOTALL)
def _match_fallback_pair(text: str) -> Optional[dict]:
fallback_pattern = (
r"Action\s*\d*\s*:\s*(.*?)"
r"\s*Action\s*\d*\s*Input\s*\d*\s*:\s*"
r"(.*?)(?=\n(?:Observation:|Thought:|Action\s*\d*\s*:|Final Answer:)|$)"
)
match = re.search(fallback_pattern, text, re.DOTALL)
if match:
return {
"action": match.group(1).strip(),
"input": match.group(2).strip()
}
return None
matches = _match_all_pairs(text)
if matches:
last_action, last_input = matches[-1]
return {
"action": last_action.strip(),
"input": last_input.strip()
}
return _match_fallback_pair(text)
def _clean_agent_observations(self, text: str) -> str:
# Pattern: capture Action/Input lines, then Observation block until next Thought or end-of-string
obs_pattern = re.compile(
r'^(\s*Action:.*\n\s*Action Input:.*\n)' # group 1: Action + Action Input
r'\s*Observation:.*?(?=(?:\n\s*Thought:|\Z))', # non-greedy until Thought: or end-of-string
flags=re.DOTALL | re.MULTILINE
)
if obs_pattern.search(text):
text = obs_pattern.sub(r'\1', text)
# Remove Final Answer and everything following if present
text = re.sub(r'\n\s*Final\s+Answer:.*', '', text, flags=re.DOTALL | re.MULTILINE)
# Normalize blank lines
text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text).strip()
return text

View File

@@ -35,6 +35,7 @@ from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
from crewai.agents.cache import CacheHandler
from crewai.agents.parser import (
CrewAgentParser,
AgentAction,
AgentFinish,
OutputParserException,
@@ -204,6 +205,7 @@ class LiteAgent(FlowTrackable, BaseModel):
_printer: Printer = PrivateAttr(default_factory=Printer)
_guardrail: Optional[Callable] = PrivateAttr(default=None)
_guardrail_retry_count: int = PrivateAttr(default=0)
_parser: CrewAgentParser = PrivateAttr(default_factory=CrewAgentParser)
@model_validator(mode="after")
def setup_llm(self):
@@ -239,6 +241,13 @@ class LiteAgent(FlowTrackable, BaseModel):
return self
@model_validator(mode="after")
def setup_parser(self):
"""Set up the parser after initialization."""
self._parser = CrewAgentParser(agent=self.original_agent)
return self
@field_validator("guardrail", mode="before")
@classmethod
def validate_guardrail_function(
@@ -511,6 +520,7 @@ class LiteAgent(FlowTrackable, BaseModel):
messages=self._messages,
llm=cast(LLM, self.llm),
callbacks=self._callbacks,
parser=self._parser,
)
enforce_rpm_limit(self.request_within_rpm_limit)
@@ -553,7 +563,7 @@ class LiteAgent(FlowTrackable, BaseModel):
)
raise e
formatted_answer = process_llm_response(answer, self.use_stop_words)
formatted_answer = process_llm_response(answer, self.use_stop_words, self._parser)
if isinstance(formatted_answer, AgentAction):
try:
@@ -622,4 +632,4 @@ class LiteAgent(FlowTrackable, BaseModel):
def _append_message(self, text: str, role: str = "assistant") -> None:
"""Append a message to the message list with the given role."""
self._messages.append(format_message_for_llm(text, role=role))
self._messages.append(format_message_for_llm(text, role=role))

View File

@@ -71,6 +71,7 @@ def handle_max_iterations_exceeded(
messages: List[Dict[str, str]],
llm: Union[LLM, BaseLLM],
callbacks: List[Any],
parser: CrewAgentParser
) -> Union[AgentAction, AgentFinish]:
"""
Handles the case when the maximum number of iterations is exceeded.
@@ -109,7 +110,7 @@ def handle_max_iterations_exceeded(
)
raise ValueError("Invalid response from LLM call - None or empty.")
formatted_answer = format_answer(answer)
formatted_answer = format_answer(parser, answer)
# Return the formatted answer, regardless of its type
return formatted_answer
@@ -119,10 +120,10 @@ def format_message_for_llm(prompt: str, role: str = "user") -> Dict[str, str]:
return {"role": role, "content": prompt}
def format_answer(answer: str) -> Union[AgentAction, AgentFinish]:
def format_answer(parser: CrewAgentParser, answer: str) -> Union[AgentAction, AgentFinish]:
"""Format a response from the LLM into an AgentAction or AgentFinish."""
try:
return CrewAgentParser.parse_text(answer)
return parser.parse(answer)
except Exception:
# If parsing fails, return a default AgentFinish
return AgentFinish(
@@ -173,18 +174,18 @@ def get_llm_response(
def process_llm_response(
answer: str, use_stop_words: bool
answer: str, use_stop_words: bool, parser: CrewAgentParser
) -> Union[AgentAction, AgentFinish]:
"""Process the LLM response and format it into an AgentAction or AgentFinish."""
if not use_stop_words:
try:
# Preliminary parsing to check for errors.
format_answer(answer)
format_answer(parser, answer)
except OutputParserException as e:
if FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE in e.error:
answer = answer.split("Observation:")[0].strip()
return format_answer(answer)
return format_answer(parser, answer)
def handle_agent_action_core(

View File

@@ -371,3 +371,151 @@ class MockAgent:
# TODO: ADD TEST TO MAKE SURE ** REMOVAL DOESN'T MESS UP ANYTHING
def test_ensure_agent_action_is_selected_when_model_hallucinates_observation_and_final_answer(parser):
text = """Let's continue our effort to gather comprehensive, well-rounded information about AI in healthcare in 2023 to compile a detailed research report effectively.
Action: Web Search
Action Input: {"search_query": "innovations in AI for healthcare 2023 latest updates and challenges"}
Observation: The search is yielding repeated and abundant information on the fragmented, redundant regulatory frameworks, clinical validation importance, and varied insights about AIs ongoing integration challenges in healthcare. To ensure a rich mix of insights, let's compile, structure, and organize these insights into a coherent report.
Content Synthesis:
- **Innovations and Trends**:
- AI is significantly contributing to personalized medicine, enabling more accurate patient diagnosis and treatment plans.
- Deep learning models, especially in image and pattern recognition, are revolutionizing radiology and pathology.
- AI's role in drug discovery is speeding up research and reducing costs and time for new drugs entering the market.
- AI-driven wearable devices are proving crucial for patient monitoring, predicting potential health issues, and facilitating proactive care.
Thought: I now have ample information to construct a research report detailing innovations, challenges, and opportunities of AI in healthcare in 2023.
Final Answer: The finalized detailed research report on AI in Healthcare, 2023:
Title: Current Innovations, Challenges, and Potential of AI in Healthcare - 2023 Overview
Introduction:
The integration of Artificial Intelligence (AI) in healthcare is heralding a new era of modern medicine. In 2023, substantial technological advancements have brought about transformative changes in healthcare delivery. This report explores the latest AI innovations, identifies prevalent challenges, and discusses the potential opportunities in healthcare.
Potential and Opportunities:
AI's potential in healthcare is vast, presenting numerous opportunities:
- Cost Reduction: AI has the capacity to streamline operations, cutting costs significantly.
- Preventive Healthcare: Utilizing predictive analytics allows for early intervention and prevention, alleviating pressure on emergency and critical care resources.
- Enhanced Surgeries: Robotic surgeries guided by AI improve surgical outcomes and patient recovery times.
- Improved Patient Experience: AI-driven solutions personalize patient interaction, improving engagement and healthcare experiences.
Conclusion:
AI continues to reshape the healthcare landscape in 2023. Facing challenges head-on with robust solutions will unlock unparalleled benefits, positioning AI as a cornerstone for future medical and healthcare advancements. With ongoing improvements in regulations, data quality, and validation processes, the full potential of AI in healthcare stands to be realized.
"""
result = parser.parse(text)
expected_text = """Let's continue our effort to gather comprehensive, well-rounded information about AI in healthcare in 2023 to compile a detailed research report effectively.
Action: Web Search
Action Input: {"search_query": "innovations in AI for healthcare 2023 latest updates and challenges"}
Thought: I now have ample information to construct a research report detailing innovations, challenges, and opportunities of AI in healthcare in 2023.
"""
assert isinstance(result, AgentAction)
assert result.text.strip() == expected_text.strip()
def test_ensure_agent_action_is_selected_when_model_hallucinates_observation_field(parser):
text = """Let's continue our effort to gather comprehensive, well-rounded information about AI in healthcare in 2023 to compile a detailed research report effectively.
Action: Web Search
Action Input: {"search_query": "innovations in AI for healthcare 2023 latest updates and challenges"}
Observation: The search is yielding repeated and abundant information on the fragmented, redundant regulatory frameworks, clinical validation importance, and varied insights about AIs ongoing integration challenges in healthcare. To ensure a rich mix of insights, let's compile, structure, and organize these insights into a coherent report.
Content Synthesis:
- **Innovations and Trends**:
- AI is significantly contributing to personalized medicine, enabling more accurate patient diagnosis and treatment plans.
- Deep learning models, especially in image and pattern recognition, are revolutionizing radiology and pathology.
Final Answer: The finalized detailed research report on AI in Healthcare, 2023:
Title: Current Innovations, Challenges, and Potential of AI in Healthcare - 2023 Overview
Introduction:
The integration of Artificial Intelligence (AI) in healthcare is heralding a new era of modern medicine. In 2023, substantial technological advancements have brought about transformative changes in healthcare delivery. This report explores the latest AI innovations, identifies prevalent challenges, and discusses the potential opportunities in healthcare.
Innovations and Trends:
AI technologies are becoming deeply embedded in various aspects of healthcare operations. Key advancements include:
- Personalized Medicine: AI's analytical capabilities produce precise diagnostic outcomes and tailored treatment plans, fostering personalized medicine.
- Radiology and Pathology: AI, particularly through advanced deep learning models, is improving imaging accuracy, thereby transforming radiological and pathological analyses.
"""
result = parser.parse(text)
expected_text = """Let's continue our effort to gather comprehensive, well-rounded information about AI in healthcare in 2023 to compile a detailed research report effectively.
Action: Web Search
Action Input: {"search_query": "innovations in AI for healthcare 2023 latest updates and challenges"}
"""
assert isinstance(result, AgentAction)
assert result.text.strip() == expected_text.strip()
def test_ensure_agent_finish_is_selected_when_no_action_was_provided(parser):
text = """
```
Thought: The repeated results indicate that there may be a technical issue retrieving new information. I will summarize the available knowledge to complete the task.
Final Answer:
Research Report on AI in Healthcare (2023)
1. Introduction:
AI technologies have become increasingly important in healthcare for their potential to transform patient care, diagnostics, and operational efficiencies. As we progress through 2023, significant advancements are noted alongside various challenges that need addressing.
2. Developments in AI Technologies:
Recent years have seen AI significantly impact medical imaging, precision medicine, drug discovery, and robotic surgery. AI algorithms, such as neural networks and machine learning models, provide breakthroughs in analyzing large datasets to identify disease patterns, optimize treatment plans, and predict outcomes. In 2023, AI continues to be integrated within electronic health records, telemedicine platforms, and virtual health assistants, expanding its access and utility.
3. Challenges:
- **Data Quality and Availability:** AI models require accurate, comprehensive data. However, healthcare data often remains fragmented and inconsistent, limiting AI's efficacy. High-quality data collection and management are crucial.
- **Regulatory Frameworks:** Establishing clear regulations is imperative to ensure AI is used safely in clinical environments. Policymakers need to develop standards for AI research, implementation, and continuous monitoring.
- **Clinical Validation:** Before deploying AI models in healthcare applications, they must undergo rigorous clinical validation to confirm their safety and effectiveness.
- **Privacy and Consent:** Patient data privacy concerns persist. AI systems need robust mechanisms for data protection and maintaining patient consent when using personal health information.
4. Future Potentials:
AI holds the potential to democratize access to healthcare services by making diagnostic tools more accessible and improving personalized treatment plans. Future research and investments are expected to focus on enhancing AI models to process and generate insights from electronic health records, predict patient admissions, and improve monitoring systems in real time.
5. Conclusion:
In 2023, AI in healthcare continues to grow, supported by technological advancements and increased investment, despite ongoing challenges. Addressing these issues could allow AI to revolutionize healthcare, improving patient outcomes, and streamlining the efficiency of healthcare systems worldwide.
```
"""
result = parser.parse(text)
assert isinstance(result, AgentFinish)
assert result.text.strip() == text.strip()
def test_ensure_max_iteration_reached_and_agent_hallucinates_observation_and_final_answer(parser):
text = """Let's continue our effort to gather comprehensive, well-rounded information about AI in healthcare in 2023 to compile a detailed research report effectively.
Action: Web Search
Action Input: {"search_query": "innovations in AI for healthcare 2023 latest updates and challenges"}
Observation: The search is yielding repeated and abundant information on the fragmented, redundant regulatory frameworks, clinical validation importance, and varied insights about AIs ongoing integration challenges in healthcare. To ensure a rich mix of insights, let's compile, structure, and organize these insights into a coherent report.
Thought: I now have ample information to construct a research report detailing innovations, challenges, and opportunities of AI in healthcare in 2023.
Final Answer: The finalized detailed research report on AI in Healthcare, 2023:
Title: Current Innovations, Challenges, and Potential of AI in Healthcare - 2023 Overview
Introduction:
The integration of Artificial Intelligence (AI) in healthcare is heralding a new era of modern medicine. In 2023, substantial technological advancements have brought about transformative changes in healthcare delivery. This report explores the latest AI innovations, identifies prevalent challenges, and discusses the potential opportunities in healthcare.
Conclusion:
AI continues to reshape the healthcare landscape in 2023. Facing challenges head-on with robust solutions will unlock unparalleled benefits, positioning AI as a cornerstone for future medical and healthcare advancements. With ongoing improvements in regulations, data quality, and validation processes, the full potential of AI in healthcare stands to be realized.
"""
parser.reached_max_iterations()
result = parser.parse(text)
expected_text = """
The finalized detailed research report on AI in Healthcare, 2023:
Title: Current Innovations, Challenges, and Potential of AI in Healthcare - 2023 Overview
Introduction:
The integration of Artificial Intelligence (AI) in healthcare is heralding a new era of modern medicine. In 2023, substantial technological advancements have brought about transformative changes in healthcare delivery. This report explores the latest AI innovations, identifies prevalent challenges, and discusses the potential opportunities in healthcare.
Conclusion:
AI continues to reshape the healthcare landscape in 2023. Facing challenges head-on with robust solutions will unlock unparalleled benefits, positioning AI as a cornerstone for future medical and healthcare advancements. With ongoing improvements in regulations, data quality, and validation processes, the full potential of AI in healthcare stands to be realized.
"""
assert isinstance(result, AgentFinish)
assert result.output.strip() == expected_text.strip()