mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2025-09-10 15:35:51 +00:00
script generation improvement (#3216)
This commit is contained in:
parent
82f0b98fca
commit
b9b7591079
14 changed files with 841 additions and 279 deletions
|
@ -18,13 +18,13 @@ from __future__ import annotations
|
|||
|
||||
import hashlib
|
||||
import keyword
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
import libcst as cst
|
||||
import structlog
|
||||
from libcst import Attribute, Call, Dict, DictElement, FunctionDef, Name, Param
|
||||
|
||||
from skyvern.core.script_generations.constants import SCRIPT_TASK_BLOCKS
|
||||
from skyvern.forge import app
|
||||
from skyvern.webeye.actions.action_types import ActionType
|
||||
|
||||
|
@ -96,7 +96,7 @@ def _value(value: Any) -> cst.BaseExpression:
|
|||
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# 2. builders #
|
||||
# 2. utility builders #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
|
||||
|
@ -134,54 +134,16 @@ def _workflow_decorator(wf_req: dict[str, Any]) -> cst.Decorator:
|
|||
)
|
||||
|
||||
|
||||
def _make_decorator(block: dict[str, Any]) -> cst.Decorator:
|
||||
bt = block["block_type"]
|
||||
deco_name = {
|
||||
"task": "task_block",
|
||||
"file_download": "file_download_block",
|
||||
"send_email": "email_block",
|
||||
"wait": "wait_block",
|
||||
"navigation": "navigation_block",
|
||||
"for_loop": "for_loop_block",
|
||||
"action": "action_block",
|
||||
"extraction": "extraction_block",
|
||||
"login": "login_block",
|
||||
"text_prompt": "text_prompt_block",
|
||||
"goto_url": "url_block",
|
||||
}[bt]
|
||||
|
||||
kwargs = []
|
||||
field_map = {
|
||||
"title": "title",
|
||||
"navigation_goal": "prompt",
|
||||
"url": "url",
|
||||
"engine": "engine",
|
||||
"model": "model",
|
||||
"totp_identifier": "totp_identifier",
|
||||
"webhook_callback_url": "webhook_callback_url",
|
||||
"max_steps_per_run": "max_steps",
|
||||
"wait_sec": "seconds",
|
||||
}
|
||||
|
||||
for src_key, kw in field_map.items():
|
||||
v = block.get(src_key)
|
||||
if v not in (None, "", [], {}):
|
||||
if isinstance(v, StrEnum):
|
||||
v = v.value
|
||||
try:
|
||||
kwargs.append(cst.Arg(value=_value(v), keyword=Name(kw)))
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
# booleans
|
||||
if block.get("complete_on_download"):
|
||||
kwargs.append(cst.Arg(value=Name("True"), keyword=Name("complete_on_download")))
|
||||
if block.get("download_suffix"):
|
||||
kwargs.append(cst.Arg(value=_value(block["download_suffix"]), keyword=Name("download_suffix")))
|
||||
|
||||
def _make_decorator(block_label: str, block: dict[str, Any]) -> cst.Decorator:
|
||||
kwargs = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("cache_key"),
|
||||
value=_value(block_label),
|
||||
)
|
||||
]
|
||||
return cst.Decorator(
|
||||
decorator=Call(
|
||||
func=Attribute(value=Name("skyvern"), attr=Name(deco_name)),
|
||||
func=Attribute(value=cst.Name("skyvern"), attr=cst.Name("cached")),
|
||||
args=kwargs,
|
||||
)
|
||||
)
|
||||
|
@ -196,31 +158,78 @@ def _action_to_stmt(act: dict[str, Any]) -> cst.BaseStatement:
|
|||
method = ACTION_MAP[act["action_type"]]
|
||||
|
||||
args: list[cst.Arg] = []
|
||||
if method in ACTIONS_WITH_XPATH:
|
||||
args.append(
|
||||
cst.Arg(
|
||||
keyword=cst.Name("xpath"),
|
||||
value=_value(act["xpath"]),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if method == "input_text":
|
||||
args.append(cst.Arg(keyword=cst.Name("text"), value=_value(act["text"])))
|
||||
args.append(
|
||||
cst.Arg(
|
||||
keyword=cst.Name("text"),
|
||||
value=_value(act["text"]),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
)
|
||||
elif method == "select_option":
|
||||
args.append(cst.Arg(keyword=cst.Name("option"), value=_value(act["option"]["value"])))
|
||||
args.append(
|
||||
cst.Arg(
|
||||
keyword=cst.Name("option"),
|
||||
value=_value(act["option"]["value"]),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
)
|
||||
elif method == "wait":
|
||||
args.append(cst.Arg(keyword=cst.Name("seconds"), value=_value(act["seconds"])))
|
||||
args.append(
|
||||
cst.Arg(
|
||||
keyword=cst.Name("seconds"),
|
||||
value=_value(act["seconds"]),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
args.extend(
|
||||
[
|
||||
cst.Arg(
|
||||
keyword=cst.Name("intention"),
|
||||
value=_value(act.get("intention") or act.get("reasoning") or ""),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("data"),
|
||||
value=cst.Attribute(value=cst.Name("context"), attr=cst.Name("parameters")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(indent=True),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
)
|
||||
if method in ACTIONS_WITH_XPATH:
|
||||
args.append(cst.Arg(keyword=cst.Name("xpath"), value=_value(act["xpath"])))
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("page"), attr=cst.Name(method)),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
# await page.method(...)
|
||||
|
@ -231,7 +240,7 @@ def _action_to_stmt(act: dict[str, Any]) -> cst.BaseStatement:
|
|||
|
||||
|
||||
def _build_block_fn(block: dict[str, Any], actions: list[dict[str, Any]]) -> FunctionDef:
|
||||
name = _safe_name(block.get("title") or block.get("label") or f"block_{block.get('workflow_run_block_id')}")
|
||||
name = block.get("label") or _safe_name(block.get("title") or f"block_{block.get('workflow_run_block_id')}")
|
||||
body_stmts: list[cst.BaseStatement] = []
|
||||
|
||||
if block.get("url"):
|
||||
|
@ -253,7 +262,7 @@ def _build_block_fn(block: dict[str, Any], actions: list[dict[str, Any]]) -> Fun
|
|||
Param(name=Name("context"), annotation=cst.Annotation(cst.Name("RunContext"))),
|
||||
]
|
||||
),
|
||||
decorators=[_make_decorator(block)],
|
||||
decorators=[_make_decorator(name, block)],
|
||||
body=cst.IndentedBlock(body_stmts),
|
||||
returns=None,
|
||||
asynchronous=cst.Asynchronous(),
|
||||
|
@ -308,38 +317,556 @@ def _build_cached_params(values: dict[str, Any]) -> cst.SimpleStatementLine:
|
|||
return cst.SimpleStatementLine([assign])
|
||||
|
||||
|
||||
def _build_run_fn(task_titles: list[str], wf_req: dict[str, Any]) -> FunctionDef:
|
||||
# --------------------------------------------------------------------- #
|
||||
# 3. statement builders #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def _build_run_task_statement(block_title: str, block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.run_task statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("max_steps"),
|
||||
value=_value(block.get("max_steps_per_run", 30)),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("cache_key"),
|
||||
value=_value(block_title),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("run_task")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_download_statement(block_title: str, block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.download statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("complete_on_download"),
|
||||
value=_value(block.get("complete_on_download", False)),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("download_suffix"),
|
||||
value=_value(block.get("download_suffix", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("cache_key"),
|
||||
value=_value(block_title),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("download")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_action_statement(block_title: str, block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.action statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("title"),
|
||||
value=_value(block_title),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("max_steps"),
|
||||
value=_value(block.get("max_steps_per_run", 30)),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("action")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_login_statement(block_title: str, block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.login statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("title"),
|
||||
value=_value(block_title),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("totp_identifier"),
|
||||
value=_value(block.get("totp_identifier", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("webhook_callback_url"),
|
||||
value=_value(block.get("webhook_callback_url", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("login")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_extract_statement(block_title: str, block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.extract statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("title"),
|
||||
value=_value(block_title),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("extract")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_navigate_statement(block_title: str, block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.navigate statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("title"),
|
||||
value=_value(block_title),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("url"),
|
||||
value=_value(block.get("url", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("max_steps"),
|
||||
value=_value(block.get("max_steps_per_run", 30)),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("run_task")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_send_email_statement(block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.send_email statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("sender"),
|
||||
value=_value(block.get("sender", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("subject"),
|
||||
value=_value(block.get("subject", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("body"),
|
||||
value=_value(block.get("body", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("recipients"),
|
||||
value=_value(block.get("recipients", [])),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("attach_downloaded_files"),
|
||||
value=_value(block.get("attach_downloaded_files", False)),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("send_email")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_validate_statement(block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.validate statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("validate")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_wait_statement(block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.wait statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("seconds"),
|
||||
value=_value(block.get("wait_sec", 1)),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("wait")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_for_loop_statement(block_title: str, block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.for_loop statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("title"),
|
||||
value=_value(block_title),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=_value(block.get("navigation_goal", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
cst.Arg(
|
||||
keyword=cst.Name("max_steps"),
|
||||
value=_value(block.get("max_steps_per_run", 30)),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("for_loop")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
def _build_goto_statement(block: dict[str, Any]) -> cst.SimpleStatementLine:
|
||||
"""Build a skyvern.goto statement."""
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("url"),
|
||||
value=_value(block.get("url", "")),
|
||||
whitespace_after_arg=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("goto")),
|
||||
args=args,
|
||||
whitespace_before_args=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
return cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# 4. function builders #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
|
||||
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(f"await {_safe_name(t)}(page, context)") for t in task_titles],
|
||||
]
|
||||
|
||||
for block in blocks:
|
||||
block_type = block.get("block_type")
|
||||
block_title = block.get("label") or block.get("title") or f"block_{block.get('workflow_run_block_id')}"
|
||||
|
||||
if block_type in SCRIPT_TASK_BLOCKS:
|
||||
# For task blocks, call the custom function with cache_key
|
||||
if block_type == "task":
|
||||
stmt = _build_run_task_statement(block_title, block)
|
||||
elif block_type == "file_download":
|
||||
stmt = _build_download_statement(block_title, block)
|
||||
elif block_type == "action":
|
||||
stmt = _build_action_statement(block_title, block)
|
||||
elif block_type == "login":
|
||||
stmt = _build_login_statement(block_title, block)
|
||||
elif block_type == "extraction":
|
||||
stmt = _build_extract_statement(block_title, block)
|
||||
elif block_type == "navigation":
|
||||
stmt = _build_navigate_statement(block_title, block)
|
||||
elif block_type == "send_email":
|
||||
stmt = _build_send_email_statement(block)
|
||||
elif block_type == "text_prompt":
|
||||
stmt = _build_validate_statement(block)
|
||||
elif block_type == "wait":
|
||||
stmt = _build_wait_statement(block)
|
||||
elif block_type == "for_loop":
|
||||
stmt = _build_for_loop_statement(block_title, block)
|
||||
elif block_type == "goto_url":
|
||||
stmt = _build_goto_statement(block)
|
||||
else:
|
||||
# Default case for unknown block types
|
||||
stmt = cst.SimpleStatementLine([cst.Expr(cst.SimpleString(f"# Unknown block type: {block_type}"))])
|
||||
|
||||
body.append(stmt)
|
||||
|
||||
# Add a final validation step if not already present
|
||||
has_validation = any(block.get("block_type") == "text_prompt" for block in blocks)
|
||||
has_task_blocks = any(block.get("block_type") in SCRIPT_TASK_BLOCKS for block in blocks)
|
||||
if not has_validation and not has_task_blocks:
|
||||
# Build the final validation statement using LibCST components
|
||||
args = [
|
||||
cst.Arg(
|
||||
keyword=cst.Name("prompt"),
|
||||
value=cst.SimpleString(
|
||||
'"Your goal is to validate that the workflow completed successfully. COMPLETE if successful, TERMINATE if there are issues."'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
call = cst.Call(
|
||||
func=cst.Attribute(value=cst.Name("skyvern"), attr=cst.Name("validate")),
|
||||
args=args,
|
||||
)
|
||||
|
||||
validation_stmt = cst.SimpleStatementLine([cst.Expr(cst.Await(call))])
|
||||
body.append(validation_stmt)
|
||||
|
||||
params = cst.Parameters(
|
||||
params=[
|
||||
Param(
|
||||
name=cst.Name("parameters"),
|
||||
annotation=cst.Annotation(cst.Name("WorkflowParameters")),
|
||||
default=cst.Name("cached_parameters"),
|
||||
whitespace_after_param=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
Param(
|
||||
name=cst.Name("title"),
|
||||
annotation=cst.Annotation(cst.Name("str")),
|
||||
default=_value(wf_req.get("title", "")),
|
||||
whitespace_after_param=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
Param(
|
||||
name=cst.Name("webhook_url"),
|
||||
annotation=cst.Annotation(cst.parse_expression("str | None")),
|
||||
default=_value(wf_req.get("webhook_url")),
|
||||
whitespace_after_param=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
Param(
|
||||
name=cst.Name("totp_url"),
|
||||
annotation=cst.Annotation(cst.parse_expression("str | None")),
|
||||
default=_value(wf_req.get("totp_url")),
|
||||
whitespace_after_param=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
),
|
||||
Param(
|
||||
name=cst.Name("totp_identifier"),
|
||||
annotation=cst.Annotation(cst.parse_expression("str | None")),
|
||||
default=_value(wf_req.get("totp_identifier")),
|
||||
whitespace_after_param=cst.ParenthesizedWhitespace(),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -350,11 +877,15 @@ def _build_run_fn(task_titles: list[str], wf_req: dict[str, Any]) -> FunctionDef
|
|||
decorators=[_workflow_decorator(wf_req)],
|
||||
params=params,
|
||||
body=cst.IndentedBlock(body),
|
||||
whitespace_before_params=cst.ParenthesizedWhitespace(
|
||||
indent=True,
|
||||
last_line=cst.SimpleWhitespace(INDENT),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# 3. entrypoint #
|
||||
# 5. entrypoint #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
|
||||
|
@ -363,7 +894,7 @@ async def generate_workflow_script(
|
|||
file_name: str,
|
||||
workflow_run_request: dict[str, Any],
|
||||
workflow: dict[str, Any],
|
||||
tasks: list[dict[str, Any]],
|
||||
blocks: list[dict[str, Any]],
|
||||
actions_by_task: dict[str, list[dict[str, Any]]],
|
||||
organization_id: str | None = None,
|
||||
run_id: str | None = None,
|
||||
|
@ -405,10 +936,11 @@ async def generate_workflow_script(
|
|||
|
||||
# --- blocks ---------------------------------------------------------
|
||||
block_fns = []
|
||||
length_of_tasks = len(tasks)
|
||||
task_blocks = [block for block in blocks if block["block_type"] in SCRIPT_TASK_BLOCKS]
|
||||
length_of_tasks = len(task_blocks)
|
||||
|
||||
# Create script first if organization_id is provided
|
||||
for idx, task in enumerate(tasks):
|
||||
for idx, task in enumerate(task_blocks):
|
||||
block_fn_def = _build_block_fn(task, actions_by_task.get(task.get("task_id", ""), []))
|
||||
|
||||
# Create script block if we have script context
|
||||
|
@ -433,12 +965,8 @@ async def generate_workflow_script(
|
|||
block_fns.append(cst.EmptyLine())
|
||||
block_fns.append(cst.EmptyLine())
|
||||
|
||||
task_titles: list[str] = [
|
||||
t.get("title") or t.get("label") or t.get("task_id") or f"unknown_title_{idx}" for idx, t in enumerate(tasks)
|
||||
]
|
||||
|
||||
# --- runner ---------------------------------------------------------
|
||||
run_fn = _build_run_fn(task_titles, workflow_run_request)
|
||||
run_fn = _build_run_fn(blocks, workflow_run_request)
|
||||
|
||||
module = cst.Module(
|
||||
body=[
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue