Skip to content

feat: pin RubyLLM's backend with a platform option - #374

Open
TonsOfFun wants to merge 7 commits into
mainfrom
claude/issues-371-373-draft-pr-e8z7q6
Open

feat: pin RubyLLM's backend with a platform option#374
TonsOfFun wants to merge 7 commits into
mainfrom
claude/issues-371-373-draft-pr-e8z7q6

Conversation

@TonsOfFun

@TonsOfFun TonsOfFun commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Closes #373. Also adds regression tests for the acronym loading fix that landed in #372 (#371, by @aoki-ryusei) — the branch is merged up with main, so the fix itself is not part of this diff.

Pin RubyLLM's backend with platform: (#373)

RubyLLM resolves which of its providers serves a request from the model ID. A model ID served by more than one backend — gemini-2.5-flash exists on both the Gemini API and Vertex AI — lands on whichever RubyLLM's PROVIDER_PREFERENCE lists first (gemini), and ActiveAgent had no way to say otherwise: the first argument to generate_with :ruby_llm selects the ActiveAgent adapter, not RubyLLM's internal backend.

The new platform: option forwards to RubyLLM's provider: when resolving the model, for embeddings as well as prompts:

class VertexAgent < ApplicationAgent
  generate_with :ruby_llm, model: "gemini-2.5-flash", platform: :vertexai
end

or in config/active_agent.yml:

production:
  ruby_llm:
    service: "RubyLLM"
    model: "gemini-2.5-flash"
    platform: "vertexai"

Auth and region stay in RubyLLM.configure (e.g. vertexai_project_id, vertexai_location). Omitting platform: passes provider: nil to RubyLLM::Models.resolve, which is identical to omitting it — today's model-based routing is unchanged.

No new version floor: Models.resolve has accepted provider: since before it accepted the config: kwarg the provider already passes (verified against ruby_llm 1.3.0 and 1.16.0).

Naming: why platform: rather than provider: or backend:

  • provider: (RubyLLM's own name, proposed in the issue) collides with ActiveAgent's provider concept — generate_with's first argument is already a provider reference.
  • backend: was the other candidate, but delegate_to already uses backend: for "the provider stack that runs a sub-agent" (delegate_to X, backend: { provider: :anthropic, ... }). Reusing it here gives the word two meanings, one nested inside the other: backend: { provider: :ruby_llm, backend: :vertexai }.
  • platform: is untaken in the codebase and names the thing being chosen — the platform serving the model (Gemini API vs Vertex AI, Bedrock, Azure). It composes cleanly with delegation: backend: { provider: :ruby_llm, platform: :vertexai }.

Renaming is a small find/replace (one attribute, one kwarg, tests, docs) if you'd rather have backend:.

Regression tests for the acronym loading fix (#371 / #372)

test/providers/ruby_llm/provider_loading_test.rb covers what #372 fixed: it registers the RubyLLM acronym the way the ruby_llm railtie does, asserts provider_load("RubyLLM") resolves, and restores the original inflections afterwards — asserting in-test that nothing leaked. Edge Rails freezes every Inflections instance after boot, so the acronym goes on an unfrozen dup swapped in for the test (Inflections#initialize_dup exists for exactly this), placed in whichever slot the running Rails reads (@__en_instance__ on 8.1+, the instance map on 7.2). Reverting #372's alias file makes these tests error with the issue's exact cannot load such file -- active_agent/providers/rubyllm_provider.

Changes

  • lib/active_agent/providers/ruby_llm/options.rbplatform attribute
  • lib/active_agent/providers/ruby_llm_provider.rb — forward provider: options.platform&.to_sym in resolve_ruby_llm_provider!
  • test/providers/ruby_llm/provider_loading_test.rb (new) — 5 tests: both require paths, provider_load with and without the acronym registered, remap variants
  • test/providers/ruby_llm/ruby_llm_provider_test.rb — 5 tests: platform reaches Models.resolve for prompts and embeddings, symbol and string values, nil default, and an end-to-end test through generate_withprepare_prompt_parameters → provider options
  • docs/providers/ruby_llm.md — "Pinning the Platform" section with the Vertex AI example
  • AGENTS.md, CHANGELOG.md — notes for both changes (changelog credits fix: load RubyLLM when the gem registers its acronym #372 for the fix)

Test plan

  • bin/test on the ruby_llm tree + provider concern tests, repeated under random seeds against the rails7, rails8, and railsmain gemfiles: 0 failures, 0 errors, 0 skips every run (the railsmain runs exercise the frozen-inflections boot state)
  • Full bin/test matches the main baseline exactly — the only failures in that environment are pre-existing missing-API-key integration tests, identical with and without this branch
  • bin/rubocop clean on all touched files
  • All 8 CI checks green on the current head

Open question

RubyLLM pairs provider: with assume_model_exists: for model IDs not in its registry (fine-tunes, self-hosted endpoints). Left out here to keep scope tight; worth a follow-up if there's demand.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5b2SozepYRbwEcFwkARek

claude added 6 commits August 23, 2026 23:46
The ruby_llm railtie registers RubyLLM as an inflector acronym, which
turns "RubyLLM".underscore into "rubyllm", so provider loading
required a nonexistent rubyllm_provider.rb and raised a LoadError.
Cover that require path with an alias file, the same fix
openai_provider.rb applies for OpenAI.

Fixes #371. Fix proposed in #372 by @aoki-ryusei.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5b2SozepYRbwEcFwkARek
RubyLLM resolves which of its providers serves a request from the
model ID, and a model served by more than one -- gemini-2.5-flash
exists on both the Gemini API and Vertex AI -- lands on whichever
RubyLLM's registry prefers, with no way to say otherwise from
ActiveAgent.

Forward a new platform option to RubyLLM's provider: when resolving
the model, for embeddings as well as prompts:

    generate_with :ruby_llm, model: "gemini-2.5-flash", platform: :vertexai

It is not named provider: because a provider reference is already the
first argument to generate_with, and not backend: because delegate_to
already uses backend: for the stack that runs a sub-agent. Omitting it
keeps model-based routing unchanged.

Closes #373.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5b2SozepYRbwEcFwkARek
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5b2SozepYRbwEcFwkARek
Takes main's rubyllm_provider.rb from #372 so the alias fix itself stays
out of this PR's diff.

# Conflicts:
#	lib/active_agent/providers/rubyllm_provider.rb
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5b2SozepYRbwEcFwkARek
The helper swapped the :en entry of Inflections' @__instance__ map, but
Rails 8.1 keeps the :en instance in a dedicated @__en_instance__, so the
swap was a no-op and the registered acronym leaked into later tests.
Mutate the live instance in both directions instead — register the
acronym, then delete it and rebuild the acronym regexes — which works on
both storage layouts, and assert the restoration inside the test so a
future leak fails loudly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5b2SozepYRbwEcFwkARek
@TonsOfFun TonsOfFun changed the title RubyLLM: load under the gem's registered acronym, pin backends with platform: feat: pin RubyLLM's backend with a platform option Aug 24, 2026
@TonsOfFun
TonsOfFun marked this pull request as ready for review August 24, 2026 20:58
Edge Rails freezes every Inflections instance after boot
(active_support.freeze_inflections), so registering and removing the
acronym on the live instance raises FrozenError on the railsmain CI job.
Swap in an unfrozen dup for the test -- dup support is what
Inflections#initialize_dup exists for -- and restore the original,
frozen or not, afterwards. The dup goes in whichever slot the running
Rails reads: @__en_instance__ where defined, the @__instance__ map on
7.2. Verified against rails7, rails8, and railsmain gemfiles locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5b2SozepYRbwEcFwkARek
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Vertex AI via RubyLLM

2 participants