Skip to content

Template Engine

Estimated time to read: 5 minutes

The template engine of POML allows you to incorporate dynamic content and control structures. Here are some key features.

Expressions

You can use expressions enclosed in double curly brackets ({{ }}) to evaluate variables or expressions dynamically:

<poml>
  <p>Hello, {{name}}!</p>
</poml>

In this example, if name is set to "Alice" (e.g., using a <let> tag, described below), the output will be "Hello, Alice!".

Usage in Attributes

Expressions can also be used within attribute values:

<poml>
  <task caption="Task #{{index}}">This is task No. {{index}}.</p>
</poml>

This renders to the following when index is set to 1.

# Task #1

This is task No. 1.

Expression Usages

POML supports various JavaScript expressions within the double curly brackets. This includes but is not limited to:

  • Variables: {{variableName}}
  • Arithmetic: {{a + b}}, {{x * y}}, {{count / total}}
  • String Concatenation: {{firstName + " " + lastName}}
  • Array Access: {{myArray[0]}}
  • Object Property Access: {{myObject.propertyName}}
  • Function Calls: {{myFunction(arg1, arg2)}} (if myFunction is defined in the context)
  • Ternary Operators: {{condition ? valueIfTrue : valueIfFalse}}
  • Accessing loop variables: {{loop.index}} (explained in the "For Attribute" section)

Let Expressions

The <let> tag allows you to define variables, import data from external files, and set values within your POML template.

Syntax 1: Setting a variable from a value

<poml>
  <let name="greeting">Hello, world!</let>
  <p>{{greeting}}</p>
</poml>

This will output "Hello, world!". When using the content approach (as shown above), the text is treated as a literal string.

Alternatively, you can use the value attribute which must contain an evaluatable JavaScript expression:

<poml>
  <let name="greeting" value="'Hello, world!'" />
  <p>{{greeting}}</p>
</poml>

Note that when using the value attribute, string literals must be properly quoted (e.g., "'Hello, world!'" or '"Hello, world!"') since the value is evaluated as JavaScript. The value attribute can contain strings, numbers, arrays, objects, or any valid JavaScript expression.

Syntax 2: Importing data from a file

<poml>
  <let name="users" src="users.json" />
  <p>First user: {{users[0].name}}</p>
</poml>

This imports the contents of users.json and assigns it to the users variable. The src attribute specifies the path to the file (relative to the POML file). The optional type attribute can specify the file type (e.g., "json", "text", "csv"). If not provided, POML attempts to infer it from the file extension.

Syntax 3: Importing data from a file without a name

<poml>
  <let src="config.json" />
  <p>API Key: {{apiKey}}</p>
</poml>
If config.json contains { "apiKey": "your_api_key" }, this will output "API Key: your_api_key". When you use src without name, and the file content is a JSON object, the properties of that object are directly added to the context.

Syntax 4: Setting a variable using inline JSON

<poml>
  <let name="person">
    {
      "name": "Alice",
      "age": 30
    }
  </let>
  <p>Name: {{person.name}}, Age: {{person.age}}</p>
</poml>

This defines a person variable with the given JSON object. You can also specify the type attribute:

<poml>
  <let name="count" type="integer">5</let>
  <p>Count: {{count}}</p>
</poml>

Syntax 5: Setting a variable from an expression

<poml>
  <let name="base" value="10" />
  <let name="increment" value="5" />
  <let name="total" value="{{ base + increment }}" />
  <p>Total: {{ total }}</p>  <!-- Output: Total: 15 -->
</poml>

Type-Autocasting in Attributes

The attributes of components will be automatically cast based on their defined types in the component documentation. This means you don't have to worry about manually converting types in many cases.

  • Boolean: If an attribute is defined as a boolean, values like "true", 1, "1", or {{true}} will be cast to the boolean value true. Similarly, "false", 0, "0", or {{false}} will be cast to false.
  • Number: If an attribute is defined as a number, values like "123", 45.6, {{anyNumber}} or {{myNumber+1.3}} will be cast to their corresponding numeric values.
  • Object: If an attribute is defined as an object, POML will attempt to parse the attribute value as a JSON string. For example, data="{{{name: 'John', age: 30}}}" or data='{"name":"John","age":30}' will be parsed into the corresponding JavaScript object.
  • String: If an attribute is a string, no casting is performed.

In the following example, the first auto-casting happened at let, where true is converted to boolean at let expression.

<poml>
  <let name="boolVar" type="boolean" value="true"/>
  <let name="numVar" type="number" value="42"/>
  <let name="objVar" type="object" value="{{ { key: 'value' } }}"/>

  <MyComponent boolProp="{{boolVar}}" numProp="{{numVar}}" objProp="{{objVar}}" stringProp="hello"/>
</poml>

If MyComponent is defined with boolProp as boolean, numProp as number, objProp as object, and stringProp as string, the values will be interpreted and auto-casted again when MyComponent is used.

For Attribute

To loop over a list, use the for attribute. The syntax is for="itemName in listName".

<poml>
  <list>
    <item for="item in ['apple', 'banana', 'cherry']">{{item}}</item>
  </list>
</poml>

This will render a list with "apple", "banana", and "cherry".

Loop Variables

Inside the loop, you have access to special loop variables:

  • loop.index: The current iteration index (starting from 0).
  • loop.length: The total number of items in the list.
  • loop.first: true if it's the first iteration, false otherwise.
  • loop.last: true if it's the last iteration, false otherwise.

Example:

<poml>
<let name="all_demos" value='[
    { "input": "What is your name?", "output": "My name is POML." },
    { "input": "What can you do?", "output": "I can generate prompts." }
]'/>
  <examples>
    <example for="example in all_demos" chat="false" caption="Example {{ loop.index + 1 }}" captionStyle="header">
      <input>{{ example.input }}</input>
      <output>{{ example.output }}</output>
    </example>
  </examples>
</poml>

This will generate two examples, with captions "Example 1" and "Example 2", displaying the input and output from each demo in the all_demos array. Note that we use loop.index + 1 because loop.index starts from 0.

If Condition

You can conditionally render elements using the if attribute:

<poml>
  <let name="isVisible" value="true"/>
  <let name="isHidden" value="{{ !isVisible }}"/>
  <p if="isVisible">This paragraph is visible.</p>
  <p if="isHidden">This paragraph is hidden.</p>
</poml>

If isVisible is true, the first paragraph will be rendered. The second paragraph will not be rendered because isHidden is false. The value of if can be a simple variable name (which is treated as a boolean) or a POML expression.

Include Files

You can split prompts into multiple files and include them using the <include> tag.

<poml>
  <include src="snippet.poml" />
</poml>

The file specified in src is read and its contents are injected as if they were written in place. Variables from the current context are available inside the included file. The for and if attributes work as expected:

<poml>
  <include src="row.poml" for="i in [1,2,3]" />
  <include src="footer.poml" if="showFooter" />
</poml>