Portable Common Lisp protocols and value types for language-model providers and bounded inference inputs.
The library defines semantic provider events and results, concrete wire clients, context assembly, structured-output contracts, shared inference budgets, read-only views, and content-addressed context objects. Applications own credential stores, model catalogs, registries, prompt policy, conversation projection, and tool dispatch.
- ASDF system:
cl-llm-provider-api - Optional concrete protocols:
cl-llm-provider-api/wire - Optional HTTP condition adapter:
cl-llm-provider-api/dexador - Test system:
cl-llm-provider-api/tests - Package:
CL-LLM-PROVIDER-API - Nickname:
LLM-PROVIDER-API
Install the locked dependencies and run all tests:
./script/bootstrap
./script/checkLoad cl-llm-provider-api/wire for Responses, Chat Completions, and Anthropic
Messages encoding and stream assembly. Use hash tables for JSON objects, vectors
for arrays, and portable Responses-shaped transcript items. Subclass the existing
provider classes to specialize wire naming, instruction placement, and provider
metadata without introducing application objects into the wire layer.
Construct WIRE-REQUEST with :model, :items, :prefix, :suffix, and an
:options property list. Supply already-projected history and instruction texts.
Call PROVIDER-REQUEST-OBJECT with a provider, the projection, and tool vector.
Responses accepts namespace declarations; Chat Completions and Messages accept
encoded tools. Use PROVIDER-WIRE-TOOLS for protocol-specific tool projection.
Shared options include :maximum-output-tokens. Responses additionally accepts
:reasoning-effort, :reasoning-summary, and :fields (alternating wire keys and
values). Chat accepts :stream-usage-p, :reasoning-parameter,
:reasoning-effort, and :output-ceiling-field. Messages accepts :cache-p and
:ephemeral-items for separately projected volatile history.
Call PROVIDER-EXECUTE-REQUEST with :transport, :event-callback, and optional
:secrets, :cleanup, :call-with-deadline, and :completion hooks. The transport
accepts the request object and returns a stream, HTTP status, and response headers.
The library normalizes HTTP and terminal failures and redacts supplied secrets
from detached headers, events, results, and diagnostics. Use :cleanup to dispose
of each acquired stream; the default abort-closes it. Cleanup is best effort:
its errors and deadline failures do not replace the request outcome. Completion
runs after valid terminal consumption and cleanup.
Load cl-llm-provider-api/dexador and wrap the request thunk with
PROVIDER--CALL-WITH-TRANSPORT-NORMALIZATION to classify socket, TLS, and deadline
conditions from transport opening and SSE line reads. Application callbacks run
outside that classifier. Pass :terminal-errors-p t only when wrapping a transport
opening directly. Convert Dexador HTTP conditions with PROVIDER-SIGNAL-HTTP-FAILURE.
Supply credentials and authenticated request headers from the application.
Resampling and transient retries use separate budgets in CALL-WITH-BOUNDED-RETRIES.
Supply :maximum-retries, :delay-function (retry number and condition), and
:call-with-attempt for a custom delay policy or attempt scope.
Create bounded CONTEXT-CONTRIBUTION values with MAKE-CONTEXT-CONTRIBUTION.
Use MAKE-CONTEXT-RESOLVER and CONTEXT-RESOLVE to select contributions by
lifetime, supersession, deduplication, conflict group, mandatory class, priority,
and available budget. A CONTEXT-SELECTION includes selected and omitted values.
Call CONTEXT-SELECTION-COMPLETE only after a successful request to acknowledge
successful-delivery lifetimes. Applications supply request identity, token costs,
rendering, and evidence trust boundaries.
Use OUTPUT-SCHEMA-NORMALIZE for the supported native schema subset and
OUTPUT-SCHEMA->JSON for its JSON representation. Validate values with
OUTPUT-SCHEMA-VALID-P. Decode exact JSON through OUTPUT-JSON-DECODE and
convert portable tagged values through OUTPUT-JSON->SEXP and
OUTPUT-SEXP->JSON. Object properties and required keys, arrays, scalar JSON
types, enums, alternatives, and additional-property constraints are supported.
Invalid contracts and values signal OUTPUT-CONTRACT-ERROR and
OUTPUT-VALUE-ERROR with field diagnostics.
All values are ordinary CLOS instances created with MAKE-INSTANCE.
PROVIDER-EVENTis the root event class.ASSISTANT-DELTA-EVENTandREASONING-DELTA-EVENTcarry streamed text.PROVIDER-ITEM-EVENTcarries one completed provider-native item.PROVIDER-COMPLETED-EVENTcarries response identity, usage, and turn state.PROVIDER-RETRY-EVENTcarries bounded reconnect metadata.PROVIDER-RESULTcarries ordered output items, tool calls, usage, routing state, and:CONTINUE,:END, or:UNSPECIFIEDturn completion.
(make-instance 'llm-provider-api:provider-result
:response-id "response-1"
:output-items (list message)
:tool-calls calls
:usage '(:total-tokens 42)
:turn-completion :end)Output items and usage metadata deliberately have type T. A provider may use
hash tables, association lists, structures, or application-specific objects.
Subclass MODEL-PROVIDER and implement PROVIDER-STREAM-TURN. The callback
receives semantic PROVIDER-EVENT instances and the method returns one
PROVIDER-RESULT.
(defclass example-provider (llm-provider-api:model-provider) ())
(defmethod llm-provider-api:provider-stream-turn
((provider example-provider) conversation
&key tool-namespaces event-callback goal-context compaction-p)
(declare (ignore provider tool-namespaces goal-context compaction-p))
(funcall event-callback
(make-instance 'llm-provider-api:assistant-delta-event
:text "hello"))
(make-instance 'llm-provider-api:provider-result
:output-items (list conversation)
:turn-completion :end))The portable provider protocol also exposes:
PROVIDER-FAMILYPROVIDER-WITH-CONFIGURATIONPROVIDER-CONSUME-STREAMPROVIDER-NATIVE-COMPACT-CONVERSATIONPROVIDER-SET-REASONING-SUMMARIESPROVIDER-OUTPUT-CEILING-PPROVIDER-NORMALIZE-OUTPUT-ITEM
Conservative defaults return :CUSTOM, NIL, the unchanged provider, or the
unchanged item where a safe default exists.
RESPONSES-API-PROVIDER and CHAT-COMPLETIONS-PROVIDER identify common wire
families. PROVIDER-WIRE-PROTOCOL returns :RESPONSES-API,
:CHAT-COMPLETIONS, or :CUSTOM.
Request implementations can specialize:
PROVIDER-WIRE-TOOL-NAME,PROVIDER-WIRE-TOOL, andPROVIDER-WIRE-TOOLSPROVIDER-WIRE-INPUT-ITEMPROVIDER-RESPONSES-WIRE-EFFORTPROVIDER-RESPONSES-REASONING-SUMMARYPROVIDER-RESPONSES-HOSTED-TOOLSPROVIDER-RESPONSES-INSTRUCTIONS-PLACEMENTPROVIDER-RESPONSES-REQUEST-NAMESPACESPROVIDER-RESPONSES-REQUEST-FIELDSPROVIDER-REQUEST-OBJECT
The Responses API default joins a namespace and tool name with a dot. Wire
objects remain application-defined values, so the library does not impose a
JSON representation. Responses instructions use conversation input items by
default. Providers may return :TOP-LEVEL from
PROVIDER-RESPONSES-INSTRUCTIONS-PLACEMENT to use a top-level instructions
request field instead.
READ-SSE-DATA decodes one server-sent event’s joined data field from a
character stream. Lines and events are bounded by
*SSE-MAXIMUM-LINE-CHARACTERS* and *SSE-MAXIMUM-EVENT-CHARACTERS*;
violations signal the class named by *STREAM-LIMIT-ERROR-CLASS*,
PROVIDER-STREAM-LIMIT-ERROR by default. A drained stream returns
*SSE-END-OF-STREAM*. Hosts needing runtime-specific inactivity deadlines
install a wrapper as *SSE-READ-LINE-FUNCTION*.
READ-CHARACTER-SEQUENCE fills a string like READ-SEQUENCE using requests
no larger than *CHARACTER-READ-SEQUENCE-WINDOW*, staying on the portable
buffered path on runtimes whose large decoded reads misbehave.
CALL-WITH-BOUNDED-RETRIES retries an attempt function over the
*BOUNDED-RETRY-DELAYS* backoff schedule when it signals
PROVIDER-RETRYABLE-ERROR, reports each wait through PROVIDER-RETRY-EVENT
callbacks, and restarts immediately on PROVIDER-RESAMPLE-REQUESTED. Hosts
bridge their own condition types by inheriting from these classes.
RLM-BUDGET-CREATE creates a subtree budget with shared call and token counters
and frame-local recursion depth. RLM-BUDGET-ACQUIRE-REQUEST atomically reserves
one call and a bounded output tranche. Settle it with
RLM-BUDGET-SETTLE-OUTPUT. Descendants created by RLM-BUDGET-DESCEND share the
same counters.
(let* ((budget (llm-provider-api:rlm-budget-create
:calls 8 :tokens 80000 :depth 2))
(tranche (llm-provider-api:rlm-budget-acquire-request budget)))
(unwind-protect
(call-provider :maximum-output-tokens tranche)
(llm-provider-api:rlm-budget-settle-output budget tranche usage-total)))Exhaustion signals RLM-BUDGET-EXHAUSTED with a :CALLS, :TOKENS, or
:DEPTH dimension and an optional task label.
RLM-VIEW-MATERIALIZE accepts a string, pathname, RLM-VIEW, or a plist with
:CONTENT or :PATH and an optional :LABEL. RLM-VIEWS-MATERIALIZE numbers
duplicate labels. RLM-VIEWS-RENDER emits digest-delimited read-only blocks.
RLM-CONTEXT-STORE-CREATE opens a directory-backed content-addressed store.
RLM-CONTEXT-INTERN and RLM-CONTEXT-INTERN-PATHNAME publish UTF-8 content
atomically under its SHA-256 digest. Reads and lookups verify the digest and
signal RLM-VIEW-ERROR if stored content is corrupt. Store roots are
canonical absolute directories. Object reads reject noncanonical cache paths,
and passing an object to another store re-interns its verified content there.
(let* ((store (llm-provider-api:rlm-context-store-create #P"context/"))
(object (llm-provider-api:rlm-context-intern
store "large immutable input" :label "corpus")))
(values (llm-provider-api:rlm-context-object-digest object)
(llm-provider-api:rlm-context-object-content object)))- Babel
- Bordeaux Threads
- Ironclad
- ASDF/UIOP
COLL-Attribution, copyright 2026 Lambda Symbolics OÜ. See LICENSE.lisp.