Class: RubyLLM::Attachment

Inherits:
Object
  • Object
show all
Includes:
Support::Inspectable
Defined in:
lib/ruby_llm/attachment.rb

Overview

An Attachment is a file sent to the model alongside a message. The source can be a local path, an http(s) URL, an IO-like object, an ActiveStorage object, or a provider-managed UploadedFile. Chat#ask builds attachments for you from its with: option:

chat.ask "What's in this image?", with: "ruby_conf.jpg"

Build one explicitly to return a file from a Tool:

RubyLLM::Attachment.new(doc.download_path)

Constant Summary collapse

DOCUMENT_EXTENSIONS =

File extensions recognized as document attachments when the MIME type alone is inconclusive.

%w[
  doc docx dot key numbers odp ods odt pages pot pps ppt pptx rtf xls xlsx
].freeze
ACTIVE_STORAGE_CLASS_NAMES =

:stopdoc:

%w[
  ActiveStorage::Blob
  ActiveStorage::Attachment
  ActiveStorage::Attached::One
  ActiveStorage::Attached::Many
].freeze

Constants included from Support::Inspectable

Support::Inspectable::TRUNCATE_AT

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Support::Inspectable

#full_inspect, #inspect, #pretty_print

Constructor Details

#initialize(source, filename: nil, config: nil) ⇒ Attachment

Creates an attachment from source: a file path, URL, IO-like object, ActiveStorage object, or UploadedFile. Derives the filename from the source when filename: is not given, then detects the MIME type.

RubyLLM::Attachment.new("diagram.png")
RubyLLM::Attachment.new(StringIO.new(data), filename: "report.pdf")

config: is the Configuration a URL source is downloaded with, and defaults to the global one.

Paths and URLs must be trusted and authorized by the application. They can be read or fetched during construction to detect the MIME type. Validate upload parameters before passing them here: an unchecked String can access local files or internal network endpoints.



75
76
77
78
79
80
81
82
# File 'lib/ruby_llm/attachment.rb', line 75

def initialize(source, filename: nil, config: nil)
  @config = config
  @source = source
  @source = source_type_cast
  @filename = filename || source_filename

  determine_mime_type
end

Instance Attribute Details

#filenameObject (readonly)

The filename given at construction or derived from the source. May be nil.



28
29
30
# File 'lib/ruby_llm/attachment.rb', line 28

def filename
  @filename
end

#mime_typeObject (readonly)

The detected MIME type string, such as "image/png".



31
32
33
# File 'lib/ruby_llm/attachment.rb', line 31

def mime_type
  @mime_type
end

#sourceObject (readonly)

The underlying source: a URI, Pathname, IO-like object, ActiveStorage object, or UploadedFile.



24
25
26
# File 'lib/ruby_llm/attachment.rb', line 24

def source
  @source
end

Class Method Details

.wrap(sources, config: nil) ⇒ Object

:startdoc:



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/ruby_llm/attachment.rb', line 48

def self.wrap(sources, config: nil) # :nodoc:
  case sources
  when nil then []
  when Hash then sources.values.flat_map { |group| wrap(group, config:) }
  else
    Support::Utils.to_safe_array(sources).filter_map do |source|
      next if source.nil? || (source.is_a?(String) && source.strip.empty?)

      source.is_a?(Attachment) ? source : new(source, config:)
    end
  end
end

Instance Method Details

#active_storage?Boolean

:nodoc:

Returns:

  • (Boolean)


119
120
121
# File 'lib/ruby_llm/attachment.rb', line 119

def active_storage? # :nodoc:
  ACTIVE_STORAGE_CLASS_NAMES.any? { |class_name| source_is_a?(class_name) }
end

#audio?Boolean

Returns whether the attachment is audio.

Returns:

  • (Boolean)


175
176
177
# File 'lib/ruby_llm/attachment.rb', line 175

def audio?
  RubyLLM::Files::MimeType.audio? mime_type
end

#byte_sizeObject

:nodoc:



217
218
219
# File 'lib/ruby_llm/attachment.rb', line 217

def byte_size # :nodoc:
  file_byte_size || loaded_byte_size || content_byte_size
end

#configObject

:nodoc:



84
85
86
# File 'lib/ruby_llm/attachment.rb', line 84

def config # :nodoc:
  @config || RubyLLM.config
end

#contentObject

Returns the raw bytes of the attachment, fetching or reading the source on the first call. Text content is returned as UTF-8.

Raises RubyLLM::Error if the attachment is a provider-managed file, which has no local content.



128
129
130
131
132
133
134
135
136
# File 'lib/ruby_llm/attachment.rb', line 128

def content
  if provider_file?
    raise Error, "Provider-managed file #{provider_file_id} cannot be read as inline attachment content"
  end

  load_content if !defined?(@content) || @content.nil?
  normalize_text_encoding
  @content
end

#document?Boolean

Returns whether the attachment is a non-PDF, non-text document format such as Word or Excel, judged by MIME type or file extension.

