RubyLLM 2.0 RC1 brings human approval, resumable agents, citations, model fallbacks, prompt caching, video, speech, OCR, reranking, files, batches, and hosted research to the same Ruby and Rails API. It also separates providers from protocols, so much more of each service is available without making your application speak its wire format.
Since 1.16.0, released June 9, this release contains 487 commits and 36 merged pull requests, with 22 first-time contributors. The comparison changes 2,005 files, with 287,461 additions and 99,033 deletions. Those are repository changes, including recorded API responses, generated model data, tests, and documentation. Library and generator files under lib/, excluding the generated model catalog and aliases, account for 34,362 additions and 9,770 deletions across 462 files.
This is the first release candidate for a major version. Start with What's New in 2.0 and the upgrade guide.
bundle add ruby_llm --version 2.0.0.rc1New
Providers and protocols
- Seventeen providers are built in. Cohere, Deepgram, ElevenLabs, and Ollama Cloud join OpenAI, Anthropic, Gemini, Vertex AI, Bedrock, Azure, xAI, DeepSeek, Mistral, OpenRouter, Perplexity, Ollama, and GPUStack. The bundled registry contains 1,619 models. By @crmne. (a5dbcda9, 71a69a07)
- Providers can reuse protocols, and one provider can offer several. Providers own authentication, endpoints, catalogs, and service settings. Protocols own requests, parsing, streaming, and error normalization. Model and operation selection pick the right protocol;
protocol:provides an explicit override. By @crmne. (d398354d, 3400654b) - The provider coverage comparison is much larger. Across the selected 40 features, built-in support rises from 170 to 395 provider-feature pairs, out of 405 offered pairs. That is 225 added pairs. The interactive coverage matrix links each cell to sources, implementation, validation, and restrictions. These totals describe that selected comparison, not every endpoint the providers operate. By @crmne. (4683f905, 6051a544)
- OpenAI uses Responses by default. Chat Completions remains selectable. Responses adds hosted tools, native reasoning replay, citations, file references, and compaction; speech, file transcription, Files, chat batches, and embedding batches use their own operations. Exact request counting uses the Responses input-token endpoint. By @crmne; thanks @mastraus, @andrew-woblavobla, @tpaulshippy, @khasinski, @afurm and @nbelzer. (0875ce2d, 18d3622e, #213, #785, #770, #786)
- Anthropic gets document citations, hosted tools, files, batches, and context compaction. Web search, web fetch, code execution, and remote MCP use named server tools, preserve their native results, and continue paused provider turns. Caching and token counting have Ruby APIs. By @crmne. (f6c0e660, 18d3622e)
- Gemini gains managed caches, multimodal embeddings, media generation, and batches. There are shared aliases for Search, URL context, code execution, Maps grounding, and prepared file-search stores. The optional Interactions protocol adds stateless model conversations and remote MCP; dedicated and streamed file transcription return typed results. Veo generation and extension use the video API. By @crmne. (fe1f0c5b, 18d3622e)
- Vertex AI routes hosted models through their native protocols. Gemini, Anthropic, Mistral, and compatible partner models share one provider configuration. The expanded operations include Imagen and Gemini images, Veo video, speech, transcription, multimodal embeddings, managed caches, GCS files, chat and embedding batches, Discovery Engine reranking, prepared Search datastores, and hosted Deep Research. Some routes require additional APIs, deployments, or storage configuration. By @crmne; thanks @crhbjk2zn2. (239dbef5, 18d3622e, #659)
- Bedrock serves Converse and Mantle models through the appropriate protocols. It adds real inference-profile discovery, credential providers, multimodal input and embeddings, citations, caching, hosted tools, Stability image generation and editing, Luma video jobs, Voxtral transcription, reranking, configured guardrail moderation, S3 files, and chat and embedding batches. Support follows the specific endpoint and model; batch and video jobs need your storage resources. By @crmne; thanks @martinemde, @jscheid and @dlackty. (44f3cc09, 18d3622e, #754, #454, #861)
- Azure covers more of both OpenAI and Foundry. Responses, image generation and edits, speech, transcription, Sora video, files, and chat batches have Azure routing. Hosted partner protocols include Cohere embeddings and reranking, while supported Responses deployments expose hosted tools and compaction. Custom deployment names, resource URLs, API versions, and model availability remain part of Azure setup. By @crmne. (8fcd3802, 18d3622e)
- xAI uses its Responses dialect and adds media, files, and batches. Shared APIs cover web and X search, code execution, file search, MCP, images and image edits, video generation/editing/extension, speech, transcription, tokenization, and manual compaction. Provider-reported request costs are retained. By @crmne. (28a8ff2b, 18d3622e)
- DeepSeek adds an optional Responses protocol. Chat Completions remains the default. The integration handles its thinking controls, structured output, supported image inputs, hosted search, and image-file uploads with expiry. Its Files API does not provide downloads. By @crmne. (4993bd47, 18d3622e)
- Cohere has a native v2 integration. Chat and streaming include tools, schemas, thinking, and citations. Standalone operations cover text and image embeddings, reranking, transcription, image OCR through Parse, datasets, chat batches, and embedding batches. Parse accepts images; PDF parsing is not claimed. By @crmne. (a5dbcda9, 18d3622e)
- Mistral expands beyond compatible chat. OCR, speech, diarized and streamed transcription, Files, chat and embedding batches, and stateless Conversations hosted tools have dedicated handling. Hosted image generation also backs
paint; generated files are downloadable. By @crmne. (b6575414, 18d3622e) - OpenRouter gains an optional Responses route and broader media and retrieval support. That includes video input and generation, speech, transcription, multimodal embeddings, reranking, files, cache boundaries, compression, hosted shell execution, and exact provider-reported costs. MCP preserves the records OpenRouter actually returns and requires approvals to be disabled. Its batch integration remains subject to the provider's beta rollout. By @crmne. (390a9a42, 18d3622e)
- Perplexity citations and embeddings use the provider's actual response and endpoint formats. An explicitly selected Router Chat Completions protocol adds function tools, tool controls, and cache boundaries for accounts with preview access. Generated-file downloads work from an existing file identifier. Sonar remains the default. By @crmne. (09546082, 18d3622e)
- Deepgram and ElevenLabs provide speech and transcription, including streamed results. Existing recordings can be transcribed over their WebSocket APIs into typed chunks and a final transcript. ElevenLabs also has image/video generation, reference-media handling, and media-asset storage for accounts with the required access. By @crmne. (757129c6, 18d3622e)
- Ollama Cloud has its own credentials and provider identity. It reuses the Ollama chat dialect for the cloud models' supported vision, thinking, and tools. Local Ollama gains audio attachment rendering for audio-capable models and a sufficiently recent server. By @crmne; thanks @dalton-cole. (16b08a15, 18d3622e, #740)
- GPUStack uses its current compatible APIs and model catalog. Depending on the deployed backend, it supports audio/video input, multimodal embeddings, reranking, speech, streamed transcription, Responses, and proxy-based tokenization and video jobs. Model-proxy setup and backend capabilities determine availability; deployment-managed MCP has explicit restrictions. By @crmne. (b0aa9a9b, 18d3622e)
Conversations, agents, and tools
-
Tools can wait for a human decision. Declare
requires_approval, inspectawaiting_approval?andpending_approvals, thenapproveordenybefore continuing. Denials become tool results the model can respond to. The same decisions persist across Rails requests and jobs. By @crmne; thanks @jondavidschober. (c460d77b, #503) -
Your application can drive the conversation one step at a time.
ask_laterstages input,generaterequests one response,run_toolsexecutes pending calls, andstepadvances one generation or tool round.completekeeps the automatic loop. Partially completed tool rounds can resume without rerunning results already in the transcript. By @crmne; thanks @jbourassa, @ramontayag and @mtoneil. (bfbb2d52, ac87f5ab, #635, #690, #681) -
A conversation can be cancelled from another request or process.
cancelandcancelled?work on plain chats, agents, and persisted records. Rails jobs poll outside the query cache, so they see a cancellation written elsewhere. By @crmne; thanks @sh1nj1. (503d5284, 99a30606, #607) -
Provider-hosted tools have one registration API.
with_server_toolsand the Agentserver_toolsmacro enable named web search, web fetch, code execution, file search, image generation, and MCP integrations where available.ServerToolCallpreserves native calls and results, including streamed output and follow-up history. Prepared search indexes remain provider resources you configure separately. By @crmne. (47a9dfb6, 18d3622e) -
Remote MCP approvals share the normal approval flow where the provider supports them.
remote?distinguishes a provider-executed request from a local Ruby tool, and the call ID identifies the pending decision. A remote call never dispatches a same-named Ruby tool. Providers without an approval lifecycle reject unsupported approval settings. By @crmne. (18d3622e) -
Citations are typed values across documents, tool search results, and the web. Read source URLs, titles, cited passages, page/character positions, file identifiers, and filenames when supplied. Citations survive streaming and Rails persistence.
SearchResultslets your own retrieval tools return citable documents. By @crmne; thanks @db0sch. (f6c0e660, 18d3622e, #52) -
Thinking can use the model's defaults or explicit controls.
with_thinkingenables it,with_thinking(false)disables it where allowed, andeffort:,budget:, anddisplay:express supported preferences. Defaults follow model switches and fallbacks; summaries are available throughresponse.thinking. By @crmne; thanks @AlexanderMamrenko. (b6dd4ca8, c4f9c05b, #714) -
Fallback models can recover from transient provider or network failures.
with_fallbackspreserves the conversation's tools, schema, and settings, withbefore_fallbackandafter_fallbackhooks. Usage includes every attempt. Fallbacks need credentials and models that support the requested features. By @crmne; thanks @kieranklaassen. (ea16d66c, ac87f5ab, #621, #674) -
Prompt caching has shared settings and explicit boundaries.
with_cachingcontrols supported cache options;cache_until_heremarks a reusable prefix and persists that boundary on Rails messages.RubyLLM.cachecreates Gemini or Vertex cached-content resources that can be found, updated, deleted, and reused withwith_caching(id:). By @crmne; thanks @arunkumarry. (3c9f4294, 18d3622e, #706, #716) -
Long conversations can compact while keeping the application's transcript.
with_compactionenables supported automatic behavior.compactexplicitly calls the OpenAI, Azure, or xAI Responses compaction operation and returns a message with its usage. Later requests use the compacted context while Rails retains the original conversation and current instructions. By @crmne; thanks @fvaleye. (644c1800, 18d3622e, #763) -
Output limits and end-user attribution have shared names. Set
with_max_output_tokensandwith_end_user, or the corresponding Agent macros. Read the configured values back throughmax_output_tokens,temperature, andend_user. Providers translate supported end-user fields. By @crmne; thanks @derikolsson. (fc724a4e, 38af607f, #789) -
Tool selection and execution settings are separate from tool registration.
with_tool_options(choice:, calls:, concurrency:)controls which tools may run, the number of calls, and the existing thread/fiber execution modes. Tools and individual options can be cleared independently. By @crmne; thanks @juanmanuelramallo. (959f42cf, 38e5a597, #806) -
Tools can return attachments alongside text or structured results. Images, audio, PDFs, and other supported files pass through each protocol's tool-result format. Hash and Array results become JSON text. Parameter inference and the schema DSL remain available, and
Tool.tool_nameexposes the conventional name. By @crmne; thanks @IvanLysikov. (f62fe516, 9359d160, #858) -
A tool can inspect the ToolCall being executed. Declare the optional
tool_call:keyword to access its ID and metadata without exposing that keyword as a model parameter. By @crmne; thanks @adamcooper. (#833, f835bb44) -
Agents can select models at runtime and handle errors declaratively. Model blocks run with the agent's inputs;
rescue_fromhandles configured exceptions around delegated operations. Inherited settings are copied so subclasses can change tools, server tools, fallbacks, and other options independently. By @crmne; thanks @kryzhovnik and @skovy. (5deb83a5, 446b57ce, #676, #708) -
Prompt rendering is available outside agents.
RubyLLM.render_promptrenders reusable text/ERB prompts with locals and nested paths, including Rails engine prompt paths. Named agents automatically use their conventional prompt when present; a blank prompt means no instructions. By @kryzhovnik and @crmne; thanks @adrianthedev. (28a3669d, 3096c9ef, #675, #857) -
Inspect or adapt the next request through public chat methods.
chat.renderreturns the rendered payload with request hooks applied.before_requestruns after framework formatting and provider-option merging, so it can inspect or change the final payload. Both are delegated through agents and persisted chats. By @crmne. (9d7d63e8, d7aa6cec) -
Responses explain why generation stopped through common readers.
stopped?,max_tokens?,tool_call_stop?, andcontent_filtered?interpret normalized finish reasons across providers, with the same readers on persisted messages. By @crmne; thanks @trevorturk and @losingle. (9737d1a0, e0bcf1d4, #568, #709) -
Plain chats can replace or import their transcript.
messages =replaces history, whileadd_messageaccepts message values or attributes. Rails can copy an existing message into another conversation without moving the original record or creating new provider usage. By @crmne; thanks @mnort9 and @marksweston. (471fc27c, 08035557, #533, discussion #495) -
Logs can be directed to a file with RUBYLLM_LOG_FILE. By @Niraj22; thanks @jordan-brough. (#836, #658)
Images, video, audio, documents, and search
- Generate video with
RubyLLM.animate.animate_laterreturns aVideoJobfor polling and collecting a typedVideo; image references, first/last frames, video edits, and extensions use the same API where the selected model supports them. Integrations include Gemini, Vertex AI, Azure, xAI, OpenRouter, Bedrock, ElevenLabs, and GPUStack. The OpenAI adapter targets its deprecated Sora/Videos API; see the coverage matrix for retirement details. By @crmne. (5ade6e24, 18d3622e) - Turn text into speech with
RubyLLM.speak. Select a voice and format, read the typed result, and save it directly. A block yieldsSpeechChunkaudio bytes on supported routes while the call still returns the completeSpeech. Binary HTTP and Mistral SSE streams preserve the provider's usage when available. By @crmne; thanks @salidux and @grgr. (68fef9e7, 18d3622e, #651, #481) - Transcription can include speakers, words, segments, and timestamps.
speaker_names:,speaker_references:,timestamps:,language:, andprompt:provide the shared controls, subject to provider support. A block yieldsTranscriptionChunkvalues and a finalTranscription. SSE and WebSocket integrations transcribe existing audio files; this does not introduce a live conversation API. By @crmne; thanks @patvice. (b81b1262, 18d3622e, #628) - Generate several images with one
paint(n:)request where supported.paintalso gains Gemini image models, broader reference-image editing, mask handling on supported providers, image metadata, and normalized usage and costs. Image size is sent only when requested. By @crmne; thanks @palladius, @myxoh, @zavan and @danieldenis01. (5f8ed0e7, 18d3622e, #31, #473, #623, #750) - Images, videos, speech, and downloaded files save the same way. Complete results expose
save(path), returning the path, andto_blobfor bytes. These values also fit Active Storage attachment workflows. By @crmne. (8f2127e4, 38e5a597) - Extract document text with
RubyLLM.ocr. Mistral Document AI and Cohere Parse return typed pages, combined Markdown, and the provider's page/image/table information.pages:selects supported pages; provider-specific annotation/output settings useprovider_options:. By @crmne. (b6575414, 18d3622e) - Embeddings can combine text with supported media.
embed(..., with:)accepts the image, audio, video, or document inputs supported by the chosen embedding model.task_type:andtitle:replace hand-built task payloads. Result shapes distinguish a single input from an array, andsparse_vectorsexposes sparse output when returned. By @crmne; thanks @Ndunge-Makau, @radeno, @goianiense and @adamcooper. (4fa3bb12, 18d3622e, #529, #788, #824, #810) Bedrock embedding work and recordings also incorporate contributions from @cgmoore120. (#677, efbe8faf) - Rerank retrieved documents with
RubyLLM.rerank. Typed results retain original indices, documents, and relevance scores, withtop_n:to limit results. Integrations cover Cohere, OpenRouter, GPUStack, Bedrock, Vertex AI Search, and Azure Cohere. By @crmne. (58ace224, 18d3622e) - Moderation accepts image input and returns typed assessments.
Moderation::Resultexposesflagged?, categories, and scores;flagged_categoriescombines the flagged names. Configured Bedrock guardrails use the same operation for text and supported images, preserving actual policy assessments without inventing a model, token usage, or price. By @crmne; thanks @decaffeinatedio. (3998b053, 18d3622e, #724, #723) - Inspect token IDs or count a request before generating.
RubyLLM.tokenizereturns IDs and a count for plain text on xAI and configured GPUStack proxies.RubyLLM.count_tokensandchat.count_tokensuse supported counting endpoints for conversation input. The counting API includes supported history, instructions, tools, schema, thinking, and attachments; hosted tools, raw provider options, compaction, and request-hook changes are excluded. By @crmne. (12779995, 18d3622e) - Action Text embedded attachments reach the model with the surrounding text. Active Storage attachables in rich text are extracted into the normal attachment flow. By @crmne. (#760)
- OpenAI Responses accepts native Office document inputs. Word, PowerPoint, Excel, and other supported document types use file inputs instead of being rejected for not being PDFs. By @crmne; thanks @aviflombaum. (#826, 997f8fb7)
- Upload once and reuse a provider file.
RubyLLM.upload,UploadedFile.find,RubyLLM.find_file, andRubyLLM.downloadprovide typed metadata and downloads, with shareduri:,content_type:, and expiry options where supported. The adapters cover provider Files APIs, S3, GCS, Cohere datasets, and ElevenLabs media assets, each with its own restrictions. By @crmne; thanks @toddkummer. (fa47b775, 18d3622e, #764) - Large chat attachments can use provider storage automatically. Attachments are prepared when the request is sent, uploads are reused per provider, and expired uploads are replaced. That keeps local history usable across subsequent requests and model/provider changes. By @crmne; thanks @altxtech. (a5f3dbdd, 9ca2c002, #426, #517)
- Run a hosted research task and recover it by job ID.
RubyLLM.researchwaits for a report;research_laterreturns aResearchJobfor finding, polling, and cancellation. The Vertex AI Deep Research integration supports remote MCP and preserves reported citations and usage. Agent identity is separate from model identity, and chats continue to use application-owned history. By @crmne. (18d3622e) - Standalone operations carry configuration and instrumentation consistently. Explicit keywords,
provider_options:, per-call metadata, andRubyLLM.contextapply to media, embeddings, moderation, files, research, and related operations without constructing a chat. By @crmne; thanks @rainerborene and @goianiense. (1bc6fc03, 18d3622e, #807, #825)
Batches, accounting, and Rails
- Submit staged chats to provider batch APIs.
RubyLLM.batchreturns aBatchwith an ID, normalized status, refresh/cancel operations where supported, ordered results, and per-request statuses.Batch.findlets another process collect the answers. Tool turns can be run locally and submitted again in another batch. By @crmne; thanks @marckohlbrugge, @thomaswitt, @toddkummer and @khasinski. (9d7d63e8, 18d3622e, #1, #342) - Batch embeddings through the same interface.
embed_laterstages textEmbeddingRequestvalues. Batch collection restores scalar, one-element-array, and multi-input shapes, preserves failed positions, and correlates reordered provider results after reloading. Provider restrictions and storage requirements are documented in the batch guide. By @crmne. (7a2833bf, 18d3622e) - Usage follows each provider attempt. Response and chat totals include reported usage from retries, fallbacks, cancelled work, and attempts without a completed message. Unknown usage or pricing stays
nil; a request known never to have reached the provider can record zero. By @crmne. (2aaddf96, b69f545c) - Generation and accounting share
TokensandCost. Readinput,output,thinking,cache_read, andcache_writethrough normalized readers.cost.totalprefers provider-reported amounts, including OpenRouter and xAI prices, while unknown prices stay unknown and a real zero price remains zero. By @crmne. (05364428, 959f42cf) - Historical Rails costs stay historical. The usage ledger stores attempts and their completion-time costs separately from messages. Later registry pricing changes do not rewrite those amounts. Batch costs use batch rates or a reported aggregate invoice and remain unknown until processing ends. By @crmne. (2b6a981f, 18d3622e)
- Name a workflow and its steps without changing how you write Ruby.
RubyLLM.workflowandworkflow.stepattach workflow, parent, step, and metadata identifiers to instrumentation. Rails usesActiveSupport::Notifications; plain Ruby uses the configured instrumenter. Ordinary Ruby handles branching, loops, and concurrency. By @crmne. (8734d81d, 101d2513) - Your application owns two conversation models; RubyLLM owns the supporting tables.
acts_as_chatandacts_as_messageuse your chats and messages alongsideruby_llm_models,ruby_llm_tool_calls,ruby_llm_usages, andruby_llm_batches. Applications no longer need supportingModel,ToolCall, orBatchclasses. By @crmne. (959f42cf, 009015ea) - Persisted agents expose the same conversation controls.
Agent.create!andAgent.findreturn the application's configured chat record with tools, instructions, and options reapplied. Approvals, cancellation, loop progress, citations, thinking/native content, cache boundaries, compaction, attachments, and usage survive reloads. By @crmne. (6dd2637a, 18d3622e) - Rails can resume work with Active Job and Hotwire. Generated and documented flows stage the user message immediately, stream assistant output, stop from another request, and park for approval. Tools must tolerate retries if a process stops before saving its result. By @crmne. (06990d66, 4683f905)
- The 1.16 upgrade is split into preparation, backfill, finish, and later cleanup. The install and upgrade generators evolve together. Backfill handles custom/namespaced models, UUID keys, duplicate tool-call IDs, raw content, existing usage, and model references, with checks before destructive cleanup. By @crmne. (3b7ebd01, 009015ea)
- Optional copy mode provides a controlled route back to 1.16.
--mode copyretains legacy tables and generates compatibility guards for both application builds. Conversations changed by 2.0 stay stored but hidden during rollback, then return on resume after reconciling intervening 1.16 writes. Rename remains the default. By @crmne. (009015ea, 47b35420) - The model registry has one storage API in Ruby and Rails.
RubyLLM.models.refreshdownloads the published registry, merges configured providers, and writes the selected store. Plain Ruby uses a per-user cache file; Rails configures its database store. Provider-gem catalogs act as registered read-only fallbacks, with the main registry winning conflicts. By @crmne. (091b16a3, fe7f9d00)
Fixed
Provider requests, streaming, and results
- Stream retries cannot duplicate output already delivered. Retry is allowed only before any output reaches the caller. Errors, refusals, incomplete Responses events, and Bedrock exception frames retain their RubyLLM error type instead of disappearing or raising parser errors. Job-creation requests are not blindly retried. By @crmne. (c8931fb2, 993c51ea)
- Streamed text, tool calls, and provider pauses assemble correctly. Tool-call keys remain stable, mixed text/tool output is preserved, Anthropic block state resets between
pause_turnsegments, and final-only citations and usage survive assembly. By @crmne. (504fca4f, 18d3622e) - Gemini keeps inline images in mixed text-and-image answers. Attachments no longer disappear because the same response also contains text. By @crmne; thanks @bubiche. (0f0ba2d1, #684)
- Claude and OpenRouter thinking context survives follow-up turns. Omitted Bedrock thinking blocks, native Claude thinking/signatures, and OpenRouter
reasoning_detailsare replayed without reconstructing or dropping the provider's content. Streamed thinking-token counts and output-budget limits are handled correctly. By @crmne; thanks @mvysny and @justwiebe. (dc97623f, 2d5caa11, #895, #897, #852, #868) - Structured-output schemas keep their intended meaning. Gemini receives JSON Schema and its own batch dialect, type unions are normalized where needed, and strict-mode rules no longer make optional properties silently required. Malformed tool-call JSON raises
ToolCallParseError. By @crmne; thanks @cbillen. (a6a4bb88, 567087d3, #894) - Provider failures produce useful RubyLLM errors. Empty completion responses, string/array/nested error bodies, context-length failures, unsupported operations, and missing optional authentication dependencies are handled explicitly. Context-size overflow spellings are recognized across additional providers. By @crmne; thanks @adrianthedev, @SiteupAgencia, @fidalgo, @orthodoX, @boolean and @lucasmo. (4e769cd7, 3b3937cd, #751, #862, #733, #722, #871, #829)
- Explicit request settings reach the provider. Temperature is no longer rewritten or silently removed by model-name guesses. Image size, transcription options, false-valued settings, and mixed-key server-tool hashes survive normalization; impossible Bedrock thinking budgets raise before sending. By @crmne; thanks @adamcooper. (a3f8b2a6, de8348ee, #719)
- TLS streaming waits on the underlying socket. This fixes the wait behavior for TLS-backed WebSocket connections. Proxy and timeout settings also apply to basic connections, and Bedrock signing sorts query values correctly. By @crmne. (92884ae0, c781438a)
- Configuration inspection redacts credentials, and cached uploads stay scoped to credentials. Plain
inspectno longer prints API keys, and an upload cached for one tenant is not reused for another tenant with different credentials. By @crmne. (d17f2c2b) - Name and capability parsing avoid excessive regexp backtracking. Tool/agent name normalization and Mistral Voxtral model matching remain efficient on long inputs, including Ruby 3.1. By @crmne. (9d75b033, dd3c8481)
- Attachments preserve their bytes and source. IO attachments are read completely, renamed attachments rebuild from the source, URL schemes are recognized case-insensitively, and file timestamps accept numeric strings. Generated-file downloads use the correct provider endpoints and avoid forwarding credentials to unrelated signed media hosts. By @crmne; thanks @andreaslillebo and @skovy. (462f0bb0, 18d3622e, #762, #835)
Conversations, Rails, accounting, and catalogs
-
Instructions do not duplicate on replay or disappear behind stale records. Persisted instructions update in place,
to_llmis memoized and synchronized, full message attributes survive reconstruction, and failed transport attempts remove empty assistant placeholders. By @crmne. (142ff20a, 62d6b794) -
Tool decisions and configuration survive the loop correctly. Dynamic Active Record tool registration works, denying a call is respected even when the tool has no approval requirement, agent subclasses inherit server tools, and Chat/Agent/record delegate lists stay aligned. By @crmne; thanks @ebeigarts. (2c446372, ac87f5ab, #689)
-
Batch collection delivers each result once. Empty batches return no fabricated messages, nested provider errors retain their failed slots, stored protocol names can be resolved after reload, and array-shaped embedding results survive reordered output. By @crmne. (74e0cdb6, 18d3622e)
-
Cost calculation keeps valid model and usage information. Unregistered response model IDs fall back to the requested model for pricing,
cost(model:)can override recorded usage pricing, and streamed server-tool counters are retained. Embedding, transcription, and speech costs can use the applicable text-pricing fallback. By @crmne; thanks @smathieu. (6a92f147, d17f2c2b, #904) -
Registry refresh no longer silently shrinks the catalog. Paginated listings are read completely, skipped or failed providers retain their models, unlisted models are marked, and refresh failures are reported. Published-registry download failure leaves the existing registry intact. By @crmne. (d288e5be, fe7f9d00)
-
Model metadata comes from actual catalogs and models.dev. Anthropic context limits, OpenAI shutdown dates, OpenRouter cache prices and knowledge cutoffs, Azure fields, Mistral capabilities, Ollama model details, Bedrock profiles, and tool-control capabilities replace unsupported guesses. By @crmne; thanks @stirkac. (83fe2cba, 919a36af, #864)
-
Concurrent Rails model creation reuses the row another process inserted. An empty registry store is populated before the first chat is saved, and the model-loading task loads Active Record before using it. By @crmne. (42d74419, 40cb2925)
-
Generated Rails files follow the application's naming and routing. Schema filenames respect Zeitwerk, upgrade migration classes handle acronym inflections, custom message/model associations resolve correctly, and chat UI routes, controllers, and tool partials use conventional names and stable ordering. By @crmne; thanks @chloerei and @toluola. (3ea57a31, 0b7f7792, #877, #879, #880)
-
Upgrade preparation can be retried safely. The generator rejects already-upgraded schemas, preserves required model references, and makes preparation idempotent. Usage rows without a real model stop the upgrade so they can be corrected from original requests. By @crmne. (4282b563, fe8419a4)
-
Debug logging honors false settings. Falsy
RUBYLLM_DEBUGandRUBYLLM_STREAM_DEBUGvalues turn logging off, and console inspection shows concise readers instead of large internal object graphs. By @crmne. (ad25123f, 97372300) -
Bedrock application inference profile ARNs work as model IDs. By @mattwebbio and @crmne. (#803)
-
Bedrock responses keep the requested model when the provider omits its ID. By @hschne. (#817)
-
Bedrock streaming no longer drops every chunk when the Faraday environment is absent. By @chen-anders. (#813)
-
Bedrock input usage no longer subtracts cache tokens twice. By @jmangel and @crmne. (#832, #828)
-
Bedrock thinking effort maps correctly for Claude models. By @Edilbek and @crmne; thanks @justwiebe. (#855, #851)
-
Bedrock structured-output support is no longer guessed from a model version number. By @shawnhutchison. (#899)
-
Azure streaming no longer latches onto an empty model ID and loses cost information. By @bdegomme and @crmne. (#830)
-
Responses function tools preserve optional parameters. Strict validation is opt-in. By @bdegomme and @crmne. (#844, #843)
-
Reasoning summaries keep the separators between their parts. By @hiasinho and @crmne. (#866, #865)
-
DeepSeek reasoning conversations keep the reasoning context required for later turns. By @iuhoay and @crmne. (#749)
-
Anthropic streams are requested without compression. This avoids buffering streamed output behind compression. By @xymbol and @crmne; thanks @dinsley. (#771)
-
Parallel Anthropic tool results are grouped into the user message the API expects. By @adamshen. (#853)
-
Anthropic input-plus-output context overflows raise ContextLengthExceededError. By @frostmark. (#907, #906)
-
Non-object JSON error bodies no longer crash the streaming error parser. By @Niraj22 and @crmne; thanks @mvysny. (#840, #837)
-
A provider response without a completion raises a clear error. By @jonthedecepticon; thanks @lucasmo. (#849, #847)
-
Automatic retries honor provider rate-limit headers. By @Niraj22 and @crmne. (#850)
-
Long-context cost calculation uses the correct pricing tier. By @Edilbek; thanks @victorface2. (#859, #854)
-
Converting persisted chats avoids N+1 message-association queries. By @matthewbjones. (#717)
-
Generated tool-call partials no longer produce duplicate DOM IDs. By @edudepetris. (#802, #804)
-
Agents and persisted chats delegate request hooks, token counting, and rendering consistently. By @toluola; thanks @danielefrisanco. (#884, #872, #883)
-
Agent request hooks also reach the wrapped chat. By Sai Asish Y. (48a7e751)
-
Marcel 2 can be used with Rails. The supported dependency range now accepts Marcel 1 and 2. By @FrancescoK. (#905)
-
Ruby 4 no longer warns about redefining the regexp-timeout setter. By @dominion525 and @crmne. (#721)
Changed in 2.0
These changes need attention when upgrading from 1.x. The upgrade guide contains the full replacement table, examples, and Rails procedure.
- Message content is text; parsed output and attachments have their own readers.
response.contentreturns the JSON string for structured output; useresponse.parsedfor the Hash.RubyLLM::Contentand raw content blocks are removed.before_requestis the hook for custom wire payloads. Message content is read-only. By @crmne; thanks @lirenzhu and @afurm. (b000774e, 74aa1d85, #707, #718) - Token and cost names are consistent. Replace direct message token readers with
message.tokens.input/output/thinking/cache_read/cache_write.Tokens.newreplacesTokens.build.Costexposes amounts; model and token information stay on the result. Old cache-price and mutable pricing-hash readers are replaced by named readers. By @crmne. (b44bb93c, 959f42cf) - Tools use keyword calls and full DSL names.
tool.call(city: "Berlin")replaces a positional argument Hash. Usedescription,parameter,parameters,parameters_schema, andprovider_optionsinstead of the old abbreviations and readers.with_toolsreplaceswith_tool; selection/concurrency options move towith_tool_options. The toolprovider_optionsmacro requires a Hash and rejectsnil;parametersdeclares a schema rather than acting as a public reader. By @crmne. (b44bb93c, 38e5a597) - The caller controls when tool execution stops.
Tool::Haltandhaltare removed; use the loop methods or approval flow.Message#tool_resultsnow returns the messages answering an assistant's tool calls; read a tool-result message's text throughcontent. By @crmne. (bfbb2d52, cd092760) - The schema DSL lives in Schematist. Replace
RubyLLM::SchemawithSchematist::Schema. Inline tool and agent schema blocks retain the DSL. Agentschema do ... endalways defines a schema; pass a lambda for a runtime-selected schema. Schematist is installed and loaded with RubyLLM. By @crmne. (52c4c44d, e834a84f, #869) - Instructions replace by default and callbacks are additive. Use
append: trueto add instructions.before_message,after_message,before_tool_call, andafter_tool_resultreplace the oldon_*names and run alongside persistence callbacks. BareAgent.instructionsreads configuration; named agents discover optional conventional prompts automatically. By @crmne. (d2d61e16, 959f42cf) - Setters and switches follow a predictable shape. Value setters accept
nilto reset and return the chat. Thinking, citations, caching, and compaction accept no argument ortrueto enable, options to configure, andfalseto disable; those four switches rejectnil. Agent macros and Rails delegates match. By @crmne. (dc18caed, d7aa6cec) - Provider-specific options have one name. Replace
with_params,params:, and toolwith_paramswithwith_provider_options/provider_options:. Values stay in the provider's own request shape. Shared concepts such as OCRpages:, uploaduri:/content_type:, and embeddingtask_type:/title:remain keywords. Instrumentation uses:provider_optionstoo. By @crmne. (9f62b332, fdf42b5f) - RubyLLM enums are Symbols.
finish_reasonis normalized to:stop,:max_tokens,:tool_calls, or:content_filter; model types, usage/batch statuses, and framework thinking efforts also use Symbols. Provider IDs and provider-owned values remain Strings. By @crmne. (9737d1a0, 5452cd3d) - Model lookup and pricing have one public interface.
RubyLLM::ModelreplacesModel::Info; usename,max_output_tokens,price(:input), andsupports?(:vision)instead of legacy readers/predicates. Passprovider:as a keyword andassume_model_exists:for explicit unknown-model use. Resultmodelreplacesmodel_id. By @crmne. (7b07bf92, 959f42cf) - Registry refresh and storage use plain method names.
refresh,load_from_json, andload_from_storereplace bang/legacy variants.model_registry_storeandmodel_registry_filereplace old source classes and application registry models. A custom store implementsreadand optionallywrite. By @crmne. (091b16a3, fe7f9d00) - Errors take the message first. Use
Error.new("message", response: response).UnsupportedAttachmentErroris a RubyLLM error, and malformed tool arguments raiseToolCallParseError. By @crmne. (567087d3, 959f42cf) - Several standalone result and option names are clearer. Transcription uses
format:instead ofresponse_format:. Moderation exposes typedresultsandflagged_categories. Image usage is read throughtokensandcost. Local/inline attachments expose bytes throughcontent; generated/downloaded results providesaveandto_blob. By @crmne. (b121fa8b, 959f42cf) - OpenAI-specific configuration must match the selected protocol. Responses is now the default; existing Chat Completions-only raw options need migration or
protocol: :chat_completions. Function tools default tostrict: falseto preserve optional parameters, with explicit strict configuration available. Provider implementation modules move toRubyLLM::Protocols. By @crmne and @bdegomme. (0875ce2d, d398354d) - Rails uses the association-based integration and framework-owned supporting tables. The legacy
acts_aspath anduse_new_acts_assetting are retired.ask_laterreplacescreate_user_message; plain transcript replacement replacesreset_messages!. Install and upgrade generators produce the new schema and supporting records. By @crmne. (b47d0f45, 009015ea)
Rails migration choices
| Mode | What it does | Returning to 1.16 |
|---|---|---|
| Rename, the default | Reuses existing model/tool-call tables under RubyLLM's ownership. | Restore the pre-upgrade database and matching application build. |
| Copy, optional | Retains legacy tables while 2.0 uses its supporting tables, with compatibility code in both builds. | Follow the coordinated rollback procedure before final cleanup; 2.0-changed conversations remain stored for resumption. |
Both modes run preparation, backfill, and finish with affected activity paused. Cleanup belongs in a later deployment. Copy mode needs additional storage and reconciliation work; it selects one active version per database and does not provide simultaneous 1.16/2.0 traffic splitting. Rehearse with a database copy and your application's own schema and write paths. By @crmne. (009015ea, 47b35420)
Documentation and development
-
The guides now cover the complete 2.0 API. New and expanded guides cover approvals, server tools/MCP, citations, caching, tokenization, video, speech, transcription, OCR, files, reranking, hosted research, batches, usage, instrumentation, durable agents, memory, RAG, generators, and upgrading. Examples connect those operations with ordinary Ruby and Rails code. By @crmne. (691ef5c9, 4683f905)
-
The website has versioned documentation and a new theme. A refreshed homepage, capability-first navigation, provider logos, updated company/sponsor presentation, API links, and separate stable/next guides make the expanded framework easier to explore. Structured metadata is escaped correctly and the model reference links to its generated registry. By @crmne. (67a1d2a0, 4683f905)
-
The public API has RDoc, including generated delegates and Rails macros. The module overview covers standalone operations as well as chats. The reviewed report contains 813 members, including 495 methods, with zero undocumented members in its configured public scope. By @crmne. (b44bb93c, e0bcf1d4)
-
The gem ships a RubyLLM agent skill and an executable. The skill teaches coding assistants the current public API, and
ruby_llm provider-gemgenerates a standalone integration with configuration, catalog tasks, specs, and CI. Generator tooling loads explicitly outside the runtime tree. By @crmne. (cd61467b, 009015ea) -
Architecture checks enforce the framework's boundaries. Archspec checks provider/domain separation, complete protocol contracts, registry ownership, public naming, Ruby/Rails isolation, and matching Agent/Chat APIs. Shared transport, streaming, accounting, registry, files, and support internals now live with their owning namespaces. By @crmne. (f1cf3b0e, 18d3622e)
-
Tests distinguish unit behavior from provider recordings. Live examples are tagged
:live; shared model-selection helpers use actual catalog models. Failed live examples remove their cassette for re-recording. HTTP and WebSocket fixtures have broader sanitization, portability, and provider coverage. By @crmne; thanks @cgmoore120. (a517b71a, 71a69a07, #815) -
CI exercises the Rails upgrade against real databases and the released 1.16 gem. The release matrix covers 19 supported combinations across Ruby 3.1 through 4.0, JRuby 10.0.2.0, and Rails 7.1 through 8.1. Separate PostgreSQL 17 and MySQL 8.4 checks exercise migrations; latest Ruby/Rails runs the generator suite and rollback/resume compatibility tests. By @crmne. (009015ea, ec710421)
-
Gem publication starts with a published GitHub release. The workflow verifies the immutable tag, gem version, prerelease flag, and main-branch ancestry, then runs security, lint, and tests before publishing the same built gem to RubyGems and GitHub Packages. It retains the 24-hour cassette-freshness gate. By @crmne. (e68aefd6, ec710421)
-
The package includes what an installed user needs. The executable, agent skill, generator templates, RDoc options, model catalogs, and operation assets ship in the gem. JSON stays below version 3 for Faraday/Rails compatibility; Schematist replaces
ruby_llm-schema. By @crmne. (73c02883, e7a15427) -
Contribution instructions describe the actual architecture and review process.
AGENTS.md, the contributing skill, provider scaffolding guidance, and advisory Copilot review instructions cover API consistency, model evidence, tool testing, docs, and release practices. By @crmne. (d112146a, 9b30f939) -
Generator specs were updated for the newer Rails integration defaults. By @xymbol. (#801)
-
Generator specs ignore user-level Rails configuration. By @andyw8 and @crmne. (#892)
-
Scaffold specs resolve their temporary directory consistently on macOS. By @toluola. (#902, #901)
-
The development RDoc dependency remains compatible with JRuby. By @xymbol. (#831)
-
The coverage dependency no longer breaks CI. By @jonthedecepticon; thanks @Niraj22. (#846, #842)
-
The ecosystem guide includes RubyLLM::TopSecret. By @stevepolitodesign and @crmne. (#731)
-
The ecosystem guide includes RubyLLM::Test. By @toddkummer and @crmne. (#752)
-
The ecosystem guide includes RubyLLM::Contract. By @justi and @crmne. (#808)
-
The ecosystem guide includes RubyLLM::Instructor, Registry, Tokenizer, and Turbovec. By @washu and @crmne. (#812)
Known issue
The RC1 provider-gem command creates the project, but its automatic Bundler step fails because the generated gemspec requires ruby_llm >= 2.0, which excludes 2.0.0.rc1. In the generated project, change that dependency to >= 2.0.0.rc1, replace the development Gemfile's Archspec GitHub entry with gem "archspec", and run bundle install again. The template correction is not part of the immutable RC1 gem.
Release checks
The final integrated local run completed 4,306 examples with 0 failures and 63 pending. The pending examples comprise 47 provider limitations or inapplicable combinations, 13 account/resource constraints, and 3 scenarios still unverified. They are not all unsupported features.
The release preparation freshly recorded 817 HTTP cassettes containing 1,547 interactions, plus 5 WebSocket fixtures. The release workflow passed all 24 jobs, including the 19 Ruby/Rails combinations, database-adapter checks, security, lint, validation, and publication. Clean installations of the published RubyGems artifact and public-source Bundler installs passed API, catalog, packaged-asset, and eager-loading checks.
Provider support is model- and endpoint-specific. The coverage matrix distinguishes implemented contracts, successful live checks, account restrictions, and remaining gaps. The release does not claim full endpoint coverage or universal live verification. By @crmne. (71a69a07, 6051a544)
Please report RC1 problems with a small reproduction, your Ruby/Rails versions, and the provider and model involved.
Thanks
Code and documentation by @crmne, @adamshen, @andyw8, @bdegomme, @chen-anders, @dominion525, @Edilbek, @edudepetris, @FrancescoK, @frostmark, @hiasinho, @hschne, @iuhoay, @jmangel, @jonthedecepticon, @justi, @kryzhovnik, @matthewbjones, @mattwebbio, @Niraj22, @shawnhutchison, @stevepolitodesign, @toddkummer, @toluola, @washu and @xymbol, and Sai Asish Y.
First contributions in this release from @adamshen, @andyw8, @bdegomme, @chen-anders, @dominion525, @Edilbek, @edudepetris, @FrancescoK, @frostmark, @hschne, @iuhoay, @jmangel, @jonthedecepticon, @justi, @matthewbjones, @mattwebbio, @Niraj22, @shawnhutchison, @stevepolitodesign, @toddkummer, @toluola and @washu.
Thanks also to @adamcooper, @adrianthedev, @afurm, @AlexanderMamrenko, @altxtech, @andreaslillebo, @andrew-woblavobla, @arunkumarry, @aviflombaum, @boolean, @bubiche, @cbillen, @cgmoore120, @chloerei, @crhbjk2zn2, @dalton-cole, @danieldenis01, @danielefrisanco, @db0sch, @decaffeinatedio, @derikolsson, @dinsley, @dlackty, @ebeigarts, @fidalgo, @fvaleye, @goianiense, @grgr, @IvanLysikov, @jbourassa, @jondavidschober, @jordan-brough, @jscheid, @juanmanuelramallo, @justwiebe, @khasinski, @kieranklaassen, @lirenzhu, @losingle, @lucasmo, @marckohlbrugge, @marksweston, @martinemde, @mastraus, @mnort9, @mtoneil, @mvysny, @myxoh, @nbelzer, @Ndunge-Makau, @orthodoX, @palladius, @patvice, @radeno, @rainerborene, @ramontayag, @salidux, @sh1nj1, @SiteupAgencia, @skovy, @smathieu, @stirkac, @thomaswitt, @tpaulshippy, @trevorturk, @victorface2 and @zavan for reports, reproductions, reviews, design discussions, and proposals tied to the changes above. Several proposals were incorporated or reworked directly on main; their authors are credited with the relevant feature rather than counted as merged PRs.
Full changelog: 1.16.0...v2.0.0.rc1