Overview

In Julep broadly speaking there are two types of steps:

The steps defined out here are in the YAML format. You can learn more about the YAML format here.

Control Flow Steps

Prompt Step

Send messages to the AI model:

YAML
# Simple prompt
- prompt: What is your name?

# Multi-message prompt
- prompt:
    - role: system
      content: You are a helpful assistant
    - role: user
      content: "Hello!"

# Prompt with settings
- prompt:
    - role: user
      content: Generate a creative story
  settings:
    model: "claude-3.5-sonnet"
    temperature: 0.8

In the prompt step we offer a bunch of Python functions to help you manipulate data. Here is a list of the functions you can use:

  • Standard library modules:

    • re: Regular expressions (safe against ReDoS)
    • json: JSON encoding/decoding
    • yaml: YAML parsing/dumping
    • string: String constants and operations
    • datetime: Date and time operations
    • math: Mathematical functions
    • statistics: Statistical operations
    • base64: Base64 encoding/decoding
    • urllib: URL parsing operations
    • random: Random number generation
    • time: Time operations
  • Constants:

    • NEWLINE: Newline character
    • true: Boolean true
    • false: Boolean false
    • null: None value

Tool Call Step

Execute tools defined in the task:

YAML
# Simple tool call
- tool: web_search
  arguments:
    query: Latest AI news

# Tool call with complex arguments
- tool: process_data
  arguments:
    input_data: $ _.previous_result
    options:
      format: "json"
      validate: true

Evaluate Step

Perform calculations or data manipulation:

YAML
# Simple evaluation
- evaluate:
    count: $ len(_.results)

# Multiple evaluations
- evaluate:
    total: $ sum(_.numbers)
    average: $ _.total / len(_.numbers)
    formatted: $ f'Average: {_.average:.2f}'

In the evaluate step we offer a bunch of Python functions to help you manipulate data. Check out the Python Expressions for more information.

Wait for Input Step

Pause workflow for user input:

YAML
# Simple input request
- wait_for_input:
    info:
      message: "Please provide your name"

# Input with validation
- wait_for_input:
    info:
      message: "Enter your age"
      validation:
        type: "number"
        minimum: 0
        maximum: 150

Subworkflow Step

Executing a subworkflow from a main workflow:

YAML
# Subworkflow
subworkflow:
- evaluate:
    main_workflow_input: $ _.content # you can use steps[0].input.content to access the input of the subworkflow
- return:
    result: "This is the subworkflow"

# Main workflow
main:
# Step 0: Evaluate step
- evaluate:
    result: "This is the main workflow"

# Step 1: Call the subworkflow
- workflow: subworkflow # name of the subworkflow
  arguments: # input to the subworkflow
    content: $ _.result # you can use steps[0].output.result to access the result of the previous step

# Step 2: Evaluate step
- evaluate:
    subworkflow_result: $ steps[1].output.result # this will be the result of the subworkflow
  • The arguments passed from the main workflow to the subworkflow are available in the steps[0].input of the subworkflow.
  • The result of the subworkflow is available in the steps[1].output.result of the main workflow.
  • The Input/Output Data References between steps is exclusive to the workflow they are defined in. A workflow cannot reference the input or output of another workflow between the steps. To learn more about Input/Output Data References click here.

Self recursion is allowed in a subworkflow but not in a main workflow.

Key-Value Steps

Get Step

Retrieve values from storage:

YAML
# Get a single value
- get: user_preference

# Get multiple values
- get:
    - preference1
    - preference2

Set Step

Store values for later use:

YAML
# Set a single value
- set:
    user_name: John

# Set multiple values
- set:
    count: $ len(_.results)
    has_data: $ _.count > 0

Values stored using the set step are added to the workflow’s global state object, which can be accessed anywhere in the workflow using state.variable_name. For example:

YAML
# Access previously set values
- evaluate:
    greeting: $ f"Hello, {state.user_name}!"
    data_status: $ f"Has data: {state.has_data}, Count: {state.count}"

Each subworkflow has its own isolated state object. Values set in one subworkflow are not accessible from other subworkflows or the parent workflow.

Label Step

Label a step to make it easier to identify and access those values later in any step:

YAML
# Step 0: Set a single value
- set:
    user_name: John
  label: get_user_name

# Step 1: Set multiple values
- set:
    count: $ len(_.results)
    has_data: $ _.count > 0
  label: get_count_and_has_data

In any steps following the label step, you can access the values set in the label step using the $ steps['label_name'].input.attribute_name or $ steps['label_name'].output.attribute_name syntax. For example:

YAML
- evaluate:
    user_name: $ steps['get_user_name'].output.user_name
- evaluate:
    count: $ steps['get_count_and_has_data'].output.count
    has_data: $ steps['get_count_and_has_data'].output.has_data

Iteration Steps

Foreach Step

Iterate over a collection:

