Lightweight Elixir client for LLM APIs.

Supports 4 dialects covering 99% of providers:

  • OpenAI Chat Completions/v1/chat/completions (also works with Ollama, Groq, OpenRouter, DeepSeek, xAI, etc.)
  • OpenAI Responses/v1/responses
  • Anthropic Messages/v1/messages
  • Gemini/v1beta/models/{model}:generateContent

Quick Start

# Simple text generation
{:ok, response} = LLM.generate("What is Elixir?",
  provider: :openai,
  model: "gpt-4"
)
response.message.content  #=> "Elixir is a..."

# Streaming
{:ok, stream} = LLM.stream("Tell me a story",
  provider: :anthropic,
  model: "claude-sonnet-4-5-20250514"
)
{:ok, response} = LLM.Stream.collect(stream, on_chunk: &IO.write/1)

# Custom provider (runtime)
{:ok, response} = LLM.generate("Hello",
  provider: %{
    dialect: LLM.Dialect.Anthropic,
    base_url: "https://my-proxy.com",
    api_key: "sk-ant-..."
  },
  model: "claude-sonnet-4-5-20250514"
)

# With tools
{:ok, response} = LLM.generate("Read mix.exs",
  provider: :openai,
  model: "gpt-4",
  tools: [MyApp.ReadFileTool]
)

Configuration

# config/config.exs
config :llm, :providers,
  openai: [api_key: "sk-..."],
  anthropic: [api_key: "sk-ant-..."]

# Or at runtime
LLM.Provider.Presets.put_key(:openai, "sk-...")

Provider

A provider can be:

  • An atom preset (:openai, :anthropic, :gemini, :openai_responses)
  • A map with :dialect, :base_url, and optionally :api_key
  • A module implementing LLM.Provider behaviour

Summary

Functions

Generate text (non-streaming). Returns the final response.

Generate text, raising on error.

List available provider presets.

Store an API key at runtime.

Stream a prompt, returning a stream handle.

Types

stream_option()

@type stream_option() ::
  {:provider, atom() | map() | module()}
  | {:model, String.t()}
  | {:max_tokens, non_neg_integer()}
  | {:temperature, float()}
  | {:thinking, atom() | map()}
  | {:tools, [module() | LLM.Tool.t() | {module(), map()}]}
  | {:auto_tools, boolean()}
  | {:max_rounds, non_neg_integer()}
  | {:on_chunk, (LLM.Stream.chunk_type() -> any())}
  | {:system, String.t()}

Functions

generate(prompt, opts \\ [])

@spec generate(String.t() | LLM.Context.t(), [stream_option()]) ::
  {:ok, LLM.Response.t()} | {:error, term()}

Generate text (non-streaming). Returns the final response.

Automatically collects the stream and executes tool calls if present.

generate!(prompt, opts \\ [])

@spec generate!(String.t() | LLM.Context.t(), [stream_option()]) :: LLM.Response.t()

Generate text, raising on error.

providers()

@spec providers() :: [atom()]

List available provider presets.

put_key(provider_name, api_key)

@spec put_key(atom(), String.t()) :: :ok

Store an API key at runtime.

stream(prompt, opts \\ [])

@spec stream(String.t() | LLM.Context.t(), [stream_option()]) ::
  {:ok, LLM.Stream.t()} | {:error, term()}

Stream a prompt, returning a stream handle.

Returns {:ok, stream} on success, {:error, reason} on failure.

The stream can be consumed with LLM.Stream.next/1 and LLM.Stream.collect/2.