RubyLLM 2.0.0 #941
crmne
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
RubyLLM 2.0 brings more of each provider's API to Ruby and Rails, with new AI operations, explicit conversation controls, and agents that can resume across requests and jobs.
Highlights
Upgrading from 1.x? Read the upgrade guide for API changes and phased Rails migrations. Start with What's New in 2.0 for working examples, or continue below for the detailed changes and credits.
New
Providers and protocols
protocol:provides an explicit override. By @crmne. (d398354d, 3400654b):web_searchalias raisesUnsupportedServerToolErrorbecause the current endpoint silently ignores it. Its Files API does not provide downloads. By @crmne. (4993bd47, 18d3622e)paint; generated files are downloadable. By @crmne. (b6575414, 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, [FEATURE] Support User Approval for tool calls #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, [FEATURE] Interrupting the agentic loop #635, [FEATURE] Add Chat#step for single-iteration execution #690, [FEATURE] Add support for client-side tool calls #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, [FEATURE] Support cancelling/aborting streaming responses #607)Provider-hosted tools have one registration API.
with_provider_toolsand the Agentprovider_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, Support for Citations #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, Fix Bedrock non-streaming thinking_tokens always returning nil #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, Feature: with_fallback for model-level failover #621, feat: Add with_fallback for model-level failover #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, [FEATURE] Add prompt caching support for providers(Currently for Anthropic and Bedrock) #706, Add prompt cache support for Anthropic and Bedrock #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, [FEATURE] Surface Anthropic compaction blocks #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, [FEATURE] Provider-agnostic safety identifier (per-user abuse attribution) #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, Forward choice: and calls: from Agent tools macro to with_tools #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, [FEATURE] Move Tool#name derivation to a class-level .tool_name method #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. ([FEATURE] Pass the current ToolCall into tool execution #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, provider tools, fallbacks, and other options independently. By @crmne; thanks @kryzhovnik and @skovy. (5deb83a5, 446b57ce, Support block/proc for dynamic model selection in Agent #676, [FEATURE] Agent-level error handling hooks (rescue_from) #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, Extract RubyLLM::Prompt from Agent's private prompt rendering #675, [FEATURE] Rails engines can't ship prompt files #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, [FEATURE] Expose stop_reason from streaming responses #568, Normalize OpenAI token params and expose provider finish reasons #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, Add with_messages for scoping chat messages #533, discussion #495)Logs can be directed to a file with RUBYLLM_LOG_FILE. By @Niraj22; thanks @jordan-brough. (Add RUBYLLM_LOG_FILE env var #836, [FEATURE] Add RUBYLLM_LOG_FILE env var #658)
Images, video, audio, documents, and search
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)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, [FEATURE] Text-to-Speech support (RubyLLM.speak) #651, Add TTS support #481)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, [FEATURE] Streaming support for RubyLLM.transcribe #628)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, How do I generate multiple images at once (multiple samples from same prompt)? #31, [FEATURE] Add support for gemini 2.5 image (Nano banana) on the.paintaction #473, [FEATURE] Support additional or custom paint params #623, Support Gemini Image models in RubyLLM.paint #750)save(path), returning the path, andto_blobfor bytes. These values also fit Active Storage attachment workflows. By @crmne. (8f2127e4, 38e5a597)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)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, [FEATURE] Add multimodal embedding support (image and video) #529, [FEATURE] Surface sparse embeddings (lexical_weights / sparse_embedding) from OpenAI-compatible providers #788, Support VertexAI embedding task_type and title #824, [BUG] VertexAI embeddings cannot send task_type for gemini-embedding-001 #810) Bedrock embedding work and recordings also incorporate contributions from @cgmoore120. (Add Bedrock embedding support via InvokeModel API #677, efbe8faf)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::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, [FEATURE] Add Image Support to Moderation #724, enable images on moderation endpoint, update docs, screen out openai project ID #723)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)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, File Upload/Download (to support Batches) #764)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)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, [FEATURE] First-class per-call metadata context for observability attribution #807, Add first-class per-call metadata for observability (#807) #825)Batches, accounting, and Rails
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, Batch Request Support for Cost Optimization #1, Add batch request support for generating API payloads #342)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)nil; a request known never to have reached the provider can record zero. By @crmne. (2aaddf96, b69f545c)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)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)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)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)--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)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
Improvements from release-candidate testing
Disabling tools works on OpenRouter's Chat Completions route. Disabled function definitions are omitted from the request to avoid empty completions, while the chat keeps its tools for later use.
Thinking history survives follow-up turns without crossing providers. Anthropic and Bedrock retain native thinking blocks, including streamed and persisted messages ([BUG] A multi-block thinking turn cannot round-trip: RubyLLM::Thinking holds one (text, signature) pair #896). Switching providers drops incompatible thinking content from the outgoing request. By @crmne and @kieranklaassen. (Drop another provider's thinking on replay #935)
Persisted attachments avoid repeated database queries. Preload attachment blobs, Action Text content, and embedded rich-text blobs when building requests. By @MatheusRich and @yorzi. (Preload attachment blobs when building the payload #909, Preload Action Text content for persisted chats #932, Preload embedded Action Text blobs #939)
Copy upgrades support online preparation and backfill on all three database adapters. Prepared 1.16 processes can continue serving on PostgreSQL, MySQL, and SQLite until the coordinated final switch. Compatibility guards preserve Active Record autosave and load after RubyLLM configuration. By @crmne.
Copy-upgrade finish can explicitly discard incomplete legacy tool calls.
--discard-incomplete-tool-callspreserves calls with results and protected 2.0 conversations. Discarded calls are not restored by rollback or resume; read the upgrade guide before choosing this option. By @crmne.Generated migrations honor configured primary-key types and acronym inflections. New installs and upgrade cleanup also remove the redundant standalone message-role index while preserving composite indexes. Copy migrations omit rename-only helpers. By @crmne. ([BUG] Install generator adds an unnecessary index on
messages.role#910)Prompt caching combines automatic placement with explicit boundaries.
with_cachingstays enabled alongsidecache_until_here; supported OpenAI-compatible models acceptmode: "explicit"for explicit-only caching. By @crmne. ([FEATURE] Allow top-level cache_control alongside explicit cache_until_here breakpoints #930)Pricing stays scoped to the provider that handled the request. Overlapping model IDs resolve correctly for streaming and Rails reloads; existing stored costs stay unchanged. By @crmne. ([BUG] Custom provider's usage costs use openrouter's pricing when model ids overlap #923)
Agent inheritance respects a child's own instructions and conventional prompt. By @crmne. (Fix instruction declarations not inheriting to Agent subclasses #915)
Provider results keep their correct input positions. Invalid reranking indices and duplicate Gemini embedding batch positions raise instead of attaching results to the wrong inputs. Empty Gemini Interactions tool arguments are accepted. By @crmne. (Return {} for empty-string tool-call arguments in the interactions protocol #911, fix: validate rerank result index before indexing into documents #917, fix: reject a duplicated embedding_index in a Gemini batch group #918)
Mistral OCR receives text attachments as data URIs. By @alannascimento1. ([BUG] RubyLLM.ocr sends text files to Mistral as a <file> wrapper instead of a data URI #919)
Bedrock video output-prefix normalization avoids excessive regex backtracking. By @crmne.
Instrumentation uses the provider instance's name. Delegated providers report the correct identity. By @toddkummer. (Use Provider Instance for Name #926)
Generated provider gems resolve their RubyLLM dependency during prereleases. The RC1 Bundler failure is fixed. By @crmne.
The Rails upload example validates uploaded files before passing them to
with:. Apps that copied the older example must update that application code; upgrading the gem alone does not change it. String paths and URLs remain supported. By @crmne.The bundled catalog and aliases have been refreshed, and 2.0 is the default documentation site. The homepage includes mobile fixes; 1.x documentation remains available. By @crmne.
Provider requests, streaming, and results
pause_turnsegments, and final-only citations and usage survive assembly. By @crmne. (504fca4f, 18d3622e)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, [BUG] Anthropic: display-omitted thinking replayed asredacted_thinking(the #852 fix landed in Converse only) #895, [BUG] Anthropic: stored thinking blocks are dropped on replay unless the *request* carries a thinking config #897, [BUG] Sonnet 5 Invaliddatainredacted_thinkingblock #852, Preserve omitted Bedrock thinking blocks #868)ToolCallParseError. By @crmne; thanks @cbillen. (a6a4bb88, 567087d3, Gemini batch: responseJsonSchema silently produces degenerate JSON on :batchGenerateContent #894)JSON::ParserError#829)inspectno longer prints API keys, and an upload cached for one tenant is not reused for another tenant with different credentials. By @crmne. (d17f2c2b)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 provider tools, and Chat/Agent/record delegate lists stay aligned. By @crmne; thanks @ebeigarts. (2c446372, ac87f5ab, Fix ActiveRecord with_tool/with_tools resetting messages mid-conversation #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, [BUG] Usage entries price against the response's model id, so an unregistered alias target leaves cost nil #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, [FEATURE] Add
supports_transcription?to model capability detection #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, [BUG]
ruby_llm:load_modelsfails withNameError: uninitialized constant#877, Use model association for chat model display #879, [BUG]chat_uigenerator: message partials break with custom message model names #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. (Support Bedrock application inference profile ARNs as model ids #803)
Bedrock responses keep the requested model when the provider omits its ID. By @hschne. (Add bedrock model ID fallback #817)
Bedrock streaming no longer drops every chunk when the Faraday environment is absent. By @chen-anders. (Fix Bedrock streaming dropping all chunks when Faraday env is nil #813)
Bedrock input usage no longer subtracts cache tokens twice. By @jmangel and @crmne. (Fix Bedrock Converse input_tokens double-subtracting cache tokens #832, [BUG] Bedrock double-subtracts cache tokens from
input_tokens(AWSinputTokensalready excludes cache) #828)Bedrock thinking effort maps correctly for Claude models. By @Edilbek and @crmne; thanks @justwiebe. (Fix Bedrock thinking effort mapping for Claude models (#851) #855, [BUG] Sonnet 5 Extra inputs are not permitted #851)
Bedrock structured-output support is no longer guessed from a model version number. By @shawnhutchison. (Stop guessing Bedrock structured output support by model id #899)
Azure streaming no longer latches onto an empty model ID and loses cost information. By @bdegomme and @crmne. (Fix streamed message model_id latching onto empty string (nil costs on Azure OpenAI) #830)
Responses function tools preserve optional parameters. Strict validation is opt-in. By @bdegomme and @crmne. (Send strict: false for function tools on the Responses API to preserve Chat Completions behavior #844, [BUG] Responses API silently enables strict tool validation — optional tool parameters are always filled in #843)
Reasoning summaries keep the separators between their parts. By @hiasinho and @crmne. (Fix reasoning summary part separators #866, [BUG] OpenAI Responses streaming joins reasoning summary parts without a separator #865)
DeepSeek reasoning conversations keep the reasoning context required for later turns. By @iuhoay and @crmne. (Fix deepseek-reasoner multi-turn requests missing reasoning_content #749)
Anthropic streams are requested without compression. This avoids buffering streamed output behind compression. By @xymbol and @crmne; thanks @dinsley. (Stream Anthropic responses uncompressed #771)
Parallel Anthropic tool results are grouped into the user message the API expects. By @adamshen. (Group parallel Anthropic tool results in one user message #853)
Anthropic input-plus-output context overflows raise ContextLengthExceededError. By @frostmark. (Classify Anthropic's "input length and max_tokens exceed context limit" as ContextLengthExceededError #907, [BUG] Anthropic "input length and max_tokens exceed context limit" is not classified as ContextLengthExceededError #906)
Non-object JSON error bodies no longer crash the streaming error parser. By @Niraj22 and @crmne; thanks @mvysny. (Fix streaming error parsing crash on non-Hash JSON error bodies #840, Streaming: parse_streaming_error crashes with 'TypeError: String does not have #dig' on a non-Hash JSON error body #837)
A provider response without a completion raises a clear error. By @jonthedecepticon; thanks @lucasmo. (Raise a clear error when a provider returns no completion message #849, [BUG]
Chat#add_message(nil)crashes with an unexplainedNoMethodError, obscuring the real cause #847)Automatic retries honor provider rate-limit headers. By @Niraj22 and @crmne. (Honor provider rate-limit headers when retrying #850)
Long-context cost calculation uses the correct pricing tier. By @Edilbek; thanks @victorface2. (Fix long-context tier pricing for cost_for (#854) #859, [BUG] RubyLLM doesn't parse costs correctly for gpt-5.6-sol long-context #854)
Converting persisted chats avoids N+1 message-association queries. By @matthewbjones. (Eager-load message associations in to_llm to prevent N+1 queries #717)
Generated tool-call partials no longer produce duplicate DOM IDs. By @edudepetris. (Fix duplicate DOM ids in chat_ui tool-call partials #802, [BUG] Duplicate DOM ids in chat_ui tool-call partials #804)
Agents and persisted chats delegate request hooks, token counting, and rendering consistently. By @toluola; thanks @danielefrisanco. (Delegate before_request, count_tokens and render to the chat #884, [BUG] Agent delegates every Chat callback except before_request #872, [BUG] acts_as_chat records don't expose before_request, count_tokens, or render #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. (Allow Marcel 2 for Rails compatibility #905)
Ruby 4 no longer warns about redefining the regexp-timeout setter. By @dominion525 and @crmne. (Fix method redefined warning for log_regexp_timeout= (Ruby 4.0+) #721)
Changed in 2.0
with_provider_tools. Release-candidate users should renamewith_server_tools, theserver_toolsreader and Agent macro, and the hosted-researchserver_tools:keyword to theirprovider_toolsequivalents. Arguments and behavior are unchanged.These changes need attention when upgrading from 1.x. The upgrade guide contains the full replacement table, examples, and Rails procedure.
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, [BUG] Inconsistent message.content type across persistence boundary #707, Fix #707 structured content persistence and harden streaming error handling #718)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)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)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)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, Depend on schematist instead of ruby_llm-schema #869)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)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)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)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)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)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)Error.new("message", response: response).UnsupportedAttachmentErroris a RubyLLM error, and malformed tool arguments raiseToolCallParseError. By @crmne. (567087d3, 959f42cf)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)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)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
Rename mode requires affected activity to stay paused through preparation, backfill, and finish. In copy mode, prepared 1.16 processes can keep serving during preparation and backfill on PostgreSQL, MySQL, and SQLite, with a controlled pause for the final switch. 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, provider 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 2.0 guides at the site root and archived 1.x guides at
/v1/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. 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, Fix flaky acts_as_model specs caused by RubyLLM.logger spec leaving config nil #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. (Fix generator spec for use_new_acts_as default #801)
Generator specs ignore user-level Rails configuration. By @andyw8 and @crmne. (Make generator specs ignore user Rails config #892)
Scaffold specs resolve their temporary directory consistently on macOS. By @toluola. (Canonicalize the scaffold spec sandbox #902, [BUG] Provider scaffold specs fail on macOS: Dir.mktmpdir returns /var, generated paths resolve to /private/var #901)
The development RDoc dependency remains compatible with JRuby. By @xymbol. (Pin rdoc below 8 to fix JRuby CI #831)
The coverage dependency no longer breaks CI. By @jonthedecepticon; thanks @Niraj22. (Pin simplecov below 1.0 to unblock CI #846, CI broken on all branches: simplecov 1.0.0 removed SimpleCov.running used by bin/rspec-queue #842)
The ecosystem guide includes RubyLLM::TopSecret. By @stevepolitodesign and @crmne. (Add
RubyLLM::TopSecretto community projects #731)The ecosystem guide includes RubyLLM::Test. By @toddkummer and @crmne. (Add RubyLLM::Test to Ecosystem #752)
The ecosystem guide includes RubyLLM::Contract. By @justi and @crmne. (Add RubyLLM::Contract to Ecosystem #808)
The ecosystem guide includes RubyLLM::Instructor, Registry, Tokenizer, and Turbovec. By @washu and @crmne. (Title: Add RubyLLM::Instructor, ::Registry, ::Tokenizer, and ::Turbovec to ecosystem page #812)
Thanks
Additional code and fixes during release-candidate testing by @alannascimento1, @MatheusRich, @yorzi, @kieranklaassen, @toddkummer, and @crmne.
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 during the RC1 development cycle 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
This discussion was created from the release RubyLLM 2.0.0.
All reactions