Returns:

  • (Boolean)


197
198
199
200
201
# File 'lib/ruby_llm/attachment.rb', line 197

def document?
  return false if pdf? || text?

  RubyLLM::Files::MimeType.document?(mime_type) || DOCUMENT_EXTENSIONS.include?(extension)
end

#encodedObject

:nodoc:



138
139
140
# File 'lib/ruby_llm/attachment.rb', line 138

def encoded # :nodoc:
  Base64.strict_encode64(content)
end

#extensionObject

:nodoc:



203
204
205
206
# File 'lib/ruby_llm/attachment.rb', line 203

def extension # :nodoc:
  extension = File.extname(filename.to_s).delete_prefix('.').downcase
  extension.empty? ? nil : extension
end

#for_llmObject

:nodoc:



142
143
144
145
146
147
148
149
# File 'lib/ruby_llm/attachment.rb', line 142

def for_llm # :nodoc:
  case type
  when :text
    "<file name='#{filename}' mime_type='#{mime_type}'>#{content}</file>"
  else
    "data:#{mime_type};base64,#{encoded}"
  end
end

#formatObject

:nodoc:



179
180
181
182
183
184
185
186
187
188
# File 'lib/ruby_llm/attachment.rb', line 179

def format # :nodoc:
  case mime_type
  when 'audio/mpeg'
    'mp3'
  when 'audio/wav', 'audio/wave', 'audio/x-wav'
    'wav'
  else
    mime_type.split('/').last
  end
end

#image?Boolean

Returns whether the attachment is an image.

Returns:

  • (Boolean)


165
166
167
# File 'lib/ruby_llm/attachment.rb', line 165

def image?
  RubyLLM::Files::MimeType.image? mime_type
end

#io_like?Boolean

:nodoc:

Returns:

  • (Boolean)


115
116
117
# File 'lib/ruby_llm/attachment.rb', line 115

def io_like? # :nodoc:
  @source.respond_to?(:read) && !path? && !active_storage? && !provider_file?
end

#path?Boolean

:nodoc:

Returns:

  • (Boolean)


111
112
113
# File 'lib/ruby_llm/attachment.rb', line 111

def path? # :nodoc:
  !provider_file? && (@source.is_a?(Pathname) || (@source.is_a?(String) && !url?))
end

#pdf?Boolean

Returns whether the attachment is a PDF.

Returns:

  • (Boolean)


191
192
193
# File 'lib/ruby_llm/attachment.rb', line 191

def pdf?
  RubyLLM::Files::MimeType.pdf? mime_type
end

#provider_file?Boolean

:nodoc:

Returns:

  • (Boolean)


92
93
94
# File 'lib/ruby_llm/attachment.rb', line 92

def provider_file? # :nodoc:
  @source.is_a?(UploadedFile)
end

#provider_file_idObject

:nodoc:



103
104
105
# File 'lib/ruby_llm/attachment.rb', line 103

def provider_file_id # :nodoc:
  @source.id if provider_file?
end

#provider_file_uriObject

:nodoc:



107
108
109
# File 'lib/ruby_llm/attachment.rb', line 107

def provider_file_uri # :nodoc:
  @source.uri if provider_file?
end

#provider_uploadsObject

Files this attachment has been auto-uploaded to, keyed by provider and credentials. Kept on the attachment so request-time preprocessing uploads once per account while history keeps the original source.



99
100
101
# File 'lib/ruby_llm/attachment.rb', line 99

def provider_uploads # :nodoc:
  @provider_uploads ||= {}
end

#text?Boolean

Returns whether the attachment is textual, like source code or CSV.

Returns:

  • (Boolean)


209
210
211
# File 'lib/ruby_llm/attachment.rb', line 209

def text?
  RubyLLM::Files::MimeType.text? mime_type
end

#to_hObject

:nodoc:



213
214
215
# File 'lib/ruby_llm/attachment.rb', line 213

def to_h # :nodoc:
  { type: type, source: @source }
end

#typeObject

Returns the attachment category as a Symbol: :image, :video, :audio, :pdf, :text, :document, or :unknown.



153
154
155
156
157
158
159
160
161
162
# File 'lib/ruby_llm/attachment.rb', line 153

def type
  return :image if image?
  return :video if video?
  return :audio if audio?
  return :pdf if pdf?
  return :text if text?
  return :document if document?

  :unknown
end

#url?Boolean

:nodoc:

Returns:

  • (Boolean)


88
89
90
# File 'lib/ruby_llm/attachment.rb', line 88

def url? # :nodoc:
  @source.is_a?(URI) || (@source.is_a?(String) && @source.match?(%r{\Ahttps?://}i))
end

#video?Boolean

Returns whether the attachment is a video.

Returns:

  • (Boolean)


170
171
172
# File 'lib/ruby_llm/attachment.rb', line 170

def video?
  RubyLLM::Files::MimeType.video? mime_type
end