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 Message {
  Message(role: Role, content: String)
}

Constructors

  • Message(role: Role, content: String)
pub type Provider {
  OpenAI
  Anthropic
}

Constructors

  • OpenAI
  • Anthropic
pub type ProviderConfig {
  ProviderConfig(
    provider: Provider,
    base_url: String,
    api_key: String,
    model: String,
    max_tokens: Int,
  )
}

Constructors

  • ProviderConfig(
      provider: Provider,
      base_url: String,
      api_key: String,
      model: String,
      max_tokens: Int,
    )
pub type Role {
  System
  User
  Assistant
}

Constructors

  • System
  • User
  • Assistant

Values

pub fn build_request(
  cfg: ProviderConfig,
  messages: List(Message),
) -> #(String, List(#(String, String)), String)
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.

✨ Search Document