diff --git a/skyvern/forge/prompts/skyvern/conditional-prompt-branch-evaluation.j2 b/skyvern/forge/prompts/skyvern/conditional-prompt-branch-evaluation.j2 index cb392384..1682daee 100644 --- a/skyvern/forge/prompts/skyvern/conditional-prompt-branch-evaluation.j2 +++ b/skyvern/forge/prompts/skyvern/conditional-prompt-branch-evaluation.j2 @@ -5,6 +5,9 @@ Criteria (order matters; align outputs to these indices): - {{ criterion.index }}: {{ criterion.expression }} {% endfor %} +Context (use this data to evaluate each criterion; if a value is absent, treat it as missing/Falsey): +{{ context_snapshot }} + Respond with JSON exactly in this shape: { "branch_results": [true | false per criterion, in order] diff --git a/skyvern/forge/sdk/workflow/models/block.py b/skyvern/forge/sdk/workflow/models/block.py index 1ffb587b..f383b985 100644 --- a/skyvern/forge/sdk/workflow/models/block.py +++ b/skyvern/forge/sdk/workflow/models/block.py @@ -4318,6 +4318,43 @@ class BranchEvaluationContext: self.block_label = block_label self.template_renderer = template_renderer + def build_llm_safe_context_snapshot(self) -> dict[str, Any]: + """ + Build a non-secret context blob for LLM-facing branch evaluation. + + Secrets are stripped/masked; only params/outputs/environment and cached + block metadata are included so the LLM can ground purely natural language + expressions without requiring inline templating. + """ + if self.workflow_run_context is None: + return {} + + ctx = self.workflow_run_context + + # Start from the recorded values (params, outputs, env, block outputs) + snapshot: dict[str, Any] = ctx.values.copy() + + # Add block metadata (e.g., loop indices/current_item) without mutating originals + snapshot["blocks_metadata"] = ctx.blocks_metadata.copy() + + # Ensure the common namespaces exist + snapshot.setdefault("params", snapshot.get("params", {})) + snapshot.setdefault("outputs", snapshot.get("outputs", {})) + snapshot.setdefault("environment", snapshot.get("environment", {})) + snapshot.setdefault("env", snapshot.get("environment", {})) + snapshot.setdefault("llm", snapshot.get("llm", {})) + + # Standard workflow identifiers for additional context + snapshot.setdefault("workflow_title", ctx.workflow_title) + snapshot.setdefault("workflow_id", ctx.workflow_id) + snapshot.setdefault("workflow_permanent_id", ctx.workflow_permanent_id) + snapshot.setdefault("workflow_run_id", ctx.workflow_run_id) + + # Mask any real secret values that may have leaked into values + snapshot = ctx.mask_secrets_in_data(snapshot) + + return snapshot + def build_template_data(self) -> dict[str, Any]: """Build Jinja template data mirroring block parameter rendering context.""" if self.workflow_run_context is None: @@ -4560,6 +4597,8 @@ class ConditionalBlock(Block): raise ValueError("organization_id is required to evaluate natural language branches") workflow_run_context = evaluation_context.workflow_run_context + context_snapshot = evaluation_context.build_llm_safe_context_snapshot() + context_snapshot_json = json.dumps(context_snapshot, default=str) rendered_branch_criteria: list[dict[str, Any]] = [] for idx, branch in enumerate(branches): @@ -4578,6 +4617,7 @@ class ConditionalBlock(Block): extraction_goal = prompt_engine.load_prompt( "conditional-prompt-branch-evaluation", branch_criteria=branch_criteria_payload, + context_snapshot=context_snapshot_json, ) data_schema = {