RubyLLM 1.16: concurrent tool execution, Rails-style instrumentation, and more

earcar1 pts0 comments

Release 1.16.0 · crmne/ruby_llm · GitHub

//releases/show" data-turbo-transient="true" />

Skip to content

Search or jump to...

Search code, repositories, users, issues, pull requests...

-->

Search

Clear

Search syntax tips

Provide feedback

--><br>We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Cancel

Submit feedback

Saved searches

Use saved searches to filter your results more quickly

-->

Name

Query

To see all available qualifiers, see our documentation.

Cancel

Create saved search

Sign in

//releases/show;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up

Appearance settings

Resetting focus

You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.

Dismiss alert

{{ message }}

crmne

ruby_llm

Public

Uh oh!

There was an error while loading. Please reload this page.

Notifications<br>You must be signed in to change notification settings

Fork<br>453

Star<br>4k

1.16.0

Latest

Latest

Compare

Choose a tag to compare

Sorry, something went wrong.

Filter

Loading

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

No results found

View all tags

crmne

released this

09 Jun 11:14

1.16.0

2cf34b9

This commit was signed with the committer’s verified signature .

crmne<br>Carmine Paolino

SSH Key Fingerprint: 0XaNsf6l/KlR9SjsIVBprJm8G1HG++OpKhtntZMNflo<br>Verified

Learn about vigilant mode.

RubyLLM 1.16: Concurrent Tool Execution + Instrumentation + Proxies Everywhere

RubyLLM 1.16 makes your tools run concurrently in threads or fibers, makes RubyLLM observable without monkey patching, and lets every native provider sit behind a proxy.

Concurrent Tool Execution

RubyLLM.Concurrent.Tool.Executions.mp4

When a model returns multiple tool calls in one response, RubyLLM has always run them one at a time. Incredibly useful for I/O-bound tools like HTTP calls, database lookups, other LLM requests.

Turn it on for every chat from one place:

RubyLLM.configure do |config|<br>config.tool_concurrency = true # :threads, :fibers, true, or false<br>end

true uses Ruby threads and needs no extra dependencies. :fibers mode uses the optional async gem.

You can also override it per PORO or Rails chat record, when a particular conversation needs different behaviour:

chat.with_tools(Weather, StockPrice, Currency, concurrency: true)<br>chat.with_tools(Weather, StockPrice, Currency, concurrency: :threads)<br>chat.with_tools(Weather, StockPrice, Currency, concurrency: :fibers)<br>chat.with_tools(Weather, StockPrice, concurrency: false)<br>chat_record.with_tools(Weather, StockPrice, concurrency: :threads)

Inside Rails, each concurrent tool call runs wrapped in the Rails executor, so connection pools, CurrentAttributes, and reloading behave the way the rest of your app expects.

Streaming Results as They Finish

Concurrency doesn't make you wait for the slowest tool to start showing progress. Each tool result is added back to the conversation the moment that tool finishes, in completion order. RubyLLM still waits for every result before asking the model for its next response, but your callbacks and streaming UI see results stream in as they land instead of all at once at the end.

This way, simply adding

RubyLLM.configure do |config|<br>config.tool_concurrency = :fibers # or :threads<br>end

gives you and your users the best performance and user experience.

Rails-Style Instrumentation

RubyLLM now emits structured events for the work it does. No specific observability backend required.

In Rails, events flow through ActiveSupport::Notifications automatically. Subscribe the same way you'd subscribe to any framework event:

# config/initializers/ruby_llm_instrumentation.rb<br>ActiveSupport::Notifications.subscribe('chat.ruby_llm') do |_name, _start, _finish, _id, payload|<br>Rails.logger.info(<br>provider: payload[:provider],<br>model: payload[:model],<br>input_tokens: payload[:input_tokens],<br>output_tokens: payload[:output_tokens]<br>end

Outside Rails, point config.instrumenter at any object that responds to instrument(name, payload) { ... }. Wire it into OpenTelemetry, StatsD, or your own logger:

RubyLLM.configure do |config|<br>config.instrumenter = AppInstrumenter.new<br>end

The events RubyLLM emits:

request.ruby_llm: HTTP request metadata: provider, method, URL, status

chat.ruby_llm: chat completion metadata: model, provider, messages, response, token usage

tool_call.ruby_llm: tool name, arguments, result

embedding.ruby_llm: embedding model, input, result, token usage, vector dimensions

models.refresh.ruby_llm: model registry refresh metadata

Payloads carry the Ruby objects observability adapters need. Message content, tool arguments, and provider responses can be sensitive, so export or log those fields only when your application policy allows it. See the new...

rubyllm tool chat rails ruby_llm config

Related Articles