mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-01 13:18:10 +00:00
Fix #6072: Display agent output in feedback prompt regardless of verbose setting
When human_input=True but verbose=False, the feedback prompt said 'Provide feedback on the Final Result above' without ever showing the result. The result display was gated on verbose via AgentLogsExecutionEvent, while the feedback prompt fired unconditionally. Now _prompt_input/_prompt_input_async accept and render the agent output in a dedicated panel before the feedback prompt, ensuring the user always sees what they are reviewing. Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
@@ -179,7 +179,8 @@ class SyncHumanInputProvider(HumanInputProvider):
|
||||
Returns:
|
||||
The final answer after feedback processing.
|
||||
"""
|
||||
feedback = self._prompt_input(context.crew)
|
||||
output_str = self._get_output_string(formatted_answer)
|
||||
feedback = self._prompt_input(context.crew, output_str)
|
||||
|
||||
if context._is_training_mode():
|
||||
return self._handle_training_feedback(formatted_answer, feedback, context)
|
||||
@@ -200,7 +201,8 @@ class SyncHumanInputProvider(HumanInputProvider):
|
||||
Returns:
|
||||
The final answer after feedback processing.
|
||||
"""
|
||||
feedback = await self._prompt_input_async(context.crew)
|
||||
output_str = self._get_output_string(formatted_answer)
|
||||
feedback = await self._prompt_input_async(context.crew, output_str)
|
||||
|
||||
if context._is_training_mode():
|
||||
return await self._handle_training_feedback_async(
|
||||
@@ -259,7 +261,9 @@ class SyncHumanInputProvider(HumanInputProvider):
|
||||
else:
|
||||
context.messages.append(context._format_feedback_message(feedback))
|
||||
answer = context._invoke_loop()
|
||||
feedback = self._prompt_input(context.crew)
|
||||
feedback = self._prompt_input(
|
||||
context.crew, self._get_output_string(answer)
|
||||
)
|
||||
|
||||
return answer
|
||||
|
||||
@@ -311,16 +315,19 @@ class SyncHumanInputProvider(HumanInputProvider):
|
||||
else:
|
||||
context.messages.append(context._format_feedback_message(feedback))
|
||||
answer = await context._ainvoke_loop()
|
||||
feedback = await self._prompt_input_async(context.crew)
|
||||
feedback = await self._prompt_input_async(
|
||||
context.crew, self._get_output_string(answer)
|
||||
)
|
||||
|
||||
return answer
|
||||
|
||||
@staticmethod
|
||||
def _prompt_input(crew: Crew | None) -> str:
|
||||
def _prompt_input(crew: Crew | None, agent_output: str | None = None) -> str:
|
||||
"""Show rich panel and prompt for input.
|
||||
|
||||
Args:
|
||||
crew: The crew instance for context.
|
||||
agent_output: The agent's final output to display for review.
|
||||
|
||||
Returns:
|
||||
User input string from terminal.
|
||||
@@ -334,6 +341,17 @@ class SyncHumanInputProvider(HumanInputProvider):
|
||||
formatter.pause_live_updates()
|
||||
|
||||
try:
|
||||
if agent_output is not None:
|
||||
result_content = Text()
|
||||
result_content.append(agent_output, style="bright_green")
|
||||
result_panel = Panel(
|
||||
result_content,
|
||||
title="Agent Final Answer",
|
||||
border_style="green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
formatter.console.print(result_panel)
|
||||
|
||||
if crew and getattr(crew, "_train", False):
|
||||
prompt_text = (
|
||||
"TRAINING MODE: Provide feedback to improve the agent's performance.\n\n"
|
||||
@@ -369,11 +387,14 @@ class SyncHumanInputProvider(HumanInputProvider):
|
||||
formatter.resume_live_updates()
|
||||
|
||||
@staticmethod
|
||||
async def _prompt_input_async(crew: Crew | None) -> str:
|
||||
async def _prompt_input_async(
|
||||
crew: Crew | None, agent_output: str | None = None
|
||||
) -> str:
|
||||
"""Show rich panel and prompt for input without blocking the event loop.
|
||||
|
||||
Args:
|
||||
crew: The crew instance for context.
|
||||
agent_output: The agent's final output to display for review.
|
||||
|
||||
Returns:
|
||||
User input string from terminal.
|
||||
@@ -387,6 +408,17 @@ class SyncHumanInputProvider(HumanInputProvider):
|
||||
formatter.pause_live_updates()
|
||||
|
||||
try:
|
||||
if agent_output is not None:
|
||||
result_content = Text()
|
||||
result_content.append(agent_output, style="bright_green")
|
||||
result_panel = Panel(
|
||||
result_content,
|
||||
title="Agent Final Answer",
|
||||
border_style="green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
formatter.console.print(result_panel)
|
||||
|
||||
if crew and getattr(crew, "_train", False):
|
||||
prompt_text = (
|
||||
"TRAINING MODE: Provide feedback to improve the agent's performance.\n\n"
|
||||
|
||||
@@ -138,4 +138,136 @@ class TestFlowHumanInputIntegration:
|
||||
for call in call_args
|
||||
if call[0]
|
||||
)
|
||||
assert training_panel_found
|
||||
assert training_panel_found
|
||||
|
||||
@patch("builtins.input", return_value="")
|
||||
def test_prompt_input_displays_agent_output(self, mock_input):
|
||||
"""Test that _prompt_input displays the agent output in a panel when provided."""
|
||||
provider = SyncHumanInputProvider()
|
||||
crew = MagicMock()
|
||||
crew._train = False
|
||||
|
||||
formatter = event_listener.formatter
|
||||
|
||||
with (
|
||||
patch.object(formatter, "pause_live_updates"),
|
||||
patch.object(formatter, "resume_live_updates"),
|
||||
patch.object(formatter.console, "print") as mock_console_print,
|
||||
):
|
||||
provider._prompt_input(crew, agent_output="The sky is blue.")
|
||||
|
||||
mock_console_print.assert_called()
|
||||
call_args = mock_console_print.call_args_list
|
||||
result_panel_found = any(
|
||||
hasattr(call[0][0], "title")
|
||||
and "Agent Final Answer" in str(call[0][0].title)
|
||||
for call in call_args
|
||||
if call[0]
|
||||
)
|
||||
assert result_panel_found, (
|
||||
"Agent output panel should be displayed when agent_output is provided"
|
||||
)
|
||||
|
||||
@patch("builtins.input", return_value="")
|
||||
def test_prompt_input_no_output_panel_when_none(self, mock_input):
|
||||
"""Test that _prompt_input does not display the output panel when agent_output is None."""
|
||||
provider = SyncHumanInputProvider()
|
||||
crew = MagicMock()
|
||||
crew._train = False
|
||||
|
||||
formatter = event_listener.formatter
|
||||
|
||||
with (
|
||||
patch.object(formatter, "pause_live_updates"),
|
||||
patch.object(formatter, "resume_live_updates"),
|
||||
patch.object(formatter.console, "print") as mock_console_print,
|
||||
):
|
||||
provider._prompt_input(crew, agent_output=None)
|
||||
|
||||
call_args = mock_console_print.call_args_list
|
||||
result_panel_found = any(
|
||||
hasattr(call[0][0], "title")
|
||||
and "Agent Final Answer" in str(call[0][0].title)
|
||||
for call in call_args
|
||||
if call[0]
|
||||
)
|
||||
assert not result_panel_found, (
|
||||
"Agent output panel should NOT be displayed when agent_output is None"
|
||||
)
|
||||
|
||||
@patch("builtins.input", return_value="looks good")
|
||||
def test_handle_feedback_passes_output_to_prompt(self, mock_input):
|
||||
"""Test that handle_feedback passes the agent output to _prompt_input.
|
||||
|
||||
This is the core fix for issue #6072: the feedback prompt should always
|
||||
display the result being reviewed, regardless of verbose setting.
|
||||
"""
|
||||
from crewai.agents.parser import AgentFinish
|
||||
|
||||
provider = SyncHumanInputProvider()
|
||||
|
||||
formatted_answer = AgentFinish(
|
||||
thought="I know the answer",
|
||||
output="The sky is blue.",
|
||||
text="Final Answer: The sky is blue.",
|
||||
)
|
||||
|
||||
context = MagicMock()
|
||||
context.crew = MagicMock()
|
||||
context.crew._train = False
|
||||
context._is_training_mode.return_value = False
|
||||
context.ask_for_human_input = False
|
||||
|
||||
formatter = event_listener.formatter
|
||||
|
||||
with (
|
||||
patch.object(formatter, "pause_live_updates"),
|
||||
patch.object(formatter, "resume_live_updates"),
|
||||
patch.object(formatter.console, "print") as mock_console_print,
|
||||
):
|
||||
provider.handle_feedback(formatted_answer, context)
|
||||
|
||||
call_args = mock_console_print.call_args_list
|
||||
result_panel_found = any(
|
||||
hasattr(call[0][0], "title")
|
||||
and "Agent Final Answer" in str(call[0][0].title)
|
||||
for call in call_args
|
||||
if call[0]
|
||||
)
|
||||
assert result_panel_found, (
|
||||
"handle_feedback must display the agent result panel "
|
||||
"so the user can see what they are reviewing (issue #6072)"
|
||||
)
|
||||
|
||||
@patch("builtins.input", return_value="")
|
||||
def test_prompt_input_displays_output_before_feedback_panel(self, mock_input):
|
||||
"""Test that the agent output panel appears before the feedback panel."""
|
||||
provider = SyncHumanInputProvider()
|
||||
crew = MagicMock()
|
||||
crew._train = False
|
||||
|
||||
formatter = event_listener.formatter
|
||||
|
||||
with (
|
||||
patch.object(formatter, "pause_live_updates"),
|
||||
patch.object(formatter, "resume_live_updates"),
|
||||
patch.object(formatter.console, "print") as mock_console_print,
|
||||
):
|
||||
provider._prompt_input(crew, agent_output="Test output")
|
||||
|
||||
call_args = mock_console_print.call_args_list
|
||||
result_panel_idx = None
|
||||
feedback_panel_idx = None
|
||||
for i, call in enumerate(call_args):
|
||||
if call[0] and hasattr(call[0][0], "title"):
|
||||
title = str(call[0][0].title)
|
||||
if "Agent Final Answer" in title:
|
||||
result_panel_idx = i
|
||||
elif "Human Feedback" in title:
|
||||
feedback_panel_idx = i
|
||||
|
||||
assert result_panel_idx is not None, "Agent output panel should be present"
|
||||
assert feedback_panel_idx is not None, "Feedback panel should be present"
|
||||
assert result_panel_idx < feedback_panel_idx, (
|
||||
"Agent output panel should appear before the feedback panel"
|
||||
)
|
||||
Reference in New Issue
Block a user