For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
BlogLog InRequest Demo
HomeProductDevelopersSelf-HostingChangelog
HomeProductDevelopersSelf-HostingChangelog
  • Getting Started
    • Overview
  • Workflows SDK
    • Introduction
    • Installation
    • Core Concepts
    • Defining Control Flow
    • Configuration
    • Custom Docker Images
      • Examples Gallery
      • Prompt Chaining
      • RAG Chatbot
      • Custom Nodes
      • Document Data Extraction
      • Workflow Execution Filtering and Monitoring
      • Branching with Ports and Expressions
      • External Workflow Server
  • Client SDK
    • Introduction
    • Authentication
    • API Versioning
LogoLogo
BlogLog InRequest Demo
On this page
  • Basic Port Types
  • LazyReference for Self-Referencing
  • Expressions
  • Equality and Inequality
  • Numeric Comparisons
  • Collection Operations
  • Null and Undefined Checks
  • Data Processing
  • Working with JSON
  • Logical Operators
  • AND Operations
  • OR Operations
  • Complex Logical Expressions
Workflows SDKTutorials

Branching with Ports and Expressions

Was this page helpful?
Previous

External Workflow Server

Next
Built with

This guide demonstrates the syntax for using Ports and Expressions to control the flow of execution in Vellum Workflows.

Basic Port Types

Below is a basic example:

1# nodes/my_prompt_node.py
2class MyNode(BaseNode):
3
4 # ... prompt node attributes go here ...
5
6 class Ports(BaseNode.Ports):
7 first_condition = Port.on_if(condition_expression)
8 second_condition = Port.on_elif(another_condition)
9 fallback = Port.on_else()
10
11# workflow.py
12class MyWorkflow(BaseWorkflow):
13 graph = {
14 # Can route to nodes or other Workflows
15 MyPromptNode.Ports.first_condition >> MyOtherNode,
16 MyPromptNode.Ports.second_condition >> AnotherNode1,
17 MyPromptNode.Ports.fallback >> AnotherNode2,
18 }
19
20 class Outputs(BaseWorkflow.Outputs):
21 final_output = MyOtherNode.Outputs.output

Prefer using Ports directly on Nodes rather than using legacy Conditional Nodes.

LazyReference for Self-Referencing

A common use case for Ports is to branch based on the result a node’s own outputs. For example, if a Prompt Node classifies text as “positive” or “negative”, you can use a Port to immediately branch based on the result.

In this case, the node needs to reference its own outputs in port conditions, use LazyReference to do so:

1from vellum.workflows.nodes.displayable import InlinePromptNode
2from vellum.workflows.ports import Port
3from vellum.workflows.references import LazyReference
4
5class SentimentAnalysisNode(InlinePromptNode):
6
7 # ... inline prompt node attributes go here ...
8
9 class Ports(InlinePromptNode.Ports):
10 # Self-referencing port condition
11 positive = Port.on_if(
12 LazyReference(lambda: SentimentAnalysisNode.Outputs.json["sentiment"].equals("positive"))
13 )
14 negative = Port.on_elif(
15 LazyReference(lambda: SentimentAnalysisNode.Outputs.json["sentiment"].equals("negative"))
16 )
17 else_port = Port.on_else()
18
19# workflow.py
20class MyWorkflow(BaseWorkflow):
21 graph = {
22 SentimentAnalysisNode.Ports.positive >> MyOtherNode,
23 SentimentAnalysisNode.Ports.negative >> AnotherNode1,
24 SentimentAnalysisNode.Ports.else_port >> MyOtherNode,
25 }
26
27 class Outputs(BaseWorkflow.Outputs):
28 final_output = MyOtherNode.Outputs.output

Expressions

Ports use Expressions to evaluate which Port to route to. Below is a list of all available expression operators.

Equality and Inequality

1# Basic equality
2Port.on_if(Inputs.category.equals("question"))
3Port.on_if(SomeNode.Outputs.status.does_not_equal("error"))
4
5# String comparisons
6Port.on_if(Inputs.text.contains("keyword"))
7Port.on_if(Inputs.text.does_not_contain("spam"))
8Port.on_if(Inputs.filename.begins_with("temp_"))
9Port.on_if(Inputs.filename.does_not_begin_with("system"))
10Port.on_if(Inputs.url.ends_with(".pdf"))
11Port.on_if(Inputs.url.does_not_end_with(".tmp"))

Numeric Comparisons

1# Numeric operators
2Port.on_if(Inputs.score.greater_than(0.8))
3Port.on_if(Inputs.count.less_than(100))
4Port.on_if(Inputs.rating.greater_than_or_equal_to(4.0))
5Port.on_if(Inputs.attempts.less_than_or_equal_to(3))
6
7# Range checks
8Port.on_if(Inputs.temperature.between(20, 30))
9Port.on_if(Inputs.age.not_between(13, 17))

Collection Operations

1# Membership testing
2Port.on_if(Inputs.status.in_(["active", "pending"]))
3Port.on_if(Inputs.category.not_in(["spam", "deleted"]))

Null and Undefined Checks

1# Null checks
2Port.on_if(Inputs.optional_field.is_null())
3Port.on_if(Inputs.required_field.is_not_null())
4
5# Nil checks (empty/blank values)
6Port.on_if(Inputs.description.is_nil())
7Port.on_if(Inputs.title.is_not_nil())
8
9# Undefined checks
10Port.on_if(Inputs.config_value.is_undefined())
11Port.on_if(Inputs.user_input.is_not_undefined())
12
13# Blank checks (empty strings, whitespace)
14Port.on_if(Inputs.comment.is_blank())
15Port.on_if(Inputs.name.is_not_blank())

Data Processing

1# JSON parsing
2Port.on_if(Inputs.json_string.parse_json())
3
4# Coalescing (fallback values)
5Port.on_if(Inputs.primary_value.coalesce(Inputs.fallback_value))

Working with JSON

Access JSON fields using bracket notation:

1# Access JSON object fields
2Port.on_if(PromptNode.Outputs.json["status"].equals("success"))
3Port.on_if(APINode.Outputs.response["data"]["count"].greater_than(10))
4
5# Combine with LazyReference for self-referencing
6Port.on_if(LazyReference(lambda: DataProcessorNode.Outputs.result)["confidence"].greater_than(0.8))
7
8# Check nested JSON values
9Port.on_if(Inputs.config["settings"]["enabled"].equals(True))
10Port.on_if(CodeNode.Outputs.analysis["metrics"]["accuracy"].between(0.8, 1.0))

Logical Operators

AND Operations

Use the & operator to combine conditions with AND logic:

1Port.on_if(
2 Inputs.category.equals("urgent")
3 & Inputs.priority.greater_than(5)
4)
5
6# Complex AND with parentheses
7Port.on_if(
8 Inputs.status.equals("active")
9 & (Inputs.verified.equals(True) & Inputs.premium.equals(True))
10)

OR Operations

Use the | operator to combine conditions with OR logic:

1Port.on_if(
2 Inputs.category.equals("error")
3 | Inputs.category.equals("warning")
4)
5
6# Mixed AND/OR with proper precedence
7Port.on_if(
8 Inputs.type.equals("admin")
9 & (Inputs.role.equals("owner") | Inputs.role.equals("manager"))
10)

Complex Logical Expressions

1# Parentheses control precedence
2Port.on_if(
3 (
4 Inputs.user_type.equals("premium")
5 & Inputs.subscription.equals("active")
6 )
7 | Inputs.admin_override.equals(True)
8)