Expand description
§normalize
Universal local-LLM consistency layer.
This module rewrites model-specific output dialects into the strict
OpenAI-shaped crate::domain::agent::LlmStreamEvent sequence that the
rest of the codebase expects. Adapters wrap the LLM stream once at the
port boundary; every downstream surface (Axum, CLI, Tauri, proxy)
consumes the canonical form.
Dialect selection is data-driven: a crate::domain::dialect::DialectSpec
— detected from the model’s own chat template at import time and persisted
per model — configures the delimited parser, and the same spec generates
the decode-time GBNF grammar, so parsing and enforcement cannot drift.
§Module map
tags—format:*constants, the legacy-row fallback vocabulary.error— non-fatalerror::NormalizationErrorsurfaced from parsers.parser— theparser::ToolCallParsertrait +parser::ParserOutput.parsers— concrete parser implementations, one file per dialect family.registry— the single dispatch site: spec → parser, plus the tag → builtin-spec fallback map.residue— chunk-safe scanner for dialect markup that survived normalization into client-visible text (the proxy’s drift alarm).
§Adding a new dialect
Usually: no code. A dialect whose template renders tool calls as
MARKERS{json}MARKERS is derived automatically by the template probe in
gglib-gguf and arrives here as a spec — registry::get_parser drives
the delimited parser with it.
For a new builtin (a dialect that needs a fallback tag because its templates are often stripped):
- Add a
pub const FORMAT_*totags. - Map the tag to a spec in
registry::dialect_for_tags.
Only a genuinely new body codec (a non-JSON, non-inner-XML body
encoding) needs parser code — a BodyCodec variant plus its decoder in
parsers; see CONTRIBUTING.md’s architecture-registry section.
The registry is the only place that knows the full set of parsers, by design — see the module docs there.
Future work: <think> handling still lives outside the spec —
stream strips think tags unconditionally and history keeps its own
marker constants. Folding reasoning markers into DialectSpec is
deliberate follow-up scope, since it changes behaviour for untagged
models.
Modules
| Module | LOC | Complexity | Coverage |
|---|---|---|---|
coerce.rs | |||
error.rs | |||
history.rs | |||
oneshot.rs | |||
parser.rs | |||
registry.rs | |||
residue.rs | |||
stream.rs | |||
tags.rs | |||
parsers/ |
Re-exports§
pub use registry::get_parser;pub use residue::ResidueScanner;pub use residue::scan_complete;
Modules§
- coerce 🔒
- Deterministic local repair of a tool-call body the parsers rejected.
- error 🔒
- Non-fatal error reporting from normalization parsers.
- history 🔒
- Cross-turn “thinking debt” removal for chat history.
- oneshot 🔒
- One-shot dialect normalization for non-streaming responses.
- parser 🔒
- The
ToolCallParsertrait and its companionParserOutput. - parsers 🔒
- parsers
- registry
- Dialect-spec parser dispatch.
- residue
- Chunk-safe scanner for dialect markup that reached client-visible text.
- stream 🔒
NormalizingStream— the single wrap point that canonicalises an LLM event stream.- tags
- Format-tag constants used to select a normalization parser.
Structs§
- Normalization
Error - A non-fatal normalization issue surfaced from a parser.
- Normalizing
Stream - Stream adapter that runs every event through a
ToolCallParserbefore re-emitting the normalized result. See module docs. - Parser
Output - Result of feeding one chunk of input to a parser.
Enums§
- Normalization
Error Kind - Discriminates the kind of malformation a parser detected.
Traits§
- Tool
Call Parser - Stream-stateful parser that normalizes a single LLM dialect into
canonical
ParserOutputfragments.
Functions§
- normalize_
chat_ completion_ body - Normalize a complete (non-streaming)
chat.completionresponse body in place, using the parser for the model’s resolveddialect. - strip_
thinking_ debt - Strip reasoning artifacts from prior assistant messages in
messages.