Skip to content

Commit bcce1d0

Browse files
committed
Add Chat#on_tool_call callback
This is useful when you want to show to the user tool calls as they are made, particularly when handling streamed output.
1 parent cccefcd commit bcce1d0

File tree

4 files changed

+246
-2
lines changed

4 files changed

+246
-2
lines changed

docs/guides/chat.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,11 @@ Refer to the [Working with Models Guide]({% link guides/models.md %}) for detail
464464

465465
## Chat Event Handlers
466466

467-
You can register blocks to be called when certain events occur during the chat lifecycle, useful for UI updates or logging.
467+
You can register blocks to be called when certain events occur during the chat lifecycle. This is particularly useful for UI updates, logging, analytics, or building real-time chat interfaces.
468+
469+
### Available Event Handlers
470+
471+
RubyLLM provides three event handlers that cover the complete chat lifecycle:
468472

469473
```ruby
470474
chat = RubyLLM.chat
@@ -483,6 +487,11 @@ chat.on_end_message do |message|
483487
end
484488
end
485489

490+
# Called when the AI decides to use a tool
491+
chat.on_tool_call do |tool_call|
492+
puts "AI is calling tool: #{tool_call.name} with arguments: #{tool_call.arguments}"
493+
end
494+
486495
# These callbacks work for both streaming and non-streaming requests
487496
chat.ask "What is metaprogramming in Ruby?"
488497
```

lib/ruby_llm/chat.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ def initialize(model: nil, provider: nil, assume_model_exists: false, context: n
2929
@schema = nil
3030
@on = {
3131
new_message: nil,
32-
end_message: nil
32+
end_message: nil,
33+
tool_call: nil
3334
}
3435
end
3536

@@ -112,6 +113,11 @@ def on_end_message(&block)
112113
self
113114
end
114115

116+
def on_tool_call(&block)
117+
@on[:tool_call] = block
118+
self
119+
end
120+
115121
def each(&)
116122
messages.each(&)
117123
end
@@ -181,6 +187,7 @@ def wrap_streaming_block(&block)
181187
def handle_tool_calls(response, &)
182188
response.tool_calls.each_value do |tool_call|
183189
@on[:new_message]&.call
190+
@on[:tool_call]&.call(tool_call)
184191
result = execute_tool tool_call
185192
message = add_message role: :tool, content: result.to_s, tool_call_id: tool_call.id
186193
@on[:end_message]&.call(message)

spec/fixtures/vcr_cassettes/chat_tool_call_callbacks_calls_on_tool_call_callback_when_tools_are_used.yml

Lines changed: 209 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/ruby_llm/chat_tools_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,25 @@ def execute
172172
end
173173
end
174174

175+
describe 'tool call callbacks' do
176+
it 'calls on_tool_call callback when tools are used' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations
177+
tool_calls_received = []
178+
179+
chat = RubyLLM.chat
180+
.with_tool(Weather)
181+
.on_tool_call { |tool_call| tool_calls_received << tool_call }
182+
183+
response = chat.ask("What's the weather in Berlin? (52.5200, 13.4050)")
184+
185+
expect(tool_calls_received).not_to be_empty
186+
expect(tool_calls_received.first).to respond_to(:name)
187+
expect(tool_calls_received.first).to respond_to(:arguments)
188+
expect(tool_calls_received.first.name).to eq('weather')
189+
expect(response.content).to include('15')
190+
expect(response.content).to include('10')
191+
end
192+
end
193+
175194
describe 'error handling' do
176195
it 'raises an error when tool execution fails' do # rubocop:disable RSpec/MultipleExpectations
177196
chat = RubyLLM.chat.with_tool(BrokenTool)

0 commit comments

Comments
 (0)