LLM Providers

All LLM providers for the ask-rb ecosystem. A single gem containing every provider — each with its own wire format, auth, and capabilities, all implementing the same Ask::Provider contract.

gem "ask-llm-providers"

Quick Start

require "ask-llm-providers"

provider = Ask::Provider.resolve(:opencode_go).new
response = provider.chat(
  [{ role: "user", content: "Say OK in one word." }],
  model: "deepseek-v4-flash"
)
response.content
# => "OK"

Every provider in the gem registers itself with Ask::Provider, so you can look one up by slug instead of hardcoding a class. The OpenAI-compatible providers read their key from the matching *_API_KEY env var; the canonical providers (OpenAI, Anthropic, …) resolve theirs through Ask::Auth.

Provider Transformation Contract

Every provider implements the Ask::LLM::ProviderConfig interface, which separates wire-format concerns into five public methods:

provider.build_request(messages, model:, tools: nil, temperature: nil, stream: nil, schema: nil)
  # => Hash (provider-native request payload)

provider.parse_response(body, model)
  # => Ask::Message

provider.parse_stream(raw, stream, model, &block)
  # => yields Ask::Chunks

provider.format_tools(tools)
  # => Array (provider-native tool format)

provider.format_message(msg)
  # => Hash (provider-native message format)

This makes each wire-format concern testable in isolation and adding a new provider mechanical — implement five methods and the provider works. Inspired by LiteLLM’s BaseConfig pattern.

Canonical Providers

These providers have distinct wire formats. Each is a dedicated class with its own serialization, streaming, and auth.

Provider Class Capabilities Auth
OpenAI Ask::Providers::OpenAI Chat, streaming, tools, vision, embeddings, image gen, transcription OPENAI_API_KEY
Anthropic Ask::Providers::Anthropic Chat, streaming, tools, vision, thinking, prompt caching ANTHROPIC_API_KEY
Google Gemini Ask::Providers::Google Chat, streaming, tools, vision, embeddings, file upload GEMINI_API_KEY
Amazon Bedrock Ask::Providers::Bedrock Chat, streaming, tools, vision AWS credentials chain
Ollama Ask::Providers::Ollama Chat, streaming, tools, embeddings (local) None needed
Mistral AI Ask::Providers::Mistral Chat, streaming, tools, structured output, embeddings MISTRAL_API_KEY
Cloudflare Ask::Providers::Cloudflare Chat, streaming, vision CLOUDFLARE_API_KEY + Account ID

OpenAI-Compatible Providers

These share OpenAI’s wire format. Each is configured via a registry entry — no subclass, no new file. Adding a new provider is one line in lib/ask/llm/openai_compatible.rb.

Provider Env Var Capabilities
DeepSeek DEEPSEEK_API_KEY Chat, streaming, tools, thinking
OpenRouter OPENROUTER_API_KEY Chat, streaming, tools, vision, thinking, structured output
Groq GROQ_API_KEY Chat, streaming, tools, vision
Together TOGETHER_API_KEY Chat, streaming, tools
Fireworks FIREWORKS_API_KEY Chat, streaming, tools
Cerebras CEREBRAS_API_KEY Chat, streaming, tools
xAI XAI_API_KEY Chat, streaming, tools, vision, thinking
Perplexity PERPLEXITY_API_KEY Chat, streaming
Moonshot MOONSHOT_API_KEY Chat, streaming
DeepInfra DEEPINFRA_API_KEY Chat, streaming, tools
Anyscale ANYSCALE_API_KEY Chat, streaming, tools
SambaNova SAMBANOVA_API_KEY Chat, streaming, tools
Nebius NEBIUS_API_KEY Chat, streaming, tools
Nvidia NIM NVIDIA_NIM_API_KEY Chat, streaming, tools
Friendli FRIENDLI_API_KEY Chat, streaming, tools
Hyperbolic HYPERBOLIC_API_KEY Chat, streaming, tools
Novita NOVITA_API_KEY Chat, streaming, tools
Nscale NSCALE_API_KEY Chat, streaming, tools
Featherless FEATHERLESS_API_KEY Chat, streaming, tools
AI/ML API AIML_API_KEY Chat, streaming, tools
AI21 AI21_API_KEY Chat, streaming, tools
Meta (Llama) LLAMA_API_KEY Chat, streaming, tools
GitHub Models GITHUB_API_KEY Chat, streaming, tools, vision
OpenCode OPENCODE_API_KEY Chat, streaming, tools
OpenCode Go OPENCODE_GO_API_KEY Chat, streaming, tools
Mimo MIMO_API_KEY Chat, streaming

Using a Model Through a Different Provider

A model is registered under a specific provider in the catalog (e.g., deepseek-v4-flash under the deepseek provider). But you may want to use it through a different provider that serves the same model — for instance, deepseek-v4-flash served by opencode_go.

Pass the provider: parameter to override which provider serves the model:

