0.0
The project is in a healthy, maintained state
One interface, every LLM. Rubycanusellm provides a unified client for OpenAI, Anthropic, and more, plus generators that scaffold the boilerplate so you go from zero to completions in 60 seconds.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

RubyCanUseLLM

A unified Ruby client for multiple LLM providers with generators. One interface, every LLM.

The Problem

Every time a Ruby developer wants to add LLMs to their app, they start from scratch: pick a provider gem, learn its API, write a service object, handle errors, parse responses. Switch providers? Rewrite everything.

The Solution

RubyCanUseLLM gives you two things:

  1. Unified client — One interface that works the same across OpenAI, Anthropic, and more. Switch providers by changing a string, not your code.
  2. Generators — Commands that scaffold ready-to-use boilerplate. You don't start from zero, you start with something that works.

Installation

gem install rubycanusellm

Or add to your Gemfile:

gem "rubycanusellm"

Quick Start

1. Generate configuration

rubycanusellm generate:config

This creates a config file with your provider and API key. In Rails it goes to config/initializers/rubycanusellm.rb, otherwise to config/llm.rb.

2. Generate a completion service

rubycanusellm generate:completion

This creates a ready-to-use service object. In Rails it goes to app/services/, otherwise to lib/.

3. Use it

RubyCanUseLLM.configure do |config|
  config.provider = :openai
  config.api_key = ENV["LLM_API_KEY"]
end

response = RubyCanUseLLM.chat([
  { role: :user, content: "What is Ruby?" }
])

puts response.content
puts "Tokens: #{response.total_tokens}"

Switch providers in one line

config.provider = :anthropic

That's it. Same code, different provider.

Supported Providers

Provider Models Status Notes
OpenAI gpt-4o-mini, gpt-4o, etc. Chat + Embeddings
Anthropic claude-sonnet-4-20250514, etc. Chat only
Mistral mistral-small-latest, mistral-large-latest, etc. Chat + Embeddings
Ollama llama3.2, mistral, etc. Chat + Embeddings (local)
Voyage AI voyage-3.5, voyage-4, etc. Embeddings only

API Reference

Configuration

RubyCanUseLLM.configure do |config|
  config.provider = :openai          # :openai, :anthropic, :mistral, or :ollama
  config.api_key = "your-key"        # required (not needed for Ollama)
  config.model = "gpt-4o-mini"       # optional, has sensible defaults
  config.timeout = 30                # optional, default 30s
  config.base_url = "http://localhost:11434"  # optional, for Ollama (default shown)
  config.embedding_provider = :voyage  # optional, for separate embedding provider
  config.embedding_api_key = "key"     # required when embedding_provider is set
end

Ollama (local, no API key needed):

RubyCanUseLLM.configure do |config|
  config.provider = :ollama
  # config.base_url = "http://localhost:11434"  # default, change if needed
end

Chat

response = RubyCanUseLLM.chat(messages, **options)

messages — Array of hashes with :role and :content:

messages = [
  { role: :system, content: "You are helpful." },
  { role: :user, content: "Hello" }
]

options — Override config per request:

RubyCanUseLLM.chat(messages, model: "gpt-4o", temperature: 0.5)

Streaming

Pass stream: true with a block to receive tokens as they arrive:

RubyCanUseLLM.chat(messages, stream: true) do |chunk|
  print chunk.content
end

Each chunk is a RubyCanUseLLM::Chunk with content (the token text) and role ("assistant"). Works with OpenAI, Anthropic, Mistral, and Ollama.

Response

response.content       # "Hello! How can I help?"
response.model         # "gpt-4o-mini"
response.input_tokens  # 10
response.output_tokens # 5
response.total_tokens  # 15
response.raw           # original provider response

Embeddings

response = RubyCanUseLLM.embed("Hello world")
response.embedding  # [0.1, 0.2, ...]
response.tokens     # 3
response.model      # "text-embedding-3-small"

OpenAI users — embeddings work out of the box, no extra config needed:

RubyCanUseLLM.configure do |config|
  config.provider = :openai
  config.api_key = ENV["OPENAI_API_KEY"]
end

