PtcLlmHttp

Bounded BEAM-native HTTP transport and wire codecs for LLM requests.

Status: pre-alpha. The bounded HTTP/1 core and public OpenAI-compatible text, function-tool, strict structured-output, and synchronous text-streaming calls are available.

What this is

A small library that turns a validated request into at most one outbound HTTP/1.1 attempt, reads a bounded response, and normalizes it through an OpenAI-compatible codec.

It owns:

It does not own model selection, provider failover, credential storage, application policy, call budgets, traces, pricing, model catalogs, or provider-native APIs. Those belong to the consumer. No module here imports or returns a consumer type.

Fixed transport behavior

A new attempt against another model or provider is a new call by the consumer, with its own authority, capacity, budget, and record. This library never decides to try again.

Non-streaming calls

Construct a validated PtcLlmHttp.Target, PtcLlmHttp.Request, absolute PtcLlmHttp.Deadline, call-local PtcLlmHttp.Credential, and aggregate PtcLlmHttp.ProcessBudget, then call PtcLlmHttp.call/4. Successful content and usage are available through explicit redacted-value accessors. Expected wire and provider failures return the closed facts in PtcLlmHttp.Error.contract/0; raw provider text is never returned.

Requests can declare bounded strict function tools, replay assistant tool calls, append ordered tool results, and request either strict JSON Schema output or the older JSON-object mode when the target advertises it. Returned function arguments are decoded to objects, checked against the declared tool schema, and exposed through redacted PtcLlmHttp.ToolCall values. Structured content is validated locally and returned as deterministic canonical JSON.

Streaming calls

PtcLlmHttp.stream/5 delivers %{delta: text} chunks to a synchronous :cont | :halt callback. The callback runs in the attempt's monitored callback process, and the socket is not read again until it returns, providing natural backpressure without an Enumerable or producer mailbox. Success returns a redacted PtcLlmHttp.StreamComplete; early halt returns a redacted PtcLlmHttp.StreamHalt. Neither value accumulates generated text.

V1 streaming is text-only. Tool-bearing requests are rejected before connect, and provider tool-call deltas are rejected as malformed streams rather than silently discarded. The same absolute deadline covers callbacks; deadline, caller cancellation, callback failure, and early halt all close the socket and drain the attempt tree before capacity is released.

Requirements

Elixir ~> 1.20 and Erlang/OTP 29 or later. This is the supported consumer and development baseline; earlier OTP measurements remain documented in docs/transport-backend.md as historical transport evidence. CI runs the complete suite on OTP 29 on Linux and macOS.

Development

mise install # Erlang/Elixir toolchain
mix deps.get
./scripts/install-hooks.sh # once per clone or worktree
mix check # per-commit gate: format, compile, Credo,
# duplication, tests
mix full_check # before push or release: adds Dialyzer, docs,
# dependency audit, release smoke, package contents

mix check is meant to stay comfortably under a minute warm. The gates are shell scripts in scripts/ci/, and the Mix aliases, the Git hooks, and GitHub Actions all run those same scripts — so a green local gate means the same thing a green job does.

Repository conventions and agent instructions live in AGENTS.md.

License

MIT. See LICENSE.