YAML
# Simple foreach
- foreach:
    in: $ _.items
    do:
      log: $ f'Processing {_}'

# Foreach with complex processing
- foreach:
    in: $ _.documents
    do:
      tool: analyze
      arguments:
        text: $ _.content
      evaluate:
        results: $ _ + [_.analysis]

Map-Reduce Step

Process collections in parallel:

YAML
# Simple map-reduce
- over: $ _.urls
  map:
    tool: fetch_content
    arguments:
      url: $ _
  reduce: $ results + [_]

# Map-reduce with parallelism
- over: $ _.queries
  map:
    tool: web_search
    arguments:
      query: $ _
  parallelism: 5 # Number of parallel steps to execute
  • By default the parallelism if not mentioned is 100. If mentioned, it is the maximum number of steps that can run in parallel concurrently.
  • When using over step, the map step is executed for each value in the collection.
  • The reduce step is executed after the map step.

Conditional Steps

If-Else Step

Conditional execution:

YAML
# Simple if
- if: $ _.count > 0
  then:
    log: Found results

# If-else
- if: $ _.score > 0.8
  then:
    log: High score
  else:
    log: Low score

# If-else with multiple conditions
- if: $ 1 > 0
  then:
    if: $ 2 > 1
    then:
      evaluate:
        x: y
  else:
    evaluate:
      x: z

Switch Step

Multiple condition handling:

YAML
# Switch statement
- switch:
    - case: $ _.category == "A"
      then:
        - log: Category A
    - case: $ _.category == "B"
      then:
        - log: Category B
    - case: $ _  # Default case
      then:
        - log: Unknown category

Other Control Flow

Sleep Step

Pause execution:

YAML
# Sleep for duration
- sleep:
    seconds: 30

# Sleep with different units
- sleep:
    minutes: 5
    # hours: 1
    # days: 1

Return Step

Return values from workflow:

YAML
# Simple return
- return: $ _.result

# Structured return
- return:
    data: $ _.processed_data
    metadata:
      count: $ _.count
      timestamp: $ datetime.now().isoformat()

Yield Step

Execute subworkflows:

YAML
# Yield to subworkflow
- yield:
    workflow: process_data
    arguments:
      input_data: $ _.raw_data

# Yield with result handling
- yield:
    workflow: analyze
    arguments:
      text: $ _.content
- evaluate:
    analysis_result: $ _

Log Step

Log messages or specific values:

YAML
- log: $ f'Processing completed for item {item_id}'

Error Step

Handle errors by specifying an error message:

YAML
- error: Invalid input provided

Example: Complex Workflow

Here’s an example combining various step types:

YAML
# yaml-language-server: $schema=https://raw.githubusercontent.com/julep-ai/julep/refs/heads/dev/schemas/create_task_request.json
name: Multi-Step Task Demonstration
description: A demonstration of multi-step task processing with research and summarization capabilities.

################################################################################
############################# INPUT SCHEMA #####################################
################################################################################
input_schema:
  type: object
  properties:
    topic:
      type: string
      description: The topic to research and summarize.

################################################################################
############################# TOOLS ############################################
################################################################################

# Describing the tools that will be used in the workflow
tools:
- name: web_search
  type: integration
  integration:
    provider: brave
    setup:
      api_key: "DEMO_API_KEY"

################################################################################
############################# MAIN WORKFLOW ####################################
################################################################################
main:
# Step 0: Generate initial research questions
- prompt:
  - role: system
    content: >-
      $ f'''
      You are a research assistant. Your task is to formulate three specific research questions about the given topic: {steps[0].input.topic}'''
  unwrap: true

# Step 1: Web search for each question
- foreach:
    in: $ _.split('\\n')
    do:
      tool: web_search
      arguments:
        query: $ _

# Step 2: Extract relevant information
- evaluate:
    relevant_info: $ [output for output in _]

# Step 3: Process and summarize information
- if: $ len(_.relevant_info) >= 3
  then:
      prompt:
      - role: system
        content: >-
          $ f'''
          Summarize the following information about {steps[0].input.topic}:
          {_.relevant_info}'''
      unwrap: true
  else:
      prompt:
      - role: system
        content: >-
          $ f'''
          Not enough information gathered. Please provide a brief overview of {steps[0].input.topic} based on your knowledge.'''
      unwrap: true

# Step 4: Record the summary
- log: >-
    $ f'''
    Summary for {steps[0].input.topic}: {_}'''

# Step 5: Prepare final output
- return: 
    summary: $ _
    topic: $ steps[0].input.topic

Best Practices

Step Organization

  • Group related steps logically
  • Use comments to explain complex steps
  • Keep step chains focused and manageable

Error Handling

  • Use if-else for error conditions
  • Provide fallback options
  • Log important state changes

Performance

  • Use parallel execution when possible
  • Optimize data passing between steps
  • Cache frequently used values

Support

If you need help with further questions in Julep: