Lmml.Narrative.Segment (lmml v0.2.0)

View Source

Splits a resolved lmml narrative into an ordered list of role-labeled turns (messages), each renderable to the same typed content-part shape Lmml.Narrative.Renderer already produces.

A bare lmml narrative is a single blob of prose plus embeds, rendered as one message. But a narrative frequently represents a multi-turn conversation -- the examples/ narrative uses headings like ## Turn 1 -- user / ## Turn 2 -- assistant. Segment turns that structural convention into an actual list of messages, so a whole conversation carried in one .lmml/.lmmlz can be sent to a chat completion API as a sequence of {role, content} messages rather than a single prompt.

Turn delimiters

A role delimiter splits the narrative and opens a new turn. Two spellings are recognized, both case-insensitive:

  • A Markdown heading whose text names a role, e.g. ## Turn 1 -- user, # assistant, ### system. The role is the first whole-word role token in the heading's text.
  • An HTML comment containing only a role, e.g. <!-- user -->, <!-- assistant -->.

The supported roles are :user, :assistant, :system, and :tool (see roles/0). The delimiter line itself is a structural marker and is not included in the turn it opens.

Preamble and consecutive roles

Narrative text before the first delimiter is a preamble and becomes a :context turn (so nothing the author wrote is ever dropped). Consecutive delimiters with the same role still open separate turns -- ## Turn 1 -- user followed by ## Turn 2 -- user yields two distinct :user messages, since consecutive user messages are legitimately distinct in a transcript.

Which embeds belong to which turn

Each embed's marker is located by byte offset in the full narrative, and the embed is assigned to the turn whose text region contains that offset. Inline @@@name ... @@@ fences and in-prose @name references are both handled; an embed opened inside one turn belongs to that turn even if its fence were to span a boundary. Because a resolved narrative lists each distinct embed name once (first occurrence), only first occurrences are located.

Rendering

render_turns/2 maps each turn through Lmml.Narrative.Renderer (by building a per-turn Lmml.Narrative.Resolver), yielding [%{role: role, content: [content_part, ...]}] ready to hand to an API's messages field.

Summary

Functions

The roles that map onto real message roles (:context is excluded).

Segments resolved and renders each turn through Lmml.Narrative.Renderer, returning [%{role: role, content: [content_part, ...]}] -- the shape a chat completion API's messages field expects.

Every role Segment recognizes, including the synthetic :context preamble role.

Splits a resolved narrative into an ordered list of t() turns.

Types

role()

@type role() :: :context | :user | :assistant | :system | :tool

t()

@type t() :: %Lmml.Narrative.Segment{
  embeds: [Lmml.Narrative.Resolver.resolved_embed()],
  narrative: binary(),
  role: role()
}

Functions

message_roles()

@spec message_roles() :: [role()]

The roles that map onto real message roles (:context is excluded).

render_turns(resolved, opts \\ [])

@spec render_turns(
  Lmml.Narrative.Resolver.t(),
  keyword()
) :: [%{role: role(), content: [map()]}]

Segments resolved and renders each turn through Lmml.Narrative.Renderer, returning [%{role: role, content: [content_part, ...]}] -- the shape a chat completion API's messages field expects.

opts are forwarded to Lmml.Narrative.Renderer.render/2 (see its :max_embed_bytes), so a per-message payload budget applies uniformly.

roles()

@spec roles() :: [role()]

Every role Segment recognizes, including the synthetic :context preamble role.

segment(resolved, opts \\ [])

@spec segment(
  Lmml.Narrative.Resolver.t(),
  keyword()
) :: [t()]

Splits a resolved narrative into an ordered list of t() turns.

Segmentation of an already-resolved narrative is pure text processing and cannot fail, so this returns the turn list directly (like Lmml.Narrative.Renderer.render/1), not a tagged tuple.

See the moduledoc for the delimiter convention, preamble handling, and embed-to-turn assignment. opts currently accepts no options (reserved for future use) but is part of the public signature for forward compatibility.