Skip to content

RubyLLM phased migration feature-flagged - #775

Draft
fluxgravity wants to merge 4 commits into
AllYourBot:mainfrom
strivedi183:rubyllm-phased-migration-with-feature-flag
Draft

RubyLLM phased migration feature-flagged#775
fluxgravity wants to merge 4 commits into
AllYourBot:mainfrom
strivedi183:rubyllm-phased-migration-with-feature-flag

Conversation

@fluxgravity

@fluxgravity fluxgravity commented Aug 18, 2026

Copy link
Copy Markdown

What

Phases 1–4 of the RubyLLM migration plan — feature-flagged (off by default) migration from three per-provider SDK gems to the unified ruby_llm gem. Each commit maps to one plan phase; they can be split into separate PRs later if narrower review is desired.

Phase 1 — Scaffolding (98afee5)

  • Adds ruby_llm ~> 1.16.0 (patchlocked; existing SDK gems untouched)
  • Adds use_ruby_llm feature flag (off by default) and initializer with intentional dummy keys
  • Empty AIBackend::RubyLLM shell with supports_driver? → false
  • APIService#ai_backend guard routes through RubyLLM only when flag is on AND driver is supported
  • Fixes latent bug where preference writes clobbered unrelated keys (now deep-merges)

Phase 2 — OpenAI text-only chat (5e39539)

  • supports_driver? returns true for openai only
  • get_oneoff_message / stream_next_conversation_message full overrides on RubyLLM's Chat API
  • stream_handler yields chunks, captures tokens, maps RubyLLM errors to ConfigurationError/RateLimitError
  • Text-only preceding_conversation_messages, tool messages skipped
  • Groq (openai driver, non-canonical URL) via openai_api_base override
  • Full TestClient::RubyLLM::Chat fake

Phase 3 — Anthropic + Gemini text chat (0666e32)

  • supports_driver? now true for all three drivers
  • provider_slug / dynamic ruby_llm_context / provider_for_url for per-provider routing and test_execute
  • Anthropic + Gemini streaming and job-level tests

Phase 4 — Image/PDF attachment parity (cc20f15)

  • Multimodal input via RubyLLM::Content.new(text, attachments) with RubyLLM::Attachment (ActiveStorage-native)
  • PDFs inlined as extracted text via document.extract_pdf_text (no native PDF upload)
  • sanitize_content strips json_of_generated_image on replay
  • Per-driver vision streaming, PDF extraction, mixed image+text, and sanitization tests

Not in this PR

No existing backends, tests, or gems are modified. Tool/function calling is deferred to Phase 5 (and the Phase 4.5 interception spike). Toolbox::Image image generation is untouched. Old backends are removed only in Phase 7.

Testing

bin/rails test — 813 runs, 0 failures, 0 errors. New RubyLLM tests opt in via stub_features(use_ruby_llm: true). With the flag on (USE_RUBY_LLM_FEATURE=true), old-SDK tests fail as expected — the test-env carve-out is deferred to Phase 6 per the plan.

@fluxgravity fluxgravity changed the title feat(rubyllm): add Phase 1 scaffolding and fix preferences merge bug RubyLLM phased migration feature-flagged Aug 19, 2026
Add ruby_llm ~> 1.16.0 gem, feature flag (use_ruby_llm, off by default),
empty AIBackend::RubyLLM shell with supports_driver? returning false,
and TestClient::RubyLLM double. The APIService#ai_backend guard remains
dead code until Phase 2 flips supports_driver? to true.

Also fix a latent bug where preference writes clobbered unrelated
keys -- both UsersController and Settings::PeopleController now
deep-merge incoming preferences instead of replacing the entire
column.

Permit feature: [:use_ruby_llm] in both preference write paths so
the flag is per-user writable (no UI yet).
Implements AIBackend::RubyLLM with streaming, token counting, oneoff
messages, and error mapping for the openai driver. RubyLLM gem's
builder-pattern Chat is used with per-request context keys.

- supports_driver? returns true for "openai" only
- get_oneoff_message and stream_next_conversation_message are full
  overrides using RubyLLM's Chat API
- stream_handler proc yields chunk content, captures input/output
  tokens, and maps RubyLLM errors to ConfigurationError/RateLimitError
- preceding_conversation_messages returns text-only {role, content}
  hashes, skipping tool messages (tool support deferred to Phase 5)
- test_execute mirrors the existing OpenAI pattern with context key
- TestClient::RubyLLM::Chat upgraded with complete/content/ask,
  class-level stubbables (text, blank_response, error_to_raise, tokens)
- 24 backend tests + 8 job integration tests
@fluxgravity
fluxgravity force-pushed the rubyllm-phased-migration-with-feature-flag branch from c7a6644 to 5e39539 Compare August 27, 2026 04:20
Extend AIBackend::RubyLLM to rout through all three providers
(OpenAI, Anthropic, Gemini) behind the use_ruby_llm feature flag.

- Add provider_slug to dynamically select provider from api_service.driver
- Update ruby_llm_context to set per-provider API keys via public_send
- Update build_chat to pass provider_slug instead of hardcoded :openai
- Add provider_for_url for test_execute routing
- Expand supports_driver? to return true for all three backends
- Add Anthropic and Gemini streaming tests (backend and job level)
- Fix duplicate use_ruby_llm key in options.yml from rebase
- Fix upstream controller tests for nested preferences params
Extend preceding_conversation_messages to pass multimodal input to
RubyLLM for all three drivers.

- Build RubyLLM::Content with image attachments for messages with documents
- Inline PDF text via document.extract_pdf_text (no native PDF upload)
- Strip json_of_generated_image from assistant content on replay
- Add per-driver vision streaming, PDF extraction, mixed image+text,
  and sanitize_content tests
This was referenced Aug 30, 2026
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.

1 participant