ALLM.Providers.Fake.Script (allm v0.3.0)

Copy Markdown View Source

Script shape detection, validation, and interpretation for ALLM.Providers.Fake. See spec §31.

Layer B — pure helper module, no runtime state.

Fake accepts two disjoint script shapes:

  • Spec §31 (user-facing) — the vocabulary the spec itself samples with {:text, "hi"}, {:finish, :stop}. Tags: :text, :tool_call, :tool_call_delta, :usage, :raw_chunk, :finish, :error (2-tuple), :delay, :sleep (deprecated alias of :delay).
  • Phase 3 harness — the vocabulary ALLM.Test.AdapterConformance and ALLM.Test.StreamAdapterConformance pass to StubAdapter. Tags: :ok, :error (3-tuple), :text_delta, :preflight_error, :error_event, :stream_error, and shared-semantics :tool_call / :finish.

This module exposes four entry points:

See spec §31 for the script entry grammar; §8 for the event union.

Summary

Types

A single Phase 3 harness-shape entry (non-streaming).

A single Phase 3 harness-shape entry (streaming).

Detected shape of a script — §31 (user-facing) or Phase 3 harness.

A single spec §31 script entry (one call's worth of events).

Functions

Classify a list of script entries as either :spec31 or :harness shape.

Fold a list of script entries into a single %ALLM.Response{}.

Translate a single script entry into a list of ALLM.Event values.

Validate adapter_opts at the Fake boundary.

Types

harness_adapter_entry()

@type harness_adapter_entry() :: {:ok, map()} | {:error, atom(), keyword()}

A single Phase 3 harness-shape entry (non-streaming).

harness_stream_entry()

@type harness_stream_entry() ::
  {:text_delta, String.t()}
  | {:finish, atom()}
  | {:preflight_error, atom(), keyword()}
  | {:error_event, atom(), keyword()}
  | {:stream_error, atom(), keyword()}

A single Phase 3 harness-shape entry (streaming).

shape()

@type shape() :: :spec31 | :harness

Detected shape of a script — §31 (user-facing) or Phase 3 harness.

spec31_entry()

@type spec31_entry() ::
  {:text, String.t()}
  | {:tool_call, keyword()}
  | {:tool_call_delta, keyword()}
  | {:usage, map()}
  | {:raw_chunk, term()}
  | {:finish, ALLM.Response.finish_reason()}
  | {:error, term()}
  | {:delay, non_neg_integer()}
  | {:sleep, non_neg_integer()}

A single spec §31 script entry (one call's worth of events).

Functions

detect_shape(entries)

@spec detect_shape([term()]) :: {shape(), [term()]}

Classify a list of script entries as either :spec31 or :harness shape.

Inspects only the leading entry's tag. The :error tag is disambiguated by tuple arity — 2-tuple → :spec31, 3-tuple → :harness. Shared-semantics tags (:finish, :tool_call) default to :spec31 (the interpreter handles both shapes identically for those tags, so the classification is inconsequential).

An empty list returns {:spec31, []} (the default).

Raises ArgumentError if the leading tag is unknown, with a message that mentions both vocabularies so script authors can spot typos.

Examples

iex> ALLM.Providers.Fake.Script.detect_shape([{:text, "hi"}, {:finish, :stop}])
{:spec31, [{:text, "hi"}, {:finish, :stop}]}

iex> ALLM.Providers.Fake.Script.detect_shape([{:ok, %{output_text: "hi"}}])
{:harness, [{:ok, %{output_text: "hi"}}]}

iex> ALLM.Providers.Fake.Script.detect_shape([])
{:spec31, []}

fold_to_response(entries)

@spec fold_to_response([spec31_entry() | harness_adapter_entry()]) ::
  ALLM.Response.t() | {:error, ALLM.Error.AdapterError.t()}

Fold a list of script entries into a single %ALLM.Response{}.

Handles both the spec §31 vocabulary (:text, :tool_call, :tool_call_delta, :usage, :raw_chunk, :finish, :error/2, :delay, :sleep) and the harness-shape terminal entries ({:ok, map}, {:error, reason, opts}). Harness-shape is one entry per call; the reducer returns on the first harness entry it sees.

A §31 {:error, term} entry short-circuits to {:error, %AdapterError{reason: :unknown, message: "scripted error", cause: term}}. {:delay, ms} / {:sleep, ms} call Process.sleep/1 so non-streaming callers can still exercise timeout paths. {:raw_chunk, _} is ignored (raw chunks have no place on %Response{}).

{:usage, map} calls struct!(ALLM.Usage, map) with last-write-wins semantics — passing an unknown field name (e.g., :prompt_tokens) raises KeyError at fold time; use the canonical field names from ALLM.Usage (spec §5.9a).

Returns %ALLM.Response{} on success (NOT {:ok, %Response{}} — the {:ok, _} wrapping is applied at the ALLM.Providers.Fake.generate/2 boundary in sub-phase 4.2), or {:error, %AdapterError{}} on script-defined failure.

Examples

iex> ALLM.Providers.Fake.Script.fold_to_response([{:text, "hi"}, {:finish, :stop}])
%ALLM.Response{output_text: "hi", finish_reason: :stop, tool_calls: [], usage: %ALLM.Usage{}}

interpret(entry)

@spec interpret(spec31_entry() | harness_stream_entry()) :: [ALLM.Event.t()]

Translate a single script entry into a list of ALLM.Event values.

Called per-entry by ALLM.Providers.Fake.stream/2. {:delay, ms} and {:sleep, ms} return [] — the stream runner handles the sleep directly before emitting the next event, so interpret/1 has no events to yield.

:text_completed is NOT emitted here. Whether to emit it depends on whether any :text was seen earlier in the same call; that state lives in the Stream.resource/3 accumulator, not here. interpret({:finish, _}) returns just [{:message_completed, _}]; the stream runner prepends :text_completed when appropriate. Revisit in sub-phase 4.3 if the orchestration needs change.

Deprecation

{:sleep, ms} is a deprecated alias for {:delay, ms} (spec §31). Passing a {:sleep, _} entry triggers a one-time Logger.warning/1 per BEAM lifetime (dedup via :persistent_term). Deletion target: v0.3.

validate!(opts)

@spec validate!(keyword()) :: :ok

Validate adapter_opts at the Fake boundary.

Guards (in order):

  1. Mixing :script and :scripts raises ArgumentError (spec §31).
  2. :script must be a list (of entries).
  3. :scripts must be a list of lists (each inner list is one call's script).
  4. :stream_script must be a list of lists.
  5. :script_cursor, when present, must be a pid or nil.

Returns :ok on success.

Users may call this directly on their adapter_opts for construction-time feedback (Non-obvious Decision #5 in the Phase 4 design doc) — Fake itself calls it at the first adapter invocation.