run script with workflow block run, task, step and artifacts built (#3234)

This commit is contained in:
Shuchang Zheng
2025-08-19 13:32:39 -07:00
committed by GitHub
parent edc9ad726a
commit ae4fbe638e
7 changed files with 562 additions and 25 deletions

View File

@@ -777,7 +777,10 @@ def _build_goto_statement(block: dict[str, Any]) -> cst.SimpleStatementLine:
def _build_run_fn(blocks: list[dict[str, Any]], wf_req: dict[str, Any]) -> FunctionDef:
body = [
cst.parse_statement("page, context = await skyvern.setup(parameters.model_dump())"),
cst.parse_statement(
"parameters = parameters.model_dump() if isinstance(parameters, WorkflowParameters) else parameters"
),
cst.parse_statement("page, context = await skyvern.setup(parameters)"),
]
for block in blocks:

View File

@@ -13,9 +13,11 @@ from skyvern.config import settings
from skyvern.forge import app
from skyvern.forge.prompts import prompt_engine
from skyvern.forge.sdk.api.files import download_file
from skyvern.forge.sdk.artifact.models import ArtifactType
from skyvern.forge.sdk.core import skyvern_context
from skyvern.webeye.actions import handler_utils
from skyvern.webeye.actions.action_types import ActionType
from skyvern.webeye.actions.actions import Action
from skyvern.webeye.scraper.scraper import ScrapedPage, scrape_website
@@ -85,9 +87,8 @@ class SkyvernPage:
"""
Decorator to record the action call.
TODOs:
- generate action record in db pre action
- generate screenshot post action
Auto-creates action records in DB before action execution
and screenshot artifacts after action execution.
"""
def decorator(fn: Callable) -> Callable:
@@ -100,18 +101,33 @@ class SkyvernPage:
) -> Any:
meta = ActionMetadata(intention, data)
call = ActionCall(action, args, kwargs, meta)
# Auto-create action before execution
action_record = await skyvern_page._create_action_before_execution(
action_type=action, intention=intention, data=data
)
try:
call.result = await fn(
skyvern_page, *args, intention=intention, data=data, **kwargs
) # real driver call
# Note: Action status would be updated to completed here if update method existed
return call.result
except Exception as e:
call.error = e
# Note: Action status would be updated to failed here if update method existed
# LLM fallback hook could go here ...
raise
finally:
skyvern_page._record(call)
# Auto-create screenshot artifact after execution
await skyvern_page._create_screenshot_after_execution(action_record)
return wrapper
return decorator
@@ -119,6 +135,73 @@ class SkyvernPage:
async def goto(self, url: str) -> None:
await self.page.goto(url)
async def _create_action_before_execution(
self, action_type: ActionType, intention: str = "", data: str | dict[str, Any] = ""
) -> Action | None:
"""Create an action record in the database before execution if task_id and step_id are available."""
try:
context = skyvern_context.current()
if not context or not context.task_id or not context.step_id:
return None
# Create action record
action = Action(
action_type=action_type,
organization_id=context.organization_id,
workflow_run_id=context.workflow_run_id,
task_id=context.task_id,
step_id=context.step_id,
step_order=0, # Will be updated by the system if needed
action_order=0, # Will be updated by the system if needed
intention=intention,
reasoning=f"Auto-generated action for {action_type.value}",
)
created_action = await app.DATABASE.create_action(action)
return created_action
except Exception:
# If action creation fails, don't block the actual action execution
return None
async def _create_screenshot_after_execution(self, action_record: Action | None = None) -> None:
"""Create a screenshot artifact after action execution if task_id and step_id are available."""
try:
context = skyvern_context.ensure_context()
if not context or not context.task_id or not context.step_id:
return
# Get browser state and take screenshot
browser_state = await app.BROWSER_MANAGER.get_for_script()
if not browser_state:
return
screenshot = await browser_state.take_fullpage_screenshot(
use_playwright_fullpage=app.EXPERIMENTATION_PROVIDER.is_feature_enabled_cached(
"ENABLE_PLAYWRIGHT_FULLPAGE",
context.workflow_run_id or context.task_id,
properties={"organization_id": context.organization_id},
)
)
if screenshot:
# Create a minimal Step object for artifact creation
step = await app.DATABASE.get_step(
context.task_id, context.step_id, organization_id=context.organization_id
)
if not step:
return
await app.ARTIFACT_MANAGER.create_artifact(
step=step,
artifact_type=ArtifactType.SCREENSHOT_ACTION,
data=screenshot,
)
except Exception:
# If screenshot creation fails, don't block execution
pass
######### Public Interfaces #########
@action_wrap(ActionType.CLICK)
async def click(self, xpath: str, intention: str | None = None, data: str | dict[str, Any] | None = None) -> None: