For the complete documentation index, see llms.txt. This page is also available as Markdown.

Output Templating

Overview

Output Templating is one of EDDI's most powerful features—it allows you to create dynamic, data-driven responses that pull information from conversation memory, API responses, or context data. Instead of static text, your agent can generate personalized, contextual replies.

Why Output Templating Matters

Without templating:

"The weather is available"

With templating:

"The weather in {context.city} is {memory.current.httpCalls.weatherData.condition} with {memory.current.httpCalls.weatherData.temperature}°F"

Result:

"The weather in Paris is sunny with 75°F"

Powered by Qute

The output templating is evaluated by the Quarkus Qute templating engine, which provides native image compatibility and a clean, expressive syntax.

Note: EDDI v6 migrated from Thymeleaf to Qute. Stored templates are converted by V6QuteMigration at startup only when eddi.migration.v6-qute.enabled=true (default false), and imported agent ZIPs are converted by the import pipeline. See Upgrading from 5.x.

Common Use Cases

  • Personalization: Greet users by name: "Hello {context.userName}!"

  • API Response Formatting: Display data from HTTP calls: "Your order #{memory.current.httpCalls.orderData.orderId} is on the way"

  • Conditional Outputs: "{#if user.isPremium}Exclusive offer for you!{/if}"

  • Iteration: Loop through arrays: "Available options: {#for opt in options}{opt}{#if opt_hasNext}, {/if}{/for}"

What You Can Access

In templates, you have access to:

  • memory.current.* - Current step data (input, httpCalls, properties)

  • memory.last.* - Previous step data

  • memory.past.* - All earlier steps (list)

  • context.* - Context passed from your application

  • properties.* - Conversation properties (stored data)

  • memory.current.httpCalls.* - Responses from external APIs (per step)

Configuration

One of the coolest features of EDDI is it will allow you dynamically template your output based on data that you would receive from httpCalls or context information, making EDDI's replies rich and dynamic.

Enabling the feature:

While creating the agent you must include eddi://ai.labs.output to one of the workflows that will be part of the agent.

Important: The templating feature will not work if it is included before eddi://ai.labs.output extension, it must be included after.

Example

Here is how the output templating should be specified inside of a workflow.

Make sure the templating is defined after the output, not before.

Template Syntax Reference

Variable Output

Conditionals

Iteration

Iteration metadata available inside {#for}:

Variable
Description

item_index

0-based index

item_indexParity

odd or even

item_hasNext

true if not the last item

item_count

Total items in the collection

item_isFirst

true if first item

item_isLast

true if last item

String Methods

All standard String methods are available as natural method calls on string variables:

Expression
Description

{str.toLowerCase()}

Convert to lowercase

{str.toUpperCase()}

Convert to uppercase

{str.replace('old', 'new')}

Replace substring

{str.substring(5)}

Substring from index

{str.substring(0, 5)}

Substring range

{str.indexOf('x')}

Find character index

{str.contains('sub')}

Check if contains

{str.startsWith('pre')}

Check prefix

{str.endsWith('suf')}

Check suffix

{str.trim()}

Trim whitespace

{str.length()}

String length

{str.isEmpty()}

Empty check

Chaining is supported: {name.replace(' ', '-').toLowerCase()}

Custom Expression Utilities

EDDI provides custom namespace extensions for use in templates (output, httpcalls, property setters).

uuidUtils — ID & URI Utilities

Expression
Description

{uuidUtils:generateUUID()}

Generates a random UUID string

{uuidUtils:extractId(locationUri)}

Extracts the resource ID from an EDDI location URI

{uuidUtils:extractVersion(locationUri)}

Extracts the version number from an EDDI location URI

extractId and extractVersion work with both MongoDB ObjectIds (24-char hex) and PostgreSQL UUIDs (36-char with dashes):

Important: Avoid using .substring() with hardcoded offsets to extract IDs from URIs—use {uuidUtils:extractId(...)} instead. Hardcoded offsets break when switching between MongoDB (24-char ObjectIds) and PostgreSQL (36-char UUIDs).

Other Custom Extensions

Namespace
Expression
Purpose

json

{json:serialize(obj)}

JSON manipulation utilities

encoder

{encoder:base64(data)}

Text encoding utilities

What a template can reach

Templates run in a restricted engine. Beyond the namespaces above, the pass-through references (${vault:…}, ${connection:…}, ${vars:…}, ${caller:…}) and Qute's str:/time: helpers, no namespace is available — in particular config:, inject: and cdi: render as empty. The sections are {#if}, {#for}/{#each}, {#let}/{#set}, {#with} and {#when}/{#switch}; there is no {#include} or {#eval}. On objects other than maps, lists and strings, only properties (getters, record components, public fields) can be read — no method with arguments is invoked. A render is capped at eddi.templating.max-output-chars characters and eddi.templating.max-iterations loop iterations. See security.md.

Values are never re-evaluated. Whatever an expression resolves to — the user's message, a property, an API response — is written out literally, even if it contains {…}. Output that an httpcall/LLM/MCP postResponse builds from a response is rendered once and is not templated again by this task.

Migration from Thymeleaf (v5 → v6)

If you are upgrading from EDDI v5, set eddi.migration.v6-qute.enabled=true for the first boot and the stored templates are converted as below. A template that cannot be converted safely (one that generates template syntax) is left unchanged, and so is the rest of its document. The first boot that finds it logs an ERROR naming the collection, id and field; later boots list the documents already reported in a single WARN. The migration is not marked complete while any remain, so it checks them again on every boot and converts one as soon as it has been fixed by hand:

v5 (Thymeleaf)
v6 (Qute)

[[${variable}]]

{variable}

[(${variable})]

{variable}

[# th:each="x : ${list}"]...[/]

{#for x in list}...{/for}

[# th:if="${condition}"]...[/]

{#if condition}...{/if}

#strings.toLowerCase(var)

{var.toLowerCase()}

#strings.substring(var, 37)

{var.substring(37)}

#uuidUtils.method()

{uuidUtils:method()}

#json.method()

{json:method()}

a + '/' + b

{a}/{b}

Previewing a template

Rather than deploying an agent to find out what a template resolves to, resolve it directly:

Requires the eddi-admin or eddi-editor role.

conversationId is optional. With it, the template resolves against that conversation's real memory; without it, against built-in sample data. Because supplying a conversation returns the flattened variable values as well as the resolved text, the role check is paired with a per-conversation ownership check — an editor cannot read someone else's conversation this way.

A template that fails to render returns the failure in error rather than as an HTTP error, so a preview of a broken template still tells you which variables were available.

Additional Information:

Quarkus Qute documentation.

Last updated

Was this helpful?