fabric/llm
Adapts an llm_wire provider to the Fabric model port.
Fabric owns the conversation. Every request is prepared from its stored
transcript; each tool response retains provider-owned data alongside its
original text and calls. The same path preserves signed Google parts and
custom adapter data across pauses and restarts, without a live continuation.
The turn’s provider metadata is stored with llm_wire’s replay codec
under the llm_wire.turn.v1 tag; earlier records still decode.
llm_wire validates provider data and exact result coverage before I/O.
A call’s arguments are replayed exactly as the model sent them, even
when they were not a JSON object and were answered with
invalid_arguments; llm_wire encodes such text for each provider
(Anthropic and Google wrap it as {"unparsed_arguments": text}, OpenAI
sends it verbatim).
The adapter asks llm_wire to report invalid tool calls rather than fail
the turn (tool.ReportInvalidToolCalls): every call reaches Fabric, whose
registry answers an unknown tool or malformed arguments per call, as for
any other model. Names outside the tool-name grammar, duplicate call ids,
and bounds still fail the turn in llm_wire.
An agent with a typed answer (agent.with_answer) puts its schema on
every model.Request (request.answer); the adapter asks the provider
for it with llm_wire.with_output, named answer, as OpenAI’s and
Anthropic’s JSON Schema output format or Google’s response schema.
llm_wire validates the reply against the schema; a final text that does
not match is still returned as the FinalAnswer, so that Fabric asks
the model to correct it (agent.with_answer_attempts) or ends the run
with run.AnswerInvalid, keeping the text.
Providers need an object at the root of an output schema. An answer
whose schema has another root (a string, a number, a boolean, a list, a
nullable value or a codec.union) is wrapped for the provider as
{"answer": <schema>} and unwrapped before Fabric reads it, so the
agent’s codec stays the natural type and the run stores the answer’s
own JSON, never the wrapper: [1, 2], not {"answer": [1, 2]}. A reply
that is not the wrapper is kept as sent, for the corrective turn. A
schema the provider still cannot take (llm_wire’s strict output refuses
a codec.union at any depth, and Google a nullable value) fails the
turn as model.InvalidRequest.
A model that declines in its own words (llm_wire.Refused) and a
provider’s content filter (error.ContentFiltered, the prompt blocked or
the output stopped) are both a model.Refusal, so the run ends as
run.Refused. A filter’s reason is error.describe’s line, which keeps
the stage and the provider’s own reason, such as
"Provider content filter stopped the output: SAFETY".
Any other failed call becomes a model.ModelError whose kind follows
llm_wire.advise: a failure llm_wire says another attempt may help is
RateLimited (HTTP 429 or a rate-limit code), TimedOut (a timer or
HTTP 408), Unreachable (the connection) or Overloaded, and is
retried; a provider’s Retry-After becomes the error’s retry_after,
which Fabric waits before the retry. Any other failure is Rejected (a
status the provider will answer again), InvalidRequest (nothing was
sent), InvalidReply or Other, and stops the run.
Values
pub fn configured_model(
client: http_gun.Client,
config: llm_wire.Config,
model_id: String,
settings: generation.Settings,
) -> @internal Port(model.Request, model.Reply, model.ModelError)
A model with fixed generation controls. Every turn applies these settings, including after application reconstruction. Fabric retains ownership of messages, tools, output decoding and correlation. Invalid settings fail during preparation before HTTP execution. The application supplies settings again on reconstruction; they are not persisted in execution records.
pub fn model(
client: http_gun.Client,
config: llm_wire.Config,
model_id: String,
) -> @internal Port(model.Request, model.Reply, model.ModelError)
A model backed by llm_wire. client is the caller’s started HTTP Gun
client: its destination, trust and connection policy apply to every
turn, and Fabric neither starts nor stops it. Each turn runs through
http_gun.with_correlation(client, request.correlation), so the HTTP and
llm_wire events of a turn carry its run’s correlation (see
fabric.start) and one model, and one agent, serves every run. config
owns the provider, endpoint, credentials, limits, and timeouts. Nothing
is allocated until a turn runs. Killing the task that runs a turn closes
its HTTP stream.