module RubyLLM::Providers::Gemini::Chat
def complete(messages, tools:, temperature:, model:, &block) # rubocop:disable Metrics/MethodLength
def complete(messages, tools:, temperature:, model:, &block) # rubocop:disable Metrics/MethodLength @model = model payload = { contents: format_messages(messages), generationConfig: { temperature: temperature } } payload[:tools] = format_tools(tools) if tools.any? # Store tools for use in generate_completion @tools = tools if block_given? stream_response payload, &block else sync_response payload end end
def completion_url
def completion_url "models/#{@model}:generateContent" end
def extract_content(data) # rubocop:disable Metrics/CyclomaticComplexity
def extract_content(data) # rubocop:disable Metrics/CyclomaticComplexity candidate = data.dig('candidates', 0) return '' unless candidate # Content will be empty for function calls return '' if function_call?(candidate) # Extract text content parts = candidate.dig('content', 'parts') text_parts = parts&.select { |p| p['text'] } return '' unless text_parts&.any? text_parts.map { |p| p['text'] }.join end
def format_messages(messages)
def format_messages(messages) messages.map do |msg| { role: format_role(msg.role), parts: format_parts(msg) } end end
def format_part(part) # rubocop:disable Metrics/MethodLength
def format_part(part) # rubocop:disable Metrics/MethodLength case part[:type] when 'text' { text: part[:text] } when 'image' Media.format_image(part) when 'pdf' Media.format_pdf(part) when 'audio' Media.format_audio(part) else { text: part.to_s } end end
def format_parts(msg) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
def format_parts(msg) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength if msg.tool_call? # Handle function calls [{ functionCall: { name: msg.tool_calls.values.first.name, args: msg.tool_calls.values.first.arguments } }] elsif msg.tool_result? # Handle function responses [{ functionResponse: { name: msg.tool_call_id, response: { name: msg.tool_call_id, content: msg.content } } }] elsif msg.content.is_a?(Array) # Handle multi-part content (text, images, etc.) msg.content.map { |part| format_part(part) } else # Simple text content [{ text: msg.content.to_s }] end end
def format_role(role)
def format_role(role) case role when :assistant then 'model' when :system, :tool then 'user' # Gemini doesn't have system, use user role, function responses use user role else role.to_s end end
def function_call?(candidate)
def function_call?(candidate) parts = candidate.dig('content', 'parts') parts&.any? { |p| p['functionCall'] } end
def parse_completion_response(response)
def parse_completion_response(response) data = response.body tool_calls = extract_tool_calls(data) Message.new( role: :assistant, content: extract_content(data), tool_calls: tool_calls, input_tokens: data.dig('usageMetadata', 'promptTokenCount'), output_tokens: data.dig('usageMetadata', 'candidatesTokenCount'), model_id: data['modelVersion'] || response.env.url.path.split('/')[3].split(':')[0] ) end