Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 65 additions & 153 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,185 +1,97 @@
# Template Engine
# LLM-Based Generic Executor for TemplateArchiveProcessor

This is the [Accord Project](https://accordproject.org) template engine. Rich-text templates are defined in TemplateMark (either as markdown files, or JSON documents) and are then merged with JSON data to produce output documents. Templates may contain [TypeScript](https://www.typescriptlang.org) expressions.
## Overview

## Resources
This contribution extends the `TemplateArchiveProcessor` to support a **generic LLM-based contract logic executor**.

- [Getting Started Video](https://vimeo.com/manage/videos/845273411)
- [Template Playground](https://playground.accordproject.org)
- [Running Code](https://replit.com/@dselman/AccordProjectTemplateEngine-Hello-World)
The goal is to enable execution of Accord Project templates **without requiring explicit TypeScript `logic.ts` files**, by delegating reasoning to a Large Language Model (LLM).

The core template engine is a JSON to JSON transformation that converts TemplateMark JSON + agreement data (JSON) to AgreementMark JSON.
This implementation introduces:
- A LLM-based reasoning backend
- structured output (schema based) to ensure strictly formatted outputs
- A runtime output metadata population
- Minimal changes to existing execution flow to preserve backward compatibility

> Note: Use the `@accordproject/markdown-transform` project to convert markdown templates to TemplateMark JSON and to convert AgreementMark JSON to output formats (HTML, PDF, DOCX etc.). For command-line usage please use `@accordproject/template-cli`.
---

TemplateMark is a document object model that describes a rich-text template, with embedded variables, conditional sections, formulae etc. TemplateMark uses embedded TypeScript expressions for conditionals and calculations.
## Key Features

> The format of both TemplateMark and AgreementMark is specified using the [Concerto](https://concerto.accordproject.org) data modeling language.
### 1. LLM Execution Modes

At a high-level the template engine converts a TemplateMark DOM to an AgreementMark DOM, evaluating TypeScript expressions for conditional sections and formulae, and replaces variable references with variable values from the supplied agreement data.
The processor now supports three execution modes:

![Template Interpreter](./assets/template-interpreter.png)
| Mode | Behavior |
|------------|--------|
| `disabled` | Only TypeScript logic is used (default behavior) |
| `fallback` | Uses LLM only if no TypeScript logic is found |
| `force` | Always uses LLM, ignoring TypeScript logic |

## Execution Pipeline Overview
---
## 2. Structured Outputs

Internally, the Template Engine evaluates templates through a structured execution pipeline:
The LLM executor enforces strict JSON outputs using schemas derived from the template’s Concerto model.

1. **Type Validation**
The TemplateMark JSON document and the incoming agreement data are validated against their respective Concerto models to ensure structural and type correctness.
- Loads `schema.json` and extracts `State`, `Response`, and `Event`
- Resolves all `$ref` pointers into a fully expanded schema
- Enforces `additionalProperties: false` to prevent invalid fields

2. **TypeScript Compilation**
Embedded TypeScript expressions (such as conditionals, clauses, and formulae) are compiled into JavaScript using the `TemplateMarkToJavaScriptCompiler`.
**Schemas generated:**
- `init` → `{ state }`
- `trigger` → `{ result, state?, events }` (no `state` for stateless templates)

3. **User Code Evaluation**
During agreement generation, compiled JavaScript expressions are evaluated using the `JavaScriptEvaluator`.
The evaluator supports two execution strategies:
- `evalDangerously()` — Executes JavaScript directly within the current process.
This should only be used with trusted code or in a sandboxed environment (e.g., browser).
- `evalChildProcess()` — Executes JavaScript in an isolated Node.js child process for improved safety and isolation and is recommended for untrusted template content on the server.
**Provider behavior:**
- OpenAI / Anthropic → native schema enforcement (full schema)
- Others → guided output + runtime validation

4. **AgreementMark Generation**
The TemplateMark document is traversed, evaluated values are inserted into the document structure, and an AgreementMark JSON document is produced.
---

5. **Output Validation**
The generated AgreementMark document is validated before being returned.
### 3. Runtime Output MetaData Population

This layered architecture ensures type-safety, deterministic execution of template logic, and isolation during runtime evaluation.
Since LLM outputs may omit runtime metadata, a normalization layer ensures compatibility with Accord runtime expectations.

## Hello World Template
The executor automatically injects:

Let's create the simplest template imaginable, the infamous "hello world"!
- `$timestamp` into:
- `result`
- `events`
- `$identifier` into:
- `state`
---

> The code for this test is available at: https://github.com/accordproject/template-engine/blob/main/test/HelloWorld.test.ts
### 4. Logging and Execution Visibility

### Template Data Model
Execution paths are explicitly logged:

First create a template data model in Concerto syntax. The data model defines the structure of the data to be merged with the template. In this case the template model contains a single property `message` of type `String`. The property is required (it is not `optional`).
- Execution mode (`force`, `fallback`, `disabled`)
- Selected executor:
- TypeScript logic
- LLM executor

```javascript
namespace helloworld@1.0.0
This allows users to verify behavior at runtime.

@template
concept TemplateData {
o String message
}
```

### TemplateMark (extended markdown)

Next define the TemplateMark for the template. In this case it is the plain-text world `"Hello"` followed by a space, then the variable `message` followed by `"."`.

```markdown
Hello {{message}}.
```
---

> Note that in this case the template is defined using an extended markdown syntax (rich-text with embedded variables etc.). The `@accordproject/markdown-transform` packages are used to convert the markdown to TemplateMark JSON, for use by the template engine.

### Generate AgreementMark from Data (JSON)

Define an **instance** of the `helloworld@1.0.0.TemplateData` data model. In this case setting the value of the `message` property to the string "World".

```typescript
const data = {
$class: 'helloworld@1.0.0.TemplateData',
message: 'World',
};
```
### Output AgreementMark (JSON)

When the TemplateMark and the data JSON is passed to the Template Engine it merges the two, in this case by simply replacing the reference to the `message` variable with its value from the data JSON and to produce an AgreementMark JSON document.

This AgreementMark JSON document can then be passed to the `@accordproject/markdown-transform` modules for conversion to markdown, PDF, HTML.

```json
{
"$class": "org.accordproject.commonmark@0.5.0.Document",
"xmlns": "http://commonmark.org/xml/1.0",
"nodes": [
{
"$class": "org.accordproject.commonmark@0.5.0.Paragraph",
"nodes": [
{
"$class": "org.accordproject.commonmark@0.5.0.Paragraph",
"nodes": [
{
"$class": "org.accordproject.commonmark@0.5.0.Text",
"text": "Hello "
},
{
"$class": "org.accordproject.ciceromark@0.6.0.Variable",
"value": "World",
"name": "message",
"elementType": "String"
},
{
"$class": "org.accordproject.commonmark@0.5.0.Text",
"text": "."
}
]
}
]
}
]
}
## Installation and running the example
Inside the repository root:
```bash
npm install
npm install openai @anthropic-ai/sdk @google/genai @mistralai/mistralai
npm run build
```

## Next Steps

The Hello World example just scratches the surface of what can be accomplished! TemplateMark can define optional sections, conditional sections, TypeScript formulae/calculations and even reference external data.

Refer to the [full](https://github.com/accordproject/template-engine/tree/main/test/templates/good/full) example for details.

> More detailed syntax documentation is to come!
Read the existing documentation at: https://docs.accordproject.org/docs/markup-templatemark.html

## Why create a new template engine?

There are many great Open Source template engines available, such as [Mustache](https://mustache.github.io), [Handlebars](https://handlebarsjs.com) or [EJS](https://ejs.co), so why create yet another?

### 1. Input and Output Format Agnostic

Most template engines are fundamentally **text based** — i.e. they treat templates as text strings and are glorified "find and replace" machines. This approach creates a coupling between the input format of the template, say, a DOCX file, and the output of the template engine, which in the case of a DOCX template, has to be a DOCX file. This makes supporting multiple input and output formats difficult.

The Accord Project template engine breaks the coupling between the template input format and the engine output format, and moves data format conversion outside of the core template engine. Templates at the engine level are TemplateMark JSON documents and the output from the template engine is an AgreementMark JSON document. Separate libraries are used to convert source templates into TemplateMark JSON, or to render AgreementMark JSON to an output format.
Sample templates are provided in `examples` folder:

This flexibility allows a markdown template to be created that is used to create HTML, PDF or DOCX. One can even imagine using DOCX templates to create HTML or PDF files, or other scenarios.
```bash
cd examples/stateful/full-payment-upon-demand

### 2. TemplateMark as a Defined Format
# Run to get the schema format
concerto compile --model model/model.cto --target jsonschema
# some examples have clause.cto insted of model.cto

TemplateMark JSON is a well-defined file format, meaning that powerful template editors can be created to define it: including widgets and user-experience for defining conditional sections, and formulae, and offering template preview and integrated testing. Template editing is closer to programming in our opinion than word-processing.
# Set API Key
export OPENAI_API_KEY="your_api_key_here"
export ANTHROPIC_API_KEY=="your_api_key_here"

We encourage community and commercial innovation in this area!

### 3. Full Logic Support

Unlike some templating systems which prohibit, or minimize, logic in templates, Accord Project templates fully embrace templates that may contain sophisticated logic: conditional logic to determine what text to include, or even calculations, for example to calculate the monthly payments for a mortgage based on the term of the mortgage, the amount and the interest rate.

### 4. Type-safety

Given the ability for templates to contain logic there's an imperative to ensure that the templates are **safe** - i.e. when a template is merged with well-structured data it is guaranteed to produce well-structured output.

Too many templating engines fail in unpredictable ways at runtime, or silently generate invalid output, when presented with data — unacceptable for enterprise usage.

Accord Project templates are therefore **strongly-typed**. The logic in templates is expressed in [TypeScript](https://www.typescriptlang.org). TypeScript is a strongly-typed, general purpose programming language, supported by a vibrant Open Source and enterprise community. TypeScript compiles to JavaScript for easy execution on most platforms.

### 5. Data Model

The rich-text with variables of a template is associated with a [Concerto data model](https://concerto.accordproject.org). The Concerto data model defines the structure of the data required for the template, and is used to statically compile the template and verify type-safety, and is also used at runtime to ensure that incoming data is well structured.

### 6. Compilation

Templates may be statically compiled to TypeScript programs, enforcing type-safety, ensuring that no unsafe code evaluation ("eval") is required at runtime, and easing integration into applications.

The template compiler is a separate project, hosted here: https://github.com/accordproject/template-compiler

## Install

Note that this module is primarily intended for tool authors, or developers embedding template engines within applications. For command-line usage please refer to the `@accordproject/template-cli` package which implements a full pipeline to convert markdown templates plus JSON data to supported output formats, such as HTML, DOCX or PDF.

```
npm install @accordproject/template-engine --save
# Run the Example
node run.js
```

## License <a name="license"></a>
Accord Project source code files are made available under the Apache License, Version 2.0 (Apache-2.0), located in the LICENSE file. Accord Project documentation files are made available under the Creative Commons Attribution 4.0 International License (CC-BY-4.0), available at http://creativecommons.org/licenses/by/4.0/.

4 changes: 4 additions & 0 deletions examples/stateful/full-payment-upon-demand/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Full Payment upon Demand

Payment made when a single milestone is reached

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
// Generated code for namespace: concerto.decorator@1.0.0

// imports
import {IConcept} from './concerto@1.0.0';

// interfaces
export interface IDecorator extends IConcept {
}

export interface IDotNetNamespace extends IDecorator {
namespace: string;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
// Generated code for namespace: concerto

// imports

// interfaces
export interface IConcept {
$class: string;
}

export interface IAsset extends IConcept {
$identifier: string;
}

export interface IParticipant extends IConcept {
$identifier: string;
}

export interface ITransaction extends IConcept {
}

export interface IEvent extends IConcept {
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
// Generated code for namespace: concerto@1.0.0

// imports

// Warning: Beware of circular dependencies when modifying these imports
import type {
IFullPaymentUponDemandState
} from './org.accordproject.fullpaymentupondemand@0.2.0';
import type {
IDigitalMonetaryAmount,
DigitalCurrencyCode,
IMonetaryAmount,
CurrencyCode,
ICurrencyConversion
} from './org.accordproject.money@0.3.0';

// Warning: Beware of circular dependencies when modifying these imports
import type {
IContract,
IClause
} from './org.accordproject.contract@0.2.0';
import type {
IState
} from './org.accordproject.runtime@0.2.0';

// Warning: Beware of circular dependencies when modifying these imports
import type {
IRequest,
IResponse
} from './org.accordproject.runtime@0.2.0';

// Warning: Beware of circular dependencies when modifying these imports
import type {
IPaymentObligationEvent
} from './org.accordproject.fullpaymentupondemand@0.2.0';
import type {
IObligation
} from './org.accordproject.runtime@0.2.0';

// interfaces
export interface IConcept {
$class: string;
}

export type ConceptUnion = IFullPaymentUponDemandState |
IDigitalMonetaryAmount |
IMonetaryAmount |
ICurrencyConversion;

export interface IAsset extends IConcept {
$identifier: string;
}

export type AssetUnion = IContract |
IClause |
IState;

export interface IParticipant extends IConcept {
$identifier: string;
}

export interface ITransaction extends IConcept {
$timestamp: Date;
}

export type TransactionUnion = IRequest |
IResponse;

export interface IEvent extends IConcept {
$timestamp: Date;
}

export type EventUnion = IPaymentObligationEvent |
IObligation;

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
// Generated code for namespace: org.accordproject.contract@0.2.0

// imports

// Warning: Beware of circular dependencies when modifying these imports
import type {
ITemplateModel
} from './org.accordproject.fullpaymentupondemand@0.2.0';
import {IAsset} from './concerto@1.0.0';

// interfaces
export interface IContract extends IAsset {
contractId: string;
}

export interface IClause extends IAsset {
clauseId: string;
}

export type ClauseUnion = ITemplateModel;

Loading