moonprompt

A pure MoonBit prompt template engine for LLM applications

llm
prompt
template
jinja
wasm
moon add uiwcvb/moonprompt@0.1.0
Download zip
Author
Version
0.1.0
License
MIT
Last updated
4 days ago
Downloads
1

Dependencies

README

#moonprompt

CI mooncakes.io

moonprompt CLI demo

A pure MoonBit prompt-template engine implementing a practical Jinja2-style subset for LLM applications. It uses no FFI. The reusable src/lib package targets wasm-gc, JavaScript, and native. The file-reading CLI is native-only; browser/wasm hosts pass template and JSON strings to the portable library.

#Quick start

let vars : @moonprompt.Vars = [
("user", Object([("name", String("Ada"))])),
("examples", Array([String("one"), String("two")])),
]
let template = @moonprompt.compile(
"Hello {{ user.name | capitalize }}!{% for x in examples %} {{ x }}{% endfor %}",
).unwrap()
let result = template.render(vars).unwrap()

CLI:

moon run src/main -- render prompt.md data.json

The command reads both files, parses the root JSON object, renders, and prints the result. Templates may begin with flat YAML frontmatter; JSON values override its defaults. File IO uses the pure-MoonBit moonbitlang/x/fs package.

#Syntax support

FeatureExample
variable / field / index{{ user.name }}, {{ items[0] }}
filter chain{{ name \| trim \| upper }}
loop metadata{% for x in xs %}{{ loop.index }}{% endfor %}
conditional{% if ok %}yes{% elif retry %}later{% else %}no{% endif %}
comment{# invisible #}
escaping{{ untrusted \| e }}
filtersupper, lower, trim, capitalize, default, join, length, replace, truncate

See the syntax specification for details.

#LLM SDK integration

Render once, then convert role sections to whichever SDK's message structure is required:

let source = #|[system]
#|You are concise.
#|[user]
#|Summarize {{ topic }}.
let rendered = @moonprompt.render(source, [("topic", String("MoonBit"))]).unwrap()
let messages = @moonprompt.split_roles(rendered).unwrap()
// messages[i].role/content map directly to OpenAI-compatible chat messages.
// moonllm, mizchi/llm and tonyfettes/openai adapters only need this two-field map.

For few-shot prompts, place examples in an array and generate turns with a for block before calling split_roles.

A complete few-shot template and data file are provided in examples/.

#Architecture

flowchart LR Text --> Lexer --> Parser --> AST --> Renderer --> Prompt --> RoleSplitter Variables --> Renderer

The library is split by responsibility (lexer, parser, renderer, filters, frontmatter) while remaining one compilation package, which prevents cyclic dependencies and keeps public types simple. See architecture notes.

The public API also includes static template statistics and variable discovery, strict role validation, compiler-style diagnostics, deterministic Value/Vars adapters, render reports and limits, filter metadata, newline normalization, and opt-in conservative input-security inspection. Security inspection reports suspicious input; it does not claim to automatically prevent prompt injection.

#Development

moon check src/lib --target wasm-gc moon test src/lib --target wasm-gc moon check src/lib --target js moon test src/lib --target js moon check --target native moon test --target native

CI treats warnings as errors for every target. The checked library contains no backend-specific extern declarations; the native-only boundary is isolated in src/main and moonbitlang/x/fs.

The current suite contains more than 200 assertions across parser, renderer, filters, invalid syntax, JSON/frontmatter, roles, diagnostics, analysis, adapters, limits, and end-to-end document rendering.

发布到 mooncakes.io:

moon login moon package --list moon publish

#Roadmap / non-goals for MVP

Macro definitions, inheritance/includes, user filter registration, native function calls, a global escaping switch, template caching, and concurrent rendering are not implemented. Future releases may add these without changing the core AST contract.

#License

MIT. This is an original implementation and does not copy Jinja2 source code.