block level script run (#3710)

This commit is contained in:
Shuchang Zheng
2025-10-14 16:17:03 -07:00
committed by GitHub
parent e13e9a5d58
commit ce35b37470
10 changed files with 352 additions and 176 deletions

View File

@@ -47,7 +47,15 @@ from skyvern.forge.sdk.workflow.models.block import (
from skyvern.forge.sdk.workflow.models.parameter import PARAMETER_TYPE, OutputParameter, ParameterType
from skyvern.forge.sdk.workflow.models.workflow import Workflow
from skyvern.schemas.runs import RunEngine
from skyvern.schemas.scripts import CreateScriptResponse, FileEncoding, FileNode, ScriptFileCreate, ScriptStatus
from skyvern.schemas.scripts import (
CreateScriptResponse,
FileEncoding,
FileNode,
Script,
ScriptFile,
ScriptFileCreate,
ScriptStatus,
)
from skyvern.schemas.workflows import BlockStatus, BlockType, FileStorageType, FileType
LOG = structlog.get_logger()
@@ -258,35 +266,18 @@ async def create_script(
raise HTTPException(status_code=500, detail="Failed to create script")
async def execute_script(
script_id: str,
organization_id: str,
parameters: dict[str, Any] | None = None,
workflow_run_id: str | None = None,
browser_session_id: str | None = None,
background_tasks: BackgroundTasks | None = None,
async def load_scripts(
script: Script,
script_files: list[ScriptFile],
) -> None:
# step 1: get the script revision
script = await app.DATABASE.get_script(
script_id=script_id,
organization_id=organization_id,
)
if not script:
raise ScriptNotFound(script_id=script_id)
# step 2: get the script files
script_files = await app.DATABASE.get_script_files(
script_revision_id=script.script_revision_id, organization_id=organization_id
)
# step 3: copy the script files to the local directory
organization_id = script.organization_id
for file in script_files:
# retrieve the artifact
if not file.artifact_id:
continue
artifact = await app.DATABASE.get_artifact_by_id(file.artifact_id, organization_id)
if not artifact:
LOG.error("Artifact not found", artifact_id=file.artifact_id, script_id=script_id)
LOG.error("Artifact not found", artifact_id=file.artifact_id, script_id=script.script_id)
continue
file_content = await app.ARTIFACT_MANAGER.retrieve_artifact(artifact)
if not file_content:
@@ -313,6 +304,31 @@ async def execute_script(
with open(file_path, "wb") as f:
f.write(file_content)
async def execute_script(
script_id: str,
organization_id: str,
parameters: dict[str, Any] | None = None,
workflow_run_id: str | None = None,
browser_session_id: str | None = None,
background_tasks: BackgroundTasks | None = None,
) -> None:
# step 1: get the script revision
script = await app.DATABASE.get_script(
script_id=script_id,
organization_id=organization_id,
)
if not script:
raise ScriptNotFound(script_id=script_id)
# step 2: get the script files
script_files = await app.DATABASE.get_script_files(
script_revision_id=script.script_revision_id, organization_id=organization_id
)
# step 3: copy the script files to the local directory
await load_scripts(script, script_files)
# step 4: execute the script
if workflow_run_id and not parameters:
parameter_tuples = await app.DATABASE.get_workflow_run_parameters(workflow_run_id=workflow_run_id)
@@ -320,6 +336,7 @@ async def execute_script(
LOG.info("Script run Parameters is using workflow run parameters", parameters=parameters)
script_path = os.path.join(settings.TEMP_PATH, script.script_id, "main.py")
if background_tasks:
# Execute asynchronously in background
background_tasks.add_task(
@@ -329,6 +346,7 @@ async def execute_script(
organization_id=organization_id,
workflow_run_id=workflow_run_id,
browser_session_id=browser_session_id,
script_id=script_id,
)
else:
# Execute synchronously
@@ -681,17 +699,7 @@ async def _fallback_to_ai_run(
organization_id=organization_id,
status=StepStatus.failed,
)
# 2. create a new step for ai run
ai_step = await app.DATABASE.create_step(
task_id=task_id,
organization_id=organization_id,
order=previous_step.order + 1,
retry_index=0,
)
context.step_id = ai_step.step_id
ai_step_id = ai_step.step_id
# 3. build the task block
# 4. run execute_step
# 2. run execute_step
organization = await app.DATABASE.get_organization(organization_id=organization_id)
if not organization:
raise Exception(f"Organization is missing organization_id={organization_id}")
@@ -717,8 +725,29 @@ async def _fallback_to_ai_run(
workflow_permanent_id=workflow_permanent_id,
workflow_run_id=workflow_run_id,
)
if workflow_run_block_id:
await _update_workflow_block(
workflow_run_block_id,
BlockStatus.failed,
task_id=task_id,
task_status=TaskStatus.failed,
failure_reason=str(error),
step_id=script_step_id,
step_status=StepStatus.failed,
label=cache_key,
)
return
# 2. create a new step for ai run
ai_step = await app.DATABASE.create_step(
task_id=task_id,
organization_id=organization_id,
order=previous_step.order + 1,
retry_index=0,
)
context.step_id = ai_step.step_id
ai_step_id = ai_step.step_id
# get the output_paramter
output_parameter = workflow.get_output_parameter(cache_key)
if not output_parameter:
@@ -777,11 +806,13 @@ async def _fallback_to_ai_run(
# Update block status to completed if workflow block was created
if workflow_run_block_id:
# refresh the task
refreshed_task = await app.DATABASE.get_task(task_id=task_id, organization_id=organization_id)
if refreshed_task:
task = refreshed_task
await _update_workflow_block(
workflow_run_block_id,
BlockStatus.completed,
task_id=context.task_id,
step_id=context.step_id,
BlockStatus(task.status.value),
label=cache_key,
)
@@ -1581,7 +1612,6 @@ async def run_script(
user_script = importlib.util.module_from_spec(spec)
spec.loader.exec_module(user_script)
# Call run_workflow from the imported module
if hasattr(user_script, "run_workflow"):
# If parameters is None, pass an empty dict
if parameters: