Skip to content
 
 

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lmml

lmml

CI Hex.pm Documentation License

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 trailing z from the archive's own filename (convo.lmmlz contains convo.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.

Requirements

  • Erlang/OTP 27+ (uses built-in :json module)
  • Elixir ~> 1.20

Installation

Add lmml to your list of dependencies in mix.exs:

def deps do
  [
    {:lmml, "~> 0.1"}
  ]
end

Usage

{:ok, bundle} = Lmml.open("convo.lmmlz")
{:ok, resolved} = Lmml.resolve(bundle)
content_parts = Lmml.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 split a multi-turn conversation into API-ready role messages:

messages = Lmml.render_turns(resolved)
# => [%{role: :user, content: [...]}, %{role: :assistant, content: [...]}]

To build a bundle programmatically:

{:ok, bundle} = Lmml.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 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 .lmml narrative file.
  • mix lmml.pack SOURCE [DEST] -- externalizes every inline embed into a real zip entry, producing a .lmmlz archive.
  • mix lmml.inline SOURCE [DEST] -- inlines every external reference back into a bare .lmml text file.
  • mix lmml.render SOURCE [--json] [--max-embed-bytes BYTES] -- renders a bundle into typed LLM content parts.
  • mix lmml.to_md SOURCE [DEST] -- exports a bundle into standard, human-readable Markdown with text placeholders.
  • 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.

About

Markdown-superset markup language for structuring LLM conversations, with a self-contained text form (.lmml) and a zip-archive form (.lmmlz) for carrying referenced files alongside the narrative.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages