Template System

This document explains how soak’s template system works, combining Jinja2 for variable substitution with struckdown for structured LLM outputs.

Overview

soak templates have two components:

  1. Jinja2 - Template engine for inserting variables and logic
  2. struckdown - Syntax for extracting structured data from LLM responses

Jinja2 Templates

Basic Variable Substitution

Insert context variables using double curly braces:

You are a: 
Your research question is: 

Analyze this text:

Accessing Node Results

Reference previous node outputs by name:

nodes:
  - name: chunks
    type: Split

  - name: codes
    type: Map
    inputs:
      - chunks

  - name: themes
    type: Transform
    inputs:
      - codes

In templates:

---#themes

Here are all the codes from previous stage:



Now generate themes...

Default Input Variable

The `` variable references the first item in the inputs list:

- name: my_node
  type: Transform
  inputs:
    - previous_result
---#my_node

Process this:
  <!-- Equivalent to  -->

Strict Undefined Behavior

soak uses StrictUndefined mode – referencing undefined variables causes an error:

  <!-- ERROR: 'undefined_var' is undefined -->

This catches typos and missing context variables early.

Conditionals and Loops

Standard Jinja2 control structures work:




However, most templates don’t need control structures – use simple variable substitution.

struckdown Syntax

struckdown extracts structured data from LLM text responses using special [[syntax]].

Return Type Syntax

General format:

[[return_type:field_name]]

or with options:

[[return_type:field_name|option1,option2,option3]]

Available Return Types

Thematic Analysis Types

[[codes:field_name]] - Extract list of Code objects

Identify codes in the text:

[[code*:codes]]

Expected LLM output format:

# frustration_with_doctors

Frustration with medical professionals who dismiss symptoms

> "The doctor said it was all in my head"

Returns:

{
  "codes": [
    Code(
      slug="frustration_with_doctors",
      name="Frustration with medical professionals",
      description="...",
      quotes=["The doctor said..."]
    )
  ]
}

[[themes:field_name]] - Extract list of Theme objects

Group codes into themes:

[[theme*:themes]]

Expected LLM output format:

# Medical System Barriers

Participants struggle to access appropriate care

Codes: frustration_with_doctors, diagnostic_delay, treatment_access

Returns:

{
  "themes": [
    Theme(
      name="Medical System Barriers",
      description="...",
      code_slugs=["frustration_with_doctors", ...]
    )
  ]
}

Classification Types

See the struckdown package docs for more information on how to use struckdown syntax for classificaiton, but know that you can extract structured information using pick, bool and other types:

What is the sentiment?
[[pick:sentiment|positive,negative,neutral,mixed]]

Returns:

{"sentiment": "negative"}
Which symptoms are mentioned?
[[pick*:symptoms|fatigue,pain,insomnia,headache]]

Returns:

{"symptoms": ["fatigue", "pain"]}

Etc…

Template Sections

Inline vs External Templates

Templates can be defined inline (within .soak files) or in separate .sd files. External templates enable reuse across pipelines. See Hybrid Template System for details.

Section Separators

Templates are separated by triple-dash headers:

---#node_name_1

Template content for node_name_1...

---#node_name_2

Template content for node_name_2...

The #node_name must match a node name in the YAML header.

Nodes Without Templates

Not all nodes need templates:

  • Split, Reduce, Batch, Filter - No LLM, no template needed
  • Map, Transform, Classifier - Require templates

If a node with LLM has no template, you’ll get an error.

Context Variables

Default Context

Pipelines define default context in YAML:

name: my_pipeline

default_context:
  persona: Experienced qualitative researcher
  research_question: None

Access in templates:

You are a: 

CLI Context Variables

Override or add context via -c flag:

uv run soak my_pipeline.soak data/*.txt \
  -c research_question="What are recovery experiences?" \
  -c persona="Clinical psychologist"

Node-Specific Context

Access node results as context:

- name: themes
  type: Transform
  inputs:
    - codes
    - preliminary_themes
---#themes

Codes:


Preliminary themes:


Consolidate into final themes...

Special Variables

** - First input in node’s inputs list

** - Current item in Map node (within iteration)

** - TrackedItem metadata (available in item context)

Debugging Templates

Inspect Rendered Templates

Check the dump directory to see what LLM received:

uv run soak my_pipeline.soak data/test.txt -o test

# View prompt sent to LLM
cat test_dump/02_Map_code_chunks/0000_*_prompt.md

Conditional Templates

Use Jinja2 to adapt template based on context:


Provide concise descriptions (30-50 words per code).


[[code*:codes]]

Next Steps


This site uses Just the Docs, a documentation theme for Jekyll.