Changelog
a r.uby.dev project
What's next
Breaking
replace the transformer setter with
LLM::Transformer
The previoustransformer=setter and 3-argumentcall(ctx, prompt, params)interface onLLM::Contexthave been replaced by the newLLM::Transformerclass interface. Configure a transformer class throughtransformer:and options throughtransformer_options:instead.cli: scope session persistence per provider and directory
bin/llm.rbno longer shares a single session file between providers. Each provider now has a~/.llm.rb/<provider>.jsonfile that maps the current working directory to a UUID-scoped session file under~/.llm.rb/<provider>/<uuid>.json, so sessions are scoped to both the provider and the directory they were started in.cli: harden the executable against bad inputs
bin/llm.rbnow prints an error message followed by the help menu and exits with status 1 when the-pswitch is given without an argument or when an unknown option is passed. Previously unknown options produced a warning but the run continued. The session-file lookup also no longer rewrites~/.llm.rb/<provider>.jsonwhen it already exists.
Core
add
LLM::Provider#build_messagesfor assembling outgoing messages
LLM::Provider#build_messagesnormalizes a prompt intoLLM::Messageobjects and prepends the existing history, replacing the per-providerbuild_complete_messagesimplementation. The method is idempotent: prompts that are alreadyLLM::Messageinstances or arrays of messages are returned as-is.copy the
paramshash inLLM::ContextandLLM::Agent
LLM::ContextandLLM::Agentnow copy theparamshash in their constructors before mutating it, leaving the caller's hash untouched. Previously the constructors deleted keys from the caller's hash in place.gemspec: ship the deepdive sub-files in the gem
The gemspec now includesresources/deepdive/*/*.mdin the gem package, so the full deepdive guide (fundamentals, advanced, protocols, and everything-else chapters) is available after installation.
Transformer
add
LLM::Transformerfor rewriting messages before they reach the provider
LLM::Transformeris a new superclass for message transformers. A transformer is bound to a context and rewrites a single message before it is sent to the provider, which makes it possible to redact personal information or rewrite any message before it goes out over the wire. Each subclass implementscall(message:, **opts)and returns the message to send, either by mutating it in place or returning a new one.LLM::Transformer::Nullis a no-op transformer used as the default.hook the transformer API into
LLM::Context
LLM::Contextnow acceptstransformer:(a transformer class defaulting toLLM::Transformer::Null) andtransformer_options:(a hash forwarded to the transformer'scallmethod). The transformer runs on the most recent message in both chat and responses turns.LLM::Stream#on_transformandLLM::Stream#on_transform_finishnow receive the transformer instance as their single argument.
Tool
- add
LLM::Tool.setfor bulk-assigning tool properties
LLM::Tool.setaccepts a hash ofname,description,parameters,required, anddefaultsto configure a tool subclass in a single call. Parameters are defined as tuples of[name, type, description, options], matching the same interface as the existingparameterDSL. Unknown keys raiseKeyError.
Change
- openai: default to
gpt-5.6-luna
The default OpenAI chat model has changed fromgpt-5.4-minitogpt-5.6-luna. The new model is OpenAI's fastest and most affordable option, matching the kind of default llm.rb aims for.
Repl
center the buffer with 20% gutters
The curses-based REPL now centersLLM::Repl::Bufferin a content area that is 60% of the terminal width, with an unused 20% gutter on each side. The drawing area is based on the available rows and columns instead of a fixed 80-column width, andBuffer#wrapnow hard-breaks words that overflow the width onto the next row, fixing a bug where a word could be cut off between rows.apply markdown to previous messages
The curses-based REPL now renders every message in the buffer with markdown styling, including messages that were already present when the session started or restored from disk. Previously only newly streamed responses were styled; older messages fell back to plain text.add
LLM::Repl#senderfor the user label
LLM::Repl#senderreturns the label used for user messages in the curses-based REPL. It defaults to"You"(previously"user"), and the buffer layout now places each label on its own line followed by the message content and a blank line.add
LLM::Repl::Colorfor coloring the curses UI
AddLLM::Repl::Coloras a new module that returns Curses color bitmasks.Color.enableinitializes 8 color pairs, and methods likeColor.bluereturn the correspondingCurses.color_pair(X)bitmask, which can be bitwise OR'ed with other attributes such asCurses::A_BOLD. User labels in the REPL are now rendered in blue instead of plain bold text.split on words rather than characters
LLM::Repl::Buffer#wrapnow breaks text on word boundaries instead of wrapping one character at a time. A word that does not fit on the current row moves to the next, and only a single word longer than the whole width is hard-broken, so text is never clipped by the window.render kramdown typographic symbols and smart quotes
Fix a bug where certain character sequences such as...were not rendered at all in the curses-based REPL. Kramdown parses them into:typographic_symand:smart_quotenodes, which previously fell through to the children clause and were dropped. The markdown renderer now maps them to their unicode equivalents: ellipsis, en and em dashes, guillemets, and single and double quotation marks.apply colors to the markdown renderer
The curses-based REPL now renders markdown with theLLM::Repl::Colorpalette: headers and strong text in white, code spans and code blocks in green, and links in underlined green, on the black background. Previously markdown styling used bold, underline, and reverse video attributes only.wrap the input line at word boundaries
The curses-based REPL input line now wraps words whole onto the next row at the terminal width instead of cutting them in half. A word that does not fit on the current row moves to the next row, and only a single word longer than the whole width is hard-broken, so typed text is never clipped by the window.distinguish the connecting and thinking status bar phases
The curses-based REPL status bar now showsConnecting • Esc to cancelwhile the model is establishing a connection, then switches toThinking • Esc to cancelonce a tool call or text fragment arrives on the stream. Active tool calls appear in the status bar with a lambda indicator.add emoji to the status bar phases
The curses-based REPL status bar now uses emoji to identify each phase at a glance: a globe (🌐) while the model is connecting, and a brain (🧠) while it is thinking. The text after the emoji still readsConnecting • Esc to cancelandThinking • Esc to cancelrespectively.render the status bar with color and attributes
The curses-based REPL status bar now supports colored and attributed status text, so the lambda indicator for active tool calls is drawn in bold red.
Registry
- refresh model metadata across providers
Updatedata/*.jsonfiles with current provider model listings and pricing.
v13.1.0
Changes since v13.0.0.
This release adds LLM::Agent class DSL attributes (path, description),
extends skills with file-path loading and the tools: all directive, adds new
built-in tools (LLM::Tool::Ruby, LLM::Tool::EditFile), introduces the
LLM::Tracer::PrettyLogger for human-readable tracing, renames Transcript
to Buffer across the REPL, ships a bin/llm.rb CLI entry point, and fixes
several agent and tool bugs around persistence, interruption, and naming.
Core
- add post install message with deepdive link
The gemspec now includes apost_install_messagethat points users to the deepdive guide athttps://r.uby.dev/llm/deepdiveafter installation, making it easier for new users to discover the project documentation.
Agent
add
descriptionclass DSL and instance method
LLM::Agentnow has adescriptionclass DSL (description "release engineer") and a corresponding#descriptioninstance method. The description is an optional self-documenting string that serves as a brief summary of the agent's purpose. It can be set via the class DSL,LLM::Agent.set(description: ...), orLLM::Agent.new(description: ...).add
pathclass DSL and instance method
LLM::Agentnow has apathclass DSL (path "contexts/admin.json") and a corresponding#pathinstance method. When a path is set, the agent automatically restores its conversation history from that file on initialization and saves it back after eachtalkoraskturn, making session persistence across process restarts transparent.
Skills
accept a path to a markdown file
LLM::Skill.loadnow accepts a path to a markdown file in addition to a directory path. When given a file path, the file is read directly instead of looking for aSKILL.mdinside a directory. This makes it possible to load a single markdown file as a skill without placing it in a dedicated directory.extend with
allkeyword for loading the full tool registry
LLM::Skillnow supportstools: all(ortools: "*") in the frontmatter to load all tools from the globalLLM::Tool.registry. Previously, thetools:frontmatter only acceptedinherit, an array of tool names, or nothing. The newallkeyword makes it possible to give a skill access to every registered tool without listing them individually.
Tools
add
LLM::Tool::Rubyfor executing Ruby code in a subprocess
LLM::Tool::Rubyis a new built-in tool that runs a string of Ruby code in a separate Ruby process with a configurable timeout (default 15s). The code runs in an isolated address space unaware of its parent, making it useful for safe(ish) dynamic code execution. It must be required explicitly withrequire "llm/tools/ruby"and requires thetest-cmd.rbgem.rename
LLM::Tool::SwapTexttoLLM::Tool::EditFile
TheSwapTexttool has been renamed toLLM::Tool::EditFileto better match the naming of sibling tools (ReadFile,WriteFile). The oldrequire "llm/tools/swap_text"path no longer exists; userequire "llm/tools/edit-file"instead.
Tracer
- add
LLM::Tracer::PrettyLoggerfor human-readable tracing
LLM::Tracer::PrettyLoggeris a new tracer that writes human-readable request and tool-call logs to a console or file. Unlike the structured JSON output ofLLM::Tracer::Logger, the pretty logger emits single-line entries with inline context, making it easier to follow agent activity at a glance. It writes to$stderrby default and accepts anio:option for file output.
Repl
rename
LLM::Repl::TranscripttoLLM::Repl::Buffer
LLM::Repl::Transcripthas been renamed toLLM::Repl::Bufferto better reflect its role as a conversation state manager. The oldstartandfinishmethods have been renamed toopenandcloserespectively. The public accessor onLLM::Replhas been renamed fromtranscripttobuffer.add
write_messagefor formatted message writing
LLM::Repl::Buffer#write_messageandLLM::Repl#write_messageprovide a convenience method that takes a username and content string, formatting the output with a bolduser:label and a trailing newline. This is simpler than the equivalent sequence ofwritecalls.add
Command#write_messageand refactorCommand#write
LLM::Command#write_messageprovides a convenience method that takes a username and content string, matching the same interface onLLM::ReplandLLM::Buffer. TheCommand#writemethod is now implemented on top ofwrite_message, always prefixing output withcommand(<name>):. Thewho:keyword argument previously accepted bywritehas been removed; usewrite_messageinstead.display pre-existing agent messages when the repl starts
WhenLLM::Agent#replstarts, any messages already in the agent's buffer are now rendered in the REPL window. Previously the REPL started with an empty transcript even when the agent carried prior conversation history, making it harder to resume a session. Tool-call and tool-return messages are skipped to avoid visual noise.
CLI
- add
bin/llm.rbfor launching the REPL from the command line
A new executable script (bin/llm.rb) provides a convenient way to start an interactive REPL session directly from the terminal. It auto-detects the provider from environment variables likeOPENAI_API_KEY, supports a-p PROVIDERflag for explicit provider selection, a-tflag for temporary (non-persistent) sessions, and-hfor help. Sessions are automatically saved to~/.llm.rb/by default.
Fix
agent: fix
pathrestore on first run
Fix a bug whereLLM::Agentcalled@ctx.restore(path:)even when the path's file did not exist. The fix checksFile.readable?(@path)before attempting to restore, so the agent starts with a blank conversation on first use instead of failing with a file-not-found error.tools: re-raise
LLM::Interruptto abort the turn
LLM::Tool::Git,LLM::Tool::Mkdir,LLM::Tool::Rg,LLM::Tool::Ruby, andLLM::Tool::Shellnow re-raiseLLM::Interruptafter killing their running command. The previous behavior rescued the interrupt and killed the child process but let the turn continue, which meant a cancelled tool call did not abort the conversation turn. Re-raising ensures the entire turn is interrupted.tools: rescue
LLM::Interruptin shell-based tools
LLM::Tool::Shell,LLM::Tool::Git,LLM::Tool::Mkdir, andLLM::Tool::Rgnow rescueLLM::Interruptand kill their running command, preventing orphaned child processes when a tool is interrupted during execution.agent: fix default name derivation
Fix a bug whereLLM::Agentused without a subclass derived its default name as"l-lm-agent"instead of"agent". The fix replaces the regex-based parameterization with a pattern that correctly handles single-word class names and multi-word namespaced names.function:
#paramsalways returns anLLM::Object
LLM::Function#paramsnow always returns anLLM::Objectrepresenting the function's parameter schema. Previously it returnednilwhen a function defined no parameters, forcing every caller to guard againstnil. All provider adapters now usefn.params.to_hinstead offn.params || {type: "object", properties: {}}.
v13.0.0
v13.0.0 relicenses the project under the MIT license, replacing the Business Source License that was introduced in v12.0.0. No commercial license is needed. Commercial, personal, educational, and all other uses are now permitted under the standard MIT terms.
Seven breaking changes. Concurrency strategies have been renamed
(:call → :sequential, :task → :async), spawn is now
task, and the :async strategy has been rebuilt from the ground
up. It no longer blocks and now supports interruption. The compactor
has been refactored into pluggable strategies. Interruption is now
reliable across all six concurrency backends. The functions and
functions? methods have been renamed to pending_functions and
pending_functions?.
Migration from v12.6.0
| Old | New |
|---|---|
fn.spawn(:call) |
fn.task(:sequential) |
fn.spawn(:task) |
fn.task(:async) |
ctx.wait(:call) |
ctx.wait(:sequential) |
agent.concurrency :task |
agent.concurrency :async |
LLM::Function::FiberGroup |
LLM::Function::Fiber::Group |
LLM::Function::CallGroup |
LLM::Function::Sequential::Group |
LLM::Function::TaskGroup |
LLM::Function::Async::Group |
Compactor.new(model:, token_threshold:) |
Compactor::Truncate.new(ctx) |
on_compaction(ctx, compactor) |
on_compaction(compactor) |
ctx.functions / ctx.functions? |
ctx.pending_functions / ctx.pending_functions? |
agent.functions / agent.functions? |
agent.pending_functions / agent.pending_functions? |
Breaking
rename
LLM::Function#spawnasLLM::Function#task
LLM::Function#task(previouslyspawn) now consistently returns aLLM::Function::Taskobject that can be spawned, waited on, and passed toLLM::Function::Group. The old implementation alternated between spawning immediately or returning a raw thread or fiber.rename concurrency strategies (
:call→:sequential,:task→:async)
The:callconcurrency strategy is now:sequential, and the:taskstrategy is now:async.LLM::Agent.concurrency,LLM::Context#wait,LLM::Function::Array#task, andLLM::Function#taskall accept the new names. The old names raiseArgumentError.rename group classes
Group classes have been moved into their strategy's namespace:FiberGroup→Fiber::Group,ThreadGroup→Thread::Group,CallGroup→Sequential::Group,TaskGroup→Async::Group,Fork::Group→Fork::Group,Ractor::Group→Ractor::Group.repurpose
LLM::Function::Taskas a task interface superclass
LLM::Function::Taskhas been repurposed from a general-purpose class that tried to support multiple concurrency strategies into an abstract base class that defines the task interface. Individual strategies (Sequential::Task,Thread::Task,Fiber::Task,Async::Task,Fork::Task,Ractor::Task) now subclass it and implementspawn,alive?,interrupt!, andwait.fix
:asyncconcurrency (now backed by a managedLLM::Function::Async::Reactoron a background thread)
The:asyncstrategy previously usedAsync {}which blocked the caller until all tasks completed and did not support interruption. The fix replaces it with a per-turnLLM::Function::Async::Reactoron a background thread. Work is submitted viasubmit(&block)and consumed by the reactor's event loop through a thread-safeQueue.Async::Groupmanages the reactor lifecycle and spawns tasks lazily onwait.
Interruption pushesLLM::Interruptto the task's result queue instead of usingFiber#raise, and results are bridged back to the caller through a secondQueue. This work drove the broader refactor of strategy naming, the spawn/wait split, and theTasksuperclass. The:asyncstrategy needed the same interface the other strategies already had.compactor: refactor to strategy-based interface
LLM::Compactorhas been refactored from a single class that performed LLM-based summarization into a strategy-based superclass. Each subclass implements a different compaction strategy viacall(**opts). The old summarization approach (usingmodel:,token_threshold:,message_threshold:, andretention_window:options) has been removed. The built-inLLM::Compactor::Truncatestrategy drops the oldest messages when the conversation exceeds a configured size.rename
LLM::Context#{functions,functions?}andLLM::Agent#{functions,functions?}
LLM::Context#functionsandLLM::Context#functions?have been renamed toLLM::Context#pending_functionsandLLM::Context#pending_functions?respectively. The same rename applies toLLM::Agent#functions(nowLLM::Agent#pending_functions). Thepending_functionsname was already available as an alias in v12.5.0; this change removes the oldfunctionsname entirely.
Core
- extend
LLM.requirewith an optional version argument
LLM.requirenow accepts a secondversionparameter that is passed toKernel#gembefore loading, enabling version constraints for optional runtime dependencies. For example,LLM.require "test-cmd.rb", "~> 1.1"ensures a minimum gem version is available. This is used internally by theGit,Rg,Mkdir, andShelltools to enforce compatibility with thetest-cmd.rbgem.
Compactor
add
Truncatestrategy for dropping oldest messages
LLM::Compactor::Truncateis a new built-in compaction strategy that drops the oldest messages when the conversation exceeds a configured size. It preserves tool call/return pairs so the algorithm never breaks in the middle of a sequence. Configured withkeep:(default 64), it emits the standardon_compactionandon_compaction_finishLLM::Streamlifecycle callbacks. No LLM call is made; the strategy is purely lossy but fast and requires no network.raise when given an unparseable
keep:value
LLM::Compactor::Truncatenow raisesArgumentErrorwhen thekeep:parameter cannot be parsed as an integer or percentage string, instead of failing with an obscure error later during execution.accept percentage string for the
keep:parameter
LLM::Compactor::Truncate#callnow accepts a percentage string such as"80%"for thekeep:parameter, which keeps approximately 80% of the most recent messages. Integer values continue to work as before. This makes it easy to trim proportionally rather than to an absolute number of messages.add
Nullstrategy for no-op compaction
LLM::Compactor::Nullis a new built-in compaction strategy that does nothing. It is used as the default compactor when no strategy is configured on a context, ensuring the compactor interface is always present without requiring a separate nil check.accept both
LLM::AgentandLLM::Context
LLM::Compactor#initializenow accepts bothLLM::AgentandLLM::Contextinstances. When given an agent, the internal context is unwrapped automatically, making the compactor API more flexible when working with agents.
Context integration
accept
compactorandcompactor_optionsparameters
LLM::Contextnow acceptscompactor:(a compactor class defaulting toLLM::Compactor::Null) andcompactor_options:(a hash of options forwarded to the compactor'scallmethod) parameters. The compactor is automatically invoked at the beginning of eachtalkturn. The previouscompactor=setter has been removed in favour of constructor-driven configuration.on_compactionandon_compaction_finishreceive a single argument
LLM::Stream#on_compactionandLLM::Stream#on_compaction_finishnow accept a single argument (the compactor instance) instead of two arguments (context and compactor). The context is still available viaLLM::Compactor#ctx, so access to the context is not lost. This simplifies the callback interface for compaction lifecycle observers.
Tools
add
LLM::Tool::Utilsmodule for shared command execution logic
A newLLM::Tool::Utilsmodule provides sharedwait(command:, timeout:)andnowhelper methods for tools that execute commands. Tools that includeUtilscan wait on a running command and automatically kill it when it exceeds the configured timeout, usingProcess.clock_gettimewithCLOCK_MONOTONICfor precise timing. The module is used by both theShellandRgtools internally.shell: add
timeoutparameter for command execution deadlines
TheLLM::Tool::Shelltool now accepts atimeoutparameter (default 60s) that automatically kills commands exceeding the specified time limit, preventing hung processes from blocking the agent indefinitely.rg: add
timeoutparameter for search execution deadlines
TheLLM::Tool::Rgtool now accepts atimeoutparameter (default 5s) that automatically kills search commands exceeding the specified time limit, preventing long-running searches from blocking the agent indefinitely.git: add
timeoutparameter for command execution deadlines
TheLLM::Tool::Gittool now accepts atimeoutparameter (default 5s) that automatically kills git commands exceeding the specified time limit, preventing hung processes from blocking the agent indefinitely.
Schema
- properties are now ordered and support indifferent access
LLM::Schema::Leaftracks property definition order in a newindexattribute, matching the convention already used byLLM::Command::Parameter. Internally,@propertiesis stored as anLLM::Objectinstead of a plainHash, so lookups with both string and symbol keys work.
Buffer
more array-like message management
LLM::Buffernow exposesfirst,reject!,select!,shift,clear,drop,take, andreverse, making it easier to query and mutateLLM::Context#messageslike an ordinary Array.reject!is aliased asdelete_iffor familiarity.last(nil)no longer returns the last message
LLM::Buffer#lastnow uses an internalUNDEFINEDsentinel to distinguish between no argument (lastreturns the last message) andnil(last(nil)is treated as an argument). Previouslynilwas indistinguishable from no argument.
Function
consolidate
callandcall!into one method
The privatecall!method has been merged into the publicLLM::Function#call. The separatecall!method existed for tracer-scoping logic now handled directly insidecall. All internal call sites now usefunction.callinstead offunction.call!.add
LLM::Function::Groupas an abstract base class
A new abstract base class (LLM::Function::Group) defines the interface that all concurrency strategy groups must implement:alive?,interrupt!, andwait. Each strategy group (Sequential::Group,Thread::Group,Fiber::Group,Async::Group,Fork::Group,Ractor::Group) now subclasses this base.split
spawnandwaitacross all strategies
spawnnow starts execution without blocking, andwaitcollects the result.on_tool_startmoved into each task'sspawnso the tracer span covers execution rather than construction. Each task and group now exposes a publicspawnmethod alongside the existingwait/valuemethods.spawn tasks lazily in
Group#wait
All concurrency strategy groups (Fiber::Group, Fork::Group, Ractor::Group, Thread::Group) now automatically spawn their tasks whenwaitis called if they haven't been spawned yet, matching the existingAsync::Groupbehavior. This makes the spawn/wait contract consistent across all six concurrency backends.
Agent
- add
nameclass DSL and instance method
LLM::Agentnow has anameclass DSL (name "admin") and a corresponding#nameinstance method. The name is resolved through the same lazy-resolution path as other agent attributes. It can be set viaLLM::Agent.set(name: ...),LLM::Agent.new(name: ...), or the class DSL. When no name is given, a default is derived from the class name (e.g.,SystemAdminbecomessystem-admin). The REPL uses the name as the prompt label and transcript prefix, making it easier to distinguish multiple sessions.
REPL
agent identity in the prompt
LLM::Agent#replandLLM::Repl.newaccept aname:parameter (defaulting toLLM::Agent#name) that sets the input prompt toprovider(name)>and labels transcript messages with the agent's name instead of a hardcodedagent:. Useful when running multiple sessions./compactcommand
New built-in/compactcommand frees context window space by dropping the oldest messages viaLLM::Compactor::Truncate. Supports both integer (/compact 32) and percentage (/compact 75%) arguments. Defaults to keeping the last 128 messages.tab-completion for
/commands
Pressing Tab on an input line starting with/autocompletes the command name. Repeated Tab presses cycle through matching commands. Powered byLLM::Command.complete(str)which is available outside the REPL too.command system enhancements
Commands can set parameter defaults in theircallmethod signature (e.g.,def call(n: 128)). Aliases like/quitnow inherit their parent's description and parameters. Commands also have access to the activeagentandreplvia public readers.tool argument sorting
Tool parameters in the status bar are now displayed in definition order (using the newindexattribute), regardless of the order the model returns them.expanded markdown rendering
The curses-based markdown renderer now handles lists (<ul>,<ol>), blockquotes, horizontal rules, hyperlinks (underline), images ([image: alt text]), and tables (aligned columns).input improvements
Ctrl+P and Ctrl+N walk through conversation history (user messages only, managed byLLM::Repl::Walker). Page Up/Down scroll the transcript by a page. ENTER and BACKSPACE are now mapped to raw character codes fromCurses.getchinstead ofCurses::Keyconstants.
Object
- preserve the original key name in
KeyErrormessages
LLM::Object#fetchnow preserves the original key name when a key is not found, instead of raisingKeyErrorwithkey not found: nil. The previous behavior occurred when the given key was not found in the stored hash, causing internal lookup to returnniland lose the original key reference.
Registry
- refresh model metadata across providers
Updatedata/*.jsonfiles with current provider model listings and pricing. Mark several DeepInfra models as deprecated (meta-llama/ Meta-Llama-3.1-8B-Instruct,Qwen/Qwen1.5-110B-Chat, andmistralai/Mixtral-8x7B-Instruct-v0.1). Correct xAI cache-read pricing from $0.50 to $0.30 per million input tokens.
Fix
agent: fix default name resolution when name is not explicitly set
Fix a bug whereLLM::Agentderived its default name fromself.classinstead ofself, causing the name to be"class"instead of a parameterized version of the actual class name (e.g.,"system-admin"forSystemAdmin). The fix usesselfdirectly, which correctly resolves the class name at the instance level.function: make Fork::Task and Ractor::Task inherit LLM::Function::Task
LLM::Function::Fork::TaskandLLM::Function::Ractor::Tasknow explicitly subclassLLM::Function::Taskand accept an options hash as their second argument, matching the constructor signature used by the other four task classes. The interface was already compatible but the inheritance was missing by mistake. It is now consistent across all six concurrency backends.google: fix
streamparameter leakage that broke the provider
Fix a bug in the Google provider wherestream: stream.enabled?was being merged into request parameters, causing API-level errors. The Google provider does not use astreamparameter. Streaming is controlled via the URL path (streamGenerateContentvsgenerateContent). The fix removes the leaked parameter and correctly routes streaming requests through the appropriate path.fork: fix deadlock on xchan.rb channel
Fix a deadlock in the:forkconcurrency strategy where both the writer and reader could get stuck on the xchan channel, preventing the reader from draining the channel. The deadlock surfaced as an errno failure, especially with large tool returns. The fix requires xchan.rb v0.22.0 and uses theSOCK_STREAMsocket type for communicating a tool's return value.
v12.6.0
Changes since v12.5.1.
This release adds bulk defaults for tools and agents: LLM::Tool.defaults
for setting parameter defaults and LLM::Agent.set for mass-assigning
class-level defaults, both mirrored on ActiveRecord and Sequel agent models.
It also makes LLM::Interrupt reliable across every concurrency strategy
(:thread, :call, :fiber, :task, :fork, and :ractor) so tool cancellation
works consistently regardless of execution backend, and fixes a stale fiber
reference in LLM::Context#talk that could prevent interruption after a
prior call.
Add
tool: add
defaultsmethod for setting parameter defaults
AddLLM::Tool.defaults(properties)for bulk-setting default values on tool parameters, matching the same interface asLLM::Schema.defaults. Each key maps to a parameter name; unknown keys raiseKeyError.agent: add
setmethod for bulk-assigning class-level defaults
AddLLM::Agent.set(properties)for mass-assigning agent defaults from a Hash. Each key maps to a class-level accessor; unknown keys raiseKeyError.active_record: expose
setonacts_as_agentmodels
ActiveRecord models usingacts_as_agentcan callsetto bulk-assign agent class-level defaults.sequel: expose
setonplugin :agentmodels
Sequel models usingplugin :agentcan callsetto bulk-assign agent class-level defaults.
Fix
function: raise
LLM::Interrupton thread where tool is running
On cancel,LLM::Interruptis now raised on the thread that is running a tool. The tool can rescueLLM::Interruptand gracefully terminate (e.g., clean up resources). The previous approach usedThread#interruptwhich was less reliable. It did not interrupt a sleeping thread.function: suppress thread exception reporting in
:threadconcurrency
Threads spawned by the:threadconcurrency strategy now havereport_on_exceptionset tofalse, preventing noisy exception messages from appearing on stderr when a thread is interrupted during tool execution.context: clear
@owneraftertalkcompletes
LLM::Context#talknow clears the@ownerreference in anensureblock after the method completes, sointerrupt!does not attempt to interrupt a stale fiber reference from a prior call.function: raise
LLM::Interrupton thread waiting inCallGroup#wait
When usingctx.wait(:call),LLM::Interruptis now raised on the thread executing the sequential tool wait.CallGroup#waittracks the active thread andinterrupt!raisesLLM::Interrupton it, enabling interruption of the:callconcurrency strategy just like the existing:threadstrategy.function: raise
LLM::Interrupton fiber-backed tool tasks
LLM::Interruptis now raised on the active fiber viaFiber#raisewhen interrupting:fiber-concurrency tools.
Task#interrupt!now dispatches by task type:Thread#raisefor threads,Fiber#raisefor fibers. Making interruption reliable across all concurrency strategies.function: raise
LLM::Interrupton fork-backed tool tasks
LLM::Interruptis now raised on the main thread of a fork child process viaThread.main.raise(LLM::Interrupt)when interrupting:fork-concurrency tools, and the forkTask#waitre-raises the interrupt on the parent side. Making interruption reliable across all concurrency strategies including:fork.function: raise
LLM::InterruptonAsync::Task-backed tool tasks
LLM::Interruptis now raised on the underlying fiber of anAsync::TaskviaFiber#raisewhen interrupting:task-concurrency tools.Task#interrupt!now detectsAsync::Taskinstances and dispatches toFiber#raise, extending reliable interruption to the:taskconcurrency strategy under the Async runtime.function: raise
LLM::Interrupton ractor-backed tool tasks
LLM::Interruptis now raised on the main thread inside a ractor viaThread.main.raise(LLM::Interrupt)when interrupting:ractor-concurrency tools. A listener thread inside the tool ractor waits for an interrupt message viaRactor.receiveand raisesLLM::Interrupton the ractor's main thread.
Task#interrupt!delegates to the mailbox to send the interrupt message. Extending reliable interruption to the:ractorconcurrency strategy.
v12.5.1
Changes since v12.5.0.
This release reverts the global LLM::Function registry fallback for tool
resolution that was added in v12.5.0.
Change
- function: remove the global registry fallback for tool resolution
Remove theLLM::Function.find_by_namefallback that was added in v12.5.0 as an intermediate step between available-tools lookup and raisingLLM::NoSuchToolError. Tool calls not found in the available tools list now go directly tofunction_missing(which raisesLLM::NoSuchToolError) without first checking the globalLLM::Functionregistry.
v12.5.0
Changes since v12.4.0.
This release extends the REPL command system with typed parameters, a
built-in /help command, command aliases (/quit), and cancellation
via the 'Esc' key.
The default HTTP timeout is increased to 15 minutes (900s) to better
accommodate reasoning models and large structured outputs.
LLM::Agent#deserialize and LLM::Agent#restore now return self for
method chaining, and LLM::Buffer#pop is added for tail-end message
removal.
Tool resolution gains a fallback to the global LLM::Function registry
before raising LLM::NoSuchToolError, and pending_functions aliases
are added on both contexts and agents for a consistent interface.
Several REPL bugs are fixed including parameter state leakage across turns and invalid tool-call error routing.
Model metadata is refreshed across all providers with new Anthropic, OpenAI, Google, DeepInfra, DeepSeek, and xAI model entries.
Add
Buffer & function internals
buffer: add
LLM::Buffer#pop
AddLLM::Buffer#popfor removing the last message from the tail of the buffer, complementing the existing#<<and array-style message management.function: add registry fallback for tool resolution
When resolving tool calls from a message, if the tool is not found in the available tools list, it now also looks up the globalLLM::Functionregistry viaLLM::Function.find_by_namebefore creating a placeholder function. This improves tool resolution for tools that are registered globally but not passed directly through the request tool set.
Consistent pending_functions aliases
context: alias
LLM::Context#functionsasLLM::Context#pending_functions
AddLLM::Context#pending_functionsas an alias forLLM::Context#functions, so callers that prefer the more descriptivepending_functionsname can use it instead offunctionswhen checking for unresolved tool work.agent: alias
LLM::Agent#functionsasLLM::Agent#pending_functions
AddLLM::Agent#pending_functionsas an alias forLLM::Agent#functions, matching the same alias onLLM::Context, so callers have a consistentpending_functionsinterface across both contexts and agents.
REPL command system
- repl: extend command system with parameter support
Commands can now declare typed parameters using theparameterDSL, modelled afterLLM::ToolandLLM::Schemaconventions. Parameters can be marked as required withrequired %i[...], and values are type-checked before being passed tocall. Argument parsing is handled by the repl: arguments are split from the input string and assigned to parameters by position.
class Greeter < LLM::Command
name "greet"
description "Greets the given name"
parameter :name, String, "The person's name"
required %i[name]
def call(name:)
write("Welcome #{name}!\n")
end
end
- repl: add
helpcommand
AddLLM::Repl::Helpas a new built-in command, registered automatically via the command registry. Typing/helpshows thehelpcommand's own name, description, and parameters, while/help <name>shows details for a specific command, including its parameters and whether each is required or optional. Unknown command names produce an error message.
class Help < Command
name "help"
description "show help for a given command"
parameter :name, String, "The name of a command"
def call(name: nil)
if name.nil?
write("\n#{self.class.help}\n\n")
elsif command = LLM::Command.find_by(name:)
write("\n#{command.help}\n\n")
else
write "\nNo help for #{name} was found" \
"\nThat command doesn't exist.\n\n"
end
end
end
- repl: add support for command aliases
Commands can now be aliased by creating a subclass of another command (withLLM::Commandas an indirect ancestor). The first alias introduced is/quitas an alias of/exit.
class Quit < Command::Exit
name "quit"
end
repl: add
Command::Parameter#optional?
Parameters now expose an#optional?method that returnstruewhen a parameter has not been marked as required, making it possible to query parameter optionality programmatically.repl: add
LLM::Repl::Command#write
Commands can now write output to the transcript via thewritemethod. Commands also receive a reference to the active repl through their#initializemethod, making it possible to interact with the repl window from within a command.repl: display command errors in the curses UI
Commands invoked with too few arguments now display an error message:command(<name>): too few arguments. Displayed directly in the curses transcript area, giving immediate feedback instead of silently failing.repl: add
LLM::Commandconvenience constant
AddLLM::Command = LLM::Repl::Commandas a shorter alias, available once"llm/repl"is required.
Misc
- repl: implement cancellation with the 'Esc' key
The curses-based REPL now supports cancelling an active model request by pressing the 'Esc' key. When a request is in progress, the status line showsthinking • Esc to cancel, and pressing Esc callsLLM::Agent#cancel!to interrupt the request. The transcript displaysrequest cancelled!to confirm the cancellation.
Change
Misc
provider: increase default timeout to 900s
The default HTTP timeout for all providers has been increased from 180 to 900 seconds (15 minutes) to better accommodate long-running requests such as reasoning models and large structured outputs.agent:
deserializeandrestorereturnself
LLM::Agent#deserializeandLLM::Agent#restorenow returnself(the agent instance) instead of forwarding the context's return value, enabling method chaining after restoring agent state.context: discard all messages from a cancelled turn
WhenLLM::Context#cancel!is called, all messages added during that turn are now discarded viaBuffer#slice!, preventing edge cases where dangling tool calls between turns caused repeated cancellation loops. The#repair!method now handles tool call cancellations on the next turn instead of mutating the conversation buffer directly at cancellation time.stream: drop the
errorargument fromon_tool_call
Theon_tool_callcallback no longer accepts anerrorargument. Previously, stream parsers passed both a tool and an optional error, requiring boilerplate likeif error; queue << error; endin every callback. Error handling is now pushed directly onto the stream queue inside each provider's stream parser, soon_tool_call(tool)is the only signature. The REPL stream and baseLLM::Streamclass have been updated accordingly.
REPL internals
repl: pass the repl instance to command constructors
LLM::Repl::Commandsubclasses now receive the active repl instance viainitialize(repl), enabling commands to write to the transcript and interact with the repl window.repl:
Command#writeprefixes messages with the command name
The#writemethod now prefixes output withcommand(<name>):so command messages are consistent with theuser:andagent:labels in the transcript. The prefix can be customised with thewho:keyword argument, or set towho: nilto disable it entirely.
Fix
Misc
- function: avoid silent skip of tools not found in available tools
When a model calls a tool that is not present in the available tools list, instead of silently skipping the tool call (vianext), aLLM::NoSuchToolErroris now raised so the model receives feedback about the invalid tool call and can correct course.
An additional fallback to the globalLLM::Functionregistry is tried before raising, so globally registered tools are still resolved even when not in the per-request tool set.
REPL bugs
repl: don't persist parameter state between turns
Parameter state (such asParameter#value) was leaking across turns because the same parameter objects were being mutated in place. A duplicate set of parameters is now created for each turn, keeping the original parameter definitions intact and preventing stale state from carrying over.repl: reply with error when given an invalid tool
When the model tries to call a tool that does not exist, the error is now pushed onto the stream queue so the model can see the error and correct course, instead of silently dropping the invalid tool call and leaving it toContext#repairto remove it from history.repl: fix save of initial runtime state
Fix a bug inLLM::Repl#configurewhere a non-existent path argument was treated as no path at all, preventing the initial runtime state from being saved after the first turn. The correct behavior is to create the file so it can be written to after the first turn completes.
Refresh
- Refresh model metadata across all providers
Update model listings, pricing, capabilities, reasoning options, modality support, context limits, and release dates across all provider registries (Anthropic, AWS Bedrock, DeepInfra, DeepSeek, Google, Mistral, OpenAI, xAI, and ZAI). Notable changes include Anthropic claude-opus-4-8 and claude-sonnet-4-6 additions with effort-based reasoning, OpenAI gpt-5.6-sol/terra/luna and gpt-5-codex additions, Google gemini-3-pro-preview and gemini-3-flash-preview additions, DeepInfra Qwen3.5 and DeepSeek V4 model additions, and updated xAI Grok model entries.
v12.4.0
Changes since v12.3.1.
This release brings major improvements to the curses-based REPL
(LLM::Agent#repl). The REPL now supports saving and restoring runtime
state across sessions, automatic paste-mode detection for fast bulk input,
a command system foundation with the /exit command, and several new
keybindings (Ctrl+F, Ctrl+K, Ctrl+Y). Tool calls are rendered with a
compact function-call syntax in the status bar.
Two new built-in tools: LLM::Tool::Ls and LLM::Tool::Which are
available as opt-in additions for file listing and executable lookup.
Model metadata has been refreshed across providers, the REPL loop
internals have been refactored to use catch/throw for cleaner command
routing, and several bugs have been fixed including a tracer restoration
issue in the agent ensure clause and a missing cursor in the REPL input
area.
Add
repl: allow runtime state to be saved and restored
LLM::Agent#replnow accepts apath:option that serializes runtime state to the filesystem. When the path already exists, runtime state is restored when the read-eval-print loop starts. Otherwise the path is written after the first turn, making it possible to resume a session across process restarts.repl: scroll to the bottom on submit
The curses-based REPL now scrolls the transcript to the bottom when the user submits their input, so the latest response is visible without needing to scroll down manually.repl: add Ctrl+F to move the cursor forward
The curses-based REPL input now supports Ctrl+F to move the cursor forward by one column, matching common terminal editing conventions found in shells like/bin/sh.repl: add Ctrl+K to erase from cursor to end of line
The curses-based REPL input now supports Ctrl+K to erase all text from the cursor position to the end of the input buffer, matching common terminal editing conventions found in shells like/bin/sh.repl: add Ctrl+Y to paste previously killed text
The curses-based REPL input now supports Ctrl+Y to insert the most recently killed text (via Ctrl+K) at the current cursor position, matching the yank/paste convention found in shells like/bin/sh. The killed text is stored in an internal copy buffer so it can be pasted multiple times or at different cursor positions.repl: add command system foundation
AddLLM::Repl::Commandas a new base class for REPL commands, along with the first built-in commandLLM::Repl::Command::Exitwhich exits the read-eval-print loop viathrow(:exit). Commands are identified by a name and can be looked up throughCommand.find_by. This is the foundation for the/command syntax used in the REPL input line.repl: connect the command system to user input
The curses-based REPL now routes user input through the command system. Any input string beginning with"/"is matched against the command registry viaCommand.find_by, and the corresponding command is executed instead of being forwarded to the model. This makes built-in commands like/exitfunctional from the input line. Command arguments are not yet supported.repl: add
LLM::Repl::Command.registry
AddLLM::Repl::Command.registryfor auto-registering command subclasses. Theinheritedhook captures each new subclass and stores it in the registry, making it possible to enumerate all available commands at runtime. Built-in commands like Exit are automatically registered when the command file is loaded.repl: detect and handle paste mode in the input line
The curses-based REPL input now detects paste operations by tracking the rate at which characters arrive. A paste rate of ≤50ms is assumed to be a burst of characters that could only be explained by a paste. No human types that fast. Multiline pastes are supported through internal refactoring of the input handling logic.repl: optimize paste mode rendering
Track the paste state with an internal@pastevariable and switch to a faster input path during paste operations. While in paste mode, the input buffer is drained viaCurses.getch, bypassing the more expensive char-by-char render path used for ordinary interactive input. This makes pasting large amounts of text noticeably faster.Add
LLM::Tool::Ls
Add a built-in tool for listing files and directories, with optional glob pattern filtering to narrow results.
It must be required explicitly withrequire "llm/tools/ls".Add
LLM::Tool::Which
Add a built-in tool for locating an executable on the system PATH. This lets an agent check whether a command is available before attempting to run it, avoiding failed subprocess calls.
It must be required explicitly withrequire "llm/tools/which".repl: render tool calls in a function-call syntax
The curses-based REPL status bar now renders tool calls with a compact function-call syntax:tool(key: value)instead oftool: name. Strings are quoted and truncated, arrays show their first two elements, and hashes collapse to{…}, making it easier to see what arguments the model is passing. Thetool donestatus message has been removed since the tool call itself conveys completion information.
Change
Refresh model metadata
Update model listings, pricing, and capabilities across providers. Fix GPT-5.6 model family names in the OpenAI registry (gpttogpt-sol,gpt-nanotogpt-luna,gpt-minitogpt-terra). Add OpenAI models (gpt-5.6-luna,gpt-5.6-sol,gpt-5.6-terra) to the AWS Bedrock registry. Update DeepInfra pricing forDeepSeek-V3andSky-T1-32B-Preview. Fix Google model knowledge cutoff dates.repl: control the loop with catch & throw
The curses-based REPL input loop now usescatch(:exit)andthrow(:exit)instead of returning the:exitsymbol and breaking out of the loop. This enables the/commandsyntax without requiring an:exitreturn value to be propagated through a potentially deeply nested call path.repl: replace Ctrl+D with shell-like delete-at-cursor
The curses-based REPL input now treats Ctrl+D as a delete action that removes the character at the current cursor position, matching the shell/Emacs convention where Ctrl+D deletes the character under the cursor instead of signalling end-of-file. The previous Ctrl+D behaviour (exiting the REPL) is superseded by the/exitcommand.repl: switch to 'Thinking' mode after tool return
The curses-based REPL status line now switches to "Thinking" mode after a tool returns, so the user can see the agent is processing the tool result rather than showing a stale tool-call status.
Fix
agent: fix a subtle typo in the ensure clause
Fix a subtle typo inLLM::Agentwhere the deprecatedtracelocal variable was given preference overtracer(the preferred local name) in anensureclause. Thetracelocal was supported for backward compatibility but the ensure clause still referencedtraceinstead oftracer, which meant the previous tracer was never restored when the REPL session ended.repl: restore the cursor in the input area
Remove theCurses.curs_set(0)call from the REPL redraw method, which was inadvertently hiding the cursor and making it impossible to see the current position in the input area. The input field is now always drawn at its full height so the cursor position is correctly maintained after each redraw.
v12.3.1
Changes since v12.3.0.
This release fixes a flickering issue in the curses-based REPL redraw. The full-screen clear that caused visible flickering has been replaced with a targeted cursor-hide approach, and stale rows from a larger transcript are now explicitly cleared to prevent ghost text from lingering when the transcript shrinks.
Fix
- repl: fix redraw flicker
ReplaceCurses.clearwithCurses.curs_set(0)in the REPL redraw method to avoid a full screen clear that caused visible flickering during redraws. The drawing order is also adjusted so the status line is drawn before the divider, and stale rows left over from a larger transcript are now explicitly cleared to prevent ghost text from lingering when the transcript shrinks.
v12.3.0
Changes since v12.2.0.
This release brings major improvements to the curses-based REPL
(LLM::Agent#repl). The status line now shows a context-usage bar and
running cost counter, the input field expands to three rows with
full cursor navigation, model responses are rendered as styled markdown,
and the UI stays responsive while the agent is working by running
requests in a separate thread. A new LLM::Stream::IO and
LLM::Stream::Disabled provide a uniform stream representation across
all stream types.
Mistral OCR support is added for extracting text from images and
documents via the /v1/ocr endpoint. The skills: and tools: options
on LLM::Agent#repl let you attach additional tools or skill directories
for the duration of a session. LLM::Object#merge! rounds out the
in-place merge API, and a new LLM.logger convenience method creates
tracer logger instances with less verbosity.
Add
Add
LLM.loggerconvenience method
AddLLM.logger(llm, ...)as a shorter, less verbose way to create anLLM::Tracer::Loggerinstance. Takes a provider and optional keyword arguments forwarded to the logger constructor.Add
skills:option toLLM::Agent#repl
LLM::Agent#replnow accepts askills:keyword argument that attaches one or more skill directories (containingSKILL.md) for the duration of the repl session. Skills are loaded and converted to tools, combining with any tools already configured on the agent, and are discarded when the session ends.Add
LLM::Provider#ocrbase method
Add a baseocr(...)method toLLM::Providerthat raisesNotImplementedErrorby default, establishing a common interface for providers that support OCR (Optical Character Recognition) on images and documents.Add Mistral OCR endpoint support
The Mistral provider now supports OCR via its/v1/ocrendpoint. Callmistral.ocr(image_url: ...)for images ormistral.ocr(document_url: ...)for documents (e.g., PDFs). Returns anLLM::Responsewith extracted pages, markdown content, and structured block data.Add
LLM::Object#merge!
AddLLM::Object#merge!for in-place merging of hash data into anLLM::Objectinstance, complementing the existing#mergemethod.Add
LLM::Stream::IOandLLM::Stream::Disabled
LLM::Stream::IOwraps IO-like objects as stream targets, forwarding streamed content via#<<.LLM::Stream::Disabledrepresents an explicitly disabled stream with no-op callbacks.
This is part of an internal refactoring that lets all stream values: IO
objects, true, false, nil, and LLM::Stream instances themselves
be represented by the same LLM::Stream interface via the new
LLM::Stream.try factory method.
Before this change the codebase had to perform ad-hoc type checks
(e.g. if LLM::Stream === stream) scattered throughout. After this
change all stream handling goes through a single uniform path, and
providers check #enabled? to decide whether to request streaming
from the API.
Change
repl: rename
trace:totracer:
Thetrace:keyword argument inLLM::Agent#replhas been renamed totracer:for consistency with the rest of the codebase. The oldtrace:name still works with a deprecation warning.repl: add context-usage bar and cost counter to the status line
The curses-based REPL status line now shows a small progress bar that indicates how much of the model's context window remains as a percentage, alongside a running cost estimate rendered on the right side of the status line. The input line has been updated to show the provider name as a prefix. Estimates are best-effort and depend on registry pricing data (seedata/).repl: keep the UI responsive while a request is in progress
The curses-based REPL now spawns the agent request in a separate thread and communicates streamed output through a queue, so the curses UI stays responsive during model processing. Users can continue to scroll through the transcript while the agent is working.repl: style transcript rows as structured data with bold labels
The curses-based REPL transcript now stores rows as structured data with style metadata instead of plain strings, enabling bold rendering of theuser:andagent:labels for improved readability during interactive sessions.repl: render a small subset of markdown
The curses-based REPL now renders model responses as styled markdown. Headers and strong text render in bold, emphasis renders in underline, and code spans and blocks are highlighted with inverted colors. Streaming content is buffered and re-rendered on each tick so the transcript reads cleanly as the agent responds. Requires the optionalkramdowngem.repl: add cursor LEFT/RIGHT movement to the input line
The curses-based REPL input now supports cursor movement with the left and right arrow keys, enabling in-place text editing before submitting a prompt. The cursor position is tracked visually and moves backwards on left-arrow and forwards on right-arrow.repl: add Ctrl+A and Ctrl+E keybindings to the input line
The curses-based REPL input now supports Ctrl+A to jump the cursor to the start of the input line and Ctrl+E to jump it to the end, matching common terminal editing conventions.repl: add
tools:option toLLM::Agent#repl
LLM::Agent#replnow accepts atools:keyword argument that attaches additional tool classes or instances for the duration of the repl session. These tools are combined with any tools already configured on the agent, and are discarded when the session ends.repl: add repl support to ActiveRecord and Sequel agent models
acts_as_agent(ActiveRecord) andplugin :agent(Sequel) models now expose areplmethod that delegates to the underlying agent's read-eval-print loop. This allows interactive debugging and inspection of persisted agent state at runtime. Note that changes made during a repl session do not persist back to the database.repl: add extra padding between markdown nodes
The curses-based REPL markdown renderer now adds extra vertical spacing between certain markdown elements: paragraphs, headers, and codeblocks for improved readability of model responses.repl: add a visual divider between transcript and the rows below it
The curses-based REPL now draws a horizontal divider line (using a unicode─character) to separate the transcript area from the status and input rows below it. A single empty buffer row is also added between the transcript and the divider, preventing transcript text from running too close to the status and input rows.repl: expand input field to 3 rows
The curses-based REPL input field now spans three rows instead of one, wrapping text that exceeds the terminal width onto subsequent lines. A scrollable viewport follows the cursor so the active line stays visible, and common navigation commands (Ctrl+A, Ctrl+E, cursor keys) work across all three rows of the expanded input area.Refresh OpenAI model metadata
Add new OpenAI models to the registry, includinggpt-5.6,gpt-5.6-luna,gpt-5.6-terra,gpt-5.6-sol, andgpt-realtime-2.1, with associated pricing, capabilities, and limits.
Fix
Fix Ollama non-streaming response handling
Fix the Ollama provider to properly handle the non-streaming path. When the provider returns a raw NDJSON response body (instead of streaming), the response is now parsed and merged into a singleLLM::Objectbefore being returned to the caller. Previously the non-streaming path was effectively broken and would fail to produce a valid completion response.repl: handle a negative context window allowance in the usage bar
Fix a crash in the curses-based REPL context-usage bar when the context window allowance is exceeded (used > total). The negative width value that resulted from this edge case could cause curses errors; it now gracefully defaults to0%and zero bar width.Fix YARD documentation across provider and tool files
Fix unnamed, misnamed, and missing@paramtags inLLM::Repl::Status,LLM::Tool::Git,LLM::Tool::Pwd,LLM::Tool::Rg, andLLM::Tool::SwapText.
v12.2.0
Changes since v12.1.0.
This release adds Mistral as a new provider with chat completions, streaming, tool calls,
structured outputs, file/image attachments, and embeddings support. It introduces
the trace: option to LLM::Agent#repl for keeping the tracer active during
interactive sessions.
Several fixes land for the Google provider (generationConfig parameter leakage),
LLM::Context#tracer= (always assigning nil), LLM::Provider#with_tracer(nil)
(nil fallback), and LLM::Context#repair! (dropping Struct returns).
The default HTTP timeout has been increased from 60s to 180s to better accommodate
reasoning models and large structured outputs, and the Anthropic default model has
been updated to claude-opus-4-8. Model metadata has been refreshed across
Anthropic, AWS Bedrock, DeepInfra, Google, and xAI, with Mistral model data added
to the registry.
Add
Add
trace:option toLLM::Agent#repl
LLM::Agent#replnow accepts atrace:keyword argument. By default the tracer is disabled for the duration of the repl session to prevent curses UI interference from output written to$stdoutor$stderr. Settrace: trueto keep the tracer active during the session, which is useful when the tracer writes to a file rather than the terminal.Add a new provider: LLM::Mistral
Mistral is now supported through its OpenAI-compatible API. The provider supports chat completions, streaming, tool calls, structured output (schema), file/image attachments, and embeddings. UseLLM.mistral(...)to create a provider instance.Add
LLM.mistral(...)convenience method
A new top-level accessor (LLM.mistral) returns anLLM::Mistralprovider instance, matching the pattern used by other providers.
Fix
Fix Google
generationConfigparameter leakage
Fix a bug in the Google provider where non-generation parameters (role,model,messages,stream) were leaking into thegenerationConfigobject alongside legitimate generation config parameters such astemperature. Non-config parameters are now filtered out before constructinggenerationConfig.Fix
LLM::Context#tracer=always assigningnil
TheLLM::Context#tracer=setter had a bug where it always assignednilregardless of the tracer value passed. It now correctly assigns the given tracer or falls back toLLM::Tracer::Null.Fix
LLM::Provider#with_tracer(nil)fallback
LLM::Provider#with_tracer(nil)now falls back toLLM::Tracer::Nullinstead of setting aniltracer directly.Fix
LLM::Context#repair!droppingStructreturns
LLM::Context#repair!used[*prompt]to wrap the prompt before grepping for return objects. SinceLLM::Function::Returnis aStruct, the splat operator expanded it into its member values instead of wrapping it, causing the grep to silently drop returns. The fix wraps both sources in an array before flattening.
Change
Increase default provider timeout from 60s to 180s
The default HTTP timeout for all providers has been increased from 60 to 180 seconds to better accommodate long-running requests such as reasoning models and large structured outputs.Change Anthropic default model to
claude-opus-4-8
The default Anthropic chat model has been updated fromclaude-sonnet-4-20250514toclaude-opus-4-8, reflecting the latest model release from Anthropic.Refresh model metadata
Update model listings, pricing, and capabilities for Anthropic, AWS Bedrock, DeepInfra, Google, and xAI. Add Mistral model data to the registry.
v12.1.0
Changes since v12.0.0.
This release adds LLM::Agent#repl with a curses-based
interactive read-eval-print loop. It requires the optional
dependency curses and it is probably the most notable
feature in this release.
Multiple opt-in tools have been added to the llm/tools/*.rb
directory. They serve as examples and as general-purpose tools
that happen to power the repository's agents.
The BSL license has been extended to grant additional free waivers for non-profits, charities and for companies with 50 or less employees.
Other changes include small-ish bug fixes.
As always, see the changelog details for a thorough overview.
Add
Add
LLM::Agent#repl
Add a curses-based read-eval-print loop forLLM::Agentthat lets developers interact with an agent after it has been set up or has performed a task. It is similar tobinding.pry: once you exit, you can continue with the rest of your program. It requires thecursesgem.Add
#tracer=setter on Provider, Context and Agent
LLM::Provider,LLM::ContextandLLM::Agentcan now configure the tracer after initialization via the#tracer=setter. It accepts a subclass ofLLM::Tracerornilto disable the tracer.Add
LLM::Tool::Shell
Add a built-in shell tool that can run a command with arguments.
It must be required explicitly withrequire "llm/tools/shell"and requires thetest-cmd.rbgem.Add
LLM::Tool::ReadFile
Add a built-in tool for reading the contents of a file, with optionalstartandstopline offsets.
It must be required explicitly withrequire "llm/tools/read_file".Add
LLM::Tool::Chdir
Add a built-in tool for changing the current working directory.
It must be required explicitly withrequire "llm/tools/chdir".Add
LLM::Tool::Git
Add a built-in tool that can perform git actions (log,diff,show,commit,checkout,branch).
It must be required explicitly withrequire "llm/tools/git"and requires thetest-cmd.rbgem.Add
LLM::Tool::Rg
Add a built-in tool that wraps therg(ripgrep) command for recursively searching the current directory for patterns.
It must be required explicitly withrequire "llm/tools/rg"and requires thetest-cmd.rbgem.Add
LLM::Tool::SwapText
Add a built-in tool that can replace an exact snippet of text in a file with a new piece of text.
It must be required explicitly withrequire "llm/tools/swap_text".Add
LLM::Tool::Pwd
Add a built-in tool that returns the current working directory.
It must be required explicitly withrequire "llm/tools/pwd".Add
LLM::Tool::WriteFile
Add a built-in tool that can write a given string to a given file path.
It must be required explicitly withrequire "llm/tools/write_file".Add
LLM::Tool::Mkdir
Add a built-in tool that can create a tree of new directories.
It must be required explicitly withrequire "llm/tools/mkdir"and requires thetest-cmd.rbgem.
Change
Extend BSL additional use grant
The Business Source License additional use grant has been extended to include non-profits, charities, and companies with 50 or fewer employees, in addition to the existing personal, education, and evaluation uses.Change LlamaCpp default port (8080 => 8013)
The default port for the LlamaCpp provider has changed from8080to8013since llamacpp itself defaults to that port.Change LlamaCpp default model to
nil
The default model for LlamaCpp is nownil, letting whatever model is served by the llamacpp server act as the default. Previously it defaulted toqwen3.
Fix
Fix
LLM::Agent.toolsSymbol resolution
When an agent defined tools viatools :method_name, the resolved symbol was incorrectly forwarded as[:method_name](an array) toLLM::Context. This fix copies the same pattern used by other attribute resolvers (e.g.,skills) so a single Symbol is resolved through the agent instance correctly.Encode strings as UTF-8 in the JSON adapter
Thejsongem will reject BINARY-encoded strings from version 3 and beyond. TheLLM::JSONAdapter.dumpmethod now walks serialized data and encodes every string into UTF-8, usingString#scrubto replace bytes that are not valid UTF-8.
v12.0.0
Changes since v11.3.1.
This release relicenses the project under the Business Source License,
defaults OpenAI to the Responses API and gpt-image models, adds the
DeepInfra provider with audio and image support, introduces
DeepSeek vector-graphics generation and schema support, extends xAI
image editing, adds LLM::Schema.defaults and schema string rendering,
and makes ActiveRecord and Sequel agent wrappers yield LLM::Agent
instead of polluting the model namespace.
Breaking
- License change
The llm.rb runtime has been developed primarily by one person for 3 years. That was done on my own time, and I haven't made a dime from that work.
So when I saw a multi-million dollar company benefit from the work and for it to become the backbone of their AI infrastructure and then see them not contribute back or offer any kind of support, I decided this is not sustainable, or fair.
I assumed good faith and for people to act in the spirit of open source but sadly, that's just not the case. I have to choose a license that respects my time and effort.
For those reasons, llm.rb is being relicensed under the Business Source license. So what does that mean?
In a nutshell:
- Free for personal use.
- Free for education.
- Free for evaluation, development, and testing.
- Commercial production use requires a commercial license.
- Exemptions on a case-by-case basis
After 4 years, the license expires and it will become available under the 0BSDL as it was before v12.0.0. These 4 years apply to a specific version, and not the project overall.
Going forward, v12.0.0 will be relicensed to respect my time, energy, and effort. llm.rb took an incredible amount of time and effort, and continues to do so, so I want to protect myself from companies who benefit from my work but don't respect the time or effort that was put into it.
- OpenAI: default to the Responses API
The responses API has both models and features that are unavailable on the chat completions API, and the responses API appears to be the API of the future for OpenAI.
Worth noting: the llm.rb implementation does not store state
server-side by default. This can be changed with the store: true
option. The legacy chat completions API can be accessed with the
mode: :completions option.
llm.rb has had support for the responses API for quite a while but it was not the default, and a number of bugs were found and fixed during the process of making it the default.
OpenAI: use gpt-image for image generation
Thedallemodels are in the process of being deprecated, and support has been dropped from llm.rb. Thegpt-imagemodels are the next-generation image-generation models from OpenAI.xAI: provide images as base64-encoded data
Both xAI, and OpenAI had the option to generate images via a URL you can fetch, or as a base64-encoded string embedded directly in the response.
OpenAI is moving away from the URL transport since deprecating dalle, and with that in mind, llm.rb has dropped support for the URL transport across all providers that supported it.
Google, xAI, and OpenAI now consistently provide generated and modified images as a base64-encoded string.
ActiveRecord: yield
LLM::Agenttoacts_as_agent
With this change we yield an instance ofLLM::Agentto theacts_as_agentmethod, and drop the methods (such asmodel,instructions, etc) that were previously defined directly on the model. This keeps the number of methods that llm.rb adds to an ActiveRecord model at a minimum and retains the same capabilities as before.Sequel: yield
LLM::Agenttoplugin(:agent)
Ditto as above but for Sequel.Remove the langsmith tracer
This code was contributed by a third party but contains many anti-patterns that are against llm.rb conventions and best practices. It was merged without oversight or review, and basically against the ethos of open source.
I also don't have a langsmith account to maintain the
code. The alternative is the LLM::Tracer::Telemetry class
that was originally written by me, and serves as a
general-purpose OTP tracer.
Add
Add a new provider: LLM::DeepInfra
DeepInfra provide OpenAI-compatible endpoints for a large catalog of hosted open-source and open-weight models.
Capabilities like tool calling, structured outputs, and reasoning can depend on the model.Add new image provider: LLM::DeepInfra::Images
DeepInfra provide access to diverse set of text-to-image models.
Learn more about the available models on their text-to-image models page.DeepSeek: add
LLM::DeepSeek::Images#createand#edit
This new API can generate and edit vector graphics (SVGs).
It is an experimental approach and API.
DeepSeek does not provide an image generation model however its text-to-text models can generate SVG documents, and that's the approach this feature takes. It is limited to vector graphics rather than raster images.
DeepSeek: attach
LLM::Response#agentto image responses
The DeepSeek image API is built on top ofLLM::Agent. Image responses now expose that agent viares.agent, which makes it possible to carry the same session across multiple generations or edits.xAI: add
LLM::XAI::Images#edit
With this change it is possible to both generate images from a prompt, and edit an existing image with a prompt. xAI now has the same edit and create capabilities that OpenAI has.Add
LLM::Schema.defaults
This method lets you map multiple property names to different default values. It is similar toLLM::Schema.requiredin the sense that it is called after the properties of a schema have been defined.Add
LLM::Schema#to_sandLLM::Schema.to_s
Schemas can now be rendered as a prompt-friendly string. This is useful when the shape of a schema needs to be described in natural-language instructions rather than passed through a native structured output interface.DeepSeek: add
LLM::Schemasupport
DeepSeek can now useschema:for structured output. llm.rb handles this by settingresponse_format: {type: "json_object"}and describing the schema in a system message.OpenAI: add local file support to the Responses API
Our responses API implementation lacked local file support.
This change fixes that by supporting both image, document, and other media types that OpenAI may support.Add
LLM::Response#idacross all providers
This method was previously implemented viamethod_missing, and the field name could change depending on the provider. The new method is a catch-all that provides a single method that works across all providers.Add
LLM::DeepInfra::Audio
DeepInfra implements most of the llm.rb audio interface with both thecreate_speechandcreate_transcriptionmethods. Thecreate_translationmethod is not implemented, and the available text-to-speech and speech-to-text models are more varied than other providers.OpenAI: normalize text-to-speech responses
Theres.audiomethod now returns anLLM::URIDataobject for OpenAI text-to-speech responses. The object providesencoded,decoded,content_type, andencoding_type.DeepInfra: normalize text-to-speech responses
Theres.audiomethod now returns anLLM::URIDataobject for DeepInfra text-to-speech responses. The object providesencoded,decoded,content_type, andencoding_type.
Fix
Fix Google
temperatureparameter fall-through
Ensure provider-leveltemperatureand othergenerationConfigparameters are forwarded to the API correctly instead of being silently dropped.Fix Google
generationConfigcollisions
Prevent duplicate or conflictinggenerationConfigkeys in the Google request adapter.
Change
Change OpenAI defaults
The default chat model is nowgpt-5.4-mini.
The default image model is nowgpt-image.Change google defaults
The default chat model is nowgemini-3.1-flash-lite
The default embeddings model is nowgemini-embedding-2Change xAI defaults
The default chat model is nowgrok-4.3.
The default image model is nowgrok-imagine-image-quality.Return an
LLM::ObjectfromLLM::Response#content!
The Hash-like, indifferent access data structure known asLLM::Objectprovides a convenient interface around a Hash object. It allows method access viaobj.key, and decays into a Hash in many cases.
The LLM::Response#content! method now wraps its content
in an LLM::Object but only after it has parsed its
content (a JSON string) into a Ruby data structure.
- Refresh model metadata
Updatedata/*.jsonfiles with current provider model listings, pricing, and capabilities.
v11.3.1
Changes since v11.3.0.
This release rebrands the project under the r.uby.dev umbrella, removes the Jekyll-based docs site in favor of a pure-markdown deepdive, and cleans up YARD documentation across the codebase.
Change
Rebrand to r.uby.dev
Update README.md with the new logo, streamlined copy, and r.uby.dev URLs. Rewriteresources/deepdive.mdas a concise walkthrough and bundle it with the gem. Remove thedocs/directory (Jekyll site). Update all references fromllmrb.github.iotor.uby.dev.Update gemspec
Update homepage, metadata URLs, email, and author list. Switch the YARD markdown processor from kramdown to redcarpet.
Fix
- Fix YARD documentation
Fix unnamed, misnamed, and missing@paramtags across provider adapters, transport classes, stream, tool, schema, registry, agent, and ActiveRecord integration files. Fix backtick-wrapped constant references and other YARD formatting issues.
v11.3.0
Changes since v11.2.0.
This release promotes LLM::Agent as the default high-level runtime,
raises LLM::NotFoundError for provider 404 responses, and adds
Symbol resolution to LLM::Agent.confirm and LLM::Agent.skills for
dynamic tool confirmation and skill lists.
Add
Raise
LLM::NotFoundErrorfor provider 404 responses
RaiseLLM::NotFoundErrorwhen a provider returns HTTP 404. One example is calling the embeddings API on DeepSeek (LLM.deepseek(...).embed(["foobar"])), which returns 404 because DeepSeek does not implement that endpoint.Add Symbol resolution to
LLM::Agent.confirm
Whenconfirmreceives a single Symbol argument, it stores it as-is instead of converting it to a string array. At initialization time,resolve_optionresolves the Symbol by calling the method with that name on the agent instance, and the result is converted to strings. This allows dynamic tool confirmation lists:class MyAgent < LLM::Agent confirm :tools_that_need_confirmation
def tools_that_need_confirmation some_condition ? %w[delete destroy] : %w[delete] endend
Ported from llmrb/mruby-llm@89a232e3 and @2dd04e2d.
Extend the same pattern to LLM::Agent.skills so the skills DSL
accepts a Symbol that resolves through the agent instance at
initialization time.
Change
- Clarify
LLM::Agentas the default high-level runtime
Document thatLLM::Contextremains at the heart of llm.rb, butLLM::Agentis the better default unless an application needs advanced manual tool loops.LLM::Agentmanages the tool loop for callers and enables guards against runaway or repeated tool-call loops.
v11.2.0
Changes since v11.1.0.
This release adds LLM::Function#skill? and LLM::Tool#skill? so
callers can inspect whether a function or tool is backed by a skill.
It introduces LLM::Transport::Request as a transport-agnostic request
object so providers no longer depend directly on Net::HTTP request
classes, and adds an optional Curb (libcurl) backend alongside symbolic
transport shortcuts such as transport: :curb.
MCP and A2A clients now accept persistent: true matching provider configuration.
Several fixes land for tool return callback emission, function comparison by
tool call ID, function array filtering, skill tool inheritance, and JSON generator
state compatibility on Ruby 4.
Add
Add
LLM::Function#skill?
Addskill?toLLM::Functionso callers can check whether a function is backed by a skill tool.Add
LLM::Tool.skill?andLLM::Tool#skill?
Add class-levelskill?and instance-levelskill?toLLM::Tool, matching the existingmcp?anda2a?pattern.Add
LLM::Transport::Request
AddLLM::Transport::Requestas a transport-agnostic request object and update providers to build requests without depending directly on Net::HTTP request classes. The built-in Net::HTTP transports still accept existing Net::HTTP request objects through a compatibility bridge, while alternative transports can handle the generic request shape directly.Add optional Curb transport support
AddLLM::Transport::Curb, an optional libcurl-backed transport that can be selected withtransport: :curb. Providers already emitLLM::Transport::Requestobjects, so the Curb backend can execute requests without routing through Net::HTTP.Add symbolic transport shortcuts
Allow providers, MCP HTTP clients, and A2A HTTP clients to accept transport shortcuts such astransport: :curbandtransport: :net_http_persistent.Add persistent HTTP selection to MCP and A2A clients
Allow MCP and A2A HTTP clients to acceptpersistent: true, matching provider configuration and selecting the persistent Net::HTTP transport by default.
Fix
Support JSON generation state on Ruby 4
Handle JSON generator state objects in the standard JSON adapter so schema objects serialize correctly when Ruby 4 calls customto_jsonmethods during provider request generation.Emit tool return callbacks for direct context waits
EmitLLM::Stream#on_tool_returnwhenLLM::Context#waitexecutes pending tool work directly instead of drainingLLM::Stream::Queue.Emit confirmed tool return callbacks once
EmitLLM::Stream#on_tool_returnfor confirmed and cancelled tool calls, and exclude confirmed functions from later waits so mixed confirmed and unconfirmed tool batches do not execute confirmed tools twice.Compare functions by tool call ID
AddLLM::Function#==,#eql?, and#hashso pending function collections can compare tool calls by provider-assigned ID instead of object identity.Preserve function array behavior after filtering
PreserveLLM::Function::Arraybehavior when subtracting function arrays so filtered tool batches can still spawn through the normal function array API.Prevent skills from inheriting skill-backed tools
Exclude skill-backed tools when a skill sub-agent usestools: inherit, preventing skills loaded through a parent context from being recursively exposed to nested skill agents.
v11.1.0
Changes since v11.0.0.
This release adds the inherit directive for skill sub-agents so they can
inherit access to the local, MCP, and A2A tools available to their parent
agent. It introduces class-level required %i[...] declarations to
LLM::Schema and wraps LLM::Function#arguments in LLM::Object for
method-style argument access. The OpenTelemetry tracer now samples all spans
regardless of environment, and the tool-call loop repair step prevents stale
history from being sent on follow-up requests.
Add
Add support for the
inheritdirective in skills
Add support for theinheritdirective so a skill sub-agent can inherit access to the local, MCP, and A2A tools available to its parent agent.Add class-level
required %i[...]support toLLM::Schema
Add class-levelrequired %i[...]declarations toLLM::Schema, so schema classes can mark existing properties as required the same wayLLM::Toolparams already can.Wrap function arguments in
LLM::Object
WrapLLM::Function#argumentsinLLM::Object, so function implementations can read arguments with method-style access while still invoking runners with keyword arguments.
Fix
Ensure all traces are sampled regardless of environment
Explicitly passSamplers::ALWAYS_ONwhen creating the OpenTelemetryTracerProviderso the in-memory exporter always captures every span, regardless of theOTEL_TRACES_SAMPLERenvironment variable.Always close the tool call loop before sending follow-up requests
Add a repair step inContext#talkthat closes assistant tool-call messages without matching tool responses before the next provider request is sent. This prevents stale tool-call history from being sent on follow-up requests, which some providers reject as invalid.
v11.0.0
Changes since v10.0.0.
This release removes several deprecated or unused APIs, including the #chat
alias from contexts and agents, the LLM::Function#register alias, and the
unused positional llm argument from MCP constructors. Generated MCP and A2A
tools are no longer added to the global tool registry by default.
On the additions side, it introduces the A2A (Agent2Agent) protocol client,
a new #ask convenience interface on contexts and agents, one-shot stdio MCP
requests outside #session, LLM::Function#def as a short alias for
LLM::Function#define, LLM::File#exist?, and LLM::Tool.a2a?.
Breaking
Remove the unused
llmargument from MCP clients
Remove the unused positionalllmargument fromLLM::MCP.new,LLM::MCP.stdio,LLM::MCP.http, andLLM.mcp.Stop globally registering generated MCP and A2A tools
Generated tools returned byLLM::Tool.mcp(...)andLLM::Tool.a2a(...)are no longer added to the globalLLM::Tool.registryorLLM::Function.registry. They still work when passed directly to a context or agent, but registry-based lookup now only sees normal loadedLLM::Toolsubclasses.Remove
LLM::Function#register
Remove theLLM::Function#registeralias and preferLLM::Function#defineorLLM::Function#defwhen binding a function to its implementation. Theregisteralias was too easy to confuse with the class-levelLLM::Tool.registerandLLM::Function.registerregistry APIs.Remove the
#chatalias from contexts and agents
Remove theLLM::Context#chatandLLM::Agent#chataliases. Prefer#talkfor all context and agent turns.
Add
Add
LLM::Function#def
AddLLM::Function#defas a short alias forLLM::Function#definewhen binding a function instance to its implementation.Add
LLM::MCP#session
AddLLM::MCP#sessionas an alias forLLM::MCP#run, and prefer it in examples for scoped stdio MCP sessions that should stay alive across discovery and tool calls.Add
#askto contexts and agents
AddLLM::Context#askandLLM::Agent#askas a RubyLLM-compatible convenience interface over#talk.#askaccepts a prompt, optionalwith:attachments, an optionalstream:target, and an optional block for streamed chunks, and returns anLLM::Response.Add
LLM::File#exist?
AddLLM::File#exist?as a small convenience wrapper for checking whether a local file exists on disk.Allow one-shot stdio MCP requests outside
#session
Allowmcp.tools,mcp.prompts,mcp.find_prompt(...), andmcp.call_tool(...)to work outsidemcp.sessionby starting and stopping a stdio transport on demand when needed. This makes stdio MCP usable without an explicit session block, while keepingmcp.sessionas the preferred pattern for efficient, stateful stdio workflows.Add A2A client support
AddLLM::A2A, a client for the Agent2Agent (A2A) protocol with REST and JSON-RPC bindings. Remote agent skills can be exposed asLLM::Toolclasses and used throughLLM::ContextorLLM::Agent, and the client also supports direct messaging, streaming, task operations, push notification configuration, extended agent cards, persistent HTTP transport selection, and optional RESTbase_pathprefixing.
Refactor shared MCP/A2A HTTP transport setup into
LLM::Transport::Utils, and extend
LLM::Transport::StreamDecoder to accept a callback block directly.
- Add
LLM::Tool.a2a?
AddLLM::Tool.a2a?and mark generated A2A-backed tool classes so callers can distinguish them from local or MCP tools.
Fix
Fix context and agent JSON serialization through
LLM.json
FixLLM::Context#to_jsonandLLM::Agent#to_jsonto serialize throughLLM.json.dump(...)instead of plainto_json.Fix block-form ORM agent DSL forwarding
Fix block-formmodel { ... },tools { ... }, andschema { ... }declarations in the ActiveRecord and Sequel agent wrappers so persisted agent models configure the internal agent class the same wayLLM::Agentdoes.Fix missing
skillsin ORM agent wrappers
Fix the ActiveRecord and Sequel agent wrappers to exposeskills, so persisted agent models can declare skills the same way asLLM::Agent.Fix
acts_as_agent#ctxreturn type
Fix the ActiveRecordacts_as_agentwrapper so itsctxhelper returns the wrappedLLM::Agentinstead of returning the underlyingLLM::Contextdirectly.
v10.0.0
Changes since v9.0.0.
This release removes the LLM::Context#respond method, and
also removes the deprecated LLM::Bot alias. All class-level
agent tunables can now be resolved lazily via a Symbol (method name),
or a Proc. The LLM::Agent class can now confirm a tool call
before it happens, and the LLM::Schema class has been extended
to support Array[String,Integer] as a shorthand for
Array[AnyOf[String, Integer]]. The LLM::Stream class has
had its public method surface reduced to help avoid accidental
collisions.
Breaking
Unify context turns under
#talk
RemoveLLM::Context#respondand route responses-mode turns throughLLM::Context#talkwithmode: :responsesinstead.Remove the
LLM::Botalias
Remove the backward-compatibleLLM::Botalias forLLM::Context. UseLLM::Contextdirectly instead.
Add
Add shared option resolution through
LLM::Utils
AddLLM::Utils.resolve_optionfor resolving configured values as literals, procs, symbol-named methods, or duplicated hashes, and use it in agent and ORM option resolution paths.Resolve all class-level agent tunables via Proc
Letmodel,tools,skills,schema,stream, andtracerdeclared with a block be lazily evaluated against the agent instance at initialization time, matching howstreamandtraceralready worked.
Add LLM::Agent#params for direct access to the underlying context
parameters.
Ported from mruby-llm.
Support
Array[...]schema and tool param types
LetLLM::Schemaproperties andLLM::Toolparams acceptArray[...]type declarations, including mixed item unions that are serialized asanyOfarray items.Add
LLM::Provider#key?
Addkey?to providers so callers can check whether a non-blank API key has been configured.Add agent tool confirmation hooks
AddLLM::Agent.confirmandLLM::Agent#on_tool_confirmationso selected tools can be approved or cancelled before execution. Pending tool resolution now relies onLLM::Context#functionsso confirmed tools are not executed twice when mixed with unconfirmed tool calls.Add
LLM::Function#spawn(:call).wait
Add task-shaped sequential execution support for directLLM::Function#spawn(:call).wait.
Fix
- Reduce private internal methods on
LLM::Stream
Removetool_not_foundand__tools__fromLLM::Stream. The__tools__logic is inlined directly into__find__since that was its only caller. Thetool_not_foundutility method was unused externally and added unnecessary surface to LLM::Stream.
Ported from mruby-llm.
v9.0.0
Changes since v8.1.0.
This release deepens llm.rb's transport and cost-tracking surface. It
replaces the old mutable persist! API with constructor-driven transport
selection, removes #call from contexts and agents in favor of explicit
ctx.wait(:call), makes queued stream waits strategy-free, and deletes
the unused LLM::Utils module.
It adds cache read/write token tracking
with corresponding cost components, audio and image token pricing,
LLM::Context#functions? for queue-aware tool loops,
LLM::Agent.stream DSL support, and exposes #stream readers on
contexts and agents.
The HTTP transport layer has been refactored around shared backends so providers, MCP, and custom transports all use the same normalized response interface.
Breaking
Remove
#callas a context and agent tool-loop API
RemoveLLM::Context#call(:functions)andLLM::Agent#call(:functions). Tool loops should usectx.wait(:call)oragent.wait(:call)instead. The ActiveRecord and Sequel wrappers no longer expose#callpassthroughs for stored llm.rb contexts.Make HTTP transport selection constructor-driven
Remove publicpersist!and.persistentmutation APIs from providers, transports, and MCP clients. Select persistent behavior at construction time withpersistent: true,LLM::Transport.net_http,LLM::Transport.net_http_persistent, or an explicittransport:override.Make queued stream waits strategy-free
ChangeLLM::Stream::Queue#waitto resolve queued work by the actual task types already present in the queue instead of accepting an external wait strategy.LLM::Stream#wait(...)remains compatible but now ignores its arguments when delegating to the queue.Remove unused
LLM::Utils
Delete theLLM::Utilsmodule and remove its remaining unused provider includes and top-level require.
Add
Expose
#streamreaders on contexts and agents
Add publicLLM::Context#streamandLLM::Agent#streamaccessors so callers can inspect the active stream object directly.Track cache read and write tokens in usage
Addcache_read_tokensandcache_write_tokenstoLLM::Usageand preserve them through completion usage adaptation and context usage aggregation.Add
LLM::Context#functions?for queue-aware tool loops
Addfunctions?toLLM::Contextand the ActiveRecord and Sequel wrappers so callers can detect pending tool work through either the bound stream queue or unresolved functions, and update the docs to preferwhile ctx.functions?overctx.functions.any?in tool-loop examples.Add
:callas a first-class wait strategy
Add:callto pending-function wait paths soctx.wait(:call)can prefer queued streamed work when present and otherwise fall back to direct sequential function execution throughspawn(:call).wait.Read provider cache usage into completion responses
Read cache read tokens from provider usage metadata, including OpenAIusage.prompt_tokens_detailsand Anthropicusage.cache_read_input_tokens. Read Anthropic cache write tokens fromusage.cache_creation_input_tokens, and expose explicit zero-valuedcache_write_tokensmethods on providers that do not report cache creation usage.Extend cost tracking with cache write pricing
ExtendLLM::Costwithcache_read_costs,cache_write_costs, andreasoning_costsalongside the existinginput_costsandoutput_costs. Add#to_hfor structured cost insight and updatectx.costto calculate all available components from registry pricing data.Price input and output audio separately
Trackinput_audio_tokensandoutput_audio_tokensin usage and includeinput_audio_costsandoutput_audio_costsinLLM::Costso multimodal requests report accurate audio spend.Track image tokens in input cost reporting
Addinput_image_tokensto usage and includeinput_image_costsinLLM::Costusing the model's generic input rate so image-bearing prompts report their input spend.Add
LLM::Agent.streamDSL support
Let agents define a defaultstreamthrough the class DSL, including block-based stream construction so each agent instance can resolve its stream the same waytracerdoes.
Change
Refactor HTTP transports around shared backends
SplitNet::HTTPandNet::HTTP::Persistentinto separateLLM::Transportimplementations, move HTTP-specific request helpers and response execution into the shared transport layer, and let MCP HTTP wrap those transports instead of maintaining a separate transient/persistent client split.Share transport overrides across providers and MCP
Let both provider construction andLLM::MCP.http(...)acceptLLM::Transportinstances or classes as HTTP transport overrides, so callers can reuse the same transport implementation across the runtime.Let custom transports adapt their own response objects
Introduce a transport response interface so custom transports can adapt backend-specific response objects to one normalized shape and have them work with the existing provider execution and error-handling code.
v8.1.0
Changes since v8.0.0.
This release adds Amazon Bedrock provider support through the Converse
API, including AWS SigV4 request signing, event stream decoding,
structured output through schema:, and a models.dev-backed registry.
It exposes llm.models.all for Bedrock via the ListFoundationModels
API and adds LLM::Object#transform_values! for in-place value
transformation. Several Bedrock-specific fixes land as well, including
response id exposure, blank text block suppression in tool turns, and
DSML tool-marker filtering in streamed text.
Add
Add AWS Bedrock provider support
AddLLM.bedrock(...)with Bedrock Converse chat support, AWS SigV4 request signing, Bedrock event stream decoding, structured output support throughschema:, and models.dev-backedbedrock.jsonregistry generation.Add AWS Bedrock Models endpoint support
Addllm.models.allfor Bedrock via the ListFoundationModels API, including SigV4 signing for the control-plane endpoint and normalizedLLM::Modelcollection responses.Add
LLM::Object#transform_values!
LetLLM::Objecttransform stored values in place through#transform_values!.
Fix
Expose response ids on Bedrock completion responses
Read the Bedrock request id intoLLM::Response#idfor completion responses adapted from the Converse API.Avoid blank assistant text blocks in Bedrock tool turns
Stop replaying assistant tool-call messages with empty text content blocks that Bedrock rejects.Suppress Bedrock DSML tool markers in streamed text
Filter"\u003c\u003cDSML\u003efunction_calls\u003e\u003e"markers out of streamed Bedrock assistant text so tool-call sentinels do not leak into user-visible output.
v8.0.0
Changes since v7.0.0.
This release adds Unix-fork concurrency for process-isolated tool
execution, extends LLM::Object with #merge and #delete, and drops
Ruby 3.2 support due to a segfault observed with the :fork path. It
promotes LLM::Pipe to the top-level namespace and adds
persistent: true on LLM::MCP.http for direct persistent transport
configuration. LLM::Function#runner is exposed as public API, agent
tracer overrides are supported, fiber execution now uses Fiber.schedule,
missing optional dependencies raise clearer LLM::LoadError guidance,
and ActiveRecord wrapper plumbing is deduplicated between acts_as_llm
and acts_as_agent.
Breaking
- Drop Ruby 3.2 support
Stop supporting Ruby 3.2 due to a segfault observed with the:forktool concurrency strategy.
Add
Add
LLM::Object#merge
LetLLM::Objectreturn a new wrapped object when merging hash-like data through#merge.Add
LLM::Object#delete
LetLLM::Objectdelete keys directly through#delete.
Change
Add fork-based tool concurrency
Add:forkas a new concurrency strategy forLLM::Function#spawn,LLM::Function::Array#wait, andLLM::Agent.concurrencythat runs class-based tools in isolated child processes. Fork-backed tools support tracer callbacks,on_interrupt/on_cancelhooks, andalive?checks. Requires thexchangem for inter-process communication with:fork. This is especially useful for tools that need process isolation, such as running shell commands or handling unsafe data.Promote
LLM::Pipefrom MCP namespace to top-level
MoveLLM::MCP::PipetoLLM::Pipeso the pipe abstraction is available outside MCP internals. The new class adds abinmode:option for binary pipes.LLM::MCP::Commandand related MCP transport code have been updated to useLLM::Pipe.Allow
persistent: trueonLLM::MCP.http
LetLLM::MCP.http(...)enable persistent HTTP transport directly throughpersistent: trueat construction time.Expose
LLM::Function#runneras public API
Promote the internal runner instantiation to a publicrunnermethod onLLM::Function, so callers can inspect or reuse the resolved tool instance that a function wraps.Allow agent instance tracer overrides
LetLLM::Agent.new(..., tracer: ...)override the class-level tracer for that agent instance.Make
:fiberuse scheduler-backed fibers
Change:fibertool execution to useFiber.scheduleand requireFiber.scheduler, instead of wrapping direct calls in raw fibers. This gives:fibera real cooperative concurrency model instead of acting as a thin wrapper around sequential execution.Read stored values from zero-argument
LLM::Objectmethod calls
Let calls likeobj.delete,obj.fetch,obj.merge,obj.key?,obj.dig,obj.slice, orobj.keysreturn a stored value when that method name exists as a key and no arguments are given.Harden
LLM::Objectagainst arbitrary key names
Move internal lookup logic offLLM::Objectinstances and onto the singleton class instead, making stored keys likemethod_missingmore resilient while preserving normal dynamic field access.Deduplicate ActiveRecord wrapper plumbing
Move shared ActiveRecord wrapper defaults and utility methods intoLLM::ActiveRecord, reducing duplication betweenacts_as_llmandacts_as_agent.Raise clearer errors for missing optional runtime dependencies
Route optionalasync,xchan, andnet/http/persistentloads throughLLM.requireso missing runtime gems raiseLLM::LoadErrorwith installation guidance instead of leaking rawLoadErrorexceptions.
Fix
Avoid
RuntimeErrorfromAsync::Task.currentlookups
CheckAsync::Task.current?before reading the current Async task so provider transports fall back toFiber.currentwithout raising when no Async task is active.Serialize
LLM::Objectvalues correctly throughLLM.json
MakeLLM::Object#to_jsoncallLLM.json.dump(to_h, ...)soLLM::Objectvalues serialize through the llm.rb JSON adapter.
v7.0.0
Changes since v6.1.0.
This release turns agent tool-loop limit errors into in-band advisory
returns so the LLM can react to rate limits and continue the loop. It
adds tool_attempts: nil as a way to opt out of advisory tool-limit
returns entirely, and fixes the default provider HTTP path to keep
net-http-persistent optional when not explicitly enabled.
Breaking
Return in-band tool-loop limit errors from agents
Stop raisingLLM::ToolLoopErrorwhen an agent exhausts its tool loop attempt budget, and instead send advisoryLLM::Function::Returnerrors back through the model so the LLM can react to the rate limit in-band and continue the loop.Allow
tool_attempts: nilto disable advisory tool-limit returns
Keep the defaulttool_attemptsbudget at25, but treat an explicittool_attempts: nilas an opt-out that disables advisory tool-limit returns entirely.
Fix
- Keep
net-http-persistentoptional on normal HTTP requests
Stop the default provider HTTP path from loadingnet/http/persistentunless persistent transport support is explicitly enabled.
v6.1.0
Changes since v6.0.0.
This release tightens interrupt and compaction behavior for long-running
contexts. It adds LLM::Buffer#rindex, supports percentage-based token
thresholds in LLM::Compactor, tracks persisted compaction state through
context serialization, reliably interrupts Async-backed requests, preserves
valid tool-call history on cancellation, keeps concurrent skill tool loops
running on streamed agents, and returns zero-valued usage objects when no
provider usage has been recorded yet.
Change
Add
LLM::Buffer#rindex
AddLLM::Buffer#rindexas a direct forward to the underlying message array so callers can find the last matching message index through the buffer API.Support percentage compaction token thresholds
LetLLM::Compactoraccepttoken_threshold:values like"90%"so compaction can trigger at a percentage of the active model context window.
Fix
Interrupt Async-backed requests reliably
Track request ownership through the provider transport so contexts use the active Async task when available, lettingctx.interrupt!reliably cancel streamed requests under Async runtimes and surface them asLLM::Interrupt.Preserve valid tool-call history on cancellation
Append cancelled tool-return messages for unresolved tool calls duringctx.interrupt!so follow-up provider requests do not fail with invalid tool-call history after pending tool work is cancelled.Preserve concurrent skill tool loops on streamed agents
Propagate the active agent concurrency through the effective request stream so nested skill agents keep using queuedwait(...)tool execution instead of falling back to direct:callexecution.Track persisted compaction state on contexts
Mark contexts as compacted afterLLM::Compactor#compact!, persist and restore that state through context serialization, and clear it after the next successful model response.Return zero-valued usage objects from contexts
MakeLLM::Context#usageconsistently return anLLM::Object, using a zero-valued usage object when no provider usage has been recorded yet.
v6.0.0
Changes since v5.4.0.
This release simplifies the ORM persistence contract around serialized
data state, removing the assumption of reserved provider, model, and
usage columns. Provider selection must now come from provider: hooks,
model defaults come from context: or agent DSL, and usage is read from the
serialized runtime state. Alongside this breaking change, Sequel JSON and
JSONB persistence is fixed, ractor-backed tools now fire tracer callbacks,
and LLM::RactorError is raised for unsupported ractor tool work.
Change
- Simplify ORM persistence to serialized
datastate
Change the built-in ActiveRecord and Sequel wrappers to treat serializeddataas the persistence contract, instead of assuming reservedprovider,model, and usage columns. Provider selection must now come fromprovider:hooks that resolve a realLLM::Providerinstance, model defaults come fromcontext:or agent DSL, andusageis read from the serialized runtime state.
Fix
Fix Sequel JSON and JSONB persistence
Load Sequel PostgreSQL JSON support whenplugin :llmis configured withformat: :jsonor:jsonb, and wrap structured payloads correctly so persisted context state can be stored in PostgreSQL JSON columns.Trace ractor-backed tool callbacks
Make tool tracers fireon_tool_startandon_tool_finishfor class-based:ractorexecution too, so ractor-backed tool calls show up in tracer callbacks like the other concurrent tool paths.Raise
LLM::RactorErrorfor unsupported ractor tool work
AddLLM::RactorErrorand fail fast when:ractorexecution is requested for unsupported tool types such as skill-backed tools, instead of letting deeper Ruby isolation errors leak out later in execution.Delegate interrupt to concurrent task implementations
MakeLLM::Function::Task#interrupt!delegate to the underlying fork or ractor task when it supports interruption, soctx.interrupt!andtask.interrupt!work correctly for fork- and ractor-backed tool execution.
v5.4.0
Changes since v5.3.0.
This release expands tracer support around agentic execution. It lets
LLM::Agent define scoped tracers through the agent DSL and fixes concurrent
tool execution so those scoped tracers stay attached when work crosses
thread, task, fiber, and skill boundaries.
Change
- Add agent-scoped tracers
LetLLM::Agentclasses definetracer ...ortracer { ... }so an agent can carry its own tracer without replacing the provider's default tracer. The resolved tracer is scoped to that agent's turns, tool loops, and pending tool access. Available through theacts_as_agentand Sequel agent plugintracerDSL too.
Fix
- Preserve scoped tracers across concurrent tool work
Keep agent- and request-scoped tracers attached when tool execution crosses:thread,:task, or:fiberboundaries, including skill execution, so spawned work does not fall back to the provider default tracer.
v5.3.0
Changes since v5.2.1.
This release deepens llm.rb's request-rewriting and tool-definition surface.
It adds transformer lifecycle hooks to LLM::Stream so UIs can surface work
like PII scrubbing before a request is sent, and it adds a more explicit
OmniAI-style tool DSL form with parameter plus separate required
declarations while keeping the older param ... required: true style working.
Change
Add transformer stream lifecycle hooks
Addon_transformandon_transform_finishtoLLM::Streamso UIs can surface request rewriting work such as PII scrubbing before a request is sent to the model.Add a separate
requiredtool DSL form
Addparameteras an alias ofparamand supportrequired %i[...]as a separate declaration, inspired by OmniAI-style tools, while keeping the existingparam ... required: trueform working too.
v5.2.1
Changes since v5.2.0.
This release tightens the streamed queue fix from v5.2.0 for concurrent
workloads. Request-local streams now stay bound long enough for wait to
drain queued work and then clear cleanly so later waits fall back to the
context's configured stream.
Fix
- Reset request-local streams after
waitdrains queued work
Keep per-callstream:bindings alive throughLLM::Context#waitso queued streamed tool work still resolves correctly, then clear the request-local stream after the wait completes to avoid leaking it into later turns.
v5.2.0
Changes since v5.1.0.
This release adds current DeepSeek V4 support through refreshed provider
metadata, including deepseek-v4-flash and deepseek-v4-pro, while fixing
request-local queue handling for concurrent streamed workloads so wait and
interruption use the active per-call stream correctly.
Change
Add
LLM::MCP#runfor scoped MCP client lifecycle
AddLLM::MCP#runso MCP clients can be started for the duration of a block and then stopped automatically, which simplifies the usualstart/stoppattern in examples and application code.Refresh provider model metadata
Add current DeepSeek and OpenAI model metadata todata/and update the Google Gemini model entry to match the current provider naming.
Fix
Reject unsupported DeepSeek multimodal prompt objects early
RaiseLLM::PromptErrorforimage_url,local_file, andremote_filein DeepSeek chat requests instead of sending invalid OpenAI-compatible payloads that the provider rejects at runtime.Preserve DeepSeek reasoning content across tool turns
Replayreasoning_contentwhen serializing prior assistant messages for DeepSeek chat completions, so thinking-mode tool calls can continue into follow-up requests without triggering invalid request errors.Default DeepSeek to
deepseek-v4-flash
ChangeLLM::DeepSeek#default_modeltodeepseek-v4-flashso new contexts and default provider usage align with the current preferred chat model.Use per-call streams when waiting on streamed tool work
Track request-local streams bound throughtalk(..., stream:)andrespond(..., stream:)soLLM::Context#waitand interruption-aware queue handling use the active stream instead of falling back to pending function spawning.
v5.1.0
Changes since v5.0.0.
This release tightens streamed tool execution around the actual request-local
runtime state. It fixes streamed resolution of per-request tools and makes
that streamed path work cleanly with LLM.function(...), MCP tools, bound
tool instances, and normal tool classes.
Fix
Resolve request-local tools during streaming
Resolve streamed tool calls throughLLM::Streamrequest-local tools before falling back to the global registry, so per-request tools and bound tool instances work correctly during streaming.Support
LLM.function(...)and MCP tools in streamed tool resolution
Let streamed tool resolution use the current request tool set, soLLM.function(...), MCP tools, bound tool instances, and normalLLM::Toolclasses all work through the same streamed tool path.
v5.0.0
Changes since v4.23.0.
This release expands llm.rb from an execution runtime into a more explicit
supervision and transformation runtime. It adds context-level guards,
transformers, and loop supervision through LLM::LoopGuard, while deepening
long-lived context behavior through compaction, interruption hooks, and
streamed ctx.spawn(...) tool execution.
Change
Make compactor thresholds explicit
Requiremessage_threshold:andtoken_threshold:to be opted into explicitly, soLLM::Compactoronly compacts automatically when one of those thresholds is configured. Context-window-derived token limits can be computed by the caller when needed.Allow assigning a compactor through
LLM::Context
LetLLM::Contextacceptctx.compactor = ...in addition to the constructorcompactor:option, so compactor config can be assigned or replaced after context initialization.Mark compaction summaries in message metadata
Mark compaction summaries withextra[:compaction]andLLM::Message#compaction?, so applications can detect or hide synthetic summary messages in conversation history.Add cooperative tool interruption hooks
Letctx.interrupt!notify queued tool work throughon_interrupt, so running tools can clean up cooperatively when a context is cancelled.Add
LLM::Contextguards
Add a newguardcapability toLLM::Contextso execution can be supervised at the runtime level. The built-inLLM::LoopGuarddetects repeated tool-call patterns and stops stuck agentic loops through in-bandLLM::GuardErrorreturns.LLM::Agentenables this guard by default.Add
LLM::Contexttransformers
Add a newtransformercapability toLLM::Contextso prompts and params can be rewritten before provider requests are sent. This makes it possible to apply context-wide behaviors such as PII scrubbing or request-level param injection without rewriting everytalkandrespondcall site.
v4.23.0
Changes since v4.22.0.
This release expands llm.rb's runtime surface for long-lived contexts and
stateful tools. It adds built-in context compaction through LLM::Compactor,
lets explicit tools: arrays accept bound LLM::Tool instances, and fixes
OpenAI-compatible no-arg tool schemas for stricter providers such as xAI.
Change
Add
LLM::Compactorfor long-lived contexts
Add built-in context compaction throughLLM::Compactor, so older history can be summarized, retained windows can stay bounded, compaction can run on its ownmodel:, thresholds can be configured explicitly, andLLM::Streamcan observe the lifecycle throughon_compactionandon_compaction_finish.Allow bound tool instances in explicit tool lists
Let explicittools:arrays acceptLLM::Toolinstances such asMyTool.new(foo: 1), so tools can carry bound state without changing the global tool registry model.
Fix
- Fix xAI/OpenAI-compatible no-arg tool schemas
Send an empty object schema for tools without declared parameters instead ofnull, so stricter providers such as xAI accept mixed tool sets that include no-arg tools.
v4.22.0
Changes since v4.21.0.
This release deepens the runtime shape of llm.rb. It reduces helper-method surface on persisted ORM models, expands real ORM coverage, and makes skills behave more like bounded sub-agents with inherited recent context and proper instruction injection.
Change
Reduce ActiveRecord wrapper model surface
Move helper methods such as option resolution, column mapping, serialization, and persistence intoUtilsfor the ActiveRecord wrappers so wrapped models include fewer internal helper methods.Reduce Sequel wrapper model surface
Move helper methods such as option resolution, column mapping, serialization, and persistence intoUtilsfor the Sequel wrappers so wrapped models include fewer internal helper methods.Expand ORM integration coverage
Add broader ActiveRecord and Sequel coverage for persisted context and agent wrappers, including real SQLite-backed records and cassette-backed OpenAI persistence paths.Make skills inherit recent parent context
RunLLM::Skillwith a curated slice of recent parent user and assistant messages, prefixed withRecent context:, so skills behave more like task-scoped sub-agents instead of instruction-only helpers.
Fix
Fix Sequel
plugin :agentload order
Require the shared Sequel plugin support fromLLM::Sequel::Agentsoplugin :agentcan load independently without raisinguninitialized constant LLM::Sequel::Plugin.Make skill execution inherit parent context request settings
RunLLM::Skillthrough a parentLLM::Contextinstead of a bare provider so nested skill agents inherit context-level settings such asmode: :responses,store: false, streaming, and other request defaults, while still keeping skill-local tools and avoiding parent schemas.Keep agent instructions when history is preseeded
InjectLLM::Agentinstructions once unless a system message is already present, so agents and nested skills still get their instructions when they start with inherited non-system context.
v4.21.0
Changes since v4.20.2.
This release expands higher-level composition in llm.rb. It adds Sequel agent
persistence through plugin :agent and introduces directory-backed skills
that load from SKILL.md, resolve named tools, and plug directly into
LLM::Context and LLM::Agent.
Change
Add
plugin :agentfor Sequel models
Add Sequel support forplugin :agent, similar to ActiveRecord'sacts_as_agent, so models can wrapLLM::Agentwith built-in persistence.Load directory-backed skills through
LLM::ContextandLLM::Agent
Addskills:toLLM::Contextandskills ...toLLM::Agentso directories withSKILL.mdcan be loaded, resolved into tools, and run through the normal llm.rb tool path.
v4.20.2
Changes since v4.20.1.
This patch release improves runtime behavior around interruption and mixed concurrency waits. It also rounds out response API uniformity for Google completion responses.
Fix
Expose Google completion response IDs through
.id
AddLLM::Response#idsupport to Google completion responses so tracer and caller code can rely on the same API used by other providers.Track interrupt ownership on the active request
BindLLM::Contextinterruption to the fiber runningtalkorrespondsointerrupt!works correctly when requests are started outside the context's initialization fiber.
Change
- Allow mixed concurrency strategies in
wait(...)
LetLLM::Context#wait,LLM::Stream#wait, andLLM::Agent.concurrencyaccept arrays such as[:thread, :ractor]so mixed tool sets can wait on more than one concurrency strategy.
v4.20.1
Changes since v4.20.0.
This patch release fixes ORM option resolution in the Sequel and
ActiveRecord wrappers. Symbol-based provider: and context: hooks now
resolve correctly, and internal default option constants are referenced
explicitly instead of relying on nested constant lookup.
Fix
Fix symbol-based ORM option hooks for provider and context hashes
Makeprovider:andcontext:resolve symbol hooks through the model in the Sequel plugin and ActiveRecord wrappers instead of falling back to an empty hash.Fix ORM wrapper constant lookup for option defaults
Qualify internalEMPTY_HASH/DEFAULTSreferences in the Sequel plugin and ActiveRecord wrappers so option resolution does not depend on nested constant lookup quirks.
v4.20.0
Changes since v4.19.0.
This release adds better support for tagged prompt content. LLM::Context
can now serialize and restore image_url, local_file, and remote_file
content cleanly, and LLM::Message now exposes helpers for inspecting
tagged image and file attachments.
Change
Round-trip tagged prompt objects through
LLM::Context
TeachLLM::Contextserialization and restore to preserveimage_url,local_file, andremote_filecontent acrossto_json/restore.Add attachment helpers to
LLM::Message
Addimage_url?,image_urls,file?, andfilesso callers can inspect messages for tagged image and file content more directly.
v4.19.0
Changes since v4.18.0.
This release tightens the ActiveRecord and ORM integration layer. It adds
inline agent DSL blocks to acts_as_agent so agent defaults can be defined
where the wrapper is declared, and it exposes the resolved provider through
public llm methods on the ActiveRecord and Sequel wrappers.
Change
Make ORM provider access public through
llm
Expose the resolved provider on the Sequel plugin and the ActiveRecordacts_as_llm/acts_as_agentwrappers through a publicllmmethod.Allow inline agent DSL blocks in
acts_as_agent
Let ActiveRecord models configuremodel,tools,schema,instructions, andconcurrencydirectly inside theacts_as_agentdeclaration block.
v4.18.0
Changes since v4.17.0.
This release improves tracing and tool execution behavior across llm.rb.
It makes provider tracers default to the provider instance, adds
LLM::Provider#with_tracer for scoped overrides, restores tool tracing for
concurrent and streamed tool execution, extends streamed tracing to MCP tools,
and adds symbol-based ORM option hooks alongside experimental ractor tool
concurrency.
Change
Make provider tracers default to the provider instance
Changellm.tracer = ...so it sets a provider default tracer instead of relying on scoped fiber-local state alone. This makes tracer configuration behave more predictably across normal tasks, threads, and fibers that share the same provider instance.Add
LLM::Provider#with_tracerfor scoped overrides
Addwith_traceras the opt-in escape hatch for request- or turn-scoped tracer overrides. Use it when you want temporary tracing on the current fiber without replacing the provider's default tracer.Trace concurrent tool calls outside ractors
Make tool tracing fire correctly when functions run through:thread,:task, or:fiberconcurrency. Experimental:ractorexecution still does not emit tool tracer events.Trace streamed tool calls, including MCP tools
Bind stream metadata throughLLM::Stream#extraso streamed tool calls inherit tracer and model context before they are handed toon_tool_call. This restores tool tracing for streamed MCP and local tool execution.Support symbol-based ORM option hooks
Letprovider:,context:, andtracer:on the Sequel plugin and the ActiveRecordacts_as_llm/acts_as_agentwrappers resolve through model method names as well as procs.Add experimental ractor tool concurrency
Add:ractorsupport toLLM::Function#spawn,LLM::Function::Array#wait,LLM::Stream#wait, andLLM::Agent.concurrencyso class-based tools with ractor-safe arguments and return values can run in Ruby ractors and report their results back into the normal LLM tool-return path. MCP tools are not supported by the current:ractormode, but mixed workloads can still branch ontool.mcp?and choose a supported strategy per tool.:ractoris especially useful for CPU-bound tools, while:task,:fiber, or:threadmay be a better fit for I/O-bound work.
v4.17.0
Changes since v4.16.1.
This release expands agent support across llm.rb. It brings LLM::Agent
closer to LLM::Context, adds configurable automatic tool concurrency
including experimental ractor support for class-based tools,
extends persisted ORM wrappers with more of the context runtime surface and
tracer hooks, and introduces built-in ActiveRecord agent persistence through
acts_as_agent.
Change
Add configurable tool concurrency to
LLM::Agent
Add the class-levelconcurrencyDSL toLLM::Agentso automatic tool loops can run with:call,:thread,:task,:fiber, or experimental:ractorsupport for class-based tools instead of always executing sequentially.Bring
LLM::Agentcloser toLLM::Context
ExpandLLM::Agentso it exposes more of the same runtime surface asLLM::Context, including returns, interruption, mode, cost, context window, structured serialization, and other context-backed helpers, while still auto-managing tool loops.Refresh agent docs and coverage
Update the README and deep dive to explain the current role ofLLM::Agent, add examples that show automatic tool execution and concurrency, and add focused specs for the expanded agent surface and tool-loop behavior.Add ORM tracer hooks for persisted contexts
Addtracer:to both the Sequel plugin andacts_as_llmso models can resolve and assign tracers onto the provider used by their persistedLLM::Context.Bring persisted ORM wrappers closer to
LLM::Context
Expand both the Sequel plugin andacts_as_llmso record-backed contexts expose more of the same runtime surface asLLM::Context, including mode, returns, interruption, prompt helpers, file helpers, and tracer access.Add ActiveRecord agent persistence with
acts_as_agent
Addacts_as_agentfor ActiveRecord models that should wrapLLM::Agent, reusing the same record-backed runtime shape asacts_as_llmwhile letting tool execution be managed by the agent.
v4.16.1
Changes since v4.16.0.
This release tightens ORM persistence by removing an unnecessary JSON
round-trip when restoring structured :json and :jsonb context
payloads.
Change
- Restore structured ORM payloads directly
TeachLLM::Context#restoreto accept parsed data payloads and use that path from the ActiveRecord and Sequel persistence wrappers forformat: :jsonand:jsonb, avoiding a redundantHash -> JSON string -> Hashround-trip on restore.
v4.16.0
Changes since v4.15.0.
This release expands ORM support with built-in ActiveRecord persistence and improves compatibility with OpenAI-compatible gateways, proxies, and self-hosted servers that use non-standard API root paths.
Change
Support OpenAI-compatible base paths
Addbase_path:to provider configuration so OpenAI-compatible endpoints can vary both host and API prefix. This supports providers, proxies, and gateways that keep OpenAI request shapes but use non-standard URL layouts such as DeepInfra's/v1/openai/....Add ActiveRecord context persistence with
acts_as_llm
Add a built-in ActiveRecord wrapper that mirrors the Sequel plugin API so applications can persistLLM::Contextstate on records with default columns, provider/context hooks, validation-backed writes, andformat: :string,:json, or:jsonbstorage.
v4.15.0
Changes since v4.14.0.
Change
Reduce OpenAI stream parser merge overhead
Special-case the most common single-field deltas, streamline incremental tool-call merging, and avoid repeated JSON parse attempts until streamed tool arguments look complete.Cache streaming callback capabilities in parsers
Cache callback support checks once at parser initialization time in the OpenAI, OpenAI Responses, Anthropic, Google, and Ollama stream parsers instead of repeatingrespond_to?checks on hot streaming paths.Reduce OpenAI Responses parser lookup overhead
Special-case the hot Responses API event paths and cache the current output item and content part so streamed output text deltas do less repeated nested lookup work.Add a Sequel context persistence plugin
Addplugin :llmfor Sequel models so apps can persistLLM::Contextstate with default columns and pass provider setup throughprovider:when needed. The plugin now also supportsformat: :string,:json, or:jsonbfor text and native JSON storage when Sequel JSON typecasting is enabled.Improve streaming parser performance
In the local replay-basedstream_parserbenchmark versusv4.14.0(median of 20 samples, 5000 iterations), plain Ruby is a small overall win: the generic eventstream path is about 0.4% faster, the OpenAI stream parser is about 0.5% faster, and the OpenAI Responses parser is about 1.6% faster, with unchanged allocations. Under YJIT on the same benchmark harness, the generic eventstream path is about 0.9% faster and the OpenAI stream parser is about 0.4% faster, while the OpenAI Responses parser is about 0.7% slower, also with unchanged allocations.
Compared to v4.13.0, the larger v4.14.0 streaming gains still
hold. The generic eventstream path remains dramatically faster than
v4.13.0, the OpenAI stream parser remains modestly faster, and the
OpenAI Responses parser is roughly flat to slightly better depending
on runtime. In other words, current keeps the large eventstream win
from v4.14.0, adds only small incremental changes beyond that, and
does not turn the post-v4.14.0 parser work into another large
benchmark jump.
v4.14.0
Changes since v4.13.0.
This release adds request interruption for contexts, reworks provider HTTP internals for lower-overhead streaming, and fixes MCP clients so parallel tool calls can safely share one connection.
Add
- Add request interruption support
AddLLM::Context#interrupt!,LLM::Context#cancel!, andLLM::Interruptfor interrupting in-flight provider requests, inspired by Go's context cancellation.
Change
Rework provider HTTP transport internals
Rework provider HTTP aroundLLM::Provider::Transport::HTTPwith explicit transient and persistent transport handling.Reduce SSE parser overhead
Dispatch raw parsed values to registered visitors instead of building anEventobject for every streamed line.Reduce provider streaming allocations
Decode streamed provider payloads directly inLLM::Provider::Transport::HTTPbefore handing them to provider parsers, which cuts allocation churn and gives a small streaming speed bump.Reduce generic SSE parser allocations
Keep unread event-stream buffer data in place until compaction is worthwhile, which lowers allocation churn in the remaining generic SSE path.Improve streaming parser performance
In the local replay-basedstream_parserbenchmark versusv4.13.0(median of 20 samples, 5000 iterations): Plain Ruby: the generic eventstream path is about 53% faster with about 32% fewer allocations, the OpenAI stream parser is about 11% faster with about 4% fewer allocations, and the OpenAI Responses parser is about 3% faster with unchanged allocations. YJIT on the current parser benchmark harness: the current tree is about 26% faster than non-YJIT on the generic eventstream path, about 18% faster on the OpenAI stream parser, and about 16% faster on the OpenAI Responses parser, with allocations unchanged.
Fix
Support parallel MCP tool calls on one client
Route MCP responses by JSON-RPC id so concurrent tool calls can share one client and transport without mismatching replies.Use explicit MCP non-blocking read errors
UseIO::EAGAINWaitReadablewhile continuing to retry onIO::WaitReadable.
v4.13.0
Changes since v4.12.0.
This release expands MCP prompt support, improves reasoning support in the OpenAI Responses API, and refreshes the docs around llm.rb's runtime model, contexts, and advanced workflows.
Add
- Add
LLM::MCP#promptsandLLM::MCP#find_promptfor MCP prompt support.
Change
- Rework the README around llm.rb as a runtime for AI systems.
- Add a dedicated deep dive guide for providers, contexts, persistence, tools, agents, MCP, tracing, multimodal prompts, and retrieval.
Fix
All of these fixes apply to MCP:
- fix(mcp): raise
LLM::MCP::MismatchErroron mismatched response ids. - fix(mcp): normalize prompt message content while preserving the original payload.
All of these fixes apply to OpenAI's Responses API:
- fix(openai): emit
on_reasoning_contentfor streamed reasoning summaries. - fix(openai): skip
previous_response_idonstore: falsefollow-up calls. - fix(openai): fall back to an empty object schema for tools without params.
- fix(openai): preserve original tool-call payloads on re-sent assistant tool messages.
- fix(openai): emit
output_textfor assistant-authored response content. - fix(openai): return
nilforsystem_fingerprinton normalized response objects.
v4.12.0
Changes since v4.11.1.
This release expands advanced streaming and MCP execution while reframing llm.rb more clearly as a system integration layer for LLMs, tools, MCP sources, and application APIs.
Add
- Add
persistentas an alias forpersist!on providers and MCP transports. - Add
LLM::Stream#on_tool_returnfor observing completed streamed tool work. - Add
LLM::Function::Return#error?.
Change
- Expect advanced streaming callbacks to use
LLM::Streamsubclasses instead of duck-typing them onto arbitrary objects. Basic#<<streaming remains supported.
Fix
- Fix Anthropic tools without params by always emitting
input_schema. - Fix Anthropic tool-only responses to still produce an assistant message.
- Fix Anthropic tool results to use the
userrole. - Fix Anthropic tool input normalization.
v4.11.1
Changes since v4.11.0.
Fix
- Cast OpenTelemetry tool-related values to strings.
Otherwise they're rejected by opentelemetry-sdk as invalid attributes.
v4.11.0
Changes since v4.10.0.
Add
- Add
LLM::Streamfor richer streaming callbacks, includingon_content,on_reasoning_content, andon_tool_callfor concurrent tool execution. - Add
LLM::Stream#waitas a shortcut forqueue.wait. - Add
LLM::Context#waitas a shortcut for the configured stream'swait. - Add
LLM::Context#call(:functions)as a shortcut forfunctions.call. - Add
LLM::Function.registryand enhanced support for MCP tools inLLM::Tool.registryfor tool resolution during streaming. - Add normalized
LLM::Responsefor OpenAI Responses, providingcontent,content!,messages/choices,usage, andreasoning_content. - Add
mode: :responsestoLLM::Contextfor routingtalkthrough the Responses API. - Add
LLM::Context#returnsfor collecting pending tool returns from the context. - Add persistent HTTP connection pooling for repeated MCP tool calls via
LLM.mcp(http: ...).persist!. - Add explicit MCP transport constructors via
LLM::MCP.stdio(...)andLLM::MCP.http(...).
Fix
- Fix Google tool-call handling by synthesizing stable ids when Gemini does not provide a direct tool-call id.
v4.10.0
Changes since v4.9.0.
Add
- Add HTTP transport for MCP with
LLM::MCP::Transport::HTTPfor remote servers - Add JSON Schema union types (
any_of,all_of,one_of) with parser integration - Add JSON Schema type array union support (e.g.,
"type": ["object", "null"]) - Add JSON Schema type inference from
const,enum, ordefaultfields
Change
- Update
LLM::MCPconstructor for exclusivehttp:orstdio:transport - Update
LLM::MCPdocumentation for HTTP transport support
v4.9.0
Changes since v4.8.0.
Add
- Add fiber-based concurrency with
LLM::Function::FiberGroupandLLM::Function::TaskGroupclasses for lightweight async execution. - Add
:thread,:task, and:fiberstrategy parameter toLLM::Function#spawnfor explicit concurrency control. - Add stdio MCP client support, including remote tool discovery and
invocation through
LLM.mcp,LLM::Context, and existing function/tool APIs. - Add model registry support via
LLM::Registry, including model metadata lookup, pricing, modalities, limits, and cost estimation. - Add context access to a model context window via
LLM::Context#context_window. - Add tracking of defined tools in the tool registry.
- Add
LLM::Schema::Enum, enablingEnum[...]as a schema/tool parameter type. - Add top-level Anthropic system instruction support using Anthropic's provider-specific request format.
- Add richer tracing hooks and extra metadata support for LangSmith/OpenTelemetry-style traces.
- Add rack/websocket and Relay-related example work, including MCP-focused examples.
- Add concurrent tool execution with
LLM::Function#spawn,LLM::Function::Array(call,wait,spawn), andLLM::Function::ThreadGroup. - Add
LLM::Function::ThreadGroup#alive?method for non-blocking monitoring of concurrent tool execution. - Add
LLM::Function::ThreadGroup#valuealias forThreadGroup#waitfor consistency with Ruby'sThread#value.
Change
- Rename
LLM::SessiontoLLM::Contextthroughout the codebase to better reflect the concept of a stateful interaction environment. - Rename
LLM::GeminitoLLM::Googleto better reflect provider naming. - Standardize model objects across providers around a smaller common interface.
- Switch registry cost internals from
LLM::EstimatetoLLM::Cost. - Update image generation defaults so OpenAI and xAI consistently return base64-encoded image data by default.
- Update
LLM::Botdeprecation warning from v5.0 to v6.0, giving users more time to migrate toLLM::Context. - Rework the README and screencast documentation to better cover MCP, registry, contexts, prompts, concurrency, providers, and example flow.
- Expand the README with architecture, production, and provider guidance while improving readability and example ordering.
Fix
- Fix local schema
$refresolution inLLM::Schema::Parser. - Fix multiple MCP issues around stdio env handling, request IDs, registry interaction, tool registration, and filtering of MCP tools from the standard tool registry.
- Fix stream parsing issues, including chunk-splitting bugs and safer handling of streamed error responses.
- Fix prompt handling across contexts, agents, and provider adapters so prompt turns remain consistent in history and completions.
- Fix several tool/context issues, including function return wrapping, tool lookup after deserialization, unnamed subclass filtering, and thread-safety around tool registry mutations.
- Fix Google tool-call handling to preserve
thoughtSignature. - Fix
LLM::Tracer::Loggerargument handling. - Fix packaging/docs issues such as registry files in the gemspec and stale provider docs.
- Fix Google provider handling of
nilfunction IDs during context deserialization. - Fix MCP stdio transport by increasing poll timeout for better reliability.
- Fix Google provider to properly cast non-Hash tool results into Hash format for API compatibility.
- Fix schema parser to support recursive normalization of
Array,LLM::Object, and nested structures. - Fix DeepSeek provider to tolerate malformed tool arguments.
- Fix
LLM::Function::TaskGroup#alive?to properly delegate toAsync::Task#alive?. - Fix various RuboCop errors across the codebase.
- Fix DeepSeek provider to handle JSON that might be valid but unexpected.
Notes
Notable merged work in this range includes:
feat(function): add fiber-based concurrency for async environments (#64)feat(mcp): add stdio MCP support (#134)Add LLM::Registry + cost support (#133)Consistent model objects across providers (#131)Add rack + websocket example (#130)feat(gemspec): add changelog URI (#136)feat(function): alias ThreadGroup#wait as ThreadGroup#value (#62)README and screencast refresh across#66,#68,#71, and#72`chore(bot): update deprecation warning from v5.0 to v6.0fix(deepseek): tolerate malformed tool argumentsrefactor(context): Rename Session as Context (#70)
Comparison base:
- Latest tag:
v4.8.0(6468f2426ee125823b7ae43b4af507b125f96ffc) - HEAD used for this changelog:
915c48da6fda9bef1554ff613947a6ce26d382e3