RubyCanUseLLM.embed("Hello world")

Anthropic users with Voyage AI (recommended by Anthropic):

RubyCanUseLLM.configure do |config|
  config.provider = :anthropic
  config.api_key = ENV["ANTHROPIC_API_KEY"]
  config.embedding_provider = :voyage
  config.embedding_api_key = ENV["VOYAGE_API_KEY"]
end

RubyCanUseLLM.embed("Hello world")

Anthropic users with OpenAI for embeddings:

RubyCanUseLLM.configure do |config|
  config.provider = :anthropic
  config.api_key = ENV["ANTHROPIC_API_KEY"]
  config.embedding_provider = :openai
  config.embedding_api_key = ENV["OPENAI_API_KEY"]
end

RubyCanUseLLM.embed("Hello world")

Cosine similarity:

a = RubyCanUseLLM.embed("cat")
b = RubyCanUseLLM.embed("dog")
a.cosine_similarity(b.embedding)  # 0.87

Error Handling

begin
  RubyCanUseLLM.chat(messages)
rescue RubyCanUseLLM::AuthenticationError
  # invalid API key
rescue RubyCanUseLLM::RateLimitError
  # too many requests
rescue RubyCanUseLLM::TimeoutError
  # request timed out
rescue RubyCanUseLLM::ProviderError => e
  # other provider error
end

Tool Calling

Define tools once, use them with any provider:

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

messages = [{ role: :user, content: "What's the weather in Paris?" }]
response = RubyCanUseLLM.chat(messages, tools: tools)

if response.tool_call?
  tc = response.tool_calls.first
  # tc.id        => "call_abc123"
  # tc.name      => "get_weather"
  # tc.arguments => { "location" => "Paris" }

  # Execute the tool and continue the conversation
  weather = fetch_weather(tc.arguments["location"])

  messages << { role: :assistant, content: response.content, tool_calls: response.tool_calls }
  messages << { role: :tool, tool_call_id: tc.id, name: tc.name, content: weather }

  final_response = RubyCanUseLLM.chat(messages, tools: tools)
  puts final_response.content
end

Works the same across providers — OpenAI, Anthropic, Mistral, and Ollama. Format differences (Anthropic uses input_schema and tool_result messages) are handled internally.

Prompt Templates

Keep prompts out of your Ruby code. Define them in YAML files with ERB for dynamic content:

# prompts/commodity_analysis.yml
system: |
  You are an expert in <%= domain %> classification.
user: |
  Analyze this item: <%= description %>
  <% if references.any? %>
  Similar references:
  <% references.each do |ref| %>
  - <%= ref %>
  <% end %>
  <% end %>
messages = RubyCanUseLLM::Prompt.load("prompts/commodity_analysis.yml",
  domain: "electronics",
  description: "capacitor 10uF",
  references: ["ceramic", "electrolytic"]
)
RubyCanUseLLM.chat(messages)

For inline prompts:

prompt = RubyCanUseLLM::Prompt.new(
  system: "You are a <%= role %> assistant.",
  user: "Help me with: <%= task %>"
)
messages = prompt.render(role: "coding", task: "fix this bug")
RubyCanUseLLM.chat(messages)

ERB is supported in both cases — loops, conditionals, any Ruby expression.

Generators

Command Description
rubycanusellm generate:config Configuration file with provider setup
rubycanusellm generate:completion Completion service object
rubycanusellm generate:embedding Embedding service object

Roadmap

  • Project setup
  • Configuration module
  • OpenAI provider
  • Anthropic provider
  • generate:config command
  • generate:completion command
  • v0.1.0 release
  • Streaming support
  • Embeddings + configurable embedding provider
  • Voyage AI provider (embeddings)
  • Mistral provider (chat + embeddings)
  • Ollama provider (chat + embeddings, local)
  • generate:embedding command
  • Prompt templates (ERB + YAML file-based)
  • Tool calling

Development

git clone https://github.com/mgznv/rubycanusellm.git
cd rubycanusellm
bin/setup
bundle exec rspec

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/mgznv/rubycanusellm.

License

The gem is available as open source under the terms of the MIT License.