Leva

repository·main·Indexed 40 days ago

https://github.com/kieranklaassen/leva

A Ruby on Rails framework for evaluating Language Models (LLMs) using ActiveRecord datasets. Leva enables developers to run experiments, implement custom evaluation logic via Leva::BaseRun and Leva::BaseEval, and optimize prompts using DSPy. It includes features for exporting datasets to JSONL and fine-tuning models via Together AI, with a built-in UI available at /leva.

Tokens
5.6K
Snippets
23
Records
28
Agent score
99%

What's inside Leva

  1. Understand Cora Email Categorization and Routing logic

    main

    Cora's email triage system makes two decisions for every email:

    1. Category: The type of email (e.g., promotion, important_draft).
    2. Routing: Where the email goes based on its category's action.

    Routing Rules

    Cora actionRoutingMeaning
    DraftAction / InboxActioninboxNeeds you now (reply needed, or time-sensitive)
    BriefActionbriefedYou should know; summarized in digest + archived
    UnsubscribeActionarchivedSpam — never inbox, never briefed

    Default Categories and Routing

    CategoryRouting
    important_draft, important_context, important_sop, timelyinbox
    important_info, action, promotion, newsletter, comments, payments, calendar, packages, otherbriefed
    spamarchived
  2. How fine-tuned models are registered and synchronized

    main

    Leva uses a ModelRegistrar to handle overlay persistence and registry synchronization. When a fine-tuning job completes successfully, the new model ID is registered to an overlay.

    This architecture allows models registered in one process (e.g., a rails runner background job) to appear in the dropdowns of another process (e.g., the running web server). The synchronization is achieved via available_models → ModelRegistrar.sync!, which reads the overlay file to update the model catalog without requiring changes to the underlying RubyLLM catalog.

  3. Make ActiveRecord models compatible with Leva

    main

    To use existing ActiveRecord models as datasets in Leva, include the Leva::Recordable concern. You must implement several methods to define how data is displayed and used for evaluation.

    Required/Recommended methods:

    • ground_truth: Returns a String representing the correct label.
    • index_attributes: Returns a Hash of attributes for the dataset records index view.
    • show_attributes: Returns a Hash of attributes for the dataset record show view.
    • to_llm_context: Returns a Hash of attributes used as context for the LLM during evaluation.
    • to_dspy_context (Optional): Returns a Hash<Symbol, String> for DSPy optimization. All values must be strings. If not defined, it falls back to to_llm_context.
    class TextContent < ApplicationRecord
      include Leva::Recordable
    
      def ground_truth
        expected_label
      end
    
      def index_attributes
        { text: text, expected_label: expected_label }
      end
    
      def show_attributes
        { text: text, expected_label: expected_label }
      end
    
      def to_llm_context
        { text: text, expected_label: expected_label }
      end
    
      def to_dspy_context
        { text: text }
      end
    end
  4. Seed Cora Email datasets in Leva

    main

    To populate Leva with the Cora email datasets, run the seeding scripts from the Leva repository root. These scripts run against the test/dummy app and are idempotent (re-running replaces the prior dataset of the same name).

    Commands

    cd test/dummy
    bin/rails runner ../../script/datasets/seed_category_dataset.rb
    bin/rails runner ../../script/datasets/seed_routing_dataset.rb

    Environment Variables

    • COUNT: Number of records to generate (default: 1000).
    • SEED: Random seed for deterministic generation (default: 42).
    • DATASET_NAME: The name of the dataset to create.
  5. Install Leva in a Rails application

    main

    To install Leva, add the gem to your Gemfile, install dependencies, run the provided migrations, and mount the engine in your routes.

    1. Add to Gemfile:
    gem 'leva'
    1. Run bundle install.
    2. Install migrations:
    rails leva:install:migrations
    rails db:migrate
    1. Mount the engine in config/routes.rb:
    Rails.application.routes.draw do
      mount Leva::Engine => "/leva"
    end

    The Leva UI will be available at /leva.

  6. Optimize prompts with DSPy

    main

    Leva supports prompt optimization via Leva::PromptOptimizer. This requires the dspy and a provider gem (like dspy-ruby_llm) in your Gemfile.

    Optimizers:

    • :bootstrap: Fast selection of few-shot examples (no extra gem).
    • :gepa: High-quality reflective prompt evolution (requires dspy-gepa).
    • :miprov2: Bayesian optimization for large datasets (requires dspy-miprov2).

    Modes:

    • :light: ~5 min (Quick experiments)
    • :medium: ~15 min (Balanced)
    • :heavy: ~30 min (Production)

    Usage:

    optimizer = Leva::PromptOptimizer.new(
      dataset: dataset,
      optimizer: :gepa,
      mode: :medium,
      model: "claude-opus-4-5"
    )
    
    result = optimizer.optimize
    # result[:system_prompt], result[:user_prompt], result[:metadata]
  7. Use Cora Email training datasets

    main

    There are two specific datasets available for fine-tuning models to perform Cora's email triage. Both use the same realistic email samples but differ in their ground truth labels.

    1. Cora Email Categorization

    • Goal: Train pure categorization (email $\rightarrow$ category).
    • Recordable: EmailSample.
    • LLM Context: { from, from_name, subject, body }.
    • Ground Truth: The category slug (e.g., "promotion").

    2. Cora Email Routing (Inbox vs Briefed)

    • Goal: Train the full decision in one shot (email $\rightarrow$ category + routing).
    • Recordable: EmailRoutingSample. | LLM Context: { from, from_name, subject, body }.
    • Ground Truth: JSON (e.g., {"category":"promotion","routing":"briefed"}).
  8. Fine-tune models using Together AI

    main

    Leva supports dataset fine-tuning via Together AI. Once a dataset meets the minimum record requirement (currently 10 records), you can trigger a fine-tuning job.

    Workflow:

    1. Navigate to the Dataset page.
    2. Ensure the dataset has at least 10 records. If it has fewer, the 'Fine-tune Model' button will be disabled and display how many more records are needed.
    3. Click 'Fine-tune Model'. This creates a FineTuneRun and enqueues a FineTuneJob.
    4. You will be redirected to a progress page to poll the job status.
    5. Upon success, the resulting model is registered via Leva::ModelRegistrar and becomes selectable in other parts of the application (e.g., Optimize-Prompt or Experiment dropdowns).

    Note: The current implementation uses a hardcoded DEFAULT_BASE_MODEL (e.g., Qwen/Qwen3-8B).

    # Ensure your environment variable is set to avoid job failure
    export TOGETHER_API_KEY='your_api_key_here'
  9. Configure fine-tuned model persistence and authorization

    main

    When using Together AI for fine-tuning, you can configure how registered models are stored and who is allowed to trigger a fine-tune via a Leva initializer.

    Use Leva.configure to set:

    • config.authorize_fine_tune: A proc that accepts a controller and returns a boolean.
    • config.fine_tuned_models_path: A string path where the model registry JSON is stored.
    # config/initializers/leva.rb
    Leva.configure do |config|
      config.authorize_fine_tune = ->(controller) { controller.current_user&.admin? }
      config.fine_tuned_models_path = Rails.root.join("storage", "leva_fine_tuned_models.json").to_s
    end
  10. Use Together AI models in Leva

    main

    Once the Leva::Providers::Together provider is registered with the Leva::Engine, you can interact with Together-served models using standard RubyLLM calls or Leva components. Because the provider implements local? as true, RubyLLM will not attempt to remote-list models during a refresh! call, relying instead on explicit registration via Leva::ModelRegistrar.

    # Example of calling a Together model via RubyLLM after registration
    RubyLLM.chat(model: "together-model-id", messages: [{ role: "user", content: "Hello!" }])
  11. Configure asset precompilation for Leva

    main

    Leva includes its own stylesheets. If you are using Sprockets, you must ensure that the Leva application CSS is explicitly included in your precompile list to avoid missing styles in production.

    If you are using Propshaft, the asset paths are automatically added by the engine, but Sprockets users should verify their assets.precompile configuration.

    # If using Sprockets, add this to your assets configuration
    Rails.application.config.assets.precompile << 'leva/application.css'
  12. Configure the Leva engine

    main

    Use Leva.configure to set host-level settings for the engine. This is typically done in a Rails initializer.

    Key configuration options:

    • authorize_fine_tune: A proc that receives the current controller and returns truthy to allow a fine-tuning process to start. Defaults to always-allow.
    • fine_tuned_models_path: An explicit string path to the overlay file that persists Leva-registered fine-tuned models. If not provided, it defaults to config/leva_fine_tuned_models.json in a Rails app, or config/leva_fine_tuned_models.json relative to the current working directory.
    Leva.configure do |config|
      # Restrict fine-tuning to admins
      config.authorize_fine_tune = ->(controller) { controller.current_user&.admin? }
    
      # Specify a custom path for model persistence
      config.fine_tuned_models_path = Rails.root.join("storage", "leva_fine_tuned_models.json").to_s
    end