Class: Leann::Rails::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/leann/rails/builder.rb

Overview

Builds a new LEANN index stored in the database

Examples:

DSL style

Leann::Rails.build("products") do
  add "Red running shoes", category: "shoes"
  add "Blue denim jeans", category: "pants"
end

Programmatic style

builder = Leann::Rails::Builder.new("products")
builder.add("Red running shoes", category: "shoes")
builder.save

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, embedding: nil, model: nil, force: false) ⇒ Builder

Returns a new instance of Builder.

Parameters:

  • name (String)

    Index name (must be unique)

  • embedding (Symbol) (defaults to: nil)

    Embedding provider (:ruby_llm, :openai, :ollama, :fastembed)

  • model (String, nil) (defaults to: nil)

    Embedding model name

  • force (Boolean) (defaults to: false)

    Overwrite existing index



31
32
33
34
35
36
37
38
39
# File 'lib/leann/rails/builder.rb', line 31

def initialize(name, embedding: nil, model: nil, force: false)
  @name = name
  @embedding_provider = embedding || Leann.configuration.embedding_provider
  @embedding_model = model || Leann.configuration.embedding_model_for(@embedding_provider)
  @force = force
  @documents = []

  check_existing_index unless force
end

Instance Attribute Details

#documentsArray<Hash> (readonly)

Returns Documents to be indexed.

Returns:

  • (Array<Hash>)

    Documents to be indexed



25
26
27
# File 'lib/leann/rails/builder.rb', line 25

def documents
  @documents
end

#nameString (readonly)

Returns Index name.

Returns:

  • (String)

    Index name



22
23
24
# File 'lib/leann/rails/builder.rb', line 22

def name
  @name
end

Instance Method Details

#add(text, **metadata) ⇒ self Also known as: <<

Add a text document

Parameters:

  • text (String)

    Document text

  • metadata (Hash)

    Additional metadata

Returns:

  • (self)

Raises:

  • (ArgumentError)


46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/leann/rails/builder.rb', line 46

def add(text, **)
  raise ArgumentError, "Text cannot be nil" if text.nil?
  raise ArgumentError, "Text cannot be empty" if text.to_s.strip.empty?

  doc = {
    id: .delete(:id) || SecureRandom.uuid,
    text: text.to_s.strip,
    metadata: 
  }

  @documents << doc
  self
end

#add_all(docs) ⇒ self

Add multiple documents at once

Parameters:

  • docs (Array<String>, Array<Hash>)

    Documents to add

Returns:

  • (self)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/leann/rails/builder.rb', line 106

def add_all(docs)
  docs.each do |doc|
    case doc
    when String
      add(doc)
    when Hash
      text = doc.delete(:text) || doc.delete("text")
      add(text, **doc.transform_keys(&:to_sym))
    else
      raise ArgumentError, "Invalid document type: #{doc.class}"
    end
  end

  self
end

#add_directory(directory, pattern: "**/*", extensions: nil, **metadata) ⇒ self

Add all files from a directory

Parameters:

  • directory (String)

    Directory path

  • pattern (String) (defaults to: "**/*")

    Glob pattern

  • extensions (Array<String>, nil) (defaults to: nil)

    Filter by extensions

  • metadata (Hash)

    Additional metadata for all files

Returns:

  • (self)

Raises:

  • (ArgumentError)


88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/leann/rails/builder.rb', line 88

def add_directory(directory, pattern: "**/*", extensions: nil, **)
  raise ArgumentError, "Directory not found: #{directory}" unless Dir.exist?(directory)

  full_pattern = File.join(directory, pattern)
  Dir.glob(full_pattern).each do |file_path|
    next unless File.file?(file_path)
    next if extensions && !extensions.include?(File.extname(file_path))

    add_file(file_path, **)
  end

  self
end

#add_file(file_path, **metadata) ⇒ self

Add content from a file

Parameters:

  • file_path (String)

    Path to file

  • metadata (Hash)

    Additional metadata

Returns:

  • (self)

Raises:

  • (ArgumentError)


68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/leann/rails/builder.rb', line 68

def add_file(file_path, **)
  raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path)

  content = File.read(file_path)
   = {
    source: file_path,
    filename: File.basename(file_path),
    extension: File.extname(file_path)
  }.merge()

  add(content, **)
end

#countInteger Also known as: size

Get number of documents added

Returns:

  • (Integer)


124
125
126
# File 'lib/leann/rails/builder.rb', line 124

def count
  @documents.size
end

#empty?Boolean

Check if any documents have been added

Returns:

  • (Boolean)


131
132
133
# File 'lib/leann/rails/builder.rb', line 131

def empty?
  @documents.empty?
end

#saveLeann::Rails::Index Also known as: build

Build and save the index to the database

Returns:

Raises:



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/leann/rails/builder.rb', line 137

def save
  raise Leann::EmptyIndexError if empty?

  puts "Building index '#{name}' with #{count} documents..."

  # Delete existing if force mode
  Index.find_by(name: name)&.destroy if @force

  # Compute embeddings
  embeddings = compute_embeddings

  # Create index record
  index = Index.create!(
    name: name,
    embedding_provider: @embedding_provider.to_s,
    embedding_model: @embedding_model,
    dimensions: embeddings.first&.size || 0,
    config: {
      hnsw_m: Leann.configuration.hnsw_m,
      hnsw_ef_construction: Leann.configuration.hnsw_ef_construction
    }
  )

  # Build and store graph
  backend = ActiveRecordBackend.new(index)
  backend.build(@documents, embeddings)

  puts "Index '#{name}' created successfully!"

  index
end