mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-16 04:18:35 +00:00
fix: preserve original task IDs after replay
Fixes #3999 When replaying a task, the original task ID was being overwritten in storage with the new task ID from the replayed crew instance. This made it impossible to replay the same task again using the original ID. The fix removes the task_id field from the update operation during replay, so the original task ID is preserved in storage. This allows users to replay the same task multiple times using the original ID. Changes: - Modified TaskOutputStorageHandler.update() to not update task_id when was_replayed is True - Added test_replay_preserves_original_task_id_after_replay to verify the fix Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
@@ -42,7 +42,6 @@ class TaskOutputStorageHandler:
|
|||||||
|
|
||||||
if log.get("was_replayed", False):
|
if log.get("was_replayed", False):
|
||||||
replayed = {
|
replayed = {
|
||||||
"task_id": str(log["task"].id),
|
|
||||||
"expected_output": log["task"].expected_output,
|
"expected_output": log["task"].expected_output,
|
||||||
"output": log["output"],
|
"output": log["output"],
|
||||||
"was_replayed": log["was_replayed"],
|
"was_replayed": log["was_replayed"],
|
||||||
|
|||||||
@@ -3511,6 +3511,111 @@ def test_replay_setup_context():
|
|||||||
assert crew.tasks[1].prompt_context == "context raw output"
|
assert crew.tasks[1].prompt_context == "context raw output"
|
||||||
|
|
||||||
|
|
||||||
|
def test_replay_preserves_original_task_id_after_replay():
|
||||||
|
"""Test that original task IDs remain valid after replay.
|
||||||
|
|
||||||
|
This test verifies the fix for issue #3999 where replaying a task would
|
||||||
|
overwrite the original task ID in storage, making it impossible to replay
|
||||||
|
the same task again using the original ID.
|
||||||
|
"""
|
||||||
|
agent = Agent(role="test_agent", backstory="Test Description", goal="Test Goal")
|
||||||
|
task1 = Task(description="First Task", expected_output="First Output", agent=agent)
|
||||||
|
task2 = Task(description="Second Task", expected_output="Second Output", agent=agent)
|
||||||
|
|
||||||
|
# Store the original task IDs
|
||||||
|
original_task1_id = str(task1.id)
|
||||||
|
original_task2_id = str(task2.id)
|
||||||
|
|
||||||
|
task1_output = TaskOutput(
|
||||||
|
description="First Task Output",
|
||||||
|
agent="test_agent",
|
||||||
|
raw="first task raw output",
|
||||||
|
pydantic=None,
|
||||||
|
json_dict={},
|
||||||
|
output_format=OutputFormat.RAW,
|
||||||
|
messages=[],
|
||||||
|
)
|
||||||
|
task2_output = TaskOutput(
|
||||||
|
description="Second Task Output",
|
||||||
|
agent="test_agent",
|
||||||
|
raw="second task raw output",
|
||||||
|
pydantic=None,
|
||||||
|
json_dict={},
|
||||||
|
output_format=OutputFormat.RAW,
|
||||||
|
messages=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
crew = Crew(agents=[agent], tasks=[task1, task2], process=Process.sequential)
|
||||||
|
|
||||||
|
# Track what gets passed to storage.update
|
||||||
|
update_calls = []
|
||||||
|
original_update = None
|
||||||
|
|
||||||
|
def mock_storage_update(task_index, **kwargs):
|
||||||
|
update_calls.append({"task_index": task_index, "kwargs": kwargs})
|
||||||
|
|
||||||
|
with patch.object(Task, "execute_sync") as mock_execute_task:
|
||||||
|
mock_execute_task.return_value = task2_output
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"crewai.utilities.task_output_storage_handler.TaskOutputStorageHandler.load",
|
||||||
|
return_value=[
|
||||||
|
{
|
||||||
|
"task_id": original_task1_id,
|
||||||
|
"output": {
|
||||||
|
"description": task1_output.description,
|
||||||
|
"summary": task1_output.summary,
|
||||||
|
"raw": task1_output.raw,
|
||||||
|
"pydantic": task1_output.pydantic,
|
||||||
|
"json_dict": task1_output.json_dict,
|
||||||
|
"output_format": task1_output.output_format,
|
||||||
|
"agent": task1_output.agent,
|
||||||
|
"messages": [],
|
||||||
|
},
|
||||||
|
"inputs": {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task_id": original_task2_id,
|
||||||
|
"output": {
|
||||||
|
"description": task2_output.description,
|
||||||
|
"summary": task2_output.summary,
|
||||||
|
"raw": task2_output.raw,
|
||||||
|
"pydantic": task2_output.pydantic,
|
||||||
|
"json_dict": task2_output.json_dict,
|
||||||
|
"output_format": task2_output.output_format,
|
||||||
|
"agent": task2_output.agent,
|
||||||
|
"messages": [],
|
||||||
|
},
|
||||||
|
"inputs": {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
):
|
||||||
|
with patch(
|
||||||
|
"crewai.memory.storage.kickoff_task_outputs_storage.KickoffTaskOutputsSQLiteStorage.update"
|
||||||
|
) as mock_update:
|
||||||
|
mock_update.side_effect = mock_storage_update
|
||||||
|
|
||||||
|
# Replay from task2
|
||||||
|
crew.replay(original_task2_id)
|
||||||
|
|
||||||
|
# Verify that the update was called but did NOT include task_id
|
||||||
|
# This is the key assertion - task_id should NOT be updated during replay
|
||||||
|
assert len(update_calls) == 1
|
||||||
|
update_kwargs = update_calls[0]["kwargs"]
|
||||||
|
|
||||||
|
# The fix ensures task_id is NOT included in the update kwargs
|
||||||
|
# so the original task_id is preserved in storage
|
||||||
|
assert "task_id" not in update_kwargs, (
|
||||||
|
"task_id should not be updated during replay to preserve "
|
||||||
|
"the original task ID for future replays"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify other fields are still updated
|
||||||
|
assert "output" in update_kwargs
|
||||||
|
assert "was_replayed" in update_kwargs
|
||||||
|
assert update_kwargs["was_replayed"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_key(researcher, writer):
|
def test_key(researcher, writer):
|
||||||
tasks = [
|
tasks = [
|
||||||
Task(
|
Task(
|
||||||
|
|||||||
Reference in New Issue
Block a user