add workflow_run.script_run and mark ai_fallback_triggered when the script falls back to ai run (#3433)

This commit is contained in:
Shuchang Zheng
2025-09-14 22:53:52 -07:00
committed by GitHub
parent 975376698d
commit 720ebac72c
9 changed files with 74 additions and 2 deletions

View File

@@ -0,0 +1,31 @@
"""db migration script
Revision ID: 8998d998feed
Revises: f78486c3f895
Create Date: 2025-09-15 05:40:16.200764+00:00
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "8998d998feed"
down_revision: Union[str, None] = "f78486c3f895"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("workflow_runs", sa.Column("script_run", sa.JSON(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("workflow_runs", "script_run")
# ### end Alembic commands ###

View File

@@ -30,6 +30,14 @@ The webhook request body is a JSON object with the following fields:
"app_url": "The URL to the run in the Skyvern app", "app_url": "The URL to the run in the Skyvern app",
"created_at": "The timestamp when the run was created", "created_at": "The timestamp when the run was created",
"modified_at": "The timestamp when the run was last modified", "modified_at": "The timestamp when the run was last modified",
"queued_at": "The timestamp when the run was queued",
"started_at": "The timestamp when the run started",
"finished_at": "The timestamp when the run finished",
"app_url": "The URL to the run in the Skyvern app",
"browser_session_id": "The ID of the browser session",
"max_screenshot_scrolls": "The maximum number of scrolls for the post action screenshot. When it's None or 0, it takes the current viewpoint screenshot",
"run_request": "The original request parameters used to start this task or workflow run",
"script_run": "The script run result containing information like whether AI fallback is triggered when the script fails",
} }
``` ```
For detailed schema, please refer to the [Run Response](/api-reference/api-reference/agent/get-run#response). For detailed schema, please refer to the [Run Response](/api-reference/api-reference/agent/get-run#response).

View File

@@ -1672,6 +1672,7 @@ class AgentDB:
status: WorkflowRunStatus | None = None, status: WorkflowRunStatus | None = None,
failure_reason: str | None = None, failure_reason: str | None = None,
webhook_failure_reason: str | None = None, webhook_failure_reason: str | None = None,
ai_fallback_triggered: bool | None = None,
) -> WorkflowRun: ) -> WorkflowRun:
async with self.Session() as session: async with self.Session() as session:
workflow_run = ( workflow_run = (
@@ -1690,6 +1691,8 @@ class AgentDB:
workflow_run.failure_reason = failure_reason workflow_run.failure_reason = failure_reason
if webhook_failure_reason is not None: if webhook_failure_reason is not None:
workflow_run.webhook_failure_reason = webhook_failure_reason workflow_run.webhook_failure_reason = webhook_failure_reason
if ai_fallback_triggered is not None:
workflow_run.script_run = {"ai_fallback_triggered": ai_fallback_triggered}
await session.commit() await session.commit()
await session.refresh(workflow_run) await session.refresh(workflow_run)
await save_workflow_run_logs(workflow_run_id) await save_workflow_run_logs(workflow_run_id)
@@ -2936,6 +2939,7 @@ class AgentDB:
http_request_parameters: dict[str, Any] | None = None, http_request_parameters: dict[str, Any] | None = None,
http_request_timeout: int | None = None, http_request_timeout: int | None = None,
http_request_follow_redirects: bool | None = None, http_request_follow_redirects: bool | None = None,
ai_fallback_triggered: bool | None = None,
) -> WorkflowRunBlock: ) -> WorkflowRunBlock:
async with self.Session() as session: async with self.Session() as session:
workflow_run_block = ( workflow_run_block = (
@@ -2993,6 +2997,8 @@ class AgentDB:
workflow_run_block.http_request_timeout = http_request_timeout workflow_run_block.http_request_timeout = http_request_timeout
if http_request_follow_redirects is not None: if http_request_follow_redirects is not None:
workflow_run_block.http_request_follow_redirects = http_request_follow_redirects workflow_run_block.http_request_follow_redirects = http_request_follow_redirects
if ai_fallback_triggered is not None:
workflow_run_block.script_run = {"ai_fallback_triggered": ai_fallback_triggered}
await session.commit() await session.commit()
await session.refresh(workflow_run_block) await session.refresh(workflow_run_block)
else: else:

View File

@@ -283,6 +283,7 @@ class WorkflowRunModel(Base):
max_screenshot_scrolling_times = Column(Integer, nullable=True) max_screenshot_scrolling_times = Column(Integer, nullable=True)
extra_http_headers = Column(JSON, nullable=True) extra_http_headers = Column(JSON, nullable=True)
browser_address = Column(String, nullable=True) browser_address = Column(String, nullable=True)
script_run = Column(JSON, nullable=True)
queued_at = Column(DateTime, nullable=True) queued_at = Column(DateTime, nullable=True)
started_at = Column(DateTime, nullable=True) started_at = Column(DateTime, nullable=True)

View File

@@ -50,7 +50,7 @@ from skyvern.forge.sdk.workflow.models.workflow import (
WorkflowRunStatus, WorkflowRunStatus,
WorkflowStatus, WorkflowStatus,
) )
from skyvern.schemas.runs import ProxyLocation from skyvern.schemas.runs import ProxyLocation, ScriptRunResponse
from skyvern.schemas.scripts import Script, ScriptBlock, ScriptFile from skyvern.schemas.scripts import Script, ScriptBlock, ScriptFile
from skyvern.schemas.workflows import BlockStatus, BlockType from skyvern.schemas.workflows import BlockStatus, BlockType
from skyvern.webeye.actions.actions import ( from skyvern.webeye.actions.actions import (
@@ -303,6 +303,7 @@ def convert_to_workflow_run(
max_screenshot_scrolls=workflow_run_model.max_screenshot_scrolling_times, max_screenshot_scrolls=workflow_run_model.max_screenshot_scrolling_times,
extra_http_headers=workflow_run_model.extra_http_headers, extra_http_headers=workflow_run_model.extra_http_headers,
browser_address=workflow_run_model.browser_address, browser_address=workflow_run_model.browser_address,
script_run=ScriptRunResponse.model_validate(workflow_run_model.script_run),
) )

View File

@@ -10,7 +10,7 @@ from skyvern.forge.sdk.schemas.task_v2 import TaskV2
from skyvern.forge.sdk.workflow.exceptions import WorkflowDefinitionHasDuplicateBlockLabels from skyvern.forge.sdk.workflow.exceptions import WorkflowDefinitionHasDuplicateBlockLabels
from skyvern.forge.sdk.workflow.models.block import BlockTypeVar from skyvern.forge.sdk.workflow.models.block import BlockTypeVar
from skyvern.forge.sdk.workflow.models.parameter import PARAMETER_TYPE, OutputParameter from skyvern.forge.sdk.workflow.models.parameter import PARAMETER_TYPE, OutputParameter
from skyvern.schemas.runs import ProxyLocation from skyvern.schemas.runs import ProxyLocation, ScriptRunResponse
from skyvern.schemas.workflows import WorkflowStatus from skyvern.schemas.workflows import WorkflowStatus
from skyvern.utils.url_validators import validate_url from skyvern.utils.url_validators import validate_url
@@ -135,6 +135,7 @@ class WorkflowRun(BaseModel):
workflow_title: str | None = None workflow_title: str | None = None
max_screenshot_scrolls: int | None = None max_screenshot_scrolls: int | None = None
browser_address: str | None = None browser_address: str | None = None
script_run: ScriptRunResponse | None = None
queued_at: datetime | None = None queued_at: datetime | None = None
started_at: datetime | None = None started_at: datetime | None = None
@@ -186,3 +187,4 @@ class WorkflowRunResponseBase(BaseModel):
browser_session_id: str | None = None browser_session_id: str | None = None
max_screenshot_scrolls: int | None = None max_screenshot_scrolls: int | None = None
browser_address: str | None = None browser_address: str | None = None
script_run: ScriptRunResponse | None = None

View File

@@ -1445,6 +1445,7 @@ class WorkflowService:
max_screenshot_scrolls=workflow_run.max_screenshot_scrolls, max_screenshot_scrolls=workflow_run.max_screenshot_scrolls,
task_v2=task_v2, task_v2=task_v2,
browser_address=workflow_run.browser_address, browser_address=workflow_run.browser_address,
script_run=workflow_run.script_run,
) )
async def clean_up_workflow( async def clean_up_workflow(
@@ -1554,6 +1555,7 @@ class WorkflowService:
screenshot_urls=workflow_run_status_response.screenshot_urls, screenshot_urls=workflow_run_status_response.screenshot_urls,
failure_reason=workflow_run_status_response.failure_reason, failure_reason=workflow_run_status_response.failure_reason,
app_url=app_url, app_url=app_url,
script_run=workflow_run_status_response.script_run,
created_at=workflow_run_status_response.created_at, created_at=workflow_run_status_response.created_at,
modified_at=workflow_run_status_response.modified_at, modified_at=workflow_run_status_response.modified_at,
run_request=WorkflowRunRequest( run_request=WorkflowRunRequest(

View File

@@ -380,6 +380,10 @@ class BlockRunRequest(WorkflowRunRequest):
) )
class ScriptRunResponse(BaseModel):
ai_fallback_triggered: bool = False
class BaseRunResponse(BaseModel): class BaseRunResponse(BaseModel):
run_id: str = Field( run_id: str = Field(
description="Unique identifier for this run. Run ID starts with `tsk_` for task runs and `wr_` for workflow runs.", description="Unique identifier for this run. Run ID starts with `tsk_` for task runs and `wr_` for workflow runs.",
@@ -419,6 +423,10 @@ class BaseRunResponse(BaseModel):
default=None, default=None,
description="The maximum number of scrolls for the post action screenshot. When it's None or 0, it takes the current viewpoint screenshot", description="The maximum number of scrolls for the post action screenshot. When it's None or 0, it takes the current viewpoint screenshot",
) )
script_run: ScriptRunResponse | None = Field(
default=None,
description="The script run result",
)
class TaskRunResponse(BaseRunResponse): class TaskRunResponse(BaseRunResponse):

View File

@@ -428,6 +428,7 @@ async def _update_workflow_block(
label: str | None = None, label: str | None = None,
failure_reason: str | None = None, failure_reason: str | None = None,
output: dict[str, Any] | list | str | None = None, output: dict[str, Any] | list | str | None = None,
ai_fallback_triggered: bool = False,
) -> None: ) -> None:
"""Update the status of a workflow run block.""" """Update the status of a workflow run block."""
try: try:
@@ -632,6 +633,13 @@ async def _fallback_to_ai_run(
task_block=task_block, task_block=task_block,
) )
# update workflow run to indicate that there's a script run
if workflow_run_id:
await app.DATABASE.update_workflow_run(
workflow_run_id=workflow_run_id,
ai_fallback_triggered=True,
)
# Update block status to completed if workflow block was created # Update block status to completed if workflow block was created
if workflow_run_block_id: if workflow_run_block_id:
await _update_workflow_block( await _update_workflow_block(
@@ -1327,6 +1335,11 @@ async def run_script(
) )
if not workflow_run: if not workflow_run:
raise WorkflowRunNotFound(workflow_run_id=workflow_run_id) raise WorkflowRunNotFound(workflow_run_id=workflow_run_id)
# update workfow run to indicate that there's a script run
workflow_run = await app.DATABASE.update_workflow_run(
workflow_run_id=workflow_run_id,
ai_fallback_triggered=False,
)
context.workflow_run_id = workflow_run_id context.workflow_run_id = workflow_run_id
context.organization_id = organization_id context.organization_id = organization_id