ALLM.Pipeline.LLM behaviour (allm_pipeline v0.1.0)

Copy Markdown View Source

The seam through which the package calls a host's LLM engine.

The package declares no host dependency (see this repo's CLAUDE.md §1), so ALLM.Pipeline.LLMStep cannot name a host's engine module (e.g. MyApp.LLMEngine). The engine is reached at RUNTIME through impl/0 instead, exactly as the repo is reached through ALLM.Pipeline.Config.repo/0 and the persistence adapter through ALLM.Pipeline.Store.impl/0.

The success shape is the host's, unchanged

generate_structured/4 returns the {:ok, %{parsed: _, tokens: _}} envelope the host's engine already returns, and the error term stays opaque to the package. This seam relocates the call; it does not redefine it. A host adapter is therefore a delegation, not a translation — e.g. a host's MyApp.Pipelines.LLM.

Engine names are the host's vocabulary

resolve_engine/1 takes an atom naming a call-site intent (:nano, :summarize, …) and returns whatever engine value the host's generate_structured/4 accepts. The package never inspects it and never validates the name — the vocabulary belongs to the host, and a step declaring engine: :nano is asserting that its host knows that name. An unknown name is the host adapter's error to raise.

There is no package default, and impl/0 raises

Unlike Store, Artifacts and Lock — each of which ships an adapter the package can fall back to — there is nothing here for the package to default to: an LLM adapter is a provider integration with credentials, retry policy and logging, all of which live in the host. A nil-returning or silently no-op default would be the documented fail-open shape (root CLAUDE.md: "a test-env default that does live I/O fails OPEN"), and its inverse — a default that quietly does nothing — is no better, because a step would then report success having called no model.

So impl/0 raises, naming the llm: registry key. That is the same shape ALLM.Pipeline.Config.repo/0 uses, and for the same reason: the alternative fails far from the cause.

Configuration

defmodule MyApp.Pipelines do
  use ALLM.Pipeline.Registry,
    repo: MyApp.Repo,
    store: , artifacts: , lock: ,
    llm: MyApp.Pipelines.LLM
end

llm: is optional on the registry: a host that runs no LLM steps should not have to name an engine. Declaring it writes config :allm_pipeline, ALLM.Pipeline.LLM, impl: MyApp.Pipelines.LLM, with put_new semantics, so an env-specific config-file override still wins (see ALLM.Pipeline.Registry, "Precedence").

Summary

Types

A host engine handle, opaque to the package.

A prompt string, or an explicit message list the host's engine understands.

The host's structured-output envelope, unchanged.

Callbacks

Dispatch a strict-mode structured-output request and return the host's envelope.

Resolve a call-site intent name (:nano, :summarize, …) to a host engine.

Functions

The host's LLM adapter.

Types

engine()

@type engine() :: term()

A host engine handle, opaque to the package.

Whatever resolve_engine/1 returns is passed straight back into generate_structured/4; nothing here inspects it.

prompt()

@type prompt() :: String.t() | [term()]

A prompt string, or an explicit message list the host's engine understands.

result()

@type result() ::
  {:ok, %{parsed: map(), tokens: non_neg_integer()}} | {:error, term()}

The host's structured-output envelope, unchanged.

parsed is the decoded JSON object with string keys — ALLM.Pipeline.LLMStep's coerce/2 reads it by wire property name.

Callbacks

generate_structured(prompt, schema, schema_name, engine)

@callback generate_structured(
  prompt :: prompt(),
  schema :: map(),
  schema_name :: String.t(),
  engine :: engine()
) :: result()

Dispatch a strict-mode structured-output request and return the host's envelope.

schema is the derived strict-mode JSON schema (__allm_schema__(:json_schema)); schema_name is the name the provider records the response format under.

resolve_engine(name)

@callback resolve_engine(name :: atom()) :: engine()

Resolve a call-site intent name (:nano, :summarize, …) to a host engine.

Raising on an unknown name is the adapter's responsibility: a typo'd engine: on a step must not silently fall back to a default engine.

Functions

impl()

@spec impl() :: module()

The host's LLM adapter.

Resolved at RUNTIME, like every config read in this package. Unlike the three adapter seams there is no package default — see the moduledoc — so this raises when the host declared no llm: rather than returning nil and failing inside a generated call_llm/1 with a BadFunctionError that names neither this key nor this package.