Changelog
r.uby.dev project
This file covers the v19 series.
Older releases:
- changelog/v18.md
- changelog/v17.md
- changelog/v16.md
- changelog/v15.md
- changelog/v14.md
- changelog/v13.md
- changelog/v12.md
What's next
No unreleased changes yet. Check back after the next release.
v19.0.1
Changes since v19.0.0.
This is a patch release. It fixes the :ractor strategy, which
called #new on the LLM::Tool instance given to
LLM::Function#task, and it makes llm.rb eval resolve a
relative path from the working directory. It also defaults the
OpenAI images model: to gpt-image-2.6-flare, ahead of the
gpt-image-1-mini retirement.
LLM::OpenAI
- openai: images default to
gpt-image-2.6-flare
LLM::OpenAI::Images#createandLLM::OpenAI::Images#editdefault themodel:parameter togpt-image-2.6-flarenow, where they defaulted it togpt-image-1-mini. OpenAI removesgpt-image-1-minifrom its API on December 1st, 2026. Amodel:that is given is sent unchanged.
LLM::Function
- function: the
:ractorstrategy runs aLLM::Toolinstance
LLM::Function#taskruns theLLM::Toolinstance it is given under the:ractorstrategy, where it called#newon that instance and raisedNoMethodError. An instance that takes an argument in#initializeruns with the state it was given.
Console
- console:
llm.rb evalresolves a relative path from the working directory
llm.rb evaljoins a relative path withDir.getwdnow, where it joined it with the root of the llm.rb checkout, so the path is read relative to the directory the command runs in.
v19.0.0
Changes since v18.0.0.
This is a smaller release than v18. It adds the LLM::Eval
framework and the llm.rb eval command that runs it,
LLM::Command, LLM::Paint, and a few tools, and it carries
breaking changes to LLM::Tracer::PrettyLogger,
LLM::Tool::Exec#call and LLM::Tool::Utils::Process#spawn.
Breaking
Migration
| Old | New |
|---|---|
LLM::Tool::Exec#call(arguments: ["echo", "hi"]) |
LLM::Tool::Exec#call(command: "echo", arguments: ["hi"]) |
LLM::Tool::Utils::Process#spawn(name:) |
LLM::Tool::Utils::Process#spawn(command:) |
LLM::Tracer::PrettyLogger |
LLM::Tracer::EnhancedLogger |
LLM::Tracer.pretty_logger, and LLM::Tracer.pretty |
LLM::Tracer.enhanced_logger, and LLM::Tracer.enhanced |
LLM::Command, an alias for LLM::Console::Command |
LLM::Command spawns a command, and LLM::Console::Command is spelled out |
LLM::Tool::Utils::Process::Command, and the test-cmd.rb gem |
LLM::Command, which ships with llm.rb |
LLM
- core:
LLM.json.dumptakes apretty:option
LLM.json.dumpdumps an object withJSON.pretty_generatewhen it is givenpretty: true, so the string it returns is indented and read by a person. Without the option it dumps withJSON.dump, the way it did before. TheLLM::JSONAdapter::Ojadapter takes no such option.
LLM::Eval
eval: add
LLM::Eval
LLM::Evalscores a model against a task the way a unit test scores code. An eval is a directory that holds aneval.rbfile and one or more step files, and the file declares a name and a description withset, the prompts the agent is given withstep, the state it runs against withsetupandteardown, the agent, tracer and intercept it runs with throughconfigure, and the score withscoreandpenalize!.LLM::Eval#callsends each step to the agent, runs the score block, subtracts each penalty, and returns{name:, score:}. A score is a float between 0.0 and 1.0.eval: add
LLM::Eval.from
LLM::Eval.fromreads aneval.rbfile, evaluates it, and binds the eval it returns to a provider, a model, and a sample number, so the same eval can be run against each of them.eval: aggregate samples into a run report
When an eval is given a tracer, every sample it runs is added toruns/<eval>/<provider>/<model>/run.json, which holds each sample beside the average score of them all, andrun.txtbeside it holds the same as a table. A sample that runs a second time replaces the entry it wrote before, so a sample number can be repeated without counting twice.eval: add
LLM::Eval::Tracer::Console
LLM::Eval::Tracer::Consoledraws an eval as it runs: a line for each request, each tool call, each error, and each interrupt, and a summary on exit with the number of turns, requests, tool calls and tokens, the slowest call, the score and each penalty. The summary is written toruns/<eval>/<provider>/<model>/<sample>/sample.txtwithout the escape sequences it draws with.eval: add
LLM::Eval::Tracer::JSON
LLM::Eval::Tracer::JSONrecords an eval as JSON: each chat request with its model, duration, tokens, reply and reasoning, each tool call with its arguments and duration, every error, and the score and its penalties. The report is written toruns/<eval>/<provider>/<model>/<sample>/sample.jsonon exit, as indented JSON.
LLM::Command
- command: add
LLM::Command
LLM::Commandspawns a command without a shell and collects its streams.#env,#stdinand#limitconfigure it,#spawnstarts it,#stdoutand#stderrreturn what it wrote, and#status,#exit_statusand#success?report how it ended. A command that is not on the system is reported by#not_found?, and#kill!sends a running commandSIGKILL. The class was imported from thetest-cmd.rbgem, which llm.rb depends on no longer.
LLM::Tool
tools: add a
git-ls-filestool
LLM::Tool::Git::LsFiles(git-ls-files) lists the files git tracks, the index beside the changes thatgit-statusreports. Like the other git tools it takes an optionalpath:, which narrows the list, and an optionaltimeout:.tools: add a
git-pulltool
LLM::Tool::Git::Pull(git-pull) pulls the changes of a remote branch into the current branch. It takes an optionalremote:, an optionalbranch:, which requires a remote, and an optionaltimeout:.tools: add
LLM::Tool::DirTree
LLM::Tool::DirTree(dir-tree) returns the contents of a directory as a nested hash: a directory is a hash of its entries and a file isnil. It takes thepath:to walk and an optionaldepth:, which is five levels by default, and it raises when the path does not exist.tools: the git tools take a
cwd:
Each tool thatLLM::Tool::Gitprovides takes an optionalcwd:, which names the repository to run in. The command is built asgit -C <cwd>rather than run from the process's working directory, so a tool call can reach a repository outside the one the process was started in, and two tool calls that run at the same time can reach different repositories. It isDir.getwdby default, and the superclass declares the parameter, so every git tool has it.tools:
LLM::Tool::Exec#calltakes a command and its arguments apart
LLM::Tool::Exec#calltakes the command to run ascommand:and its arguments asarguments:, wherearguments:alone took the command and its arguments as one array. The parameter the model is given is split the same way, so a model passes acommandand, separately, anargumentsarray, andcommandis the required one.tools:
LLM::Tool::Utils::Process#spawntakescommand:
LLM::Tool::Utils::Process#spawntakes the command to run ascommand:, where it took it asname:, so the keyword matches the oneLLM::Tool::Exec#calltakes. A tool of your own that spawns a command passescommand:now.
LLM::Function
function:
LLM::Function#task(:ractor)accepts a tool instance
LLM::Function#taskaccepts an instance ofLLM::Toolwhen it is given the:ractorstrategy, where it took a class alone and raisedLLM::RactorErrorfor an instance. A runner that is neither a class nor an instance ofLLM::Toolstill raisesLLM::RactorError.function: an unknown tool returns its error in band under
:ractor
A tool call for a tool the runtime does not know is turned into an anonymous subclass ofLLM::Toolwhose#callraisesLLM::NoSuchToolError, and the runtime returns that error to the model in band, the way the other strategies do. The tool is a class rather than a proc, so the:ractorstrategy runs it, where it raisedLLM::RactorErrorbefore.function: the
LLM::RactorErrornames the function and its runner
TheLLM::RactorErrorthatLLM::Function#taskraises for a runner the:ractorstrategy cannot use names the function and inspects the runner, where its message was one generic line.
Console
console: add
llm.rb eval
llm.rb eval -p <provider> [-m <model>] [-n <samples>] <path...>runs the evals it is given, and it hands off tolibexec/llm-eval. The provider is built from-p, which is required, the model is taken from-mor from the provider'sdefault_model, and each eval is run-ntimes, where-nis 1 by default. A relative path is resolved against the root of the llm.rb checkout.console:
models.dev:downloadisupdate-data
The command that refreshes thedata/directory from models.dev isllm.rb update-datanow, andllm.rb models.dev:downloadis still accepted.
LLM::Paint
paint: add
LLM::Paint
LLM::Paintwraps a string in the escape sequences a terminal understands, egLLM::Paint["hello", :blue, :bold], and returns the string unchanged when color is disabled. It honors$NO_COLOR, it is a small stand-in for thepaintgem, and it is what the eval console tracer uses.paint: add
LLM::Paint::IO
LLM::Paint::IOwraps an IO so that the text written to it is wrapped in the escape sequences a terminal understands, and written unchanged when color is disabled.LLM::Paint::IO#writewrites text with the colors and effects it is given,LLM::Paint::IO#putswrites it with a newline,LLM::Paint::IO#color?returns true when the wrapper writes color,LLM::Paint::IO#color=sets it, andLLM::Paint::IO#ioreturns the IO that is wrapped.paint:
LLM::Paint.detecttakes an IO
LLM::Paint.detecttakes the IO to inspect as an argument now, where it inspected$stdoutalone. It still returns false when$NO_COLORis set, and it returns false for an IO that does not respond totty?.
LLM::Tracer
tracer: rename
LLM::Tracer::PrettyLoggerasLLM::Tracer::EnhancedLogger
LLM::Tracer::EnhancedLoggeris the tracer thatLLM::Tracer::PrettyLoggerwas.LLM::Tracer.enhanced_loggerbuilds it,LLM::Tracer.enhancedis its alias, and both replaceLLM::Tracer.pretty_loggerandLLM::Tracer.pretty.tracer: the enhanced logger draws a readable log
A chat request, a tool call and an interrupt are each a line of their own, with a marker that names the event, a duration for a request and a tool call, and the tokens a request used. The timestamp, and the comma separated fields it prefixed, are gone.tracer: the enhanced logger writes a summary on exit
LLM::Tracer::EnhancedLogger#on_exitwrites the provider, the model, the number of requests, the tool calls with a breakdown by name, the tokens returned in and out, the cost the registry prices them at, the slowest tool call, the errors, and the elapsed time. It is written only when the tracer saw a request or a tool call, and it closes the file the tracer opened.tracer: the enhanced logger writes color
LLM::Tracer::EnhancedLoggerwrites through anLLM::Paint::IO, so its log is colored when the IO it writes to is a terminal, and plain when it is not, when$NO_COLORis set, or whenLLM::Paint.enabledis false.