maglev-rb 0.1.1 → 0.2.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 +4 -4
- data/CHANGELOG.md +46 -0
- data/README.ja.md +618 -0
- data/README.md +533 -248
- data/README.zh-CN.md +457 -250
- data/lib/generators/maglev/install/install_generator.rb +20 -0
- data/lib/generators/maglev/upgrade_index_version/upgrade_index_version_generator.rb +27 -0
- data/lib/generators/maglev/upgrade_source_identity/upgrade_source_identity_generator.rb +52 -0
- data/lib/maglev/active_record_extension.rb +141 -24
- data/lib/maglev/adapters/faraday_client.rb +94 -0
- data/lib/maglev/adapters/faraday_embedding.rb +51 -0
- data/lib/maglev/adapters/faraday_generation.rb +49 -0
- data/lib/maglev/adapters/faraday_planner.rb +88 -0
- data/lib/maglev/answerer.rb +30 -12
- data/lib/maglev/chunker.rb +39 -4
- data/lib/maglev/configuration.rb +66 -1
- data/lib/maglev/content_source_graph.rb +17 -11
- data/lib/maglev/dependency_graph.rb +72 -13
- data/lib/maglev/embedding_adapter.rb +10 -0
- data/lib/maglev/hybrid_candidate_set.rb +25 -0
- data/lib/maglev/hybrid_coordinator.rb +112 -0
- data/lib/maglev/hybrid_result.rb +25 -0
- data/lib/maglev/index_diagnostics.rb +83 -0
- data/lib/maglev/index_identity.rb +70 -0
- data/lib/maglev/index_state.rb +9 -0
- data/lib/maglev/indexer.rb +185 -35
- data/lib/maglev/knowledge_config.rb +27 -5
- data/lib/maglev/knowledge_registry.rb +33 -0
- data/lib/maglev/planner.rb +172 -0
- data/lib/maglev/planner_adapter.rb +25 -0
- data/lib/maglev/planner_evaluation.rb +49 -0
- data/lib/maglev/query_compiler.rb +197 -0
- data/lib/maglev/query_ir.rb +143 -0
- data/lib/maglev/query_validator.rb +311 -0
- data/lib/maglev/railtie.rb +9 -0
- data/lib/maglev/registry.rb +72 -0
- data/lib/maglev/reindex_job.rb +34 -2
- data/lib/maglev/relation_order.rb +16 -0
- data/lib/maglev/request.rb +22 -0
- data/lib/maglev/request_executor.rb +101 -0
- data/lib/maglev/resource_config.rb +222 -0
- data/lib/maglev/response.rb +2 -2
- data/lib/maglev/result.rb +30 -0
- data/lib/maglev/retrieval_outcome.rb +52 -0
- data/lib/maglev/retrieval_result.rb +25 -0
- data/lib/maglev/retriever.rb +282 -27
- data/lib/maglev/router.rb +77 -0
- data/lib/maglev/routing_adapter.rb +25 -0
- data/lib/maglev/schema_compiler.rb +17 -4
- data/lib/maglev/schema_snapshot.rb +159 -0
- data/lib/maglev/search_result.rb +7 -3
- data/lib/maglev/snapshot.rb +21 -1
- data/lib/maglev/snapshot_budget.rb +57 -0
- data/lib/maglev/snapshot_builder.rb +89 -11
- data/lib/maglev/source_extractor.rb +43 -0
- data/lib/maglev/source_fragment.rb +9 -0
- data/lib/maglev/structured_answer_composer.rb +67 -0
- data/lib/maglev/structured_evidence_builder.rb +56 -0
- data/lib/maglev/structured_executor.rb +157 -0
- data/lib/maglev/structured_result.rb +97 -0
- data/lib/maglev/trace.rb +56 -0
- data/lib/maglev/vector_stores/base.rb +12 -0
- data/lib/maglev/vector_stores/document.rb +14 -5
- data/lib/maglev/vector_stores/document_id.rb +27 -0
- data/lib/maglev/vector_stores/memory.rb +68 -6
- data/lib/maglev/vector_stores/metadata_filter.rb +55 -0
- data/lib/maglev/vector_stores/pgvector.rb +94 -7
- data/lib/maglev/version.rb +1 -1
- data/lib/maglev-rb.rb +3 -0
- data/lib/maglev.rb +36 -3
- data/lib/tasks/maglev.rake +43 -0
- metadata +71 -11
- data/lib/maglev/adapters/ruby_llm_attachment_extractor.rb +0 -15
- data/lib/maglev/adapters/ruby_llm_embedding.rb +0 -22
- data/lib/maglev/adapters/ruby_llm_generation.rb +0 -22
- data/lib/maglev/adapters/ruby_llm_provider.rb +0 -64
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7160a0b2627617a578d25f6d23e58557a16eee4ef857b2fe245354f9492b9be1
|
|
4
|
+
data.tar.gz: 1fd9b7ec0765e1f7bbfa3e53ef4141d81bed45f94b962d54b3b036bdd6027464
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41e33c02a81bb65ad035799c8a876bde9c84c7f0369b73baa53f62d95ada885f29b31af0e19d88157834c21ff6fe903cb1290fafa6dcc554305e5daeeb69a694
|
|
7
|
+
data.tar.gz: b5bd0c4637baa038ec0869f9c3ff974403916d6c308060ad1e8ac070334d8e4dd3cbf86535632da1f76a8575e2dd58d87f0e6ce7041f1b52d04f376457104a3d
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. Maglev follows Semantic Versioning.
|
|
4
|
+
|
|
5
|
+
## [0.2.1] - 2026-07-21
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Added the documented `maglev-rb` require entry point and tightened release metadata coverage.
|
|
10
|
+
- Preserved structured query values through planner serialization and compilation, including association paths.
|
|
11
|
+
- Improved resource registration validation and made index diagnostics updates safer.
|
|
12
|
+
|
|
13
|
+
## [0.2.0] - 2026-07-19
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Explicit `maglev_resource` registration for structured query fields, associations, scopes, aggregates, limits, authorization policy, and knowledge sources.
|
|
18
|
+
- Immutable Query IR v1, deterministic validation, ActiveRecord-first compilation on an authorized base relation, bounded read-only execution, structured evidence, and redacted traces.
|
|
19
|
+
- Provider-neutral planning, explicit intent routing, a unified request/result envelope, inspectable source-aware RAG retrieval, and two fixed hybrid workflows.
|
|
20
|
+
- Source identity and index diagnostics with reversible migration generators and deterministic provider-free evaluation fixtures.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Ruby 3.3 is now the minimum supported Ruby version; CI covers Ruby 3.3/4.0 and Rails 7.1/8.0.
|
|
25
|
+
- Vector stores receive validated source/tenant/authorization filters and must preserve atomic owner replacement semantics.
|
|
26
|
+
- `maglev_resource` is the only model DSL. Use its `queryable` block for structured ActiveRecord queries and its `knowledge` block for RAG; the pre-release `has_knowledge` DSL was removed without a compatibility alias.
|
|
27
|
+
|
|
28
|
+
### Security
|
|
29
|
+
|
|
30
|
+
- Structured compilation can only narrow the supplied base relation and rejects SQL, Ruby, Arel, unregistered fields/scopes, writes, locks, and relation widening.
|
|
31
|
+
- Evidence and traces are bounded and redact record values, source text, secrets, and raw provider payloads by default.
|
|
32
|
+
|
|
33
|
+
### Upgrade from 0.1.x
|
|
34
|
+
|
|
35
|
+
1. Upgrade the gem and run `bin/rails generate maglev:upgrade_index_version` if the existing installation has no `index_version` column.
|
|
36
|
+
2. Run `bin/rails generate maglev:upgrade_source_identity` to add source identity, tenant filtering metadata, and index diagnostics state.
|
|
37
|
+
3. Review both generated migrations, adapt owner key types when the application uses UUIDs, and run `bin/rails db:migrate`.
|
|
38
|
+
4. If embedding dimensions changed, migrate the pgvector column and rebuild its HNSW index before reindexing.
|
|
39
|
+
5. Run `bin/rails maglev:reindex_all`. Legacy rows are intentionally unavailable until the full reindex completes.
|
|
40
|
+
6. Replace every `has_knowledge` declaration with `maglev_resource :identifier do ... knowledge do ... end ... end`. No compatibility alias is provided. Model `search` and record/model `ask` remain available on resources that declare `knowledge`.
|
|
41
|
+
|
|
42
|
+
Rollback requires migrating the source-identity migration down, restoring the prior gem version, and performing a full reindex. Never reuse a partially upgraded index across versions.
|
|
43
|
+
|
|
44
|
+
## [0.1.4] - 2026-07-18
|
|
45
|
+
|
|
46
|
+
- Hardened the RAG correctness, lifecycle, and index identity baseline.
|
data/README.ja.md
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
# Maglev
|
|
2
|
+
|
|
3
|
+
[English](README.md) | [简体中文](README.zh-CN.md) | [日本語](README.ja.md)
|
|
4
|
+
|
|
5
|
+
> **Technical translation review pending:** この日本語版は v0.2.1 のコードと英語版 README に同期していますが、公開前に日本語ネイティブによる技術レビューが必要です。英語版 [README.md](README.md) が正本です。
|
|
6
|
+
|
|
7
|
+
[](https://github.com/benjis/maglev/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.ruby-lang.org/)
|
|
9
|
+
[](https://rubyonrails.org/)
|
|
10
|
+
[](LICENSE.txt)
|
|
11
|
+
|
|
12
|
+
Maglev 0.2 は ActiveRecord アプリケーション向けの Rails ネイティブな読み取り専用の知識・クエリレイヤーです。自然言語の質問を三つの明示的なルートで処理します。
|
|
13
|
+
|
|
14
|
+
- **Structured:** 質問 → 検証済み Query IR → 合成可能な
|
|
15
|
+
`ActiveRecord::Relation` または制限付き集約値。
|
|
16
|
+
- **RAG:** 質問 → 認可済みセマンティック検索 → 必要に応じて根拠付き回答。
|
|
17
|
+
- **Hybrid:** 構造化フィルタと RAG 証拠を組み合わせる二つの固定ワークフロー。
|
|
18
|
+
|
|
19
|
+
モデルから公開されるのはアプリケーションが明示した allowlist のみです。構造化コンパイルは呼び出し元の base relation から始まり、それを狭めることしかできません。RAG の検索と回答生成は分離されています。
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
resource_authorizer = ->(_entry, user) { user.account_id == current_account.id }
|
|
23
|
+
result = current_account.invoices.maglev_request(
|
|
24
|
+
"500ドルを超える未払い請求書",
|
|
25
|
+
mode: :structured,
|
|
26
|
+
planner_adapter: planner,
|
|
27
|
+
authorizer: resource_authorizer,
|
|
28
|
+
user: current_user
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
retrieval = SupportTicket.retrieve("解約手続きで止まった顧客", user: current_user)
|
|
32
|
+
answer = SupportTicket.ask("繰り返し発生している解約問題は?", user: current_user)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## はじめに:データ経路を選ぶ
|
|
36
|
+
|
|
37
|
+
Maglev の全アーキテクチャを理解してから使い始める必要はありません。まず、回答に
|
|
38
|
+
必要なデータの種類を選びます。
|
|
39
|
+
|
|
40
|
+
| ユーザーの質問 | 必要なデータ | 宣言 | 呼び出し |
|
|
41
|
+
| --- | --- | --- | --- |
|
|
42
|
+
| 「期限切れ請求書はいくつ?」 | 正確な column、filter、count | `queryable` | `maglev_request(..., mode: :structured)` |
|
|
43
|
+
| 「解約時に顧客が訴える問題は?」 | 本文、comment、attachment | `knowledge` | `retrieve` または `ask` |
|
|
44
|
+
| 「二重請求に触れた open ticket は?」 | 正確な status + semantic text | 両方 | `maglev_request(..., mode: :hybrid)` |
|
|
45
|
+
|
|
46
|
+
基本の mental model は四つです。
|
|
47
|
+
|
|
48
|
+
1. `maglev_resource :support_tickets` は model に安定した Maglev resource 名を付けます。
|
|
49
|
+
2. `queryable` は planner が filter、sort、join、aggregate できる ActiveRecord allowlist です。
|
|
50
|
+
3. `knowledge` は semantic evidence として index する内容を選びます。
|
|
51
|
+
4. 認可と structured query の base `ActiveRecord::Relation` はアプリケーションが
|
|
52
|
+
提供します。Maglev が権限を広げることはありません。
|
|
53
|
+
|
|
54
|
+
## Maglev を選ぶ理由
|
|
55
|
+
|
|
56
|
+
- `Maglev::Railtie` を持つ通常の Ruby gem。Rails Engine や別 API ではありません。
|
|
57
|
+
- ActiveRecord-first の構造化クエリ。不制限の SQL/Ruby を生成・実行しません。
|
|
58
|
+
- フィールド、関連、scope、集約、知識ソースを明示的に登録します。
|
|
59
|
+
- テナント・認可制約はアプリケーション所有の relation が保持します。
|
|
60
|
+
- pgvector、正規化スコア、決定的な予算、検査可能な証拠を備えた source-aware RAG。
|
|
61
|
+
- 不変の plan/result と既定で秘匿化された trace。
|
|
62
|
+
- デフォルトテストは決定的 fake adapter を使い、外部 provider を呼びません。
|
|
63
|
+
|
|
64
|
+
## アーキテクチャ
|
|
65
|
+
|
|
66
|
+
```mermaid
|
|
67
|
+
flowchart TD
|
|
68
|
+
Q["質問 + 登録済みスコープ"] --> R["Router"]
|
|
69
|
+
R --> S["Structured planner"]
|
|
70
|
+
R --> G["RAG retrieval"]
|
|
71
|
+
R --> H["固定 Hybrid coordinator"]
|
|
72
|
+
S --> I["信頼されていない Query IR"]
|
|
73
|
+
I --> V["決定的 validator"]
|
|
74
|
+
V --> C["base relation 上で compile"]
|
|
75
|
+
C --> E["読み取り専用・制限付き実行"]
|
|
76
|
+
G --> X["認可済みセマンティック証拠"]
|
|
77
|
+
H --> E
|
|
78
|
+
H --> X
|
|
79
|
+
E --> O["Evidence + Result"]
|
|
80
|
+
X --> O
|
|
81
|
+
O --> A["任意の根拠付き回答"]
|
|
82
|
+
Q --> T["秘匿化 trace"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Registry は権限境界です。リクエストごとの schema snapshot には認可された登録済みリソースだけが含まれ、レコード値は含まれません。Provider 出力は決定的検証が成功するまで信頼されません。
|
|
86
|
+
|
|
87
|
+
## インストール
|
|
88
|
+
|
|
89
|
+
Ruby 3.3+、Rails 7.1 または 8.0、PostgreSQL、pgvector が必要です。
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# Gemfile
|
|
93
|
+
gem "maglev-rb", "~> 0.2.1"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bundle install
|
|
98
|
+
bin/rails generate maglev:install --embedding-dimensions=1536
|
|
99
|
+
bin/rails db:migrate
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Generator は initializer、`maglev_chunks`、source/tenant metadata、HNSW cosine index、`maglev_index_states` を作成します。Owner が UUID 主キーを使う場合は生成 migration を調整してください。
|
|
103
|
+
|
|
104
|
+
## 設定
|
|
105
|
+
|
|
106
|
+
組み込み embedding と generation クライアントは OpenAI-compatible HTTP API を使います。Planner は既定で OpenAI の `json_schema` response format を使い、未対応の provider では制約の弱い `json_object` format を選択できます。Embedding と generation は別 provider にできます。
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
Maglev.configure do |config|
|
|
110
|
+
config.embedding_provider do |provider|
|
|
111
|
+
provider.url = ENV.fetch("MAGLEV_EMBEDDING_URL", "https://api.openai.com/v1")
|
|
112
|
+
provider.api_key = ENV["MAGLEV_EMBEDDING_API_KEY"]
|
|
113
|
+
provider.model = "text-embedding-3-small"
|
|
114
|
+
provider.dimensions = 1536
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
config.generation_provider do |provider|
|
|
118
|
+
provider.url = ENV.fetch("MAGLEV_GENERATION_URL", "https://api.openai.com/v1")
|
|
119
|
+
provider.api_key = ENV["MAGLEV_GENERATION_API_KEY"]
|
|
120
|
+
provider.model = "gpt-4.1-mini"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
config.planner_adapter = Maglev::Adapters::FaradayPlanner.new
|
|
124
|
+
# json_schema に対応していない provider の場合:
|
|
125
|
+
# config.planner_adapter = Maglev::Adapters::FaradayPlanner.new(response_format: :json_object)
|
|
126
|
+
config.routing_adapter = MyRoutingAdapter.new # mode: :auto の場合のみ必要
|
|
127
|
+
|
|
128
|
+
config.chunk_size = 1000
|
|
129
|
+
config.minimum_similarity = nil
|
|
130
|
+
config.retrieval_max_candidates = 1000
|
|
131
|
+
config.context_max_characters = 4000
|
|
132
|
+
config.context_per_owner_characters = 1200
|
|
133
|
+
|
|
134
|
+
config.snapshot_attribute_max_characters = 20_000
|
|
135
|
+
config.snapshot_related_record_max_characters = 50_000
|
|
136
|
+
config.snapshot_max_characters = 100_000
|
|
137
|
+
config.snapshot_max_chunks = 100
|
|
138
|
+
|
|
139
|
+
config.structured_query_timeout = 5
|
|
140
|
+
config.structured_evidence_max_rows = 100
|
|
141
|
+
config.structured_evidence_max_bytes = 32_768
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
必要に応じて独自の `embedding_adapter`、`generation_adapter`、`planner_adapter`、`routing_adapter`、`attachment_extractor`、`authorization_adapter` を注入できます。
|
|
146
|
+
|
|
147
|
+
## リソースの登録
|
|
148
|
+
|
|
149
|
+
`maglev_resource` が v0.2 の主要 DSL です。Structured と knowledge の capability は独立しており、片方または両方を宣言できます。
|
|
150
|
+
|
|
151
|
+
### コメント付き structured resource
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
class Invoice < ApplicationRecord
|
|
155
|
+
# 通常の Rails association/scope が引き続き source of truth です。
|
|
156
|
+
belongs_to :account
|
|
157
|
+
scope :due_before, ->(date) { where(due_on: ..date) }
|
|
158
|
+
|
|
159
|
+
# :invoices は Maglev plan/request で使う安定した resource identifier です。
|
|
160
|
+
maglev_resource :invoices do
|
|
161
|
+
# Planner が resource の意味を理解するための説明です。
|
|
162
|
+
description "認可されたアカウントに属する請求書"
|
|
163
|
+
synonyms "bills"
|
|
164
|
+
|
|
165
|
+
# この block は structured ActiveRecord query だけを制御します。
|
|
166
|
+
queryable do
|
|
167
|
+
# 実在する database column の正確な filter/sort を許可します。
|
|
168
|
+
# enum は planner が使える status 値も制限します。
|
|
169
|
+
field :status, enum: %w[draft open paid void]
|
|
170
|
+
field :amount, description: "アカウント通貨での請求総額"
|
|
171
|
+
field :due_on, synonyms: ["deadline"]
|
|
172
|
+
field :paid_at
|
|
173
|
+
|
|
174
|
+
# Planner が要求しても sensitive column を明示的に拒否します。
|
|
175
|
+
prohibit :number, :internal_note
|
|
176
|
+
|
|
177
|
+
# 別途登録された :accounts resource への association を許可します。
|
|
178
|
+
association :account, resource: :accounts
|
|
179
|
+
|
|
180
|
+
# 既存 Rails scope と、その typed parameter を許可します。
|
|
181
|
+
scope :due_before,
|
|
182
|
+
parameters: {date: {type: :date, required: true}}
|
|
183
|
+
|
|
184
|
+
# 許可する aggregate function/column を限定します。
|
|
185
|
+
aggregates count: true, sum: [:amount], average: [:amount]
|
|
186
|
+
|
|
187
|
+
# Global/request limit に加えて resource-level ceiling を設定します。
|
|
188
|
+
limits rows: 50, operations: 8, joins: 1
|
|
189
|
+
|
|
190
|
+
# Structured plan ごとに caller の認可を必須にします。
|
|
191
|
+
authorization :required
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# この別 block は semantic indexing と RAG を制御します。
|
|
195
|
+
# 同じ field をここに書いても structured 権限は広がりません。
|
|
196
|
+
knowledge do
|
|
197
|
+
expose :status, :amount, :due_on, :paid_at
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
暗黙に公開されるものはありません。`authorization :required` のリソースは呼び出し元が認可しない限り schema snapshot に入りません。`allow_unscoped_model_queries` は明示的 opt-in で、本当に公開されたデータだけに使ってください。
|
|
204
|
+
|
|
205
|
+
`queryable` は制約された ActiveRecord query contract だけを定義します。
|
|
206
|
+
`knowledge` は RAG の indexing/retrieval source だけを定義します。
|
|
207
|
+
`maglev_resource` は統一 resource 宣言で、どちらか一方または両方を含められます。
|
|
208
|
+
`knowledge` を宣言しない model は `search`、`retrieve`、`ask`、snapshot、
|
|
209
|
+
indexing callback を利用できません。
|
|
210
|
+
|
|
211
|
+
この宣言は schema dump ではなく allowlist です。`field` にない column は Query IR に
|
|
212
|
+
入れず、`expose` などの knowledge source にない値は semantic snapshot に入りません。
|
|
213
|
+
|
|
214
|
+
### 同じ field を両方に宣言する理由
|
|
215
|
+
|
|
216
|
+
同じ column が異なる役割を持つことがあります。
|
|
217
|
+
|
|
218
|
+
- `field :status` は `status = "open"` のような正確な条件を許可します。
|
|
219
|
+
- `expose :status` は `status: open` を snapshot に書き、検索 evidence の context を保ちます。
|
|
220
|
+
|
|
221
|
+
片方の宣言からもう片方は推論されません。ID、日付、enum、金額など正確さが重要な値は
|
|
222
|
+
`queryable`、本文、説明、comment、resolution、attachment text は `knowledge`、status、
|
|
223
|
+
priority、product area のような context field は両方に置くのが一般的です。
|
|
224
|
+
|
|
225
|
+
### RAG の価値が分かる resource
|
|
226
|
+
|
|
227
|
+
答えが一つの column ではなく人間の文章に存在するとき、RAG が役立ちます。この例では
|
|
228
|
+
structured query が open/high-priority ticket を選び、RAG が ticket 本文、comment、
|
|
229
|
+
resolution、添付 log にある「二重請求」のさまざまな表現を理解します。
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
class SupportTicket < ApplicationRecord
|
|
233
|
+
belongs_to :account
|
|
234
|
+
has_many :comments
|
|
235
|
+
has_many_attached :files
|
|
236
|
+
has_rich_text :resolution
|
|
237
|
+
|
|
238
|
+
maglev_resource :support_tickets do
|
|
239
|
+
description "顧客サポート request と調査 evidence"
|
|
240
|
+
|
|
241
|
+
queryable do
|
|
242
|
+
# 正確で typed な structured filter に適した field。
|
|
243
|
+
field :status, enum: %w[open pending resolved closed]
|
|
244
|
+
field :priority, enum: %w[low normal high urgent]
|
|
245
|
+
field :product_area
|
|
246
|
+
field :created_at
|
|
247
|
+
prohibit :requester_email, :internal_notes
|
|
248
|
+
limits rows: 100, operations: 8, joins: 1
|
|
249
|
+
authorization :required
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
knowledge do
|
|
253
|
+
# Exact filter では表せない意味を prose から取得します。
|
|
254
|
+
expose :subject, :body
|
|
255
|
+
|
|
256
|
+
# Queryable field と重ねて evidence に business context を残します。
|
|
257
|
+
expose :status, :priority, :product_area
|
|
258
|
+
|
|
259
|
+
# Related conversation を bounded/deterministic に追加します。
|
|
260
|
+
include_related :comments, depth: 1, limit: 20,
|
|
261
|
+
order: {created_at: :desc}
|
|
262
|
+
|
|
263
|
+
# 対応 attachment text と Action Text content を追加します。
|
|
264
|
+
expose_attached :files
|
|
265
|
+
expose_rich_text :resolution
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`include_related` の対象 model も独自の `maglev_resource ... knowledge` を宣言する
|
|
272
|
+
必要があります。RAG だけなら `queryable` を、structured だけなら `knowledge` を省略します。
|
|
273
|
+
|
|
274
|
+
Index 後、次の三つは異なる目的を持ちます。
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
# Semantic evidence だけを返し、generation provider は呼びません。
|
|
278
|
+
evidence = SupportTicket.retrieve(
|
|
279
|
+
"解約後も二重請求されたと顧客が説明している",
|
|
280
|
+
limit: 10,
|
|
281
|
+
user: current_user
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# 選択された ticket evidence に基づく prose answer。
|
|
285
|
+
answer = SupportTicket.ask(
|
|
286
|
+
"解約後に繰り返される二重請求 pattern は?",
|
|
287
|
+
limit: 5,
|
|
288
|
+
user: current_user
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Database field で正確に filter してから、その集合内を semantic retrieval。
|
|
292
|
+
result = current_account.support_tickets.maglev_request(
|
|
293
|
+
"二重請求を訴える未解決の urgent ticket",
|
|
294
|
+
mode: :hybrid,
|
|
295
|
+
hybrid_plan: :structured_first,
|
|
296
|
+
planner_adapter: planner,
|
|
297
|
+
authorizer: resource_authorizer,
|
|
298
|
+
user: current_user
|
|
299
|
+
)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
標準 attachment extractor は plain text、Markdown、HTML、XHTML を処理します。PDF、Office、OCR、画像、音声、動画にはアプリケーション所有の extractor が必要です。Snapshot、relation、attachment、chunk にはハード上限があります。
|
|
303
|
+
|
|
304
|
+
Provider を呼ばずに公開内容を検査できます。
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
SupportTicket.maglev_schema
|
|
308
|
+
ticket.maglev_snapshot
|
|
309
|
+
ticket.maglev_context_preview(question: "なぜ未解決ですか?")
|
|
310
|
+
ticket.maglev_index_status
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### DSL API リファレンス
|
|
314
|
+
|
|
315
|
+
Resource-level DSL:
|
|
316
|
+
|
|
317
|
+
| DSL | 目的 |
|
|
318
|
+
| --- | --- |
|
|
319
|
+
| `maglev_resource :identifier` | Model に安定した resource identifier を登録します。 |
|
|
320
|
+
| `description "..."` | Record 値を含まない planner 用説明です。 |
|
|
321
|
+
| `synonyms "...", "..."` | Resource の別名です。 |
|
|
322
|
+
| `queryable { ... }` | Structured ActiveRecord capability。宣言は一回だけです。 |
|
|
323
|
+
| `knowledge { ... }` | RAG/indexing capability。宣言は一回だけです。 |
|
|
324
|
+
|
|
325
|
+
`queryable` DSL:
|
|
326
|
+
|
|
327
|
+
| DSL | 目的と option |
|
|
328
|
+
| --- | --- |
|
|
329
|
+
| `field :name` | 実在 column を allowlist。`description:`、`synonyms:`、`enum:`、`sensitive:` を指定できます。Sensitive field は planner schema から除外されます。 |
|
|
330
|
+
| `prohibit :a, :b` | 実在 column を明示的に拒否します。同じ field は allow/prohibit できません。 |
|
|
331
|
+
| `association :account, resource: :accounts` | 登録済み ActiveRecord association path を許可します。`description:`、`synonyms:` を指定でき、target resource も登録が必要です。 |
|
|
332
|
+
| `scope :due_before, parameters: {...}` | 既存 model scope を一つ許可します。Parameter metadata は `type`、`required`、`nullable`、`enum_values`、`minimum`、`maximum`。任意 scope は呼べません。 |
|
|
333
|
+
| `aggregates count: true, sum: [:amount]` | `count`、`sum`、`average`、`minimum`、`maximum` と対象 field を限定します。 |
|
|
334
|
+
| `limits rows:, operations:, joins:` | 正の resource ceiling。最も厳しい configured limit が有効です。 |
|
|
335
|
+
| `authorization :required` | Default。`authorizer` が承認した場合だけ schema snapshot に入ります。 |
|
|
336
|
+
| `authorization :public` | Resource schema を public にします。Record access は supplied relation に制約されます。 |
|
|
337
|
+
| `allow_unscoped_model_queries true` | Base relation なしの structured request を許可します。既定は off です。 |
|
|
338
|
+
|
|
339
|
+
Scope parameter の `type` は `:string`、`:integer`、`:float`、`:decimal`、
|
|
340
|
+
`:boolean`、`:date`、`:datetime`、`:timestamp`、`:time` を受け付けます。未対応の type は resource 登録時に拒否されます。
|
|
341
|
+
|
|
342
|
+
`knowledge` DSL:
|
|
343
|
+
|
|
344
|
+
| DSL | 目的と option |
|
|
345
|
+
| --- | --- |
|
|
346
|
+
| `expose :subject, :body` | 選択した non-nil model attribute を searchable snapshot に追加します。 |
|
|
347
|
+
| `hide :internal_notes` | 公開しない attribute を明示します。同じ attribute は expose/hide できません。 |
|
|
348
|
+
| `tags :support, :customer` | この model の全 snapshot に固定分類 label を追加します。 |
|
|
349
|
+
| `include_related :comments, depth:, limit:` | Bounded related-record snapshot。`inverse:` で reverse association、`order:` で column または `{column: :asc/:desc}` を指定します。 |
|
|
350
|
+
| `expose_attached :files` | Active Storage attachment から抽出した text を追加します。 |
|
|
351
|
+
| `expose_rich_text :resolution` | Action Text attribute の plain text を追加します。 |
|
|
352
|
+
|
|
353
|
+
Database visibility から権限を推論しません。DSL は登録時に検証され、未知の field、
|
|
354
|
+
association、scope、attachment は `Maglev::ConfigurationError` になります。
|
|
355
|
+
|
|
356
|
+
## Structured query
|
|
357
|
+
|
|
358
|
+
Plan と execute は意図的に分離されています。
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
base = current_account.invoices.where(archived: false)
|
|
362
|
+
|
|
363
|
+
plan = Maglev.plan(
|
|
364
|
+
"今月期限で500ドルを超える未払い請求書",
|
|
365
|
+
resource: :invoices,
|
|
366
|
+
base_relation: base,
|
|
367
|
+
authorizer: ->(entry, user) { user.account_id == current_account.id },
|
|
368
|
+
user: current_user,
|
|
369
|
+
constraints: {rows: 25, operations: 8, joins: 1},
|
|
370
|
+
adapter: planner
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
plan.status # :ready / :clarification_required / :unsupported / :invalid
|
|
374
|
+
plan.ir # ready の場合は不変 Maglev::QueryIR::Query
|
|
375
|
+
plan.explanation
|
|
376
|
+
plan.policy_limits
|
|
377
|
+
plan.evidence_requirements
|
|
378
|
+
plan.trace_id
|
|
379
|
+
|
|
380
|
+
result = Maglev.execute(plan)
|
|
381
|
+
result.status # :succeeded
|
|
382
|
+
result.kind # :relation または :aggregate
|
|
383
|
+
result.value # 保護された relation または制限付き scalar
|
|
384
|
+
result.evidence
|
|
385
|
+
result.render
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Record relation は読み取りまで lazy かつ合成可能です。読み取りは `statement_timeout` を持つ PostgreSQL の read-only transaction 内で実行され、返された record は read-only、bulk write は拒否されます。
|
|
389
|
+
|
|
390
|
+
Query IR v1 は登録済み scope、`eq`、`not_eq`、`gt`、`gte`、`lt`、`lte`、`in`、`not_in`、`is_null`、`is_not_null`、`between`、最大二段の join、sort、distinct、limit、count、sum、average、minimum、maximum をサポートします。SQL、Arel、Ruby、任意メソッド、write、lock、ネストした boolean group、window、subquery、`HAVING`、planner 定義 tool は含められません。
|
|
391
|
+
|
|
392
|
+
## RAG:search、retrieve、ask
|
|
393
|
+
|
|
394
|
+
Knowledge-enabled model では直接 API を利用できます。
|
|
395
|
+
|
|
396
|
+
```ruby
|
|
397
|
+
matches = SupportTicket.search(
|
|
398
|
+
"解約処理の失敗",
|
|
399
|
+
limit: 10,
|
|
400
|
+
minimum_similarity: 0.65,
|
|
401
|
+
user: current_user
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
matches.first.owner
|
|
405
|
+
matches.first.source_identity
|
|
406
|
+
matches.first.source_type
|
|
407
|
+
matches.first.similarity
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`search` は owner ごとに最大一つの `SearchResult` を返します。生成なしで完全な retrieval 診断が必要なら `retrieve` を使います。
|
|
411
|
+
|
|
412
|
+
```ruby
|
|
413
|
+
retrieval = SupportTicket.retrieve(
|
|
414
|
+
"解約処理の失敗",
|
|
415
|
+
limit: 10,
|
|
416
|
+
chunks_per_owner: 2,
|
|
417
|
+
user: current_user
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
retrieval.considered
|
|
421
|
+
retrieval.selected
|
|
422
|
+
retrieval.rejected
|
|
423
|
+
retrieval.context
|
|
424
|
+
retrieval.budgets
|
|
425
|
+
retrieval.reasons
|
|
426
|
+
retrieval.timings
|
|
427
|
+
retrieval.trace_id
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
文章回答が必要な場合だけ生成を呼びます。
|
|
431
|
+
|
|
432
|
+
```ruby
|
|
433
|
+
answer = SupportTicket.ask(
|
|
434
|
+
"繰り返し発生する解約エラーは?",
|
|
435
|
+
limit: 5,
|
|
436
|
+
chunks_per_owner: 2,
|
|
437
|
+
minimum_similarity: 0.65,
|
|
438
|
+
user: current_user
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
answer.text
|
|
442
|
+
answer.sources
|
|
443
|
+
answer.metadata
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
認可、similarity、context budget の後に証拠が残らない場合、`ask` は generation provider を呼ばず deterministic な insufficient context を返します。
|
|
447
|
+
|
|
448
|
+
## 統一リクエストとルーティング
|
|
449
|
+
|
|
450
|
+
統一 Result envelope や route 選択が必要なら `Maglev.request`、`Model.maglev_request`、`relation.maglev_request` を使います。
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
result = current_account.invoices.maglev_request(
|
|
454
|
+
"期限切れの未払い請求書はいくつ?",
|
|
455
|
+
mode: :structured,
|
|
456
|
+
planner_adapter: planner,
|
|
457
|
+
authorizer: resource_authorizer,
|
|
458
|
+
user: current_user
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
result.status
|
|
462
|
+
result.route
|
|
463
|
+
result.kind
|
|
464
|
+
result.value
|
|
465
|
+
result.evidence
|
|
466
|
+
result.warnings
|
|
467
|
+
result.trace_id
|
|
468
|
+
result.confidence
|
|
469
|
+
result.reasons
|
|
470
|
+
result.metadata
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Mode は `:structured`、`:rag`、`:hybrid`、`:auto`。明示 mode は routing classifier を呼びません。自動ルーティングには `routing_adapter` が必要で、渡されるのは bounded capability summary のみです。Record 値や source text は渡されません。Application-level request は resources/models または base relation を指定する必要があり、全モデルを自動探索しません。
|
|
474
|
+
|
|
475
|
+
Routing adapter は `classify(question:, capabilities:)` を実装し、例えば
|
|
476
|
+
`{"route" => "structured", "confidence" => 0.9, "reasons" => ["exact fields"]}`
|
|
477
|
+
を返します。Confidence は参考情報であり、権限を与えません。
|
|
478
|
+
|
|
479
|
+
統一 API から生成済み RAG answer が必要なら `answer: true` を指定します。指定しない RAG route は `kind: :semantic_matches` を返します。
|
|
480
|
+
|
|
481
|
+
## Hybrid ワークフロー
|
|
482
|
+
|
|
483
|
+
Hybrid は二つの固定 shape だけをサポートし、queryable と knowledge の両 capability を持つ一つの resource が必要です。
|
|
484
|
+
|
|
485
|
+
```ruby
|
|
486
|
+
result = current_account.support_tickets.maglev_request(
|
|
487
|
+
"解約について言及した未解決 ticket",
|
|
488
|
+
mode: :hybrid,
|
|
489
|
+
hybrid_plan: :structured_first, # または :rag_first
|
|
490
|
+
planner_adapter: planner,
|
|
491
|
+
authorizer: resource_authorizer,
|
|
492
|
+
candidate_limit: 100,
|
|
493
|
+
user: current_user
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
result.kind # :hybrid_answer
|
|
497
|
+
result.value.records
|
|
498
|
+
result.evidence
|
|
499
|
+
result.metadata[:plan_shape]
|
|
500
|
+
result.metadata[:operations]
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Structured-first は record を先に絞ってから検索します。RAG-first は owner 候補を検索してから認可済み relation で検証します。候補の受け渡しは型変換済み主キーだけで、上限があります。各段階で registry、tenant、base relation、authorization を再適用します。Loop や autonomous tool call はありません。
|
|
504
|
+
|
|
505
|
+
## 認可とテナント
|
|
506
|
+
|
|
507
|
+
Base relation が structured/hybrid の権限境界です。
|
|
508
|
+
|
|
509
|
+
```ruby
|
|
510
|
+
current_account.invoices.maglev_request(
|
|
511
|
+
question, mode: :structured, authorizer: resource_authorizer, user: current_user
|
|
512
|
+
)
|
|
513
|
+
policy_scope(Invoice).maglev_request(
|
|
514
|
+
question, mode: :structured, authorizer: resource_authorizer, user: current_user
|
|
515
|
+
) # Pundit
|
|
516
|
+
Invoice.accessible_by(current_ability).maglev_request(
|
|
517
|
+
question, mode: :structured, authorizer: resource_authorizer, user: current_user
|
|
518
|
+
) # CanCanCan
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
RAG 認可には任意 adapter を使います。
|
|
522
|
+
|
|
523
|
+
```ruby
|
|
524
|
+
class MaglevAuthorization
|
|
525
|
+
def scope(model:, user:) = model.where(account_id: user.account_id)
|
|
526
|
+
def authorize(record:, user:) = record.account_id == user.account_id
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
Maglev.configure do |config|
|
|
530
|
+
config.authorization_adapter = MaglevAuthorization.new
|
|
531
|
+
config.tenant_id_resolver = lambda do |record: nil, user: nil|
|
|
532
|
+
(record || user)&.account_id&.to_s
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
RAG authorization adapter がない場合、既定ではすべての record が許可されます。User-scoped retrieval では adapter を設定し、必ず `user:` を渡してください。
|
|
538
|
+
|
|
539
|
+
Store が対応する場合は認可 filter を query に push down し、hydrate 後にも各 record を再確認します。認可 scope が 1,000 owner ID を超える場合は fail closed します。
|
|
540
|
+
|
|
541
|
+
## Index、upgrade、運用
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
bin/rails maglev:status
|
|
545
|
+
bin/rails maglev:reindex[SupportTicket]
|
|
546
|
+
bin/rails maglev:reindex_all
|
|
547
|
+
bin/rails maglev:evaluate_planner
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
関連 transaction の commit 後に `Maglev::ReindexJob` が enqueue されます。Indexing は idempotent で、未変更 chunk を再利用し、owner 単位で検索可能 generation を atomic replace し、安全な status/failure diagnostics を記録します。
|
|
551
|
+
|
|
552
|
+
0.1.x からの upgrade は [CHANGELOG.md](CHANGELOG.md) に従ってください。
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
bin/rails generate maglev:upgrade_index_version
|
|
556
|
+
bin/rails generate maglev:upgrade_source_identity
|
|
557
|
+
bin/rails db:migrate
|
|
558
|
+
bin/rails maglev:reindex_all
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
生成 migration を確認してください。Embedding dimension 変更時は vector column を別途 migrate し、HNSW index を再構築してから full reindex します。現在の index identity を持たない旧 row は検索対象外です。
|
|
562
|
+
|
|
563
|
+
### Index identity と安全な replacement
|
|
564
|
+
|
|
565
|
+
各 chunk は `index_version` を保持します。Fingerprint format version 1 は
|
|
566
|
+
`maglev-index` namespace を使い、embedding model/dimension、adapter ID/version、
|
|
567
|
+
chunking algorithm/size、`application_index_version` を含みます。Custom
|
|
568
|
+
embedding adapter は `maglev_adapter_id` と `maglev_adapter_version` を実装するか、
|
|
569
|
+
`embedding_adapter_id` と `embedding_adapter_version` を設定します。
|
|
570
|
+
|
|
571
|
+
`upgrade_index_version` migration は意図的に nullable な `index_version` を
|
|
572
|
+
追加します。Legacy row は full reindex で現在の identity を得るまで検索不能です。
|
|
573
|
+
Dimension 変更では reindex より先に vector column を migrate します。Owner
|
|
574
|
+
replacement が失敗した場合、直前の完全な generation を保持します。
|
|
575
|
+
|
|
576
|
+
## Vector store 契約
|
|
577
|
+
|
|
578
|
+
PostgreSQL/pgvector が production default です。`Maglev::VectorStores::Memory` は test/local experiment 用です。Custom store は `fetch(ids:)`、`upsert(documents:)`、`search(vector:, filters:, limit:)`、`delete(ids:)`、`delete_by_owner(owner_type:, owner_id:)`、atomic な `replace_owner(owner_type:, owner_id:, documents:)`、`healthcheck`、`capabilities` を実装します。
|
|
579
|
+
|
|
580
|
+
`delete_by_owner` の後に `upsert` する方法は atomic replacement ではありません。同じ owner の concurrent replacement/deletion は linearizable でなければならず、replacement failure は前世代全体を保持しなければなりません。
|
|
581
|
+
|
|
582
|
+
## Trace、証拠、安全境界
|
|
583
|
+
|
|
584
|
+
Maglev Result は bounded evidence と trace ID を持ちます。Trace は identifier、decision、operation 名、limit、安全な timing、warning、error class を記録します。Record 値、source text、prompt、secret、生 provider payload は既定で除外されます。Audit persistence/retention はホストアプリケーションが所有します。
|
|
585
|
+
|
|
586
|
+
Maglev 0.2 が**提供しないもの**:
|
|
587
|
+
|
|
588
|
+
- 自然言語 write/mutation;
|
|
589
|
+
- 不制限 SQL、Ruby、Arel、scope、code execution;
|
|
590
|
+
- autonomous/iterative agent;
|
|
591
|
+
- Rails Engine、REST API、admin UI、必須 frontend;
|
|
592
|
+
- 組み込み PDF/Office/OCR/audio parser;
|
|
593
|
+
- streaming、conversation memory;
|
|
594
|
+
- Qdrant など必須外部 vector service。
|
|
595
|
+
|
|
596
|
+
検索された document は証拠であり、route、permission、Query IR、execution policy を変更できる命令ではありません。
|
|
597
|
+
|
|
598
|
+
## Runtime support と開発
|
|
599
|
+
|
|
600
|
+
| Component | サポート |
|
|
601
|
+
| --- | --- |
|
|
602
|
+
| Ruby | 3.3、4.0 |
|
|
603
|
+
| Rails | 7.1、8.0 |
|
|
604
|
+
| Database | PostgreSQL + pgvector |
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
bundle exec rspec
|
|
608
|
+
bundle exec standardrb
|
|
609
|
+
bundle exec rubocop
|
|
610
|
+
bundle exec rake build
|
|
611
|
+
bundle exec rake maglev:release_audit
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
デフォルト suite は deterministic fake を使い、live LLM/embedding provider を呼びません。
|
|
615
|
+
|
|
616
|
+
## ライセンス
|
|
617
|
+
|
|
618
|
+
Maglev は [MIT License](LICENSE.txt) で提供されます。
|