require "ask-agent"

session = Ask::Agent::Session.new(
  model: "deepseek-v4-flash",
  provider: :opencode_go,  # use opencode_go instead of the default deepseek provider
  tools: []
)

Or when using Ask::Agent::Chat directly:

require "ask-agent"

chat = Ask::Agent::Chat.new(
  model: "deepseek-v4-flash",
  provider: :opencode_go
)
chat.model_id  # => "deepseek-v4-flash"

This works because the agent checks provider: first, then falls back to the catalog’s provider. It also means you can use any OpenAI-compatible provider with any model name the provider supports, without adding model entries to the catalog.

The provider: parameter is available on both Ask::Agent::Session.new and Ask::Agent::Chat.new — it passes through to the underlying provider resolution.

Provider Registration

All providers auto-register with Ask::Provider on gem load:

require "ask-llm-providers"

Ask::Provider.providers.keys
# => [:openai,
#  :anthropic,
#  :gemini,
#  :bedrock,
#  :ollama,
#  :mistral,
#  :cloudflare,
#  :openai_codex,
#  :aiml,
#  :ai21,
#  :anyscale,
#  :cerebras,
#  :deepinfra,
#  :deepseek,
#  :featherless,
#  :fireworks,
#  :friendli,
#  :github,
#  :groq,
#  :hyperbolic,
#  :meta,
#  :mimo,
#  :moonshot,
#  :nebius,
#  :novita,
#  :nscale,
#  :nvidia_nim,
#  :opencode,
#  :opencode_go,
#  :github_copilot,
#  :openrouter,
#  :perplexity,
#  :sambanova,
#  :together,
#  :xai]
Ask::Provider.resolve(:openai)  # => Ask::Providers::OpenAI

Adding a new OpenAI-compatible provider

Add one entry to the registry — no new file, no subclass:

# lib/ask/llm/openai_compatible.rb
OPENAI_COMPATIBLE = {
  groq: { api_base: "https://api.groq.com/openai/v1", api_key_env: "GROQ_API_KEY",
          capabilities: { chat: true, streaming: true, tool_calls: true, vision: true } },
  # ... add yours here
}

Special quirks are handled declaratively:

deepseek: { api_base: "https://api.deepseek.com", api_key_env: "DEEPSEEK_API_KEY",
            reasoning_content: true },  # injects reasoning_content for tool call messages

openrouter: { api_base: "https://openrouter.ai/api/v1", api_key_env: "OPENROUTER_API_KEY",
              extra_headers: { "HTTP-Referer" => "...", "X-Title" => "ask-rb" } },

opencode_go: { api_base: "https://opencode.ai/zen/go/v1", api_key_env: "OPENCODE_GO_API_KEY" },

Streaming

stream = provider.chat(
  [{ role: "user", content: "Tell me a story" }],
  model: "deepseek-v4-flash",
  stream: true
) do |chunk|
  print chunk.content
end

Tool Calls

tools = [{
  name: "get_weather",
  description: "Get weather for a location",
  parameters: {
    type: "object",
    properties: { location: { type: "string" } },
    required: ["location"]
  }
}]

response = provider.chat(
  [{ role: "user", content: "What's the weather in NYC?" }],
  model: "deepseek-v4-flash",
  tools: tools
)
# response.tool_call? => true
# response.tool_calls => [{ name: "get_weather", arguments: "..." }]

Capabilities Introspection

require "ask-llm-providers"

Ask::Providers::OpenAI.capabilities
# => {chat: true,
#  streaming: true,
#  tool_calls: true,
#  vision: true,
#  thinking: true,
#  structured_output: true,
#  embed: true,
#  transcribe: true,
#  paint: true,
#  moderate: true,
#  prompt_caching: true}

Ask::Providers::Ollama.local?   # => true
Ask::Providers::OpenAI.local?   # => false

Error Handling

Provider errors map to structured Ask::Error types:

Ask::RateLimitError       # 429
Ask::Unauthorized         # 401/403
Ask::ServerError          # 500
Ask::ServiceUnavailable   # 503
Ask::ContextLengthExceeded # context_length_exceeded
Ask::ProviderError        # other errors

Model Catalog

The gem automatically populates Ask::ModelCatalog when loaded:

require "ask-llm-providers"

model = Ask::ModelCatalog.find("deepseek-v4-flash")
model.provider           # => "deepseek"
model.context_window     # => 1000000
model.max_output_tokens  # => 384000
model.supports?(:vision) # => false

Models are defined in JSON files under lib/ask/llm/models/ with pricing, context windows, and capabilities. Short names are resolved via aliases:

require "ask-llm-providers"

Ask::LLM::Aliases.resolve("claude-sonnet-4")
# => "claude-sonnet-4-6"

Development

git clone https://github.com/ask-rb/ask-llm-providers
cd ask-llm-providers
bundle exec rake test

This site uses Just the Docs, a documentation theme for Jekyll.