You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[BUG] Fabricated-Observation recovery in process_llm_response is dead code since #2483 — real tool calls silently discarded for models without stop-word support #6449
process_llm_response() in lib/crewai/src/crewai/utilities/agent_utils.py contains a recovery block that is meant to handle models that don't support the stop parameter (use_stop_words=False: the gpt-5 family, the o1 family, and any custom BaseLLM that ignores stop sequences). Without a "\nObservation:" stop sequence, the LLM generates straight past the real tool call and fabricates the rest of the ReAct loop in a single completion:
Thought: ...
Action: web_search
Action Input: {"search_query": "..."}
Observation: <fabricated — the tool never ran>
Final Answer: <fabricated>
The recovery block is supposed to discard the fabricated continuation so the real Action executes:
This block has been dead code since April 2025. It catches an error that can no longer be raised:
From Removing LangChain and Rebuilding Executor #1322 (e77442cf, Oct 2024), CrewAgentParser.parse() raised FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE whenever a response contained both a parseable Action and a Final Answer, and the executor caught it and truncated at "Observation:". This worked — the real tool executed.
Feat/individual react agent #2483 (efe27bd5, Apr 2025) removed the raise from the parser (commit log: "Remove redundant error handling for action and final answer in CrewAgentParser") and deleted its regression test test_action_and_final_answer_error — while, in the same PR, moving the catch block into the newly created process_llm_response(). The raise was not redundant; it was the trigger for that catch. Since then parse() returns the fabricated AgentFinish whenever Final Answer: appears anywhere in the text, and the recovery can never run.
Other leftovers of the original intent are still in the tree:
FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE still sits in crewai/agents/constants.py, imported only by the dead catch.
The experimental executor has an unreachable warning block (experimental/agent_executor.py, "LLM returned 'Final Answer:' but parsed as AgentAction") that only makes sense with the recovery alive.
User-visible impact: this is, as far as I can tell, the root cause of the many reports collected in #3154 ("Agent does not actually invoke tools, only simulates tool usage with fabricated output"). That thread independently observed the exact correlation this explains: gpt-4.1 works (stop words supported → generation stops before a fabricated Observation:), while gpt-5 / o-series fabricate (supports_stop_words() returns False for both, so nothing stops the completion and the dead recovery never rescues the real tool call).
Steps to Reproduce
fromcrewai.utilities.agent_utilsimportprocess_llm_response# What a model without stop-word support typically returns (shape from #3154):answer="""Thought: I should use the Web Search Tool to investigate trends in AI.Action: Web Search ToolAction Input: {"search_query": "trends in AI 2025"}Observation: The search results show that the main trends include multimodal models.Thought: I now have enough information.Final Answer: The main AI trends are multimodal models."""result=process_llm_response(answer, use_stop_words=False)
print(type(result).__name__) # AgentFinish — the fabricated answer wins
Expected behavior
AgentAction(tool="Web Search Tool", ...) — the fabricated Observation/Final Answer continuation is discarded and the real tool call executes, as it did before #2483.
Screenshots/Code snippets
See above; the dead block is at lib/crewai/src/crewai/utilities/agent_utils.py (in process_llm_response), current main (2b90117e).
Operating System
macOS (reproduces on any OS)
Python Version
3.13
crewAI Version
current main (1.15.2a2, commit 2b90117e)
crewAI Tools Version
n/a
Virtual Environment
uv
Evidence
Regression timeline verified with git log -S FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE: raise introduced in e77442cf (#1322), removed in efe27bd5 (#2483) together with its test, catch block moved (already dead) into process_llm_response in the same commit.
Possible Solution
Replace the dead except block with an explicit position check — no exception plumbing, so it is immune to format_answer()'s exception swallowing and independent of #3771:
Only the exact fabrication shape (Action → Observation → Final Answer, with stop words unsupported) is affected; every other response shape, and all behavior for stop-word-supporting models, is unchanged. This also fixes all four call sites at once (experimental AgentExecutor, CrewAgentExecutor, LiteAgent, step_executor) and makes the experimental executor's existing warning reachable.
I have this implemented with regression tests (fails-before/passes-after) and will open a PR referencing this issue.
Additional context
Found by code inspection while tracing why the recovery path never fires. AI-assisted analysis (Claude); every claim above was verified by hand against git history and a local reproduction.
Description
process_llm_response()inlib/crewai/src/crewai/utilities/agent_utils.pycontains a recovery block that is meant to handle models that don't support thestopparameter (use_stop_words=False: the gpt-5 family, the o1 family, and any customBaseLLMthat ignores stop sequences). Without a"\nObservation:"stop sequence, the LLM generates straight past the real tool call and fabricates the rest of the ReAct loop in a single completion:The recovery block is supposed to discard the fabricated continuation so the real
Actionexecutes:This block has been dead code since April 2025. It catches an error that can no longer be raised:
e77442cf, Oct 2024),CrewAgentParser.parse()raisedFINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGEwhenever a response contained both a parseableActionand aFinal Answer, and the executor caught it and truncated at"Observation:". This worked — the real tool executed.efe27bd5, Apr 2025) removed the raise from the parser (commit log: "Remove redundant error handling for action and final answer in CrewAgentParser") and deleted its regression testtest_action_and_final_answer_error— while, in the same PR, moving the catch block into the newly createdprocess_llm_response(). The raise was not redundant; it was the trigger for that catch. Since thenparse()returns the fabricatedAgentFinishwheneverFinal Answer:appears anywhere in the text, and the recovery can never run.format_answer()also swallows every exception — that's the separate, orthogonal issue [BUG]format_answer()catchesOutputParserException, breaking retry logic for malformed LLM outputs #3771/[BUG] format_answer() catches OutputParserException, breaking retry logic #4113. Fixing that alone would not revive this recovery, because the error message it looks for no longer exists anywhere in the codebase.)Other leftovers of the original intent are still in the tree:
FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGEstill sits increwai/agents/constants.py, imported only by the dead catch.experimental/agent_executor.py, "LLM returned 'Final Answer:' but parsed as AgentAction") that only makes sense with the recovery alive.User-visible impact: this is, as far as I can tell, the root cause of the many reports collected in #3154 ("Agent does not actually invoke tools, only simulates tool usage with fabricated output"). That thread independently observed the exact correlation this explains: gpt-4.1 works (stop words supported → generation stops before a fabricated
Observation:), while gpt-5 / o-series fabricate (supports_stop_words()returnsFalsefor both, so nothing stops the completion and the dead recovery never rescues the real tool call).Steps to Reproduce
Expected behavior
AgentAction(tool="Web Search Tool", ...)— the fabricatedObservation/Final Answercontinuation is discarded and the real tool call executes, as it did before #2483.Screenshots/Code snippets
See above; the dead block is at
lib/crewai/src/crewai/utilities/agent_utils.py(inprocess_llm_response), currentmain(2b90117e).Operating System
macOS (reproduces on any OS)
Python Version
3.13
crewAI Version
current
main(1.15.2a2, commit2b90117e)crewAI Tools Version
n/a
Virtual Environment
uv
Evidence
Regression timeline verified with
git log -S FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE: raise introduced ine77442cf(#1322), removed inefe27bd5(#2483) together with its test, catch block moved (already dead) intoprocess_llm_responsein the same commit.Possible Solution
Replace the dead
exceptblock with an explicit position check — no exception plumbing, so it is immune toformat_answer()'s exception swallowing and independent of #3771:Only the exact fabrication shape (
Action→Observation→Final Answer, with stop words unsupported) is affected; every other response shape, and all behavior for stop-word-supporting models, is unchanged. This also fixes all four call sites at once (experimentalAgentExecutor,CrewAgentExecutor,LiteAgent,step_executor) and makes the experimental executor's existing warning reachable.I have this implemented with regression tests (fails-before/passes-after) and will open a PR referencing this issue.
Additional context
Found by code inspection while tracing why the recovery path never fires. AI-assisted analysis (Claude); every claim above was verified by hand against git history and a local reproduction.