Lmml.Narrative.Renderer (lmml v0.2.0)

View Source

Turns a Lmml.Narrative.Resolver.t() into the list of typed content parts a multimodal LLM chat completion API expects for a message's content field -- the same %{"type" => ...} shape already built by hand elsewhere in this workspace for vision models (see dsh's DeepSeekHarness.CLI.ContextExpander/Brain.Session, which construct %{"type" => "image_url", "image_url" => %{"url" => data_uri}} and %{"type" => "text", "text" => text} parts).

Scope

The narrative's raw text becomes a single verbatim "text" part, included as-is rather than rewritten to strip or splice around embed occurrences. This is a deliberate difference from ContextExpander (which does rewrite free-form chat text, replacing an @ref with a "[Image: label]" placeholder): an lmml narrative's embed syntax (@name.ext / @@@name.ext ... @@@) is always clearly delimited and meaningful Markdown-superset text on its own, so a model reading the raw narrative already sees exactly where each embed occurs -- no destructive rewriting is needed to convey position, and every resolved embed's part is simply appended after the text part, in first-occurrence order.

Each resolved embed becomes one further content part, typed by mapping its name's file extension to a MIME type: image extensions become "image_url" parts (a base64 data URI, exactly like ContextExpander); everything else becomes an "attachment" part carrying the resolved content directly (%{"type" => "attachment", "name" => ..., "mime" => ..., "content" => ...}).

Summary

Functions

True when name's inferred MIME type is an image type.

The MIME type inferred for name by its file extension, defaulting to application/octet-stream when unrecognized.

Renders a resolved narrative into an ordered list of content parts, text first. Equivalent to render/2 with no options.

Renders a resolved narrative into an ordered list of content parts, text first, honoring opts.

Exports a narrative to standard, human-readable Markdown by replacing embed syntax (@name.ext and @@@name.ext ... @@@) with clean text placeholders (e.g. [Image: logo.png] or [Attachment: config.yaml]).

The total byte size of all resolved embed content in resolved (the sum of every embed's resolved bytes, not counting the narrative text). Useful for gauging a narrative's payload against a budget before rendering.

Types

content_part()

@type content_part() :: %{required(String.t()) => String.t() | map()}

opts()

@type opts() :: [{:max_embed_bytes, non_neg_integer()}]

Options for render/2.

Functions

image?(name)

@spec image?(String.t()) :: boolean()

True when name's inferred MIME type is an image type.

mime_type(name)

@spec mime_type(String.t()) :: String.t()

The MIME type inferred for name by its file extension, defaulting to application/octet-stream when unrecognized.

render(resolved)

@spec render(Lmml.Narrative.Resolver.t()) :: [content_part()]

Renders a resolved narrative into an ordered list of content parts, text first. Equivalent to render/2 with no options.

render(resolver, opts)

@spec render(Lmml.Narrative.Resolver.t(), opts()) :: [content_part()]

Renders a resolved narrative into an ordered list of content parts, text first, honoring opts.

Options:

  • :max_embed_bytes -- a per-embed byte budget. Any resolved embed whose content exceeds this size is omitted from the result (it is not truncated, since a partial image or config is worse than none). This is a safety valve for sending a narrative to an API with a payload/token budget: set it to the largest single asset you are willing to transmit and oversized embeds quietly drop out rather than blowing the request. Defaults to no limit (all embeds kept).

The narrative's raw text always becomes the first, verbatim "text" part regardless of opts.

to_md(target, opts \\ [])

Exports a narrative to standard, human-readable Markdown by replacing embed syntax (@name.ext and @@@name.ext ... @@@) with clean text placeholders (e.g. [Image: logo.png] or [Attachment: config.yaml]).

total_embed_bytes(resolver)

@spec total_embed_bytes(Lmml.Narrative.Resolver.t()) :: non_neg_integer()

The total byte size of all resolved embed content in resolved (the sum of every embed's resolved bytes, not counting the narrative text). Useful for gauging a narrative's payload against a budget before rendering.