mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Remove task completion status message from finish action display (#9977)
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
This commit is contained in:
parent
6f44b7352e
commit
c2fc84e6ea
@ -28,7 +28,6 @@ describe("EventMessage", () => {
|
||||
action: "finish" as const,
|
||||
args: {
|
||||
final_thought: "Task completed successfully",
|
||||
task_completed: "success" as const,
|
||||
outputs: {},
|
||||
thought: "Task completed successfully",
|
||||
},
|
||||
@ -114,7 +113,6 @@ describe("EventMessage", () => {
|
||||
action: "finish" as const,
|
||||
args: {
|
||||
final_thought: "Task completed successfully",
|
||||
task_completed: "success" as const,
|
||||
outputs: {},
|
||||
thought: "Task completed successfully",
|
||||
},
|
||||
|
||||
@ -77,25 +77,8 @@ const getMcpActionContent = (event: MCPAction): string => {
|
||||
const getThinkActionContent = (event: ThinkAction): string =>
|
||||
event.args.thought;
|
||||
|
||||
const getFinishActionContent = (event: FinishAction): string => {
|
||||
let content = event.args.final_thought;
|
||||
|
||||
switch (event.args.task_completed) {
|
||||
case "success":
|
||||
content += `\n\n\n${i18n.t("FINISH$TASK_COMPLETED_SUCCESSFULLY")}`;
|
||||
break;
|
||||
case "failure":
|
||||
content += `\n\n\n${i18n.t("FINISH$TASK_NOT_COMPLETED")}`;
|
||||
break;
|
||||
case "partial":
|
||||
default:
|
||||
content += `\n\n\n${i18n.t("FINISH$TASK_COMPLETED_PARTIALLY")}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return content.trim();
|
||||
};
|
||||
|
||||
const getFinishActionContent = (event: FinishAction): string =>
|
||||
event.args.final_thought.trim();
|
||||
const getNoContentActionContent = (): string => "";
|
||||
|
||||
export const getActionContent = (event: OpenHandsAction): string => {
|
||||
|
||||
@ -64,7 +64,6 @@ export interface FinishAction extends OpenHandsActionEvent<"finish"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
final_thought: string;
|
||||
task_completed: "success" | "failure" | "partial";
|
||||
outputs: Record<string, unknown>;
|
||||
thought: string;
|
||||
};
|
||||
|
||||
@ -123,7 +123,6 @@ def response_to_actions(
|
||||
elif tool_call.function.name == FinishTool['function']['name']:
|
||||
action = AgentFinishAction(
|
||||
final_thought=arguments.get('message', ''),
|
||||
task_completed=arguments.get('task_completed', None),
|
||||
)
|
||||
|
||||
# ================================================
|
||||
|
||||
@ -13,8 +13,6 @@ The message should include:
|
||||
- Any next steps for the user
|
||||
- Explanation if you're unable to complete the task
|
||||
- Any follow-up questions if more information is needed
|
||||
|
||||
The task_completed field should be set to True if you believed you have completed the task, and False otherwise.
|
||||
"""
|
||||
|
||||
FinishTool = ChatCompletionToolParam(
|
||||
@ -24,17 +22,12 @@ FinishTool = ChatCompletionToolParam(
|
||||
description=_FINISH_DESCRIPTION,
|
||||
parameters={
|
||||
'type': 'object',
|
||||
'required': ['message', 'task_completed'],
|
||||
'required': ['message'],
|
||||
'properties': {
|
||||
'message': {
|
||||
'type': 'string',
|
||||
'description': 'Final message to send to the user',
|
||||
},
|
||||
'task_completed': {
|
||||
'type': 'string',
|
||||
'enum': ['true', 'false', 'partial'],
|
||||
'description': 'Whether you have completed the task.',
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
@ -81,7 +81,6 @@ def response_to_actions(
|
||||
elif tool_call.function.name == FinishTool['function']['name']:
|
||||
action = AgentFinishAction(
|
||||
final_thought=arguments.get('message', ''),
|
||||
task_completed=arguments.get('task_completed', None),
|
||||
)
|
||||
else:
|
||||
raise FunctionCallNotExistsError(
|
||||
|
||||
@ -141,7 +141,6 @@ def response_to_actions(
|
||||
if tool_call.function.name == FinishTool['function']['name']:
|
||||
action = AgentFinishAction(
|
||||
final_thought=arguments.get('message', ''),
|
||||
task_completed=arguments.get('task_completed', None),
|
||||
)
|
||||
|
||||
# ================================================
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from openhands.core.schema import ActionType
|
||||
@ -20,26 +19,18 @@ class ChangeAgentStateAction(Action):
|
||||
return f'Agent state changed to {self.agent_state}'
|
||||
|
||||
|
||||
class AgentFinishTaskCompleted(Enum):
|
||||
FALSE = 'false'
|
||||
PARTIAL = 'partial'
|
||||
TRUE = 'true'
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentFinishAction(Action):
|
||||
"""An action where the agent finishes the task.
|
||||
|
||||
Attributes:
|
||||
final_thought (str): The message to send to the user.
|
||||
task_completed (enum): Whether the agent believes the task has been completed.
|
||||
outputs (dict): The other outputs of the agent, for instance "content".
|
||||
thought (str): The agent's explanation of its actions.
|
||||
action (str): The action type, namely ActionType.FINISH.
|
||||
"""
|
||||
|
||||
final_thought: str = ''
|
||||
task_completed: AgentFinishTaskCompleted | None = None
|
||||
outputs: dict[str, Any] = field(default_factory=dict)
|
||||
thought: str = ''
|
||||
action: str = ActionType.FINISH
|
||||
|
||||
@ -56,6 +56,10 @@ def handle_action_deprecated_args(args: dict[str, Any]) -> dict[str, Any]:
|
||||
if 'keep_prompt' in args:
|
||||
args.pop('keep_prompt')
|
||||
|
||||
# task_completed has been deprecated - remove it from args to maintain backward compatibility
|
||||
if 'task_completed' in args:
|
||||
args.pop('task_completed')
|
||||
|
||||
# Handle translated_ipython_code deprecation
|
||||
if 'translated_ipython_code' in args:
|
||||
code = args.pop('translated_ipython_code')
|
||||
|
||||
@ -121,6 +121,9 @@ def event_to_dict(event: 'Event') -> dict:
|
||||
props.pop(key, None)
|
||||
if 'security_risk' in props and props['security_risk'] is None:
|
||||
props.pop('security_risk')
|
||||
# Remove task_completed from serialization when it's None (backward compatibility)
|
||||
if 'task_completed' in props and props['task_completed'] is None:
|
||||
props.pop('task_completed')
|
||||
if 'action' in d:
|
||||
d['args'] = props
|
||||
if event.timeout is not None:
|
||||
|
||||
@ -306,12 +306,11 @@ Review the changes and make sure they are as expected. Edit the file again if ne
|
||||
""",
|
||||
},
|
||||
'finish': {
|
||||
'task_completed': """
|
||||
'example': """
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Let me know if you have any further requests!
|
||||
<function=finish>
|
||||
<parameter=message>The task has been completed. The web server is running and displaying numbers 1-10 in a table format at http://127.0.0.1:5000.</parameter>
|
||||
<parameter=task_completed>true</parameter>
|
||||
</function>
|
||||
"""
|
||||
},
|
||||
@ -373,7 +372,7 @@ USER: Create a list of numbers from 1 to 10, and display them in a web page at p
|
||||
example += TOOL_EXAMPLES['execute_bash']['run_server_again']
|
||||
|
||||
if 'finish' in available_tools:
|
||||
example += TOOL_EXAMPLES['finish']['task_completed']
|
||||
example += TOOL_EXAMPLES['finish']['example']
|
||||
|
||||
example += """
|
||||
--------------------- END OF EXAMPLE ---------------------
|
||||
|
||||
@ -75,13 +75,36 @@ def test_agent_finish_action_serialization_deserialization():
|
||||
'args': {
|
||||
'outputs': {},
|
||||
'thought': '',
|
||||
'task_completed': None,
|
||||
'final_thought': '',
|
||||
},
|
||||
}
|
||||
serialization_deserialization(original_action_dict, AgentFinishAction)
|
||||
|
||||
|
||||
def test_agent_finish_action_legacy_task_completed_serialization():
|
||||
"""Test that old conversations with task_completed can still be loaded."""
|
||||
original_action_dict = {
|
||||
'action': 'finish',
|
||||
'args': {
|
||||
'outputs': {},
|
||||
'thought': '',
|
||||
'final_thought': 'Task completed',
|
||||
'task_completed': 'true', # This should be ignored during deserialization
|
||||
},
|
||||
}
|
||||
# This should work without errors - task_completed should be stripped out
|
||||
event = event_from_dict(original_action_dict)
|
||||
assert isinstance(event, Action)
|
||||
assert isinstance(event, AgentFinishAction)
|
||||
assert event.final_thought == 'Task completed'
|
||||
# task_completed attribute should not exist anymore
|
||||
assert not hasattr(event, 'task_completed')
|
||||
|
||||
# When serialized back, task_completed should not be present
|
||||
event_dict = event_to_dict(event)
|
||||
assert 'task_completed' not in event_dict['args']
|
||||
|
||||
|
||||
def test_agent_reject_action_serialization_deserialization():
|
||||
original_action_dict = {
|
||||
'action': 'reject',
|
||||
|
||||
@ -183,7 +183,7 @@ def test_get_example_for_tools_single_tool():
|
||||
assert TOOL_EXAMPLES['execute_bash']['kill_server'] in example
|
||||
assert TOOL_EXAMPLES['str_replace_editor']['create_file'] not in example
|
||||
assert TOOL_EXAMPLES['browser']['view_page'] not in example
|
||||
assert TOOL_EXAMPLES['finish']['task_completed'] not in example
|
||||
assert TOOL_EXAMPLES['finish']['example'] not in example
|
||||
|
||||
|
||||
def test_get_example_for_tools_single_tool_is_finish():
|
||||
@ -205,7 +205,7 @@ def test_get_example_for_tools_single_tool_is_finish():
|
||||
'USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000.'
|
||||
in example
|
||||
)
|
||||
assert TOOL_EXAMPLES['finish']['task_completed'] in example
|
||||
assert TOOL_EXAMPLES['finish']['example'] in example
|
||||
assert TOOL_EXAMPLES['execute_bash']['check_dir'] not in example
|
||||
assert TOOL_EXAMPLES['str_replace_editor']['create_file'] not in example
|
||||
assert TOOL_EXAMPLES['browser']['view_page'] not in example
|
||||
@ -274,7 +274,7 @@ def test_get_example_for_tools_multiple_tools():
|
||||
assert TOOL_EXAMPLES['str_replace_editor']['create_file'] in example
|
||||
assert TOOL_EXAMPLES['str_replace_editor']['edit_file'] in example
|
||||
assert TOOL_EXAMPLES['browser']['view_page'] not in example
|
||||
assert TOOL_EXAMPLES['finish']['task_completed'] not in example
|
||||
assert TOOL_EXAMPLES['finish']['example'] not in example
|
||||
|
||||
|
||||
def test_get_example_for_tools_multiple_tools_with_finish():
|
||||
@ -374,7 +374,7 @@ def test_get_example_for_tools_multiple_tools_with_finish():
|
||||
assert TOOL_EXAMPLES['browser']['view_page'] in example
|
||||
|
||||
# Check for finish part
|
||||
assert TOOL_EXAMPLES['finish']['task_completed'] in example
|
||||
assert TOOL_EXAMPLES['finish']['example'] in example
|
||||
|
||||
|
||||
def test_get_example_for_tools_all_tools():
|
||||
@ -393,7 +393,7 @@ def test_get_example_for_tools_all_tools():
|
||||
assert TOOL_EXAMPLES['execute_bash']['kill_server'] in example
|
||||
assert TOOL_EXAMPLES['str_replace_editor']['create_file'] in example
|
||||
assert TOOL_EXAMPLES['str_replace_editor']['edit_file'] in example
|
||||
assert TOOL_EXAMPLES['finish']['task_completed'] in example
|
||||
assert TOOL_EXAMPLES['finish']['example'] in example
|
||||
|
||||
# These are not in global FNCALL_TOOLS
|
||||
# assert TOOL_EXAMPLES['web_read']['read_docs'] not in example # web_read is removed
|
||||
|
||||
@ -349,7 +349,6 @@ async def test_unsafe_bash_command(temp_dir: str):
|
||||
name=ActionType.FINISH,
|
||||
arguments={
|
||||
'outputs': {'content': 'outputs content'},
|
||||
'task_completed': None,
|
||||
'final_thought': '',
|
||||
},
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user