gleamunison/llm
LLM ability host surface (gap analysis concept A).
The LLM call is an effect; providers, retries, fallbacks, and mocks are composable handlers over the effects.run stack — no client registry, no codegen. Op contract at the handler boundary: args[0] = messages JSON BitArray ([{“role”:“user”,“content”:“…”}]) result passed to continuation = {ok, CompletionJsonBin} | {error, ReasonBin}
Types
pub type Completion {
Completion(
content: String,
model: String,
input_tokens: Int,
output_tokens: Int,
)
}
Constructors
-
Completion( content: String, model: String, input_tokens: Int, output_tokens: Int, )
pub type LlmError {
TransportError(reason: String)
HttpError(status: Int, body: String)
DecodeError(reason: String)
}
Constructors
-
TransportError(reason: String) -
HttpError(status: Int, body: String) -
DecodeError(reason: String)
pub type ProviderConfig {
ProviderConfig(
provider: Provider,
base_url: String,
api_key: String,
model: String,
max_tokens: Int,
reasoning_effort: String,
)
}
Constructors
-
ProviderConfig( provider: Provider, base_url: String, api_key: String, model: String, max_tokens: Int, reasoning_effort: String, )Arguments
- reasoning_effort
-
OpenAI-compatible reasoning effort (“low”/“medium”/“high”/“max”). Empty string = omit from the request (provider default).
Values
pub fn ability_ref() -> identity.DefinitionRef
pub fn build_request(
cfg: ProviderConfig,
messages: List(Message),
) -> #(String, List(#(String, String)), String)
pub fn complete_env(prompt: String) -> Result(String, String)
One-shot completion from environment config: the default live handler body. Reads OPENAI_API_KEY/OPENAI_BASE_URL/OPENAI_MODEL (or ANTHROPIC_* when its key is set). Kept dependency-free so the Erlang runtime can call it directly as the ambient handler.
pub fn completion_json(c: Completion) -> BitArray
pub fn error_string(e: LlmError) -> String
pub fn frame(
handler: fn(
List(dynamic.Dynamic),
fn(dynamic.Dynamic) -> dynamic.Dynamic,
) -> dynamic.Dynamic,
) -> effects.HandlerFrame
Wrap any LLM OpHandler as a HandlerFrame for effects.run.
pub fn messages_json(msgs: List(Message)) -> String
pub fn mock_handler(
response: Completion,
) -> fn(
List(dynamic.Dynamic),
fn(dynamic.Dynamic) -> dynamic.Dynamic,
) -> dynamic.Dynamic
Deterministic mock: always answers the same completion. The live call and the test double are the same handler swap — that is the whole point.
pub fn parse_response(
provider: Provider,
status: Int,
body: String,
) -> Result(Completion, LlmError)
pub fn provider_handler(
cfg: ProviderConfig,
) -> fn(
List(dynamic.Dynamic),
fn(dynamic.Dynamic) -> dynamic.Dynamic,
) -> dynamic.Dynamic
Live provider handler: performs the HTTP call via http_client.