Deterministic, scripted adapter for image-generation testing. Implements
ALLM.ImageAdapter. See spec §35.8.
Layer B — runtime. FakeImages is the canonical testing image adapter; it
ships in lib/ (not test/support/) because users need it for their
own application tests, mirroring the v0.2 ALLM.Providers.Fake
precedent.
What FakeImages is (and isn't)
FakeImages mostly ignores the %ALLM.ImageRequest{} passed to
generate/2. It does inspect :operation to enforce the
supported_operations/0 gate, and reads :metadata to round-trip onto
the response per the ImageAdapter contract; otherwise the scripted
response is produced irrespective of the request.
Script shapes
opts[:adapter_opts][:image_script] accepts a list of script entries.
See script/1 for the full grammar.
adapter_opts: [
image_script: [
{:ok, [%ALLM.Image{...}], usage: %ALLM.ImageUsage{...}},
{:error, %ALLM.Error.ImageAdapterError{reason: :rate_limited}},
{:retry_until_call, 3}
]
]{:retry_until_call, n} (added in 14.3) returns a synthetic
%ALLM.Error.ImageAdapterError{reason: :rate_limited, retry_after_ms: 0}
for the first n - 1 calls against this entry, then advances the
cursor to the next entry on call n. Vehicle for testing
ALLM.Retry.run/3 integration in ALLM.generate_image/3.
Multi-call scripting uses the same list — each call advances a
process-local cursor (or an explicit Agent cursor passed via
adapter_opts[:script_cursor] from start_script_cursor/0).
Cursor behaviour
By default the cursor lives in the process dictionary at
{:allm_fake_images_cursor, :erlang.phash2(scripts)} — isolated per
ExUnit test process (async: true), GC'd on pid-down. Pass
adapter_opts[:script_cursor] with the pid returned from
start_script_cursor/0 to share / isolate cursors explicitly across
processes.
Test-only capture seam
Pass adapter_opts[:capture_pid] with a pid to receive a side-channel
message every time generate/2 is invoked, BEFORE the script is
consulted. The message has the form:
{ALLM.Providers.FakeImages, :call, %{request: request, opts: opts}}This is purely a side-channel — it does NOT affect the response. It
exists so test files can assert on what the adapter received without
the Process.register/2 + named-pid pattern (which forces
async: false). With :capture_pid in scope, tests stay
async: true and use assert_receive {ALLM.Providers.FakeImages, :call, _} to pattern-match on the captured payload.
Examples
iex> img = ALLM.Image.from_binary(<<137, 80, 78, 71>>, "image/png")
iex> req = ALLM.ImageRequest.new(prompt: "a kestrel")
iex> opts = [adapter_opts: [image_script: [{:ok, [img]}]]]
iex> {:ok, resp} = ALLM.Providers.FakeImages.generate(req, opts)
iex> length(resp.images)
1
Summary
Types
One scripted image-generation result.
Functions
Read the current cursor index for an Agent-backed cursor. Used in tests to assert how many calls have been consumed.
Execute a scripted image-generation request. See spec §35.3.
Document and validate the script grammar for adapter_opts[:image_script].
Start an Agent-backed script cursor for cross-process multi-call scripting and for disambiguating content-equal scripts in the same process.
Return the list of operations FakeImages can perform.
Types
@type script_entry() :: {:ok, [ALLM.Image.t()]} | {:ok, [ALLM.Image.t()], keyword()} | {:error, ALLM.Error.ImageAdapterError.t()} | {:retry_until_call, pos_integer()}
One scripted image-generation result.
Functions
@spec cursor_index(pid()) :: non_neg_integer()
Read the current cursor index for an Agent-backed cursor. Used in tests to assert how many calls have been consumed.
@spec generate( ALLM.ImageRequest.t(), keyword() ) :: {:ok, ALLM.ImageResponse.t()} | {:error, ALLM.Error.ImageAdapterError.t()}
Execute a scripted image-generation request. See spec §35.3.
Returns {:error, %ImageAdapterError{reason: :unsupported_operation, metadata: %{operation: op}}} BEFORE consulting the script when
request.operation not in supported_operations() (entry-point gate per
the conformance contract).
Otherwise reads the script from opts[:adapter_opts][:image_script],
advances the process-local cursor, and returns the entry verbatim.
Empty / exhausted script returns
{:error, %ImageAdapterError{reason: :unknown, metadata: %{cause: :no_scripted_image}}}.
Propagates opts[:request_id] onto response.request_id when the
script does not already populate it. Round-trips request.metadata
onto response.metadata when the script does not populate it.
When opts[:adapter_opts][:capture_pid] is a pid, sends
{ALLM.Providers.FakeImages, :call, %{request: request, opts: opts}}
to that pid BEFORE the operation gate fires (so even rejected calls
are captured). Side-channel only — does not affect the response.
Examples
iex> img = ALLM.Image.from_url("https://example.com/x.png")
iex> req = ALLM.ImageRequest.new(prompt: "a kestrel")
iex> opts = [adapter_opts: [image_script: [{:ok, [img]}]]]
iex> {:ok, resp} = ALLM.Providers.FakeImages.generate(req, opts)
iex> resp.usage.images
1
iex> img = ALLM.Image.from_binary(<<1>>, "image/png")
iex> req = ALLM.ImageRequest.new(prompt: "a kestrel")
iex> opts = [adapter_opts: [image_script: [{:ok, [img]}], capture_pid: self()]]
iex> {:ok, _} = ALLM.Providers.FakeImages.generate(req, opts)
iex> receive do
...> {ALLM.Providers.FakeImages, :call, %{request: ^req}} -> :ok
...> after
...> 0 -> :no_message
...> end
:ok
@spec script([script_entry()]) :: :ok
Document and validate the script grammar for adapter_opts[:image_script].
Each entry is one of:
{:ok, [%Image{}, ...]}— return the listed images plus a default%ImageUsage{images: length(images)}.{:ok, [%Image{}, ...], usage: %ImageUsage{}}— return the listed images plus the supplied usage.{:error, %ImageAdapterError{}}— return the struct verbatim.
Returns :ok when the script is well-formed; raises ArgumentError
on the first invalid entry. (Validation is opt-in — the runtime
generate/2 path tolerates a mix of legal entries and surfaces a
cursor-exhausted shape on nil lookups.)
Examples
iex> img = ALLM.Image.from_binary(<<1>>, "image/png")
iex> ALLM.Providers.FakeImages.script([{:ok, [img]}])
:ok
@spec start_script_cursor() :: pid()
Start an Agent-backed script cursor for cross-process multi-call scripting and for disambiguating content-equal scripts in the same process.
Pass the returned pid as adapter_opts[:script_cursor]; subsequent
calls increment the cursor on the Agent rather than on the process
dictionary.
Examples
iex> pid = ALLM.Providers.FakeImages.start_script_cursor()
iex> ALLM.Providers.FakeImages.cursor_index(pid)
0
@spec supported_operations() :: [ALLM.ImageRequest.operation()]
Return the list of operations FakeImages can perform.
Defaults to all three image operations; tests asserting per-adapter
rejection paths typically construct a custom @behaviour ALLM.ImageAdapter module rather than narrowing this list.
Examples
iex> ALLM.Providers.FakeImages.supported_operations()
[:generate, :edit, :variation]