assist: The Assist Seam (opt-in, degrade-only LM suggestions)#

The deterministic degradation path always runs and always produces a result; an Assist can only improve a lowering the rules flagged as a judgment call. NullAssist is the default and the only path when assists are disabled – that mode makes zero network attempts. The gateway-backed assist posts tiny schema-constrained prompts to the local OpenAI-compatible gateway and memoizes results on disk by node content hash.

get_assist is the one seam callers need: it resolves enabled/config precedence and returns either NullAssist (disabled), GatewayAssist (enabled without caching), or CachedAssist wrapping a gateway (enabled with caching), importing network-facing modules only when enabled.

The Assist Seam (opt-in, degrade-only LM suggestions).

The deterministic degradation path always runs and always produces a result; an Assist can only improve a lowering the rules flagged as a judgment call. NullAssist is the default and the only path when assists are disabled – that mode makes zero network attempts. The gateway-backed assist posts tiny schema-constrained prompts to the local OpenAI-compatible gateway and memoizes results on disk by node content hash.

get_assist is the one seam callers need: it resolves enabled/config precedence and returns either NullAssist (disabled), GatewayAssist (enabled without caching), or CachedAssist wrapping a gateway (enabled with caching), importing network-facing modules only when enabled.

class myform.assist.Assist#

The assist seam: improve a deterministic lowering, or defer to it.

suggest(request: AssistRequest) → AssistResult | None#

Return an improved suggestion for request, or None to use the fallback.

class myform.assist.AssistRequest(**data: Any)#

One improve-this-lowering ask, built by the degradation engine from a flagged node.

node is the flagged node’s model_dump(mode='json') payload rather than the live pydantic object – an Assist never needs to reconstruct a typed node, only read it, and the payload is directly usable as LM prompt content. content_hash is that same node’s ~myform.model.Node.content_hash (the cache key ingredient); carrying it here rather than recomputing it from node keeps Assist implementations from needing the node model at all.

node: dict[str, Any]#

The flagged node, dumped (model_dump(mode='json')); never the live pydantic object.

content_hash: str#

~myform.model.Node.content_hash of node – the assist-cache key ingredient.

source_format: str#

The document’s source format name (e.g. 'myst').

target_format: str#

The writer’s target format name (e.g. 'typst').

fallback: str#

A human-readable description of what the deterministic engine would do without an assist.

purpose: PurposeTag#

Which kind of judgment call this is; see PurposeTag.

class myform.assist.AssistResult(**data: Any)#

One assist answer: a replacement/suggestion plus how much to trust it.

Exactly one of replacement (ready-to-use text) or suggestion (a structured lowering hint the degradation engine interprets, e.g. a placement/ordering choice) must be set – a result that supplies neither is a decline in disguise and should be a bare None from Assist.suggest instead.

replacement: str | None#

Ready-to-use replacement text, when the judgment call is textual (e.g. a caption summary).

suggestion: dict[str, Any] | None#

A structured lowering hint (e.g. a placement choice) for the degradation engine to apply.

confidence: float#

How much to trust this answer, in [0, 1].

note: str#

A short human-readable note (surfaced in the ConversionReport).

class myform.assist.CachedAssist(inner: Assist, cache_dir: Path | None = None)#

Wraps another Assist, memoizing suggest on disk by (node hash, target, model).

suggest(request: AssistRequest) → AssistResult | None#

Return the cached answer for request, asking inner and caching it on a miss.

class myform.assist.GatewayAssist(base_url: str | None = None, model: str = 'bulletin', api_key_env: str = 'LITELLM_TOKEN', timeout: float = 5.0)#

Posts tiny JSON-schema-constrained prompts to an OpenAI-compatible chat-completions API.

disabled#

Set on the first failure; short-circuits every later suggest call on this instance.

suggest(request: AssistRequest) → AssistResult | None#

Ask the gateway to improve request, or None on decline/failure/disabled.

class myform.assist.NullAssist#

The default, network-free assist – always defers to the deterministic fallback.

suggest(request: AssistRequest) → AssistResult | None#

Always decline; the only path when assists are disabled (AC5: zero network calls).

myform.assist.default_cache_dir() → Path#

$XDG_CACHE_HOME/myform/assist/, defaulting to ~/.cache/myform/assist/.

myform.assist.get_assist(config: Mapping[str, Any] | BaseModel | None = None, *, enabled: bool | None = None) → Assist#

Build the Assist configured by config, defaulting to disabled (NullAssist).

Precedence for whether assists are enabled: an explicit enabled keyword beats config['enabled'], which beats the default (off). When disabled, this returns NullAssist without importing .gateway/.cache from this function – a disabled seam makes zero network attempts (AC5), which holds regardless of import path, since neither submodule touches the network at import time either.

Call this once per conversion run and hold the result for every node it processes: GatewayAssist’s “disable after one failure, never retry” guarantee is scoped to that instance (self.disabled). Calling get_assist fresh per node would build a new, not-yet-disabled GatewayAssist each time and reproduce the retry storm the contract forbids – the process-wide warning would still log only once, but the network attempts would not be suppressed.

Parameters:
  • config – The typed config-layer AssistConfig (or its raw mapping equivalent). Unknown mapping keys are ignored, mirroring myform.options.MystOptions.from_options. Recognized keys: cache, and cache_dir.

  • enabled – Explicit override, taking precedence over config['enabled'].

Returns:

NullAssist when disabled; a ~myform.assist.gateway.GatewayAssist when enabled with caching off; otherwise a ~myform.assist.cache.CachedAssist wrapping the gateway.