Flatten timeline so forloops and taskv2 blocks play nice (#3946)

This commit is contained in:
pedrohsdb
2025-11-07 17:32:10 -08:00
committed by GitHub
parent d1d0c9414b
commit ea7361c9f2
3 changed files with 161 additions and 36 deletions

View File

@@ -113,6 +113,10 @@ TASKV2_TO_BLOCK_STATUS: dict[TaskV2Status, BlockStatus] = {
TaskV2Status.timed_out: BlockStatus.timed_out,
}
# ForLoop constants
DEFAULT_MAX_LOOP_ITERATIONS = 100
DEFAULT_MAX_STEPS_PER_ITERATION = 50
class Block(BaseModel, abc.ABC):
# Must be unique within workflow definition
@@ -1263,6 +1267,27 @@ class ForLoopBlock(Block):
current_block: BlockTypeVar | None = None
for loop_idx, loop_over_value in enumerate(loop_over_values):
# Check max_iterations limit
if loop_idx >= DEFAULT_MAX_LOOP_ITERATIONS:
LOG.info(
f"ForLoopBlock: Reached max_iterations limit ({DEFAULT_MAX_LOOP_ITERATIONS}), stopping loop",
workflow_run_id=workflow_run_id,
loop_idx=loop_idx,
max_iterations=DEFAULT_MAX_LOOP_ITERATIONS,
)
failure_block_result = await self.build_block_result(
success=False,
status=BlockStatus.failed,
failure_reason=f"Reached max_loop_iterations limit of {DEFAULT_MAX_LOOP_ITERATIONS}",
workflow_run_block_id=workflow_run_block_id,
organization_id=organization_id,
)
block_outputs.append(failure_block_result)
return LoopBlockExecutedResult(
outputs_with_loop_values=outputs_with_loop_values,
block_outputs=block_outputs,
last_block=current_block,
)
LOG.info("Starting loop iteration", loop_idx=loop_idx, loop_over_value=loop_over_value)
# context parameter has been deprecated. However, it's still used by task v2 - we should migrate away from it.
context_parameters_with_value = self.get_loop_block_context_parameters(workflow_run_id, loop_over_value)
@@ -1270,6 +1295,16 @@ class ForLoopBlock(Block):
workflow_run_context.set_value(context_parameter.key, context_parameter.value)
each_loop_output_values: list[dict[str, Any]] = []
# Track steps for current iteration
iteration_step_count = 0
LOG.info(
f"ForLoopBlock: Starting iteration {loop_idx} with max_steps_per_iteration={DEFAULT_MAX_STEPS_PER_ITERATION}",
workflow_run_id=workflow_run_id,
loop_idx=loop_idx,
max_steps_per_iteration=DEFAULT_MAX_STEPS_PER_ITERATION,
)
for block_idx, loop_block in enumerate(self.loop_blocks):
metadata: BlockMetadata = {
"current_index": loop_idx,
@@ -1327,6 +1362,37 @@ class ForLoopBlock(Block):
)
loop_block = original_loop_block
block_outputs.append(block_output)
# Check max_steps_per_iteration limit after each block execution
iteration_step_count += 1 # Count each block execution as a step
if iteration_step_count >= DEFAULT_MAX_STEPS_PER_ITERATION:
LOG.info(
f"ForLoopBlock: Reached max_steps_per_iteration limit ({DEFAULT_MAX_STEPS_PER_ITERATION}) in iteration {loop_idx}, stopping iteration",
workflow_run_id=workflow_run_id,
loop_idx=loop_idx,
max_steps_per_iteration=DEFAULT_MAX_STEPS_PER_ITERATION,
iteration_step_count=iteration_step_count,
)
# Create a failure block result for this iteration
failure_block_result = await self.build_block_result(
success=False,
status=BlockStatus.failed,
failure_reason=f"Reached max_steps_per_iteration limit of {DEFAULT_MAX_STEPS_PER_ITERATION}",
workflow_run_block_id=workflow_run_block_id,
organization_id=organization_id,
)
block_outputs.append(failure_block_result)
# If continue_on_failure is False, stop the entire loop
if not self.continue_on_failure:
outputs_with_loop_values.append(each_loop_output_values)
return LoopBlockExecutedResult(
outputs_with_loop_values=outputs_with_loop_values,
block_outputs=block_outputs,
last_block=current_block,
)
# If continue_on_failure is True, break out of the block loop for this iteration
break
if block_output.status == BlockStatus.canceled:
LOG.info(
f"ForLoopBlock: Block with type {loop_block.block_type} at index {block_idx} during loop {loop_idx} was canceled for workflow run {workflow_run_id}, canceling for loop",
@@ -3431,8 +3497,17 @@ class TaskV2Block(Block):
from skyvern.services import task_v2_service # noqa: PLC0415
workflow_run_context = self.get_workflow_run_context(workflow_run_id)
# Simple template resolution - no complex dynamic resolution to prevent recursion
try:
self.format_potential_template_parameters(workflow_run_context)
# Use the resolved values directly
resolved_prompt = self.prompt
resolved_url = self.url
resolved_totp_identifier = self.totp_identifier
resolved_totp_verification_url = self.totp_verification_url
except Exception as e:
output_reason = f"Failed to format jinja template: {str(e)}"
await self.record_output_parameter_value(
@@ -3447,14 +3522,14 @@ class TaskV2Block(Block):
organization_id=organization_id,
)
if not self.url:
if not resolved_url:
browser_state = app.BROWSER_MANAGER.get_for_workflow_run(workflow_run_id)
if browser_state:
page = await browser_state.get_working_page()
if page:
current_url = await SkyvernFrame.get_url(frame=page)
if current_url != "about:blank":
self.url = current_url
resolved_url = current_url
if not organization_id:
raise ValueError("Running TaskV2Block requires organization_id")
@@ -3468,12 +3543,12 @@ class TaskV2Block(Block):
try:
task_v2 = await task_v2_service.initialize_task_v2(
organization=organization,
user_prompt=self.prompt,
user_url=self.url,
user_prompt=resolved_prompt,
user_url=resolved_url,
parent_workflow_run_id=workflow_run_id,
proxy_location=workflow_run.proxy_location,
totp_identifier=self.totp_identifier,
totp_verification_url=self.totp_verification_url,
totp_identifier=resolved_totp_identifier,
totp_verification_url=resolved_totp_verification_url,
max_screenshot_scrolling_times=workflow_run.max_screenshot_scrolls,
)
await app.DATABASE.update_task_v2(