agentscope-chat / src /agentscope /tool /_task /_update_task.py
Akshay66777's picture
AgentScope Gradio chat app — ZeroGPU-ready
9792ea7 verified
Raw
History Blame Contribute Delete
9.57 kB
# -*- coding: utf-8 -*-
"""The task updated tool class."""
from typing import Literal
from pydantic import BaseModel, Field
from ._task_tool_base import _TaskToolBase
from .._response import ToolChunk
from ...state import AgentState
from ...exception import DeveloperOrientedException
from ...message import TextBlock, ToolResultState
class _TaskUpdateParams(BaseModel):
"""The params of the update task."""
task_id: str = Field(description="The task id.")
subject: str | None = Field(
default=None,
description="New subject for the task",
)
description: str | None = Field(
default=None,
description="New description for the task",
)
add_blocks: list[str] | None = Field(
default=None,
description="Task IDs that this task blocks",
)
status: Literal[
"pending",
"in_progress",
"completed",
"deleted",
] | None = Field(
default=None,
description="New status for the task",
)
add_blocked_by: list[str] | None = Field(
default=None,
description="Task IDs that block this task",
)
owner: str | None = Field(
default=None,
description="New owner for the task",
)
metadata: dict | None = Field(
default=None,
description="Metadata keys to merge into the task. "
"Set a key to null to delete it.",
)
class TaskUpdate(_TaskToolBase):
"""The tool to update the agent task."""
name: str = "TaskUpdate"
description: str = """Use this tool to update a task in the task list.
## When to Use This Tool
**Mark tasks as resolved:**
- When you have completed the work described in a task
- When a task is no longer needed or has been superseded
- IMPORTANT: Always mark your assigned tasks as resolved when you finish them
- After resolving, call TaskList to find your next task
- ONLY mark a task as completed when you have FULLY accomplished it
- If you encounter errors, blockers, or cannot finish, keep the task as in_progress
- When blocked, create a new task describing what needs to be resolved
- Never mark a task as completed if:
- Tests are failing
- Implementation is partial
- You encountered unresolved errors
- You couldn't find necessary files or dependencies
**Delete tasks:**
- When a task is no longer relevant or was created in error
- Setting status to `deleted` permanently removes the task
**Update task details:**
- When requirements change or become clearer
- When establishing dependencies between tasks
## Fields You Can Update
- **status**: The task status (see Status Workflow below)
- **subject**: Change the task title (imperative form, e.g., "Run tests")
- **description**: Change the task description
- **owner**: Change the task owner (agent name)
- **metadata**: Merge metadata keys into the task (set a key to null to delete it)
- **add_blocks**: Mark tasks that cannot start until this one completes
- **add_blocked_by**: Mark tasks that must complete before this one can start
## Status Workflow
Status progresses: `pending` → `in_progress` → `completed`
Use `deleted` to permanently remove a task.
## Staleness
Make sure to read a task's latest state using `TaskGet` before updating it.
## Examples
Mark task as in progress when starting work:
```json
{"task_id": "1", "status": "in_progress"}
```
Mark task as completed after finishing work:
```json
{"task_id": "1", "status": "completed"}
```
Delete a task:
```json
{"task_id": "1", "status": "deleted"}
```
Claim a task by setting owner:
```json
{"task_id": "1", "owner": "my-name"}
```
Set up task dependencies:
```json
{"task_id": "2", "add_blocked_by": ["1"]}
```""" # noqa: E501
input_schema: dict = _TaskUpdateParams.model_json_schema()
async def call(
self,
_agent_state: AgentState,
task_id: str,
subject: str | None = None,
description: str | None = None,
add_blocks: list[str] | None = None,
status: Literal["pending", "completed", "in_progress", "deleted"]
| None = None,
add_blocked_by: list[str] | None = None,
owner: str | None = None,
metadata: dict | None = None,
) -> ToolChunk:
"""Update the agent task."""
if not isinstance(_agent_state, AgentState):
# Expose error to the developer
raise DeveloperOrientedException(
f"Error: {self.name} requires AgentState to be provided, got "
f"{_agent_state} instead.",
)
index = None
for i, task in enumerate(_agent_state.tasks_context.tasks):
if task.id == task_id:
index = i
if index is None:
return ToolChunk(
content=[
TextBlock(
text=f"TaskNotFoundError: "
f"The task (id={task_id}) does not exist.",
),
],
state=ToolResultState.ERROR,
)
updated_fields = []
if subject:
updated_fields.append("subject")
_agent_state.tasks_context.tasks[index].subject = subject
if description is not None:
updated_fields.append("description")
_agent_state.tasks_context.tasks[index].description = description
existed_ids = [_.id for _ in _agent_state.tasks_context.tasks]
if add_blocks:
current_blocks = _agent_state.tasks_context.tasks[index].blocks
new_blocks = [
_
for _ in add_blocks
if _ not in current_blocks and _ in existed_ids
]
if new_blocks:
updated_fields.append("add_blocks")
for block_id in new_blocks:
self._update_block_relation(
task_id,
block_id,
_agent_state,
)
if add_blocked_by is not None:
current_blocked_by = _agent_state.tasks_context.tasks[
index
].blocked_by
new_blocked_by = [
_
for _ in add_blocked_by
if _ not in current_blocked_by and _ in existed_ids
]
if new_blocked_by:
updated_fields.append("add_blocked_by")
for blocked_by_id in new_blocked_by:
self._update_block_relation(
blocked_by_id,
task_id,
_agent_state,
)
if status:
if status == "deleted":
# Permanently remove the task
_agent_state.tasks_context.tasks.pop(index)
# Remove task id from all the blocks and blocked_by
for task in _agent_state.tasks_context.tasks:
if task_id in task.blocks:
task.blocks.remove(task_id)
if task_id in task.blocked_by:
task.blocked_by.remove(task_id)
return ToolChunk(
content=[
TextBlock(
text=f"Task (id={task_id}) has been deleted.",
),
],
)
# Update the status
updated_fields.append("status")
_agent_state.tasks_context.tasks[index].state = status
if owner is not None:
updated_fields.append("owner")
_agent_state.tasks_context.tasks[index].owner = owner
if metadata:
updated_fields.append("metadata")
for k, v in metadata.items():
if v is None:
_agent_state.tasks_context.tasks[index].metadata.pop(
k,
None,
)
else:
_agent_state.tasks_context.tasks[index].metadata[k] = v
if updated_fields:
res = f'Update task (id={task_id}) {", ".join(updated_fields)}.'
else:
res = (
f"No updates were made to the task (id={task_id}). "
f"Make sure you provided at least one field to update and "
f"the values are correct."
)
if _agent_state.tasks_context.tasks[index].state == "completed":
res += (
"\n\nTask completed. Call TaskList now to find your next "
"available task or see if your work unblocked others."
)
return ToolChunk(content=[TextBlock(text=res)])
@staticmethod
def _update_block_relation(
block_id: str,
blocked_by_id: str,
_agent_state: AgentState,
) -> None:
"""Update the block relationship between the tasks.
Args:
block_id (`str`):
The id of the task that blocks the other tasks.
blocked_by_id (`str`):
The id of the task blocked by the task of `block_id`.
_agent_state (`AgentState`):
The agent state to update.
"""
# Update the blocks
for task in _agent_state.tasks_context.tasks:
if task.id == block_id and blocked_by_id not in task.blocks:
task.blocks.append(blocked_by_id)
if task.id == blocked_by_id and block_id not in task.blocked_by:
task.blocked_by.append(block_id)