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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +408 -0
- data/README.zh-CN.md +408 -0
- data/lib/generators/maglev/install/install_generator.rb +70 -0
- data/lib/maglev/active_record_extension.rb +100 -0
- data/lib/maglev/adapters/ruby_llm_attachment_extractor.rb +15 -0
- data/lib/maglev/adapters/ruby_llm_embedding.rb +22 -0
- data/lib/maglev/adapters/ruby_llm_generation.rb +22 -0
- data/lib/maglev/adapters/ruby_llm_provider.rb +64 -0
- data/lib/maglev/answerer.rb +58 -0
- data/lib/maglev/attachment_extractor.rb +106 -0
- data/lib/maglev/authorization.rb +43 -0
- data/lib/maglev/chunk.rb +14 -0
- data/lib/maglev/chunker.rb +58 -0
- data/lib/maglev/configuration.rb +76 -0
- data/lib/maglev/content_source_graph.rb +66 -0
- data/lib/maglev/context_assembler.rb +94 -0
- data/lib/maglev/context_preview.rb +13 -0
- data/lib/maglev/dependency_graph.rb +129 -0
- data/lib/maglev/embedding_adapter.rb +9 -0
- data/lib/maglev/errors.rb +21 -0
- data/lib/maglev/extracted_document.rb +36 -0
- data/lib/maglev/generation_adapter.rb +9 -0
- data/lib/maglev/indexer.rb +175 -0
- data/lib/maglev/knowledge_config.rb +136 -0
- data/lib/maglev/prompt_builder.rb +27 -0
- data/lib/maglev/provider_call.rb +49 -0
- data/lib/maglev/provider_configuration.rb +18 -0
- data/lib/maglev/railtie.rb +19 -0
- data/lib/maglev/reindex_job.rb +16 -0
- data/lib/maglev/response.rb +26 -0
- data/lib/maglev/retriever.rb +89 -0
- data/lib/maglev/schema_compiler.rb +51 -0
- data/lib/maglev/search_result.rb +22 -0
- data/lib/maglev/snapshot.rb +14 -0
- data/lib/maglev/snapshot_builder.rb +204 -0
- data/lib/maglev/vector_stores/base.rb +31 -0
- data/lib/maglev/vector_stores/document.rb +39 -0
- data/lib/maglev/vector_stores/memory.rb +76 -0
- data/lib/maglev/vector_stores/pgvector.rb +94 -0
- data/lib/maglev/version.rb +5 -0
- data/lib/maglev.rb +37 -0
- data/lib/tasks/maglev.rake +31 -0
- metadata +202 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 34583270cc6f3febaac2fbe823e4d8f55d1f8ec15594410c011bada98535c102
|
|
4
|
+
data.tar.gz: e1a9c7a3db4c9d40e6fc38be340ba5b9baac12410a5a560f1556b439de2d1b93
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a3c193d43ba5cc89e444b079b765aa9044b63bbc960911e6c187550070e0fee149a5d9726f2c1c434f6127f6f74e094b12d12a3d158ee73417f40cb198496e47
|
|
7
|
+
data.tar.gz: 4d9d4f863ec0f910150edb2110d1bbb785941453e340fb6b73528c974c73537e5435f0d01d950140f0af58ce8b7c82c795f285f0ca4845b875d5486a6f750276
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maglev contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Maglev
|
|
2
|
+
|
|
3
|
+
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
[](https://github.com/benjis/maglev/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.ruby-lang.org/)
|
|
7
|
+
[](https://rubyonrails.org/)
|
|
8
|
+
[](LICENSE.txt)
|
|
9
|
+
|
|
10
|
+
**Give your Rails models semantic memory, without building a second application beside Rails.**
|
|
11
|
+
|
|
12
|
+
Maglev is a Rails-native semantic knowledge layer for ActiveRecord object graphs. Declare which parts of your domain are safe and useful to understand, and Maglev turns records, relationships, attachments, and rich text into searchable knowledge. It keeps that knowledge fresh through normal Rails lifecycle hooks and gives you semantic search and grounded answers through familiar model APIs.
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
Product.search("laptops with battery or usability complaints")
|
|
16
|
+
|
|
17
|
+
response = Product.ask("What recurring product issues should we investigate?", user: current_user)
|
|
18
|
+
response.text
|
|
19
|
+
response.sources # the ActiveRecord records and chunks behind the answer
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Maglev is deliberately focused on retrieval-augmented generation (RAG). ActiveRecord remains the source of truth for exact filters, joins, reporting, and aggregation; Maglev handles questions expressed in human language.
|
|
23
|
+
|
|
24
|
+
## Why Maglev?
|
|
25
|
+
|
|
26
|
+
- **Rails-native:** a gem and Railtie, not a separate service, engine, or API.
|
|
27
|
+
- **Model-driven:** declare knowledge next to the ActiveRecord model that owns it.
|
|
28
|
+
- **Graph-aware:** traverse direct, `has_many :through`, and polymorphic associations with explicit depth and record limits.
|
|
29
|
+
- **Fresh by default:** reindex owners after declared records, direct associations, attachments, or Action Text content change.
|
|
30
|
+
- **Grounded:** answers are generated only from retrieved context and include their sources.
|
|
31
|
+
- **Production-minded:** authorization hooks, content limits, sanitization, retries, instrumentation, and idempotent reindexing are built in.
|
|
32
|
+
- **Extensible:** use the default PostgreSQL/pgvector store or implement the small vector-store contract.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Install prerequisites
|
|
37
|
+
|
|
38
|
+
Maglev requires Ruby 3.2+, Rails 7.1 or 8.0, PostgreSQL, and the
|
|
39
|
+
[`pgvector`](https://github.com/pgvector/pgvector) extension.
|
|
40
|
+
|
|
41
|
+
Add Maglev to your application:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# Gemfile
|
|
45
|
+
gem "maglev-rb"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle install
|
|
50
|
+
bin/rails generate maglev:install --embedding-dimensions=1536
|
|
51
|
+
bin/rails db:migrate
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The generator writes `--embedding-dimensions` to both `config/initializers/maglev.rb` and the `maglev_chunks` vector column, and includes a cosine HNSW index.
|
|
55
|
+
|
|
56
|
+
### 2. Configure your provider
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# config/initializers/maglev.rb
|
|
60
|
+
Maglev.configure do |config|
|
|
61
|
+
config.embedding_provider do |provider|
|
|
62
|
+
provider.url = "http://localhost:11434/v1"
|
|
63
|
+
provider.api_key = ENV["LOCAL_EMBEDDING_API_KEY"]
|
|
64
|
+
provider.model = "Qwen3-Embedding-0.6B-8bit"
|
|
65
|
+
provider.dimensions = 1024
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
config.generation_provider do |provider|
|
|
69
|
+
provider.url = "https://api.deepseek.com/v1"
|
|
70
|
+
provider.api_key = Rails.application.credentials.dig(:deepseek_api_key)
|
|
71
|
+
provider.model = "deepseek-chat"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
config.chunk_size = 1000
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Embedding and generation endpoints are independent and may use different URLs, API keys, and models. The default provider bridge expects OpenAI-compatible HTTP endpoints. Applications can still inject custom Maglev adapters for other protocols.
|
|
79
|
+
|
|
80
|
+
For an existing installation, change the configured dimensions and the database vector column together. Maglev checks their consistency before requesting an embedding.
|
|
81
|
+
|
|
82
|
+
### 3. Declare model knowledge
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
class Product < ApplicationRecord
|
|
86
|
+
has_many :reviews, inverse_of: :product
|
|
87
|
+
has_many :product_categories, inverse_of: :product
|
|
88
|
+
has_many :categories, through: :product_categories
|
|
89
|
+
has_many_attached :images
|
|
90
|
+
has_rich_text :description
|
|
91
|
+
|
|
92
|
+
has_knowledge do
|
|
93
|
+
expose :name, :sku, :price, :status
|
|
94
|
+
tags :product
|
|
95
|
+
|
|
96
|
+
include_related :reviews, depth: 1, limit: 10
|
|
97
|
+
include_related :categories, depth: 1, limit: 10, inverse: :products
|
|
98
|
+
|
|
99
|
+
expose_attached :images
|
|
100
|
+
expose_rich_text :description
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class Review < ApplicationRecord
|
|
105
|
+
belongs_to :product, inverse_of: :reviews
|
|
106
|
+
|
|
107
|
+
has_knowledge do
|
|
108
|
+
expose :rating, :title, :body
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Only explicitly exposed fields and content sources enter Maglev's knowledge snapshot. Relation `limit` bounds the number of records per association. Relation `depth` bounds association hops: `depth: 1` includes the directly related record but does not expand that record's relations. `config.max_relation_depth` is the hard root-to-leaf ceiling for every snapshot. Related models define their own exposed knowledge, so sensitive join-model fields are not flattened accidentally.
|
|
114
|
+
|
|
115
|
+
### 4. Index existing records
|
|
116
|
+
|
|
117
|
+
New and updated records enqueue `Maglev::ReindexJob` automatically. Backfill existing data once after installation:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bin/rails maglev:reindex[Product]
|
|
121
|
+
# or every model that declares has_knowledge
|
|
122
|
+
bin/rails maglev:reindex_all
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Make sure your Active Job backend is running in production.
|
|
126
|
+
|
|
127
|
+
### 5. Search and ask
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
results = Product.search(
|
|
131
|
+
"laptops with battery or usability complaints",
|
|
132
|
+
limit: 10,
|
|
133
|
+
user: current_user
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
results.each do |result|
|
|
137
|
+
result.owner # => Product
|
|
138
|
+
result.content # retrieved snapshot chunk
|
|
139
|
+
result.source # => "snapshot"
|
|
140
|
+
result.distance # cosine distance; lower is closer
|
|
141
|
+
result.similarity # normalized convenience score
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Generate an answer grounded in the retrieved records:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
response = Product.ask(
|
|
149
|
+
"What recurring product issues should the merchandising team investigate?",
|
|
150
|
+
limit: 5,
|
|
151
|
+
user: current_user
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
response.text
|
|
155
|
+
response.sources # owner, chunk, distance, and the exact retrieved content
|
|
156
|
+
response.metadata
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For example, an answer might summarize a loud fan, intermittent trackpad responsiveness, and lower-than-advertised battery life from retrieved review content. Treat it as a summary of the retrieved context—not an aggregate over every product—and use `response.sources` to show the evidence behind each conclusion.
|
|
160
|
+
|
|
161
|
+
Instance-level questions stay scoped to one owner:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
product.ask("Summarize the reported strengths and weaknesses of this product.", user: current_user)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Maglev also follows declared relationships when Rails data changes. Moving a review queues reindexing for both the previous and current product after commit, so their searchable knowledge stays current:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
review.update!(product: replacement_product)
|
|
171
|
+
# Maglev::ReindexJob is queued for both affected products.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
When retrieval yields no usable context, Maglev returns a deterministic insufficient-context response instead of asking the model to improvise.
|
|
175
|
+
|
|
176
|
+
## How It Works
|
|
177
|
+
|
|
178
|
+
```mermaid
|
|
179
|
+
flowchart LR
|
|
180
|
+
A["ActiveRecord object graph"] --> B["Explicit knowledge schema"]
|
|
181
|
+
B --> C["Bounded, sanitized snapshot"]
|
|
182
|
+
C --> D["Chunking + embeddings"]
|
|
183
|
+
D --> E["Vector store<br/>pgvector by default"]
|
|
184
|
+
Q["search / ask"] --> F["Semantic retrieval"]
|
|
185
|
+
E --> F
|
|
186
|
+
F --> G["Context assembly"]
|
|
187
|
+
G --> H["Grounded generation"]
|
|
188
|
+
H --> I["Response + sources"]
|
|
189
|
+
J["Rails commits and content changes"] --> K["Active Job reindex"]
|
|
190
|
+
K --> C
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
1. `has_knowledge` compiles an explicit schema for a model and its declared relationships.
|
|
194
|
+
2. Maglev builds a deterministic text snapshot from allowed attributes, related records, attachments, rich text, and tags.
|
|
195
|
+
3. The snapshot is split into bounded chunks and embedded through the configured adapter.
|
|
196
|
+
4. Chunks are upserted into a vector store. The default store persists them in PostgreSQL using pgvector.
|
|
197
|
+
5. `search` embeds the query and performs cosine nearest-neighbor retrieval.
|
|
198
|
+
6. `ask` assembles retrieved chunks within context budgets, builds a grounded prompt, and returns the answer with source metadata.
|
|
199
|
+
7. Rails callbacks propagate declared record changes through the graph and enqueue reindexing for affected owners.
|
|
200
|
+
|
|
201
|
+
Maglev does not duplicate your relational model. Vector documents point back to their ActiveRecord owners; your application remains responsible for transactions, business rules, and structured queries.
|
|
202
|
+
|
|
203
|
+
## Knowledge Sources
|
|
204
|
+
|
|
205
|
+
### ActiveRecord graphs
|
|
206
|
+
|
|
207
|
+
`include_related` supports bounded traversal across ordinary associations, `has_many :through`, and polymorphic relationships. Use `inverse:` when Maglev cannot infer how changes on a related model should find their owning knowledge record.
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
has_knowledge do
|
|
211
|
+
include_related :tickets, depth: 2, limit: 25
|
|
212
|
+
include_related :events, depth: 1, limit: 20, inverse: :eventable
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
When a related record moves between owners, Maglev reindexes both the previous and current owner.
|
|
217
|
+
|
|
218
|
+
Creating, deleting, or reassigning a join row changes a `has_many :through` relationship without changing the related record itself. After such join-model changes, explicitly enqueue or run owner reindexing from your application.
|
|
219
|
+
|
|
220
|
+
### Active Storage and Action Text
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
has_knowledge do
|
|
224
|
+
expose_attached :contracts, :brief
|
|
225
|
+
expose_rich_text :notes
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
HTML and Action Text content are sanitized before indexing. Attachments are constrained by content type, byte size, and extracted character count. Changes to declared attachments and rich text trigger owner reindexing.
|
|
230
|
+
|
|
231
|
+
### Inspect before indexing
|
|
232
|
+
|
|
233
|
+
The developer experience APIs let you inspect exactly what a model exposes without calling an embedding or generation provider:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
Customer.maglev_schema
|
|
237
|
+
customer.maglev_snapshot
|
|
238
|
+
|
|
239
|
+
preview = customer.maglev_context_preview(
|
|
240
|
+
question: "Why is this customer at risk?"
|
|
241
|
+
)
|
|
242
|
+
preview.text
|
|
243
|
+
preview.metadata # includes provider_calls: 0
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Authorization
|
|
247
|
+
|
|
248
|
+
Maglev is policy-library agnostic. Configure a small adapter to apply your application's authorization rules during retrieval and answering:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
class MaglevAuthorization
|
|
252
|
+
def scope(model:, user:)
|
|
253
|
+
model.accessible_by(user)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def authorize(record:, user:)
|
|
257
|
+
record.account_id == user.account_id
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
Maglev.configure do |config|
|
|
262
|
+
config.authorization_adapter = MaglevAuthorization.new
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The adapter must implement:
|
|
267
|
+
|
|
268
|
+
- `scope(model:, user:)`, returning the records visible to the user.
|
|
269
|
+
- `authorize(record:, user:)`, returning `false` to deny a record.
|
|
270
|
+
|
|
271
|
+
Without an adapter, all records are allowed. Pass `user:` consistently anywhere retrieval must be scoped.
|
|
272
|
+
|
|
273
|
+
`customer.explain` is a convenience API for applications without user-scoped authorization; use `customer.ask(Maglev.configuration.explain_question, user: current_user)` when a user context is required. Authorization scopes apply to the default pgvector retrieval path. See [Vector Stores](#vector-stores) before combining direct `search` calls with a custom store.
|
|
274
|
+
|
|
275
|
+
## Configuration
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
Maglev.configure do |config|
|
|
279
|
+
config.embedding_provider do |provider|
|
|
280
|
+
provider.url = ENV.fetch("MAGLEV_EMBEDDING_URL", "https://api.openai.com/v1")
|
|
281
|
+
provider.api_key = ENV["MAGLEV_EMBEDDING_API_KEY"]
|
|
282
|
+
provider.model = "text-embedding-3-small"
|
|
283
|
+
provider.dimensions = 1536
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
config.generation_provider do |provider|
|
|
287
|
+
provider.url = ENV.fetch("MAGLEV_GENERATION_URL", "https://api.openai.com/v1")
|
|
288
|
+
provider.api_key = ENV["MAGLEV_GENERATION_API_KEY"]
|
|
289
|
+
provider.model = "gpt-4.1-mini"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
config.chunk_size = 1000
|
|
293
|
+
|
|
294
|
+
config.context_max_characters = 4000
|
|
295
|
+
config.context_per_owner_characters = 1200
|
|
296
|
+
config.max_relation_depth = 3
|
|
297
|
+
|
|
298
|
+
config.attachment_allowed_content_types = [
|
|
299
|
+
"text/plain",
|
|
300
|
+
"text/markdown",
|
|
301
|
+
"text/html"
|
|
302
|
+
]
|
|
303
|
+
config.attachment_max_bytes = 5 * 1024 * 1024
|
|
304
|
+
config.attachment_max_characters = 20_000
|
|
305
|
+
|
|
306
|
+
config.provider_max_attempts = 2
|
|
307
|
+
config.provider_timeout = 30
|
|
308
|
+
config.logger = Rails.logger
|
|
309
|
+
end
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`provider_timeout` applies to each provider attempt. Timed-out attempts are retryable and count toward `provider_max_attempts`.
|
|
313
|
+
|
|
314
|
+
Inject custom `embedding_adapter`, `generation_adapter`, `attachment_extractor`, `authorization_adapter`, or `source_redactor` objects when your application needs different provider or policy behavior. Tests can use deterministic adapters without making network calls.
|
|
315
|
+
|
|
316
|
+
## Vector Stores
|
|
317
|
+
|
|
318
|
+
PostgreSQL with pgvector is the default production path. Maglev also exposes a compact backend contract for applications that need another store:
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
class MyVectorStore < Maglev::VectorStores::Base
|
|
322
|
+
def upsert(documents:)
|
|
323
|
+
# Persist or replace documents by document.id
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def search(vector:, filters:, limit:)
|
|
327
|
+
# Return nearest Maglev::VectorStores::Document objects
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def delete(ids:)
|
|
331
|
+
# Delete documents with these stable IDs
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def delete_by_owner(owner_type:, owner_id:)
|
|
335
|
+
# Delete every document belonging to this owner
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def healthcheck = :ok
|
|
339
|
+
def capabilities = {metadata_filtering: true}
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
Maglev.configure do |config|
|
|
343
|
+
config.vector_store = MyVectorStore.new
|
|
344
|
+
end
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`Maglev::VectorStores::Memory` is useful for tests and local experiments. Custom stores should preserve document metadata filtering and stable document identity semantics.
|
|
348
|
+
|
|
349
|
+
Custom vector stores currently receive model and owner metadata filters, but not the configured authorization scope. `ask` still authorizes retrieved owners individually; direct `search(..., user:)` with a custom store must not be treated as authorization-filtered. Apply tenant or policy filtering inside the custom store, or use the default pgvector path when authorization-scoped search is required.
|
|
350
|
+
|
|
351
|
+
## Operations and Observability
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
bin/rails maglev:status
|
|
355
|
+
bin/rails maglev:reindex[Customer]
|
|
356
|
+
bin/rails maglev:reindex_all
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Reindexing is safe to repeat: unchanged chunks are reused and obsolete chunks are removed. Maglev emits ActiveSupport notifications for indexing start/success/failure, retrieval, generation, and provider retries:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
ActiveSupport::Notifications.subscribe(/\Amaglev\./) do |name, start, finish, id, payload|
|
|
363
|
+
Rails.logger.info(
|
|
364
|
+
event: name,
|
|
365
|
+
duration_ms: ((finish - start) * 1000).round(1),
|
|
366
|
+
**payload
|
|
367
|
+
)
|
|
368
|
+
end
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Security and Boundaries
|
|
372
|
+
|
|
373
|
+
- Treat extracted content as untrusted context. Maglev sanitizes supported HTML sources, but authorization and model exposure remain application responsibilities.
|
|
374
|
+
- Expose only fields and content sources that are appropriate to send to your configured providers.
|
|
375
|
+
- Default attachment limits are 5 MiB and 20,000 extracted characters; the default allowlist covers plain text, Markdown, HTML, and XHTML.
|
|
376
|
+
- The generated v1 migration uses `bigint` owner IDs. UUID-backed owners require a customized migration.
|
|
377
|
+
- Maglev is RAG-only. It does not generate SQL, answer aggregate questions through database computation, expose REST endpoints, provide an admin UI, run agents, or manage conversation memory.
|
|
378
|
+
|
|
379
|
+
## Compatibility
|
|
380
|
+
|
|
381
|
+
The CI matrix tests:
|
|
382
|
+
|
|
383
|
+
| | Supported |
|
|
384
|
+
|---|---|
|
|
385
|
+
| Ruby | 3.2, 3.3 |
|
|
386
|
+
| Rails | 7.1, 8.0 |
|
|
387
|
+
| Database | PostgreSQL with pgvector |
|
|
388
|
+
|
|
389
|
+
Versions outside this matrix may work but are not currently guaranteed.
|
|
390
|
+
|
|
391
|
+
## Development
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
bundle install
|
|
395
|
+
|
|
396
|
+
# PostgreSQL + pgvector integration tests
|
|
397
|
+
MAGLEV_REQUIRE_POSTGRESQL=true MAGLEV_DATABASE=maglev_test bundle exec rspec
|
|
398
|
+
|
|
399
|
+
bundle exec standardrb
|
|
400
|
+
bundle exec rubocop
|
|
401
|
+
bundle exec rake build
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The default test suite uses deterministic fake adapters and never calls a live LLM or embedding provider.
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
Maglev is available as open source under the [MIT License](LICENSE.txt).
|