llmshim (Ruby client)
A thin, dependency-free Ruby client for the llmshim multi-provider LLM proxy. Send OpenAI-style chat requests to a running llmshim proxy and let it translate to OpenAI, Anthropic, Google Gemini, or xAI.
This gem talks to a proxy over plain HTTP — it does not spawn the Rust
binary. Start the proxy separately (llmshim proxy, default http://localhost:3000).
- Standard library only (
net/http,json,uri) — no runtime gem dependencies. - Non-streaming and SSE streaming chat, model listing, health check.
- Typed responses and a typed
Llmshim::APIError.
Install
From RubyGems (once published):
gem install llmshim
Or add to your Gemfile:
gem "llmshim"
Or build locally from this directory:
gem build llmshim.gemspec
gem install ./llmshim-*.gem
Prerequisite: run the proxy
llmshim proxy # listens on 0.0.0.0:3000 by default
Configure provider API keys for the proxy via llmshim configure or the
OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY / XAI_API_KEY
environment variables. The Ruby client never sees your keys — the proxy holds them.
Quickstart
require "llmshim"
client = Llmshim::Client.new(base_url: "http://localhost:3000")
resp = client.chat(model: "claude-sonnet-4-6", messages: "What is Rust?")
puts resp.content # => "Rust is a systems programming language..."
puts resp.provider # => "anthropic"
puts resp.usage.total_tokens
messages: accepts a single string (treated as one user message) or an array
of message hashes:
resp = client.chat(
model: "openai/gpt-5.5",
messages: [
{ role: "system", content: "You are a pirate." },
{ role: "user", content: "Hello!" }
],
max_tokens: 500,
temperature: 0.7,
reasoning_effort: "high"
)
Module-level convenience
A shared default client (base URL from LLMSHIM_BASE_URL, else http://localhost:3000):
require "llmshim"
resp = Llmshim.chat(model: "gpt-5.5", messages: "Explain quicksort")
puts resp.content
Streaming
stream yields a Llmshim::StreamEvent for each SSE event and stops after the
done event. Event types: content, reasoning, tool_call, usage,
done, error.
client.stream(model: "claude-sonnet-4-6", messages: "Write a haiku") do |event|
case event.type
when "reasoning" then print event.text # thinking tokens
when "content" then print event.text # answer tokens
when "tool_call" then puts "\n[tool] #{event.name}(#{event.arguments})"
when "usage" then puts "\ntokens: #{event.usage.total_tokens}"
when "error" then warn "error: #{event.}"
end
end
Predicate helpers are available too: event.content?, event.reasoning?,
event.tool_call?, event.usage?, event.done?, event.error?.
Called without a block, stream returns the collected array of events:
events = client.stream(model: "gpt-5.5", messages: "Hi")
text = events.select(&:content?).map(&:text).join
Tools, provider passthrough, and fallback
resp = client.chat(
model: "anthropic/claude-sonnet-4-6",
messages: "What's the weather in SF?",
tools: [
{ type: "function",
function: { name: "get_weather",
parameters: { type: "object",
properties: { city: { type: "string" } } } } }
],
tool_choice: "auto",
# Raw provider-specific JSON merged into the underlying request:
provider_config: { thinking: { type: "adaptive" } },
# Try these models if the primary fails with a retryable error:
fallback: ["openai/gpt-5.5", "gemini/gemini-3-flash-preview"]
)
resp..tool_calls.each do |tc|
puts "#{tc.name} -> #{tc.arguments}"
end
tools and tool_choice are folded into provider_config (passed straight
through to the provider). max_tokens, temperature, top_p, top_k,
stop, and reasoning_effort are folded into the request config.
Models and health
client.models.each { |m| puts "#{m.id} (#{m.provider})" }
h = client.health
puts h.status # => "ok"
puts h.providers # => ["anthropic", "openai", ...]
Error handling
Any non-2xx response raises Llmshim::APIError, populated from the proxy's
ErrorResponse body:
begin
client.chat(model: "bogus/model", messages: "hi")
rescue Llmshim::APIError => e
warn "#{e.status} #{e.code}: #{e.}"
end
Llmshim::APIError exposes #status (Integer), #code (String or nil),
#message, and #body (raw response body).
Configuration
Llmshim::Client.new options:
| Option | Default | Description |
|---|---|---|
base_url |
http://localhost:3000 |
Proxy base URL |
headers |
{} |
Extra headers sent on every request |
timeout |
120 |
Open/read timeout in seconds |
Development
bundle install # optional; only for dev tools
rake test # runs the mocked test suite (no network, no API keys)
Tests use a local WEBrick mock server returning canned JSON and SSE — they never contact a real provider and never require a running proxy.
License
MIT