Module: RubyLLM::Protocols::ChatCompletions::Images

Defined in:
lib/ruby_llm/protocols/chat_completions/images.rb

Overview

Image generation methods for the OpenAI API integration

Class Method Summary collapse

Class Method Details

.attachments?(value) ⇒ Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 117

def attachments?(value)
  Array(value).any? { |item| !blank_attachment?(item) }
end

.blank_attachment?(value) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 121

def blank_attachment?(value)
  value.nil? || (value.is_a?(String) && value.strip.empty?)
end

.build_image_reference(source) ⇒ Object



82
83
84
85
86
87
88
89
90
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 82

def build_image_reference(source)
  attachment = Attachment.new(source, config: @config)
  return { file_id: attachment.provider_file_id } if attachment.provider_file?
  return { image_url: attachment.source.to_s } if attachment.url?

  raise UnsupportedAttachmentError, attachment.mime_type unless attachment.image?

  { image_url: attachment.for_llm }
end

.build_image_references(sources) ⇒ Object



74
75
76
77
78
79
80
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 74

def build_image_references(sources)
  Array(sources).filter_map do |source|
    next if blank_attachment?(source)

    build_image_reference(source)
  end
end

.build_upload_part(source) ⇒ Object



106
107
108
109
110
111
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 106

def build_upload_part(source)
  attachment = Attachment.new(source, config: @config)
  raise UnsupportedAttachmentError, attachment.mime_type unless attachment.image?

  Faraday::UploadIO.new(StringIO.new(attachment.content), attachment.mime_type, attachment.filename)
end

.build_upload_parts(sources) ⇒ Object



92
93
94
95
96
97
98
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 92

def build_upload_parts(sources)
  Array(sources).filter_map do |source|
    next if blank_attachment?(source)

    build_upload_part(source)
  end
end

.editing?(with, mask) ⇒ Boolean

Returns:

  • (Boolean)


113
114
115
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 113

def editing?(with, mask)
  attachments?(with) || !mask.nil?
end

.images_url(with: nil, mask: nil) ⇒ Object



13
14
15
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 13

def images_url(with: nil, mask: nil)
  editing?(with, mask) ? 'images/edits' : 'images/generations'
end

.json_image_references?(model) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 70

def json_image_references?(model)
  model.match?(/\A(gpt-image|chatgpt-image)/)
end

.parse_image_response(response, model:) ⇒ Object



29
30
31
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 29

def parse_image_response(response, model:)
  parse_image_responses(response, model:).first
end

.parse_image_responses(response, model:) ⇒ Object

Raises:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 33

def parse_image_responses(response, model:)
  data = response.body
  entries = Array(data['data'])

  raise Error, 'Unexpected response format from OpenAI image API' if entries.empty?

  entries.map.with_index do |image_data, index|
    Image.new(
      url: image_data['url'],
      mime_type: 'image/png', # DALL-E typically returns PNGs
      revised_prompt: image_data['revised_prompt'],
      model: model,
      data: image_data['b64_json'],
      usage: index.zero? ? (data['usage'] || {}) : {}
    )
  end
end

.render_edit_payload(prompt, model:, size:, with:, mask:, provider_options:, count: nil) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 57

def render_edit_payload(prompt, model:, size:, with:, mask:, provider_options:, count: nil)
  payload = { model: model, prompt: prompt, n: count || 1 }
  if json_image_references?(model)
    payload[:images] = build_image_references(with)
    payload[:mask] = build_image_reference(mask) if mask
    payload[:size] = size if size
  else
    payload[:image] = single_upload_part(build_upload_parts(with))
    payload[:mask] = build_upload_part(mask) if mask
  end
  payload.merge(provider_options)
end

.render_image_payload(prompt, model:, size:, count: nil, with: nil, mask: nil, provider_options: {}) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 17

def render_image_payload(prompt, model:, size:, count: nil, with: nil, mask: nil, provider_options: {})
  return render_edit_payload(prompt, model:, size:, count:, with:, mask:, provider_options:) if editing?(with,
                                                                                                         mask)

  {
    model: model,
    prompt: prompt,
    n: count || 1,
    size: size
  }.merge(provider_options)
end

.single_upload_part(parts) ⇒ Object

Retries only rewind upload parts at the top level of the payload, so a lone image rides there instead of inside an array.



102
103
104
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 102

def single_upload_part(parts)
  parts.one? ? parts.first : parts
end

.validate_paint_inputs!(with:, mask:) ⇒ Object

Raises:

  • (ArgumentError)


51
52
53
54
55
# File 'lib/ruby_llm/protocols/chat_completions/images.rb', line 51

def validate_paint_inputs!(with:, mask:)
  return unless editing?(with, mask)

  raise ArgumentError, 'with: is required when mask: is provided' if mask && !attachments?(with)
end