Skip to main content
v2

Thymeleaf and SpEL in Prompts and Verbose Messages

ConvEngine 2.0.9 routes prompt rendering and verbose text rendering through the shared ThymeleafTemplateRenderer.

This applies to:

  • ce_prompt_template.system_prompt
  • ce_prompt_template.user_prompt
  • ce_verbose.message
  • ce_verbose.error_message

It means you can use Thymeleaf text expressions backed by Spring Expression Language (SpEL) against live session/runtime data.

Where it is used

Rendering surfaces

SurfaceRendered byTypical use
ce_prompt_template.system_promptPromptTemplateRenderer -> ThymeleafTemplateRendererLLM behavior instructions
ce_prompt_template.user_promptPromptTemplateRenderer -> ThymeleafTemplateRendererCurrent request/context assembly
ce_verbose.messageDbVerboseMessageResolver -> ThymeleafTemplateRendererProgress text shown to UI
ce_verbose.error_messageDbVerboseMessageResolver -> ThymeleafTemplateRendererError text shown to UI
ConvEngineVerboseAdapter.publishText(...)VerboseMessagePublisher -> ThymeleafTemplateRendererDirect consumer-emitted UI verbose text

Available runtime variables

Common variables available during rendering:

  • user_input
  • resolved_user_input
  • standalone_query
  • intent
  • state
  • context
  • inputParams
  • schema
  • session

resolved_user_input is the preferred current-turn input when query rewrite / standalone query is present.

What Spring Expression Language (SpEL) means here

Thymeleaf is the text templating layer. SpEL is the expression language inside the ${...} part.

In other words:

  • [[${intent}]]
    • [[...]] is Thymeleaf text rendering
    • ${intent} is a SpEL expression

ConvEngine uses this so templates can read values, branch on conditions, and apply simple defaults without writing Java.

Common SpEL patterns

SpEL patterns you can use

PatternExampleWhat it does
Property access[[${context.customerId}]]Reads a nested value from runtime context.
Boolean check[[${inputParams.awaiting_confirmation == true}]]Evaluates a true/false condition.
Ternary[[${state == 'CONFIRMATION' ? 'Please confirm.' : 'Continuing.'}]]Chooses one of two strings based on a condition.
Null/default fallback[[${context.customerId != null ? context.customerId : 'UNKNOWN_CUSTOMER'}]]Supplies a fallback value when the real value is absent.
Logical operators[[${context.mcp != null && context.mcp.finalAnswer != null ? context.mcp.finalAnswer : 'Pending'}]]Combines multiple checks before rendering.

Supported practical operators you will use most:

  • equality: ==, !=
  • logical: &&, ||, !
  • comparisons: >, <, >=, <=
  • ternary: condition ? a : b
Recommended scope for SpEL

Use SpEL for lightweight reads, conditionals, and defaults. Keep business routing in ce_rule and pipeline steps, not in large prompt expressions.

Use Thymeleaf text expressions for new templates:

  • [[${intent}]]
  • [[${state}]]
  • [[${context.customerId}]]
  • [[${inputParams.confirmation_key}]]

Legacy placeholders still work:

  • {{user_input}}
  • {{context}}

But Thymeleaf expressions are the native path now.

Practical rule

Use [[...]] for rendered text output. Use ${...} inside conditions and default expressions.

Prompt examples

ce_prompt_template.user_prompt example
TEXT
User input:
[[${resolved_user_input}]]

Current intent/state:
- intent: [[${intent}]]
- state: [[${state}]]

Loan draft:
- customerId: [[${context.customerId}]]
- requestedAmount: [[${context.requestedAmount}]]
- tenureMonths: [[${context.tenureMonths}]]

If awaiting confirmation is true, ask for confirmation only.
Awaiting confirmation: [[${inputParams.awaiting_confirmation}]]

Conditional/default examples

You can use SpEL-backed Thymeleaf expressions for simple branching and fallbacks:

Conditional and fallback examples
TEXT
[[${context.customerId != null ? context.customerId : 'UNKNOWN_CUSTOMER'}]]
[[${inputParams.correction_applied == true ? 'Updated value captured.' : 'No correction applied.'}]]
[[${context.mcp != null && context.mcp.finalAnswer != null ? context.mcp.finalAnswer : 'Processing is still in progress.'}]]

SpEL examples for confirmation-first flows

These are the kinds of checks that pair well with SET_INPUT_PARAM, DialogueActStep, and CorrectionStep:

Confirmation-focused SpEL examples
TEXT
[[${inputParams.awaiting_confirmation == true ? 'Awaiting user confirmation.' : 'No confirmation gate active.'}]]
[[${inputParams.routing_decision == 'PROCEED_CONFIRMED' ? 'User confirmed. Start processing.' : 'Still waiting for confirmation.'}]]
[[${inputParams.correction_applied == true ? 'Updated ' + inputParams.correction_target_field + '.' : 'No correction applied.'}]]

Verbose message examples

ce_verbose.message example
SQL
INSERT INTO ce_verbose
(intent_code, state_code, step_match, step_value, determinant, message, error_message, priority, enabled)
VALUES
('LOAN_APPLICATION', 'CONFIRMATION', 'EXACT', 'CorrectionStep', 'CORRECTION_PATCH_APPLIED',
'Updated [[${correction_target_field}]]. Please confirm customer [[${context.customerId}]] again.',
'Could not apply the requested correction for [[${context.customerId}]].',
10, true);

Direct adapter example

ConvEngineVerboseAdapter with Thymeleaf text
JAVA
@Component
@RequiredArgsConstructor
public class LoanHook implements EngineStepHook {

private final ConvEngineVerboseAdapter verboseAdapter;

@Override
public void beforeStep(EngineStep.Name stepName, EngineSession session) {
verboseAdapter.publishText(
session,
this,
"PRECHECK_STARTED",
"Starting review for [[${context.customerId}]] in state [[${state}]]."
);
}
}

What happens when variables are missing

  • Legacy {{...}} placeholders are validated.
  • If a required legacy variable cannot be resolved, prompt rendering can fail with unresolved prompt metadata.
  • For Thymeleaf expressions, prefer explicit fallbacks when a value may be absent.
Avoid brittle templates

Do not assume every nested node exists. For optional values, use fallback expressions instead of hard-failing on deep property chains.

Best practices

  • Use resolved_user_input instead of user_input when current-turn rewrites matter.
  • Keep business control in ce_rule; use templates for wording, not state transitions.
  • Use ce_verbose / ConvEngineVerboseAdapter for UI progress text, not to mutate runtime state.
  • Prefer compact expressions; large branching logic belongs in rules or Java, not prompt text.