README
View Source
lmml
lmml is a pure-Elixir markup language for talking to LLMs, designed as
a strict superset of Markdown: any bare .md/text file already parses
and means something sensible as an lmml narrative.
A narrative can be stored in one of two forms:
.lmml-- a bare, self-contained Markdown-superset text file. There is nowhere for an external file to live alongside it, so every embed it mentions must be carried inline..lmmlz-- a zip archive whose canonical narrative entry is named by stripping the trailingzfrom the archive's own filename (convo.lmmlzcontainsconvo.lmml), plus whatever other files its narrative references.
Both forms express the same underlying model -- an ordered narrative
with zero or more named embeds -- differing only in whether each
embed's content is carried inline (@@@name ... @@@) or externally
(@name, resolved against the zip). See
docs/LANGUAGE_REFERENCE.md for the full
syntax and semantics.
Syntax at a glance
# Project context
Please review @diagram.png before the meeting.
@@@settings.yaml
model: gpt-5
temperature: 0.2
@@@@diagram.png is an external reference (resolved from a .lmmlz
archive's entries); @@@settings.yaml ... @@@ is an inline embed
(carried directly in the text). Everything else is ordinary Markdown.
Installation
Add lmml to your list of dependencies in mix.exs:
def deps do
[
{:lmml, "~> 0.1"}
]
endUsage
{:ok, bundle} = Lmml.Bundle.open("convo.lmmlz")
{:ok, resolved} = Lmml.Narrative.Resolver.resolve(bundle)
content_parts = Lmml.Narrative.Renderer.render(resolved)content_parts is a list of %{"type" => ...} maps ready to hand to a
multimodal chat completion API's content field ("text",
"image_url", or "attachment" parts) -- see
Lmml.Narrative.Renderer.
To build a bundle programmatically:
{:ok, bundle} = Lmml.Bundle.new_zip("convo", "See @a.png.", %{"a.png" => image_bytes})
:ok = Lmml.Bundle.write!(bundle, "convo.lmmlz")See the examples/ directory in the repository for two complete worked
examples: a settings-only bare .lmml narrative, and a multi-turn
conversation with an embedded image packed as .lmmlz, resolved and
rendered end to end.
Mix tasks
mix lmml.new PATH-- creates a new, empty.lmmlnarrative file.mix lmml.pack SOURCE [DEST]-- externalizes every inline embed into a real zip entry, producing a.lmmlzarchive.mix lmml.inline SOURCE [DEST]-- inlines every external reference back into a bare.lmmltext file.mix lmml.validate SOURCE-- cross-checks a bundle's references, entries, and embed names, failing (non-zero exit) if any issue is found; suitable as a CI check.
Documentation
Full API and syntax documentation can be generated locally with ExDoc:
mix docs
or at hexdocs.
See docs/LANGUAGE_REFERENCE.md for the
complete language reference, including several non-obvious findings
about the underlying Md parser library that shaped the design (magnet
vs. block category ordering, an HTML-entity-escaping quirk in fenced
blocks, and Erlang's own zip path-traversal handling).
Status and possible future directions
Phases 0 through 6 of the implementation plan are complete: the
narrative parser, the core Embed/Document/Bundle data model, an
optional manifest.json convention, bundle-level validation, an LLM
payload resolver/renderer, lossless .lmml <-> .lmmlz packing, and a
handful of mix lmml.* CLI tasks.
As a stretch idea (not implemented, and out of this project's own
scope): a tool like dsh's Brain.SessionStore could optionally
persist and load its conversation sessions as .lmml/.lmmlz bundles
instead of (or alongside) its own session format, using
Lmml.Narrative.Resolver/Renderer directly to build the multimodal
message payloads it already sends to an LLM API.