Skip to content

Commit 4371a1b

Browse files
committed
Handle empty tool results across providers
1 parent 6c6ccda commit 4371a1b

6 files changed

Lines changed: 77 additions & 14 deletions

File tree

lib/ruby_llm/providers/anthropic/tools.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ def format_tool_use_block(tool_call)
4545
end
4646

4747
def format_tool_result_block(msg)
48+
content = msg.content
49+
content = '(no output)' if content.nil? || (content.respond_to?(:empty?) && content.empty?)
50+
4851
{
4952
type: 'tool_result',
5053
tool_use_id: msg.tool_call_id,
51-
content: Media.format_content(msg.content)
54+
content: Media.format_content(content)
5255
}
5356
end
5457

lib/ruby_llm/providers/bedrock/chat.rb

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -154,19 +154,23 @@ def render_tool_result_block(msg)
154154

155155
def render_tool_result_content(content)
156156
return render_raw_tool_result_content(content.value) if content.is_a?(RubyLLM::Content::Raw)
157+
return [{ json: content }] if content.is_a?(Hash) || content.is_a?(Array)
158+
return render_content_tool_result_content(content) if content.is_a?(RubyLLM::Content)
157159

158-
if content.is_a?(Hash) || content.is_a?(Array)
159-
[{ json: content }]
160-
elsif content.is_a?(RubyLLM::Content)
161-
blocks = []
162-
blocks << { text: content.text } if content.text
163-
content.attachments.each do |attachment|
164-
blocks << { text: attachment.for_llm }
165-
end
166-
blocks
167-
else
168-
[{ text: content.to_s }]
169-
end
160+
[text_tool_result_block(content)]
161+
end
162+
163+
def render_content_tool_result_content(content)
164+
blocks = []
165+
blocks << text_tool_result_block(content.text) unless content.text.to_s.empty?
166+
content.attachments.each { |attachment| blocks << text_tool_result_block(attachment.for_llm) }
167+
blocks.empty? ? [text_tool_result_block(nil)] : blocks
168+
end
169+
170+
def text_tool_result_block(text)
171+
text = text.to_s
172+
text = '(no output)' if text.empty?
173+
{ text: text }
170174
end
171175

172176
def render_raw_tool_result_content(raw_value)

lib/ruby_llm/providers/gemini/tools.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ def format_tool_call(msg) # rubocop:disable Metrics/PerceivedComplexity
4646

4747
def format_tool_result(msg, function_name = nil)
4848
function_name ||= msg.tool_call_id
49+
content = msg.content
50+
content = '(no output)' if content.nil? || (content.respond_to?(:empty?) && content.empty?)
4951

5052
[{
5153
functionResponse: {
5254
name: function_name,
5355
response: {
5456
name: function_name,
55-
content: Media.format_content(msg.content)
57+
content: Media.format_content(content)
5658
}
5759
}
5860
}]

spec/ruby_llm/providers/anthropic/tools_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,30 @@
164164
]
165165
})
166166
end
167+
168+
it 'uses a placeholder when the tool returns no content' do
169+
msg = instance_double(RubyLLM::Message,
170+
tool_call_id: 'tool_123',
171+
content: '')
172+
173+
result = tools.format_tool_result(msg)
174+
175+
expect(result).to eq({
176+
role: 'user',
177+
content: [
178+
{
179+
type: 'tool_result',
180+
tool_use_id: 'tool_123',
181+
content: [
182+
{
183+
type: 'text',
184+
text: '(no output)'
185+
}
186+
]
187+
}
188+
]
189+
})
190+
end
167191
end
168192

169193
describe '.parse_tool_calls' do

spec/ruby_llm/providers/bedrock/chat_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
require 'spec_helper'
44

55
RSpec.describe RubyLLM::Providers::Bedrock::Chat do
6+
describe '.render_tool_result_content' do
7+
it 'uses a placeholder when the tool returns no content' do
8+
result = described_class.render_tool_result_content('')
9+
10+
expect(result).to eq([{ text: '(no output)' }])
11+
end
12+
end
13+
614
describe '.render_payload' do
715
let(:model) do
816
instance_double(RubyLLM::Model::Info,

spec/ruby_llm/providers/gemini/tools_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,27 @@
7272
}
7373
])
7474
end
75+
76+
it 'uses a placeholder when the tool returns no content' do
77+
message = RubyLLM::Message.new(
78+
role: :tool,
79+
content: '',
80+
tool_call_id: 'uuid-123'
81+
)
82+
83+
result = test_obj.format_tool_result(message)
84+
85+
expect(result).to eq([
86+
{
87+
functionResponse: {
88+
name: 'uuid-123',
89+
response: {
90+
name: 'uuid-123',
91+
content: [{ text: '(no output)' }]
92+
}
93+
}
94+
}
95+
])
96+
end
7597
end
7698
end

0 commit comments

Comments
 (0)