PtcRunner.LLM behaviour (PtcRunner v0.15.0)

Copy Markdown View Source

Provider-neutral LLM adapter boundary used by trusted Kernel provider builders.

prepare/2 seals a model selector into an immutable prepared target under a closed requirements map. callback/2 binds that target to a credential and cache flag and returns an arity-two requester. Kernel policy, retries, prompt construction, and protocol recovery live in shipped Lisp libraries rather than this transport adapter.

Summary

Types

Provider-reported token usage. :total_cost is absent when pricing is unavailable; a present zero-microunit object is a measured zero-cost response. Adapter responses may use a non-negative USD number or bounded decimal string; the Kernel immediately replaces it with this fixed-point object.

Callbacks

Make an LLM call.

Preload adapter-owned model metadata into a shared, process-independent store (e.g. a :persistent_term/ETS catalog) so the first per-request provider worker does not pay a large one-time load inside its bounded heap.

Prepares an adapter-owned request target and attests the sealed requirements.

Names the OTP application this adapter needs running to serve model.

Attests that the exact adapter target is safe to publish in canonical traces.

Functions

Returns the configured LLM adapter module.

Creates a normalized requester for a prepared model.

Returns the closed public warning fact for an uncataloged prepared model.

Resolves a configured selector into an immutable adapter-owned request target.

Types

catalog_status()

@type catalog_status() :: :cataloged | :uncataloged | :unavailable

chunk()

@type chunk() :: %{delta: String.t()} | %{done: true, tokens: tokens()}

message()

@type message() :: %{role: :system | :user | :assistant | :tool, content: String.t()}

output_limit()

@type output_limit() :: %{
  name: :max_tokens,
  value: pos_integer(),
  bindings: [output_limit_binding()]
}

output_limit_binding()

@type output_limit_binding() ::
  :application_limit
  | :installation_param
  | :configured
  | :adapter_default
  | :model_output_limit
  | :remaining_context

requester_context()

@type requester_context() :: %{llm_request_deadline_ms: integer() | nil}

response()

@type response() :: %{content: String.t(), tokens: tokens()}

runtime_binding()

@type runtime_binding() :: %{credential: binary() | nil, cache: boolean()}

tokens()

@type tokens() :: %{
  optional(:input) => non_neg_integer(),
  optional(:output) => non_neg_integer(),
  optional(:cache_creation) => non_neg_integer(),
  optional(:cache_read) => non_neg_integer(),
  optional(:total_cost) => usd_cost() | non_neg_integer() | float() | String.t()
}

tool_call_response()

@type tool_call_response() :: %{
  :tool_calls => [map()],
  :content => String.t() | nil,
  :tokens => tokens(),
  optional(:finish_reason) => atom(),
  optional(:output_limit) => output_limit()
}

usd_cost()

@type usd_cost() :: %{currency: String.t(), microunits: non_neg_integer()}

Provider-reported token usage. :total_cost is absent when pricing is unavailable; a present zero-microunit object is a measured zero-cost response. Adapter responses may use a non-negative USD number or bounded decimal string; the Kernel immediately replaces it with this fixed-point object.

Callbacks

call(model, invocation)

@callback call(model :: term(), invocation :: PtcRunner.LLM.Invocation.t()) ::
  {:ok, map()} | {:error, PtcRunner.Kernel.ProviderError.t()}

Make an LLM call.

The request map contains:

  • :system - System prompt string
  • :messages - List of message maps
  • :schema - JSON Schema map (triggers structured output when the sealed installation mode is json_schema or json_object)
  • :tools - Tool definitions (triggers tool calling)
  • :cache - Boolean for prompt caching

Returns {:ok, response} or {:error, reason}.

ensure_ready()

(optional)
@callback ensure_ready() :: :ok

Preload adapter-owned model metadata into a shared, process-independent store (e.g. a :persistent_term/ETS catalog) so the first per-request provider worker does not pay a large one-time load inside its bounded heap.

Optional. The Kernel invokes it during selected provider-application admission and capability-build time for direct embedding paths. An adapter may also invoke its own callback during audited local contract attestation; that path runs before provider activity inside a bounded worker, so it must use only process-independent local metadata. Implementations must be idempotent.

prepare_model(model, requirements)

@callback prepare_model(
  model :: String.t(),
  requirements :: PtcRunner.LLM.Requirements.t()
) ::
  {:ok, target :: term(), catalog_status(), PtcRunner.LLM.Requirements.t()}
  | {:error, term()}

Prepares an adapter-owned request target and attests the sealed requirements.

The returned attestation must be exactly the canonical requirements map. An adapter that cannot preserve the requested options, mode, reporting guarantees, or reservation authority returns {:error, :unsupported_model_option}. An adapter that has positively established an uncataloged target and cannot supply the USD rates required by a requested cost reservation returns the payload-free sentinel {:error, :uncataloged_cost_reservation_pricing_unavailable}. The Kernel maps only that exact sentinel to the pricing-specific model_contract_unsupported diagnostic and model_uncataloged warning; payload-bearing variants fail closed.

provider_application(model)

(optional)
@callback provider_application(model :: String.t()) :: :req_llm | nil

Names the OTP application this adapter needs running to serve model.

Optional. An adapter backed by a dependency the core does not start returns that application's name so a host can report an unstarted provider application as a host misconfiguration rather than as a retryable transport failure. The answer is per model, because one adapter may route some models through a dependency and others straight over HTTP. Adapters with no such dependency omit the callback or return nil.

Constrained to :req_llm because that is the only backing application the installation catalog validates and the CLI's ownership selection understands. Admitting arbitrary applications means widening those two consumers as well, which is a deliberate change rather than a side effect of this callback.

public_model(model)

(optional)
@callback public_model(model :: String.t()) :: {:ok, String.t()} | :private

Attests that the exact adapter target is safe to publish in canonical traces.

Optional. Return {:ok, model} only when the complete value is public model identity. Return :private for local, endpoint-bearing, deployment-private, or otherwise sensitive targets. PtcRunner rejects altered, malformed, or oversized values and treats a missing or raising callback as private.

reservation_bound(prepared_target, normalized_request, reservation_tariff)

(optional)
@callback reservation_bound(
  prepared_target :: term(),
  normalized_request :: map(),
  reservation_tariff :: PtcRunner.LLM.Requirements.cost_tariff() | nil
) :: map()

Functions

adapter!()

@spec adapter!() :: module()

Returns the configured LLM adapter module.

Resolution order:

  1. config :ptc_runner, :llm_adapter, MyAdapter
  2. PtcRunner.LLM.ReqLLMAdapter if req_llm is available
  3. Raises if no adapter found

callback(prepared, binding)

@spec callback(PtcRunner.LLM.PreparedModel.t(), runtime_binding()) ::
  {:ok,
   (map(), requester_context() ->
      {:ok, map()} | {:error, PtcRunner.Kernel.ProviderError.t()})}
  | {:error, :invalid_prepared_model | :invalid_llm_binding}

Creates a normalized requester for a prepared model.

The binding is a closed map of credential and cache. The returned requester is arity two: a provider-neutral request plus %{llm_request_deadline_ms: integer() | nil}. An uncataloged selector emits one concise warning while the requester is built.

catalog_warning(prepared)

@spec catalog_warning(PtcRunner.LLM.PreparedModel.t()) ::
  {:model_uncataloged, binary() | nil} | nil

Returns the closed public warning fact for an uncataloged prepared model.

The adapter remains the authority on whether its selector is public. Direct HTTP selectors therefore stay private even when catalog facts are absent.

prepare(model, requirements, adapter \\ adapter!())

@spec prepare(String.t(), PtcRunner.LLM.Requirements.t(), module()) ::
  {:ok, PtcRunner.LLM.PreparedModel.t()} | {:error, term()}

Resolves a configured selector into an immutable adapter-owned request target.

Generated prepare/2 means (model, requirements). Explicit prepare/3 means (model, requirements, adapter). Preparation runs adapter warmup and model resolution once, validates exact requirements/attestation equality, and returns a typed error immediately when either operation cannot produce a valid prepared value.