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::Providers::OpenAI.new(api_key: "sk-...")
response = provider.chat(
[{ role: "user", content: "Hello!" }],
model: "gpt-4o"
)
puts response.content
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_API_KEY or 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:
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:
chat = Ask::Agent::Chat.new(
model: "deepseek-v4-flash",
provider: :opencode_go
)
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:
Ask::Provider.providers # => { openai: OpenAI, anthropic: Anthropic, deepseek: ..., groq: ..., ... }
Ask::Provider.resolve(:openai) # => Ask::Providers::OpenAI
Ask::Provider.resolve(:deepseek) # => resolves to the OpenAICompatible subclass for DeepSeek
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_API_KEY" },
# Also accepts OPENCODE_GO_API_KEY via slug convention
Streaming
stream = provider.chat(
[{ role: "user", content: "Tell me a story" }],
model: "gpt-4o",
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: "gpt-4o",
tools: tools
)
# response.tool_call? => true
# response.tool_calls => [{ name: "get_weather", arguments: "..." }]
Capabilities Introspection
Ask::Providers::OpenAI.capabilities
# => { chat: true, streaming: true, tool_calls: true, vision: 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:
Ask::ModelCatalog.find("gpt-4o")
Ask::ModelCatalog.find("claude-sonnet-4-6")
Models are defined in JSON files under lib/ask/llm/models/ with pricing, context windows, and capabilities. Short names are resolved via aliases:
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