maglev-rb 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +408 -0
  4. data/README.zh-CN.md +408 -0
  5. data/lib/generators/maglev/install/install_generator.rb +70 -0
  6. data/lib/maglev/active_record_extension.rb +100 -0
  7. data/lib/maglev/adapters/ruby_llm_attachment_extractor.rb +15 -0
  8. data/lib/maglev/adapters/ruby_llm_embedding.rb +22 -0
  9. data/lib/maglev/adapters/ruby_llm_generation.rb +22 -0
  10. data/lib/maglev/adapters/ruby_llm_provider.rb +64 -0
  11. data/lib/maglev/answerer.rb +58 -0
  12. data/lib/maglev/attachment_extractor.rb +106 -0
  13. data/lib/maglev/authorization.rb +43 -0
  14. data/lib/maglev/chunk.rb +14 -0
  15. data/lib/maglev/chunker.rb +58 -0
  16. data/lib/maglev/configuration.rb +76 -0
  17. data/lib/maglev/content_source_graph.rb +66 -0
  18. data/lib/maglev/context_assembler.rb +94 -0
  19. data/lib/maglev/context_preview.rb +13 -0
  20. data/lib/maglev/dependency_graph.rb +129 -0
  21. data/lib/maglev/embedding_adapter.rb +9 -0
  22. data/lib/maglev/errors.rb +21 -0
  23. data/lib/maglev/extracted_document.rb +36 -0
  24. data/lib/maglev/generation_adapter.rb +9 -0
  25. data/lib/maglev/indexer.rb +175 -0
  26. data/lib/maglev/knowledge_config.rb +136 -0
  27. data/lib/maglev/prompt_builder.rb +27 -0
  28. data/lib/maglev/provider_call.rb +49 -0
  29. data/lib/maglev/provider_configuration.rb +18 -0
  30. data/lib/maglev/railtie.rb +19 -0
  31. data/lib/maglev/reindex_job.rb +16 -0
  32. data/lib/maglev/response.rb +26 -0
  33. data/lib/maglev/retriever.rb +89 -0
  34. data/lib/maglev/schema_compiler.rb +51 -0
  35. data/lib/maglev/search_result.rb +22 -0
  36. data/lib/maglev/snapshot.rb +14 -0
  37. data/lib/maglev/snapshot_builder.rb +204 -0
  38. data/lib/maglev/vector_stores/base.rb +31 -0
  39. data/lib/maglev/vector_stores/document.rb +39 -0
  40. data/lib/maglev/vector_stores/memory.rb +76 -0
  41. data/lib/maglev/vector_stores/pgvector.rb +94 -0
  42. data/lib/maglev/version.rb +5 -0
  43. data/lib/maglev.rb +37 -0
  44. data/lib/tasks/maglev.rake +31 -0
  45. metadata +202 -0
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "adapters/ruby_llm_embedding"
4
+ require_relative "authorization"
5
+ require_relative "chunk"
6
+ require_relative "provider_call"
7
+ require_relative "search_result"
8
+ require_relative "vector_stores/pgvector"
9
+
10
+ module Maglev
11
+ class Retriever
12
+ def initialize(model_class, chunk_model: Chunk, embedding_adapter: Maglev.configuration.embedding_adapter, authorization: Authorization.new, vector_store: Maglev.configuration.vector_store)
13
+ @model_class = model_class
14
+ @chunk_model = chunk_model
15
+ @embedding_adapter = embedding_adapter || Adapters::RubyLLMEmbedding.new
16
+ @authorization = authorization
17
+ @vector_store = vector_store
18
+ end
19
+
20
+ def search(query, limit:, owner: nil, user: nil)
21
+ embedding = ProviderCall.new.call(operation: "embed") { @embedding_adapter.embed(query) }
22
+ return search_vector_store(embedding, limit: limit, owner: owner, user: user) if @vector_store
23
+
24
+ scope = @chunk_model.where(owner_model_name: @model_class.name)
25
+ scope = scope.where(owner: owner) if owner
26
+ scope = apply_authorization_scope(scope, user) unless owner
27
+ rows = scope
28
+ .nearest_neighbors(:embedding, embedding, distance: "cosine")
29
+
30
+ unique_owner_results(rows).first(limit)
31
+ end
32
+
33
+ private
34
+
35
+ def search_vector_store(embedding, limit:, owner:, user:)
36
+ documents = @vector_store.search(vector: embedding, filters: filters_for(owner), limit: limit)
37
+ unique_owner_results(documents).first(limit)
38
+ end
39
+
40
+ def filters_for(owner)
41
+ filters = {owner_model_name: @model_class.name}
42
+ if owner
43
+ filters[:owner_type] = owner.class.name
44
+ filters[:owner_id] = owner.id
45
+ end
46
+ filters
47
+ end
48
+
49
+ def apply_authorization_scope(scope, user)
50
+ return scope unless @authorization.configured? && user
51
+
52
+ authorized_scope = @authorization.scope(model: @model_class, user: user)
53
+ if authorized_scope.respond_to?(:select)
54
+ scope.where(owner_id: authorized_scope.select(:id))
55
+ else
56
+ scope
57
+ end
58
+ end
59
+
60
+ def unique_owner_results(rows)
61
+ seen_owners = {}
62
+ results = []
63
+
64
+ rows.each do |row|
65
+ owner = owner_for(row)
66
+ next if seen_owners[owner]
67
+
68
+ seen_owners[owner] = true
69
+ results << SearchResult.new(
70
+ owner: owner,
71
+ content: row.content,
72
+ source: row.source,
73
+ distance: row.respond_to?(:neighbor_distance) ? row.neighbor_distance : row.distance,
74
+ chunk_index: row.respond_to?(:chunk_index) ? row.chunk_index : nil
75
+ )
76
+ end
77
+
78
+ results
79
+ end
80
+
81
+ def owner_for(row)
82
+ if row.owner
83
+ row.owner
84
+ elsif row.respond_to?(:owner_type) && row.respond_to?(:owner_id)
85
+ row.owner_type.constantize.find(row.owner_id)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration"
4
+ require_relative "errors"
5
+
6
+ module Maglev
7
+ class SchemaCompiler
8
+ SUPPORTED_MACROS = %i[belongs_to has_one has_many].freeze
9
+
10
+ CompiledSchema = Struct.new(:model_class, :relations)
11
+ CompiledRelation = Struct.new(:name, :depth, :limit, :inverse, :macro, :related_class)
12
+
13
+ def initialize(config, max_depth: Maglev.configuration.max_relation_depth)
14
+ @config = config
15
+ @max_depth = max_depth
16
+ end
17
+
18
+ def compile
19
+ CompiledSchema.new(@config.model_class, @config.relations.map { |relation| compile_relation(relation) }.freeze)
20
+ end
21
+
22
+ private
23
+
24
+ def compile_relation(relation)
25
+ reflection = @config.model_class.reflect_on_association(relation.name.to_sym)
26
+ unless reflection
27
+ raise ConfigurationError, "Unknown Maglev association #{@config.model_class.name}.#{relation.name}"
28
+ end
29
+
30
+ unless SUPPORTED_MACROS.include?(reflection.macro)
31
+ raise ConfigurationError, "Unsupported Maglev association #{@config.model_class.name}.#{relation.name}: #{reflection.macro}"
32
+ end
33
+
34
+ if relation.depth > @max_depth
35
+ raise ConfigurationError, "Maglev association #{@config.model_class.name}.#{relation.name} exceeds maximum depth #{@max_depth}"
36
+ end
37
+
38
+ related_class = reflection.klass
39
+ unless related_class.respond_to?(:maglev_config) && related_class.maglev_config
40
+ raise ConfigurationError, "Related Maglev model #{related_class.name} must declare has_knowledge"
41
+ end
42
+
43
+ inverse = relation.inverse || reflection.inverse_of&.name&.to_s
44
+ unless inverse && related_class.reflect_on_association(inverse.to_sym)
45
+ raise ConfigurationError, "Maglev association #{@config.model_class.name}.#{relation.name} requires an inverse for invalidation"
46
+ end
47
+
48
+ CompiledRelation.new(relation.name, relation.depth, relation.limit, inverse, reflection.macro, related_class)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ class SearchResult
5
+ attr_reader :owner, :content, :source, :distance, :chunk_index
6
+
7
+ def initialize(owner:, content:, source:, distance:, chunk_index: nil)
8
+ @owner = owner
9
+ @content = content
10
+ @source = source
11
+ @distance = distance
12
+ @chunk_index = chunk_index
13
+ freeze
14
+ end
15
+
16
+ def similarity
17
+ return nil if distance.nil?
18
+
19
+ 1.0 - distance.to_f
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ class Snapshot
5
+ def initialize(lines)
6
+ @text = lines.join("\n").freeze
7
+ freeze
8
+ end
9
+
10
+ def to_s
11
+ @text
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+
5
+ require_relative "attachment_extractor"
6
+ require_relative "snapshot"
7
+
8
+ module Maglev
9
+ class SnapshotBuilder
10
+ def initialize(record, config, path: nil, visited: nil, attachment_extractor: nil, remaining_depth: Maglev.configuration.max_relation_depth)
11
+ @record = record
12
+ @config = config
13
+ @path = path
14
+ @visited = visited || {}
15
+ @attachment_extractor = attachment_extractor || Maglev.configuration.attachment_extractor || AttachmentExtractor.new
16
+ @remaining_depth = remaining_depth
17
+ end
18
+
19
+ def build
20
+ return Snapshot.new([]) if visited?
21
+
22
+ mark_visited
23
+ Snapshot.new(lines)
24
+ end
25
+
26
+ private
27
+
28
+ def lines
29
+ [
30
+ header,
31
+ *attribute_lines,
32
+ *tag_lines,
33
+ *attachment_lines,
34
+ *rich_text_lines,
35
+ *relation_lines
36
+ ]
37
+ end
38
+
39
+ def header
40
+ label = "#{@record.class.name}##{record_id}"
41
+ @path ? "#{@path} #{label}" : label
42
+ end
43
+
44
+ def record_id
45
+ id = @record.respond_to?(:id) ? @record.id : nil
46
+ id.nil? ? "new_record" : id
47
+ end
48
+
49
+ def attribute_lines
50
+ @config.exposed_attributes.filter_map do |attribute|
51
+ value = @record.public_send(attribute)
52
+ "#{attribute_path(attribute)}: #{value}" unless value.nil?
53
+ end
54
+ end
55
+
56
+ def tag_lines
57
+ return [] if @config.tags.empty?
58
+
59
+ ["tags: #{@config.tags.join(", ")}"]
60
+ end
61
+
62
+ def relation_lines
63
+ return [] unless @remaining_depth.positive?
64
+
65
+ @config.relations.flat_map do |relation|
66
+ remaining_depth = [@remaining_depth, relation.depth].min - 1
67
+ relation_records, collection = relation_records(relation)
68
+ relation_records.each_with_index.flat_map do |related_record, index|
69
+ relation_path = path_for(relation, index, collection: collection)
70
+ SnapshotBuilder.new(
71
+ related_record,
72
+ related_record.class.maglev_config,
73
+ path: relation_path,
74
+ visited: @visited,
75
+ attachment_extractor: @attachment_extractor,
76
+ remaining_depth: remaining_depth
77
+ ).build.to_s.split("\n")
78
+ end
79
+ end
80
+ end
81
+
82
+ def attachment_lines
83
+ @config.attached_sources.flat_map do |source|
84
+ attachment_blobs(source.name).flat_map do |blob|
85
+ document = extract_attachment(source.name, blob)
86
+ if document.extracted?
87
+ ["#{document.source_identifier}.text: #{document.text}"]
88
+ else
89
+ ["#{document.source_identifier}.skipped: #{document.metadata[:reason]}"]
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def attachment_blobs(source_name)
96
+ value = @record.public_send(source_name)
97
+ attachments = if value.respond_to?(:attachments)
98
+ value.attachments.to_a
99
+ elsif value.respond_to?(:attachment)
100
+ [value.attachment].compact
101
+ elsif value.respond_to?(:blob)
102
+ [value]
103
+ elsif value.respond_to?(:to_ary)
104
+ value.to_ary
105
+ else
106
+ [value].compact
107
+ end
108
+
109
+ attachments.map { |attachment| attachment.respond_to?(:blob) ? attachment.blob : attachment }
110
+ end
111
+
112
+ def extract_attachment(source_name, blob)
113
+ @attachment_extractor.extract(blob, source_name: source_name)
114
+ rescue
115
+ ExtractedDocument.skipped(
116
+ source_identifier: "#{source_name}[blob:#{blob_identifier(blob)}]",
117
+ reason: "extraction_failed",
118
+ metadata: {
119
+ filename: blob.respond_to?(:filename) ? blob.filename.to_s : "unknown",
120
+ content_type: blob.respond_to?(:content_type) ? blob.content_type.to_s : nil
121
+ }
122
+ )
123
+ end
124
+
125
+ def rich_text_lines
126
+ @config.rich_text_sources.filter_map do |source|
127
+ text = rich_text_value(source.name)
128
+ "#{rich_text_identifier(source.name)}.text: #{text}" unless text.nil? || text.empty?
129
+ end
130
+ end
131
+
132
+ def rich_text_value(source_name)
133
+ value = @record.public_send(source_name)
134
+ text = if value.respond_to?(:body) && value.body.respond_to?(:to_plain_text)
135
+ value.body.to_plain_text
136
+ elsif value.respond_to?(:to_plain_text)
137
+ value.to_plain_text
138
+ elsif value.respond_to?(:body) && value.body.respond_to?(:to_html)
139
+ sanitize_html(value.body.to_html.to_s)
140
+ else
141
+ sanitize_html(value.to_s)
142
+ end
143
+ text.squeeze(" ").strip
144
+ end
145
+
146
+ def rich_text_identifier(source_name)
147
+ "rich_text.#{source_name}"
148
+ end
149
+
150
+ def blob_identifier(blob)
151
+ if blob.respond_to?(:id) && blob.id
152
+ blob.id
153
+ elsif blob.respond_to?(:key) && blob.key
154
+ blob.key
155
+ elsif blob.respond_to?(:filename)
156
+ blob.filename.to_s
157
+ else
158
+ "unknown"
159
+ end
160
+ end
161
+
162
+ def sanitize_html(html)
163
+ without_scripts = html.gsub(%r{<script\b[^>]*>.*?</script>}im, " ")
164
+ .gsub(%r{<style\b[^>]*>.*?</style>}im, " ")
165
+ CGI.unescapeHTML(without_scripts.gsub(/<[^>]+>/, " ").squeeze(" ").strip)
166
+ end
167
+
168
+ def relation_records(relation)
169
+ value = @record.public_send(relation.name)
170
+ records, collection = if value.nil?
171
+ [[], false]
172
+ elsif value.respond_to?(:to_ary)
173
+ [value.to_ary, true]
174
+ elsif value.respond_to?(:limit)
175
+ [value.limit(relation.limit).to_a, true]
176
+ else
177
+ [[value], false]
178
+ end
179
+
180
+ [records.first(relation.limit), collection]
181
+ end
182
+
183
+ def path_for(relation, index, collection:)
184
+ segment = collection ? "#{relation.name}[#{index}]" : relation.name
185
+ @path ? "#{@path}.#{segment}" : segment
186
+ end
187
+
188
+ def attribute_path(attribute)
189
+ @path ? "#{@path}.#{attribute}" : attribute
190
+ end
191
+
192
+ def visited?
193
+ @visited.key?(record_key)
194
+ end
195
+
196
+ def mark_visited
197
+ @visited[record_key] = true
198
+ end
199
+
200
+ def record_key
201
+ [@record.class.name, record_id]
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module VectorStores
5
+ class Base
6
+ def upsert(documents:)
7
+ raise NotImplementedError, "#{self.class.name} must implement #upsert"
8
+ end
9
+
10
+ def search(vector:, filters:, limit:)
11
+ raise NotImplementedError, "#{self.class.name} must implement #search"
12
+ end
13
+
14
+ def delete(ids:)
15
+ raise NotImplementedError, "#{self.class.name} must implement #delete"
16
+ end
17
+
18
+ def delete_by_owner(owner_type:, owner_id:)
19
+ raise NotImplementedError, "#{self.class.name} must implement #delete_by_owner"
20
+ end
21
+
22
+ def healthcheck
23
+ raise NotImplementedError, "#{self.class.name} must implement #healthcheck"
24
+ end
25
+
26
+ def capabilities
27
+ {}
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ module VectorStores
5
+ class Document
6
+ attr_reader :id, :owner_type, :owner_id, :owner_model_name, :source,
7
+ :chunk_index, :content, :content_checksum, :embedding_model,
8
+ :embedding, :owner, :distance
9
+
10
+ def initialize(owner_type:, owner_id:, owner_model_name:, source:, chunk_index:,
11
+ content:, content_checksum:, embedding_model:, embedding:, id: nil,
12
+ owner: nil, distance: nil)
13
+ @owner_type = owner_type
14
+ @owner_id = owner_id
15
+ @owner_model_name = owner_model_name
16
+ @source = source
17
+ @chunk_index = chunk_index
18
+ @content = content
19
+ @content_checksum = content_checksum
20
+ @embedding_model = embedding_model
21
+ @embedding = embedding
22
+ @id = id || "#{owner_type}:#{owner_id}:#{source}:#{chunk_index}"
23
+ @owner = owner
24
+ @distance = distance
25
+ freeze
26
+ end
27
+
28
+ def metadata
29
+ {
30
+ owner_type: owner_type,
31
+ owner_id: owner_id,
32
+ owner_model_name: owner_model_name,
33
+ source: source,
34
+ chunk_index: chunk_index
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "document"
5
+
6
+ module Maglev
7
+ module VectorStores
8
+ class Memory < Base
9
+ def initialize
10
+ @documents = {}
11
+ end
12
+
13
+ def upsert(documents:)
14
+ documents.each { |document| @documents[document.id] = document }
15
+ end
16
+
17
+ def search(vector:, filters:, limit:)
18
+ @documents.values
19
+ .select { |document| matches_filters?(document, filters) }
20
+ .map { |document| with_distance(document, cosine_distance(vector, document.embedding)) }
21
+ .sort_by(&:distance)
22
+ .first(limit)
23
+ end
24
+
25
+ def delete(ids:)
26
+ ids.each { |id| @documents.delete(id) }
27
+ end
28
+
29
+ def delete_by_owner(owner_type:, owner_id:)
30
+ @documents.delete_if do |_id, document|
31
+ document.owner_type == owner_type && document.owner_id == owner_id
32
+ end
33
+ end
34
+
35
+ def healthcheck
36
+ :ok
37
+ end
38
+
39
+ def capabilities
40
+ {metadata_filtering: true, in_memory: true}
41
+ end
42
+
43
+ private
44
+
45
+ def matches_filters?(document, filters)
46
+ filters.all? { |key, value| document.metadata.fetch(key) == value }
47
+ end
48
+
49
+ def cosine_distance(left, right)
50
+ dot = left.zip(right).sum { |a, b| a.to_f * b.to_f }
51
+ left_norm = Math.sqrt(left.sum { |value| value.to_f**2 })
52
+ right_norm = Math.sqrt(right.sum { |value| value.to_f**2 })
53
+ return 1.0 if left_norm.zero? || right_norm.zero?
54
+
55
+ 1.0 - (dot / (left_norm * right_norm))
56
+ end
57
+
58
+ def with_distance(document, distance)
59
+ Document.new(
60
+ id: document.id,
61
+ owner_type: document.owner_type,
62
+ owner_id: document.owner_id,
63
+ owner_model_name: document.owner_model_name,
64
+ source: document.source,
65
+ chunk_index: document.chunk_index,
66
+ content: document.content,
67
+ content_checksum: document.content_checksum,
68
+ embedding_model: document.embedding_model,
69
+ embedding: document.embedding,
70
+ owner: document.owner,
71
+ distance: distance
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../chunk"
4
+ require_relative "base"
5
+ require_relative "document"
6
+
7
+ module Maglev
8
+ module VectorStores
9
+ class Pgvector < Base
10
+ def initialize(chunk_model: Chunk)
11
+ @chunk_model = chunk_model
12
+ end
13
+
14
+ def upsert(documents:)
15
+ @chunk_model.transaction do
16
+ documents.each do |document|
17
+ scope = identity_scope(document)
18
+ existing = scope.find_by(chunk_index: document.chunk_index, content_checksum: document.content_checksum)
19
+ next if existing
20
+
21
+ scope.where(chunk_index: document.chunk_index).delete_all
22
+ @chunk_model.create!(
23
+ owner_type: document.owner_type,
24
+ owner_id: document.owner_id,
25
+ owner_model_name: document.owner_model_name,
26
+ owner: document.owner,
27
+ source: document.source,
28
+ chunk_index: document.chunk_index,
29
+ content: document.content,
30
+ content_checksum: document.content_checksum,
31
+ embedding_model: document.embedding_model,
32
+ embedding: document.embedding
33
+ )
34
+ end
35
+ end
36
+ end
37
+
38
+ def search(vector:, filters:, limit:)
39
+ scope = filters.reduce(@chunk_model.all) do |current_scope, (key, value)|
40
+ current_scope.where(key => value)
41
+ end
42
+ scope.nearest_neighbors(:embedding, vector, distance: "cosine")
43
+ .first(limit)
44
+ .map { |row| document_for(row) }
45
+ end
46
+
47
+ def delete(ids:)
48
+ ids.each do |id|
49
+ owner_type, owner_id, source, chunk_index = id.split(":", 4)
50
+ @chunk_model.where(owner_type: owner_type, owner_id: owner_id, source: source, chunk_index: chunk_index).delete_all
51
+ end
52
+ end
53
+
54
+ def delete_by_owner(owner_type:, owner_id:)
55
+ @chunk_model.where(owner_type: owner_type, owner_id: owner_id).delete_all
56
+ end
57
+
58
+ def healthcheck
59
+ @chunk_model.connection.active? ? :ok : :unavailable
60
+ end
61
+
62
+ def capabilities
63
+ {metadata_filtering: true, pgvector: true}
64
+ end
65
+
66
+ private
67
+
68
+ def identity_scope(document)
69
+ @chunk_model.where(
70
+ owner_type: document.owner_type,
71
+ owner_id: document.owner_id,
72
+ owner_model_name: document.owner_model_name,
73
+ source: document.source
74
+ )
75
+ end
76
+
77
+ def document_for(row)
78
+ Document.new(
79
+ owner_type: row.owner_type,
80
+ owner_id: row.owner_id,
81
+ owner_model_name: row.owner_model_name,
82
+ source: row.source,
83
+ chunk_index: row.chunk_index,
84
+ content: row.content,
85
+ content_checksum: row.content_checksum,
86
+ embedding_model: row.embedding_model,
87
+ embedding: row.embedding,
88
+ owner: row.owner,
89
+ distance: row.respond_to?(:neighbor_distance) ? row.neighbor_distance : row.distance
90
+ )
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maglev
4
+ VERSION = "0.1.1"
5
+ end
data/lib/maglev.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "maglev/version"
4
+ require_relative "maglev/configuration"
5
+ require_relative "maglev/provider_configuration"
6
+ require_relative "maglev/errors"
7
+ require_relative "maglev/authorization"
8
+ require_relative "maglev/provider_call"
9
+ require_relative "maglev/knowledge_config"
10
+ require_relative "maglev/snapshot"
11
+ require_relative "maglev/snapshot_builder"
12
+ require_relative "maglev/chunker"
13
+ require_relative "maglev/embedding_adapter"
14
+ require_relative "maglev/adapters/ruby_llm_embedding"
15
+ require_relative "maglev/generation_adapter"
16
+ require_relative "maglev/adapters/ruby_llm_generation"
17
+ require_relative "maglev/extracted_document"
18
+ require_relative "maglev/attachment_extractor"
19
+ require_relative "maglev/adapters/ruby_llm_attachment_extractor"
20
+ require_relative "maglev/search_result"
21
+ require_relative "maglev/vector_stores/base"
22
+ require_relative "maglev/vector_stores/document"
23
+ require_relative "maglev/vector_stores/memory"
24
+ require_relative "maglev/vector_stores/pgvector"
25
+ require_relative "maglev/response"
26
+ require_relative "maglev/prompt_builder"
27
+ require_relative "maglev/context_assembler"
28
+ require_relative "maglev/context_preview"
29
+ require_relative "maglev/indexer"
30
+ require_relative "maglev/reindex_job"
31
+ require_relative "maglev/retriever"
32
+ require_relative "maglev/schema_compiler"
33
+ require_relative "maglev/dependency_graph"
34
+ require_relative "maglev/content_source_graph"
35
+ require_relative "maglev/answerer"
36
+
37
+ require_relative "maglev/railtie" if defined?(Rails::Railtie)