Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions lib/ruby_llm/providers/gemini/chat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def parse_completion_response(response)

Message.new(
role: :assistant,
content: extract_text_parts(parts) || parse_content(data),
content: parse_content(data),
thinking: Thinking.build(
text: extract_thought_parts(parts),
signature: extract_thought_signature(parts)
Expand Down Expand Up @@ -140,21 +140,13 @@ def parse_content(data)
candidate = data.dig('candidates', 0)
return '' unless candidate

return '' if function_call?(candidate)

parts = candidate.dig('content', 'parts')
return '' unless parts&.any?

non_thought_parts = parts.reject { |part| part['thought'] }
return '' unless non_thought_parts.any?

build_response_content(non_thought_parts)
end
content_parts = parts.reject { |part| part['thought'] || part['functionCall'] }
return '' unless content_parts.any?

def extract_text_parts(parts)
text_parts = parts.reject { |p| p['thought'] }
content = text_parts.filter_map { |p| p['text'] }.join
content.empty? ? nil : content
build_response_content(content_parts)
end

def extract_thought_parts(parts)
Expand Down
6 changes: 5 additions & 1 deletion lib/ruby_llm/providers/gemini/media.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ def build_response_content(parts) # rubocop:disable Metrics/PerceivedComplexity
text = nil if text.empty?
return text if attachments.empty?

Content.new(text:, attachments:)
Content.new(text).tap do |content|
attachments.each do |attachment|
content.add_attachment(attachment.source, filename: attachment.filename)
end
end
end

def build_inline_attachment(inline_data, index)
Expand Down
43 changes: 43 additions & 0 deletions spec/ruby_llm/providers/gemini/chat_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,49 @@
expect(message.output_tokens).to eq(8)
expect(message.cached_tokens).to eq(21)
end

it 'handles message where both text and attachments are present' do
raw_data = 'fake-image-bytes'
encoded_data = Base64.strict_encode64(raw_data)
response = Struct.new(:body, :env).new(
{
'candidates' => [
{
'content' => {
'parts' => [
{
'functionCall' => {
'name' => 'lookup_weather',
'args' => { 'city' => 'Paris' }
}
},
{ 'text' => 'Here is the result with an image.' },
{
'inlineData' => {
'mimeType' => 'image/png',
'data' => encoded_data
}
}
]
}
}
],
'usageMetadata' => {}
},
Struct.new(:url).new(Struct.new(:path).new('/v1/models/gemini-2.5-flash-image:generateContent'))
)

provider = RubyLLM::Providers::Gemini.new(RubyLLM.config)
message = provider.send(:parse_completion_response, response)
attachment = message.content.attachments.first

expect(message.content).to be_a(RubyLLM::Content)
expect(message.content.text).to eq('Here is the result with an image.')
expect(message.content.attachments.size).to eq(1)
expect(attachment).to be_a(RubyLLM::Attachment)
expect(attachment.mime_type).to eq('image/png')
expect(attachment.content).to eq(raw_data)
end
end

it 'correctly sums candidatesTokenCount and thoughtsTokenCount' do
Expand Down