script gen post action (#3480)

This commit is contained in:
Shuchang Zheng
2025-09-19 08:50:21 -07:00
committed by GitHub
parent b4669f7477
commit c5280782b0
17 changed files with 536 additions and 264 deletions

View File

@@ -3060,6 +3060,9 @@ class TaskV2Block(Block):
finally:
context: skyvern_context.SkyvernContext | None = skyvern_context.current()
current_run_id = context.run_id if context and context.run_id else workflow_run_id
root_workflow_run_id = (
context.root_workflow_run_id if context and context.root_workflow_run_id else workflow_run_id
)
skyvern_context.set(
skyvern_context.SkyvernContext(
organization_id=organization_id,
@@ -3067,6 +3070,7 @@ class TaskV2Block(Block):
workflow_id=workflow_run.workflow_id,
workflow_permanent_id=workflow_run.workflow_permanent_id,
workflow_run_id=workflow_run_id,
root_workflow_run_id=root_workflow_run_id,
run_id=current_run_id,
browser_session_id=browser_session_id,
max_screenshot_scrolls=workflow_run.max_screenshot_scrolls,

View File

@@ -1,5 +1,4 @@
import asyncio
import base64
import json
import uuid
from datetime import UTC, datetime
@@ -7,14 +6,11 @@ from typing import Any
import httpx
import structlog
from jinja2.sandbox import SandboxedEnvironment
from skyvern import analytics
from skyvern.client.types.output_parameter import OutputParameter as BlockOutputParameter
from skyvern.config import settings
from skyvern.constants import GET_DOWNLOADED_FILES_TIMEOUT, SAVE_DOWNLOADED_FILES_TIMEOUT
from skyvern.core.script_generations.generate_script import generate_workflow_script as generate_python_workflow_script
from skyvern.core.script_generations.transform_workflow_run import transform_workflow_run_to_code_gen_input
from skyvern.exceptions import (
BlockNotFound,
BrowserSessionNotFound,
@@ -99,7 +95,6 @@ from skyvern.forge.sdk.workflow.models.workflow import (
WorkflowRunStatus,
)
from skyvern.schemas.runs import ProxyLocation, RunStatus, RunType, WorkflowRunRequest, WorkflowRunResponse
from skyvern.schemas.scripts import FileEncoding, Script, ScriptFileCreate
from skyvern.schemas.workflows import (
BLOCK_YAML_TYPES,
BlockStatus,
@@ -109,7 +104,7 @@ from skyvern.schemas.workflows import (
WorkflowDefinitionYAML,
WorkflowStatus,
)
from skyvern.services import script_service
from skyvern.services import script_service, workflow_script_service
from skyvern.webeye.browser_factory import BrowserState
LOG = structlog.get_logger()
@@ -205,6 +200,7 @@ class WorkflowService:
request_id=request_id,
workflow_id=workflow_id,
workflow_run_id=workflow_run.workflow_run_id,
root_workflow_run_id=workflow_run.workflow_run_id,
run_id=current_run_id,
workflow_permanent_id=workflow_run.workflow_permanent_id,
max_steps_override=max_steps_override,
@@ -353,7 +349,7 @@ class WorkflowService:
return workflow_run
# Check if there's a related workflow script that should be used instead
workflow_script, _ = await self._get_workflow_script(workflow, workflow_run, block_labels)
workflow_script, _ = await workflow_script_service.get_workflow_script(workflow, workflow_run, block_labels)
is_script = workflow_script is not None
if workflow_script is not None:
LOG.info(
@@ -365,9 +361,7 @@ class WorkflowService:
)
workflow_run = await self._execute_workflow_script(
script_id=workflow_script.script_id,
workflow=workflow,
workflow_run=workflow_run,
api_key=api_key,
organization=organization,
browser_session_id=browser_session_id,
)
@@ -375,9 +369,7 @@ class WorkflowService:
workflow_run = await self._execute_workflow_blocks(
workflow=workflow,
workflow_run=workflow_run,
api_key=api_key,
organization=organization,
close_browser_on_completion=close_browser_on_completion,
browser_session_id=browser_session_id,
block_labels=block_labels,
block_outputs=block_outputs,
@@ -422,9 +414,7 @@ class WorkflowService:
self,
workflow: Workflow,
workflow_run: WorkflowRun,
api_key: str,
organization: Organization,
close_browser_on_completion: bool,
browser_session_id: str | None = None,
block_labels: list[str] | None = None,
block_outputs: dict[str, Any] | None = None,
@@ -2457,66 +2447,10 @@ class WorkflowService:
return result
async def _get_workflow_script(
self, workflow: Workflow, workflow_run: WorkflowRun, block_labels: list[str] | None = None
) -> tuple[Script | None, str]:
"""
Check if there's a related workflow script that should be used instead of running the workflow.
Returns the tuple of (script, rendered_cache_key_value).
"""
cache_key = workflow.cache_key or ""
rendered_cache_key_value = ""
if not workflow.generate_script:
return None, rendered_cache_key_value
if block_labels:
# Do not generate script or run script if block_labels is provided
return None, rendered_cache_key_value
try:
parameter_tuples = await app.DATABASE.get_workflow_run_parameters(
workflow_run_id=workflow_run.workflow_run_id,
)
parameters = {wf_param.key: run_param.value for wf_param, run_param in parameter_tuples}
jinja_sandbox_env = SandboxedEnvironment()
rendered_cache_key_value = jinja_sandbox_env.from_string(cache_key).render(parameters)
# Check if there are existing cached scripts for this workflow + cache_key_value
existing_scripts = await app.DATABASE.get_workflow_scripts_by_cache_key_value(
organization_id=workflow.organization_id,
workflow_permanent_id=workflow.workflow_permanent_id,
cache_key_value=rendered_cache_key_value,
)
if existing_scripts:
LOG.info(
"Found cached script for workflow",
workflow_id=workflow.workflow_id,
cache_key_value=rendered_cache_key_value,
workflow_run_id=workflow_run.workflow_run_id,
script_count=len(existing_scripts),
)
return existing_scripts[0], rendered_cache_key_value
return None, rendered_cache_key_value
except Exception as e:
LOG.warning(
"Failed to check for workflow script, proceeding with normal workflow execution",
workflow_id=workflow.workflow_id,
workflow_run_id=workflow_run.workflow_run_id,
error=str(e),
exc_info=True,
)
return None, rendered_cache_key_value
async def _execute_workflow_script(
self,
script_id: str,
workflow: Workflow,
workflow_run: WorkflowRun,
api_key: str,
organization: Organization,
browser_session_id: str | None = None,
) -> WorkflowRun:
@@ -2584,7 +2518,7 @@ class WorkflowService:
# Do not generate script if block_labels is provided
return None
existing_script, rendered_cache_key_value = await self._get_workflow_script(
existing_script, rendered_cache_key_value = await workflow_script_service.get_workflow_script(
workflow,
workflow_run,
block_labels,
@@ -2605,62 +2539,9 @@ class WorkflowService:
run_id=workflow_run.workflow_run_id,
)
# 3) Generate script code from workflow run
try:
LOG.info(
"Generating script for workflow",
workflow_run_id=workflow_run.workflow_run_id,
workflow_id=workflow.workflow_id,
workflow_name=workflow.title,
cache_key_value=rendered_cache_key_value,
)
codegen_input = await transform_workflow_run_to_code_gen_input(
workflow_run_id=workflow_run.workflow_run_id,
organization_id=workflow.organization_id,
)
python_src = await generate_python_workflow_script(
file_name=codegen_input.file_name,
workflow_run_request=codegen_input.workflow_run,
workflow=codegen_input.workflow,
blocks=codegen_input.workflow_blocks,
actions_by_task=codegen_input.actions_by_task,
task_v2_child_blocks=codegen_input.task_v2_child_blocks,
organization_id=workflow.organization_id,
script_id=created_script.script_id,
script_revision_id=created_script.script_revision_id,
)
except Exception:
LOG.error("Failed to generate workflow script source", exc_info=True)
return
# 4) Persist script and files, then record mapping
content_bytes = python_src.encode("utf-8")
content_b64 = base64.b64encode(content_bytes).decode("utf-8")
files = [
ScriptFileCreate(
path="main.py",
content=content_b64,
encoding=FileEncoding.BASE64,
mime_type="text/x-python",
)
]
# Upload script file(s) as artifacts and create rows
await script_service.build_file_tree(
files=files,
organization_id=workflow.organization_id,
script_id=created_script.script_id,
script_version=created_script.version,
script_revision_id=created_script.script_revision_id,
)
# Record the workflow->script mapping for cache lookup
await app.DATABASE.create_workflow_script(
organization_id=workflow.organization_id,
script_id=created_script.script_id,
workflow_permanent_id=workflow.workflow_permanent_id,
cache_key=workflow.cache_key or "",
cache_key_value=rendered_cache_key_value,
workflow_id=workflow.workflow_id,
workflow_run_id=workflow_run.workflow_run_id,
await workflow_script_service.generate_workflow_script(
workflow_run=workflow_run,
workflow=workflow,
script=created_script,
rendered_cache_key_value=rendered_cache_key_value,
)