RubyLLM Documentation

repository·main·Indexed 26 days ago

https://github.com/crmne/ruby_llm

A Ruby AI framework providing a unified interface for AI providers like OpenAI, Anthropic, and Google. It enables the development of chats, agents, RAG, and tool-calling using idiomatic Ruby. Features include support for embeddings, transcription, text-to-speech, content moderation, and structured output via RubyLLM::Schema. Includes Rails integration with ActiveRecord support via acts_as_chat and a Chat UI, as well as advanced agentic workflows such as sequential, routing, parallel, and fan-out/fan-in patterns.

Tokens
197.9K
Snippets
287
Records
434
Agent score
81%

What's inside RubyLLM

  1. Overview of RubyLLM capabilities

    main

    RubyLLM is an open source Ruby gem providing a consistent framework for building AI applications. It allows developers to interact with multiple AI providers using a unified interface.

    Supported providers include:

    • OpenAI
    • Anthropic Claude
    • Google Gemini
    • AWS Bedrock
    • DeepSeek
    • Mistral
    • Ollama
    • OpenRouter
    • Perplexity
    • GPUStack
    • xAI
    • Any OpenAI-compatible provider

    Core features supported by the framework:

    • Chat and streaming responses
    • File attachments and multi-file analysis
    • Image generation
    • Embeddings
    • Audio transcription (speech-to-text)
    • Text-to-speech
    • Content moderation
    • Tool use and AI Agents
    • Structured output
    • Rails integration
    • Async workloads
    • Model registry access
  2. Persist Thinking Data with ActiveRecord

    main

    If you are using acts_as_chat and acts_as_message, you can persist thinking output by adding specific columns to your database.

    Required columns for the messages table:

    • thinking_text (text)
    • thinking_signature (text)
    • thinking_tokens (integer)

    Note: thinking_tokens is a breakdown of work. Do not add thinking_tokens to output_tokens for cost calculations, as output_tokens is the normalized billable bucket. For models with distinct reasoning pricing, use response.cost.thinking.

  3. Use unlisted or custom models with `assume_model_exists`

    main

    If you are using a new model release, a custom fine-tune, or a private deployment that is not in the RubyLLM registry, use assume_model_exists: true.

    Note: When using this flag, you must specify a provider:, as RubyLLM cannot infer the provider from the registry. RubyLLM will build a synthetic model object with all capabilities enabled (function_calling, streaming, vision, structured_output), but it will include a metadata warning that capabilities may not be accurate. You are responsible for ensuring the model actually supports the features you attempt to use.

    Local providers like ollama and gpustack do not require this flag; they assume models exist automatically.

    chat = RubyLLM.chat(
      model: "my-custom-deployment",
      provider: :openai,
      assume_model_exists: true
    )
    
    chat.model.capabilities  # => ["function_calling", "streaming", "vision", "structured_output"]
    chat.model.metadata      # => { warning: "Assuming model exists, capabilities may not be accurate" }
  4. Migrate to RubyLLM 2.0: Breaking Changes

    main
    Upgrading to version 2.0 involves several breaking changes to the API. Key areas of change include the removal of the legacy acts_as API, the replacement of on_* callbacks with Rails-style callbacks, and a shift toward more explicit naming conventions (e.g., using without_* instead of passing nil to with_* methods).
  5. Configure instrumentation outside of Rails

    main

    To use RubyLLM instrumentation in non-Rails environments, assign an object to RubyLLM.configure { |config| config.instrumenter = ... }. The object must implement an instrument(name, payload) method that yields to a block.

    If an exception occurs within the block, it is recommended to merge :exception and :exception_object into the payload to maintain consistency with Rails-style instrumentation.

    class AppInstrumenter
      def instrument(name, payload)
        started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    
        result = yield if block_given?
        result
      rescue StandardError => error
        payload = payload.merge(
          exception: [error.class.name, error.message],
          exception_object: error
        )
        raise
      ensure
        duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
        Observability.record(name, payload.merge(duration: duration))
      end
    end
    
    RubyLLM.configure do |config|
      config.instrumenter = AppInstrumenter.new
    end
  6. Stage questions for batch processing with `ask_later`

    main

    To prepare chats for a batch, use ask_later instead of ask. This method adds the question to the conversation and returns the chat object immediately without waiting for a model response. This is used to create a list of 'staged' chats that are ready for submission.

    Note that chat.complete? will return false for these chats because the model has not yet responded.

    chat = RubyLLM.chat(model: "claude-haiku-4-5").with_instructions("Be terse.").ask_later("What is 2 + 2?")
    chat.complete? # => false
  7. Refresh the RubyLLM Model Registry

    main

    RubyLLM uses a registry to discover and validate AI models. By default, it uses a bundled snapshot, but you can refresh it to fetch the latest catalog from rubyllm.com and discover new models from configured providers.

    Calling refresh! replaces the in-memory registry and persists it to your active store (the platform cache in plain Ruby or the Active Record database in Rails). It returns a chainable Models instance.

    Note: A failed refresh raises RubyLLM::ModelRegistryError but leaves the previously loaded registry available.

  8. Upgrade to RubyLLM 2.0: Providers and Protocols Split

    main

    RubyLLM 2.0 separates providers (host, auth, catalog) from protocols (wire format). While the public API (RubyLLM.chat, with_params, embed, paint) remains unchanged, the underlying implementation has shifted.

    OpenAI Protocol Changes

    OpenAI now defaults to the Responses API. To continue using the Chat Completions protocol, you must explicitly configure it globally or per chat:

    # Global configuration
    RubyLLM.configure do |config|
      config.openai_protocol = :chat_completions
    end
    
    # Per-chat selection
    RubyLLM.chat(model: 'gpt-5.4', protocol: :chat_completions)

    Note: If you use with_provider_options with Chat Completions-only keys (like response_format), you must either switch the chat to :chat_completions or use the Responses API equivalents (e.g., text: { format: ... }).

    Internal Module Changes

    Wire-format internals have moved to RubyLLM::Protocols. For example, RubyLLM::Providers::OpenAI::Chat is now RubyLLM::Protocols::ChatCompletions::Chat. Provider classes no longer inherit from each other; instead, a provider declares its protocols.

    RubyLLM.configure do |config|
      config.openai_protocol = :chat_completions
    end
    
    # or per chat, as part of model selection
    RubyLLM.chat(model: 'gpt-5.4', protocol: :chat_completions)
  9. Setup RAG with pgvector and neighbor

    main

    To implement Retrieval-Augmented Generation (RAG), add neighbor and ruby_llm to your Gemfile. Use the neighbor generator to set up pgvector support in your database.

    1. Add gems to Gemfile:
      gem 'neighbor'
      gem 'ruby_llm'
    2. Generate and run migrations:
      bin/rails generate neighbor:vector
      bin/rails db:migrate
    3. Create a migration with a :vector column. For OpenAI embeddings, use a limit of 1536 and an hnsw index with vector_l2_ops for efficient similarity search.
    # Gemfile
    gem 'neighbor'
    gem 'ruby_llm'
    
    # Generate migration for pgvector
    bin/rails generate neighbor:vector
    bin/rails db:migrate
    
    class CreateDocuments < ActiveRecord::Migration[7.1]
      def change
        create_table :documents do |t|
          t.text :content
          t.string :title
          t.vector :embedding, limit: 1536 # OpenAI embedding size
          t.timestamps
        end
    
        add_index :documents, :embedding, using: :hnsw, opclass: :vector_l2_ops
      end
    end
  10. Get structured Ruby objects with RubyLLM::Instructor

    main

    Use RubyLLM::Instructor to receive fully-hydrated, validated Ruby objects (plain Ruby classes or ActiveModels) from LLM calls. It automatically infers schemas from attr_accessor or ActiveModel attributes and automatically re-prompts the LLM with specific validation errors if the output fails domain validation.

    Install via:

    gem install ruby_llm-instructor
  11. Configure Bedrock regions for model resolution

    main

    For AWS Bedrock, RubyLLM automatically applies a region prefix to model IDs based on your configuration. This ensures the resolved ID matches the expected format for Bedrock inference profiles.

    Set the bedrock_region in the global configuration to enable this.

    RubyLLM.configure { |config| config.bedrock_region = "us-east-1" }
    
    chat = RubyLLM.chat(model: "claude-haiku-4-5", provider: :bedrock)
    chat.model.id  # => "us.anthropic.claude-haiku-4-5-20251001-v1:0"  (region prefix applied)
  12. Configure API keys for supported providers

    main

    Configure API keys for the specific providers you intend to use via the RubyLLM.configure block. RubyLLM only requires configuration for the providers you actually call; attempting to use an unconfigured provider will raise RubyLLM::ConfigurationError.

    Supported providers include Anthropic, Azure, Bedrock, DeepSeek, Gemini, GPUStack, Mistral, Ollama, OpenAI, OpenRouter, Perplexity, Vertex AI, and xAI. Most providers support an optional *_api_base configuration for custom endpoints (available in v1.16+ for most).

    RubyLLM.configure do |config|
      # Anthropic
      config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
      config.anthropic_api_base = ENV['ANTHROPIC_API_BASE']
    
      # Azure
      config.azure_api_base = ENV['AZURE_API_BASE']
      config.azure_api_key = ENV['AZURE_API_KEY']
      config.azure_ai_auth_token = ENV['AZURE_AI_AUTH_TOKEN']
    
      # Bedrock
      config.bedrock_api_key = ENV['AWS_ACCESS_KEY_ID']
      config.bedrock_secret_key = ENV['AWS_SECRET_ACCESS_KEY']
      config.bedrock_region = ENV['AWS_REGION']
      config.bedrock_session_token = ENV['AWS_SESSION_TOKEN']
      config.bedrock_api_base = ENV['BEDROCK_API_BASE']
    
      # DeepSeek
      config.deepseek_api_key = ENV['DEEPSEEK_API_KEY']
      config.deepseek_api_base = ENV['DEEPSEEK_API_BASE']
    
      # Gemini
      config.gemini_api_key = ENV['GEMINI_API_KEY']
      config.gemini_api_base = ENV['GEMINI_API_BASE']
    
      # GPUStack
      config.gpustack_api_base = ENV['GPUSTACK_API_BASE']
      config.gpustack_api_key = ENV['GPUSTACK_API_KEY']
    
      # Mistral
      config.mistral_api_key = ENV['MISTRAL_API_KEY']
      config.mistral_api_base = ENV['MISTRAL_API_BASE']
    
      # Ollama
      config.ollama_api_base = 'http://localhost:11434/v1'
      config.ollama_api_key = ENV['OLLAMA_API_KEY']
    
      # OpenAI
      config.openai_api_key = ENV['OPENAI_API_KEY']
      config.openai_api_base = ENV['OPENAI_API_BASE']
    
      # OpenRouter
      config.openrouter_api_key = ENV['OPENROUTER_API_KEY']
      config.openrouter_api_base = ENV['OPENROUTER_API_BASE']
    
      # Perplexity
      config.perplexity_api_key = ENV['PERPLEXITY_API_KEY']
      config.perplexity_api_base = ENV['PERPLEXITY_API_BASE']
    
      # Vertex AI
      config.vertexai_project_id = ENV['GOOGLE_CLOUD_PROJECT']
      config.vertexai_location = ENV['GOOGLE_CLOUD_LOCATION']
      config.vertexai_service_account_key = ENV['VERTEXAI_SERVICE_ACCOUNT_KEY']
      config.vertexai_api_base = ENV['VERTEXAI_API_BASE']
    
      # xAI
      config.xai_api_key = ENV['XAI_API_KEY']
      config.xai_api_base = ENV['XAI_API_BASE']
    end