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
data/README.zh-CN.md
CHANGED
|
@@ -1,48 +1,93 @@
|
|
|
1
1
|
# Maglev
|
|
2
2
|
|
|
3
|
-
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
3
|
+
[English](README.md) | [简体中文](README.zh-CN.md) | [日本語](README.ja.md)
|
|
4
4
|
|
|
5
5
|
[](https://github.com/benjis/maglev/actions/workflows/ci.yml)
|
|
6
|
-
[](https://www.ruby-lang.org/)
|
|
7
7
|
[](https://rubyonrails.org/)
|
|
8
8
|
[](LICENSE.txt)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Maglev 0.2 是面向 ActiveRecord 应用的 Rails 原生只读知识与查询层。应用可以通过三条明确路线回答自然语言问题:
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- **结构化查询:** 问题 → 经过验证的 Query IR → 可组合的
|
|
13
|
+
`ActiveRecord::Relation` 或有界聚合值。
|
|
14
|
+
- **RAG:** 问题 → 经过授权的语义检索 → 可选的有据回答。
|
|
15
|
+
- **混合查询:** 用两种固定流程之一组合结构化筛选与 RAG 证据。
|
|
16
|
+
|
|
17
|
+
模型只暴露应用显式声明的 allowlist。结构化编译始终从调用方提供的 base relation 开始,并且只能继续收窄。RAG 检索与答案生成彼此独立。
|
|
13
18
|
|
|
14
19
|
```ruby
|
|
15
|
-
|
|
20
|
+
resource_authorizer = ->(_entry, user) { user.account_id == current_account.id }
|
|
21
|
+
result = current_account.invoices.maglev_request(
|
|
22
|
+
"金额超过 500 美元的未结发票",
|
|
23
|
+
mode: :structured,
|
|
24
|
+
planner_adapter: planner,
|
|
25
|
+
authorizer: resource_authorizer,
|
|
26
|
+
user: current_user
|
|
27
|
+
)
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
response.sources # 支撑回答的 ActiveRecord 记录和内容分块
|
|
29
|
+
retrieval = SupportTicket.retrieve("取消流程中受阻的客户", user: current_user)
|
|
30
|
+
answer = SupportTicket.ask("反复出现了哪些取消问题?", user: current_user)
|
|
20
31
|
```
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
## 从这里开始:先选择数据路径
|
|
34
|
+
|
|
35
|
+
第一次使用 Maglev 时,不需要先理解完整架构。先判断问题需要哪种数据:
|
|
36
|
+
|
|
37
|
+
| 用户问题 | 所需数据 | 声明 | 调用方式 |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| “有多少发票已经逾期?” | 精确字段、筛选、计数 | `queryable` | `maglev_request(..., mode: :structured)` |
|
|
40
|
+
| “客户取消服务时主要抱怨什么?” | 正文、评论、附件等自由文本 | `knowledge` | `retrieve` 或 `ask` |
|
|
41
|
+
| “哪些未关闭工单提到了重复扣款?” | 精确状态 + 语义文本 | 两者都声明 | `maglev_request(..., mode: :hybrid)` |
|
|
42
|
+
|
|
43
|
+
只需要记住四件事:
|
|
44
|
+
|
|
45
|
+
1. `maglev_resource :support_tickets` 给模型一个稳定的 Maglev 资源名。
|
|
46
|
+
2. `queryable` 是结构化查询 allowlist:规划器只能筛选、排序、关联或聚合这里声明的内容。
|
|
47
|
+
3. `knowledge` 选择哪些内容会变成可语义检索的证据。
|
|
48
|
+
4. 授权仍由应用提供;结构化查询还必须从调用方的
|
|
49
|
+
`ActiveRecord::Relation` 开始。Maglev 不会自行扩大权限。
|
|
23
50
|
|
|
24
51
|
## 为什么选择 Maglev?
|
|
25
52
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
53
|
+
- 普通 Ruby gem + `Maglev::Railtie`,不是 Rails Engine 或独立 API 服务。
|
|
54
|
+
- ActiveRecord-first 结构化查询;不生成或执行不受限 SQL/Ruby。
|
|
55
|
+
- 显式注册可查询字段、关联、scope、聚合和知识来源。
|
|
56
|
+
- 由应用拥有的 relation 携带租户和授权约束。
|
|
57
|
+
- 基于 pgvector 的来源感知 RAG,具有归一化相似度、确定性预算和可检查证据。
|
|
58
|
+
- 不可变计划/结果和默认脱敏 trace。
|
|
59
|
+
- 默认测试使用确定性 fake adapter,不调用线上模型服务。
|
|
33
60
|
|
|
34
|
-
##
|
|
61
|
+
## 架构
|
|
35
62
|
|
|
36
|
-
|
|
63
|
+
```mermaid
|
|
64
|
+
flowchart TD
|
|
65
|
+
Q["问题 + 已注册范围"] --> R["路由器"]
|
|
66
|
+
R --> S["结构化规划器"]
|
|
67
|
+
R --> G["RAG 检索"]
|
|
68
|
+
R --> H["固定混合协调器"]
|
|
69
|
+
S --> I["不受信任的 Query IR"]
|
|
70
|
+
I --> V["确定性验证器"]
|
|
71
|
+
V --> C["在 base relation 上编译"]
|
|
72
|
+
C --> E["只读有界执行"]
|
|
73
|
+
G --> X["已授权语义证据"]
|
|
74
|
+
H --> E
|
|
75
|
+
H --> X
|
|
76
|
+
E --> O["证据 + Result"]
|
|
77
|
+
X --> O
|
|
78
|
+
O --> A["可选的有据答案"]
|
|
79
|
+
Q --> T["脱敏 trace"]
|
|
80
|
+
```
|
|
37
81
|
|
|
38
|
-
|
|
39
|
-
[`pgvector`](https://github.com/pgvector/pgvector) 扩展。
|
|
82
|
+
Registry 是权限边界。一次请求的 schema snapshot 只包含已注册且已授权的资源,绝不包含记录值。Provider 输出在通过确定性验证前一律不受信任。
|
|
40
83
|
|
|
41
|
-
|
|
84
|
+
## 安装
|
|
85
|
+
|
|
86
|
+
Maglev 需要 Ruby 3.3+、Rails 7.1 或 8.0、PostgreSQL 和 pgvector。
|
|
42
87
|
|
|
43
88
|
```ruby
|
|
44
89
|
# Gemfile
|
|
45
|
-
gem "maglev-rb"
|
|
90
|
+
gem "maglev-rb", "~> 0.2.1"
|
|
46
91
|
```
|
|
47
92
|
|
|
48
93
|
```bash
|
|
@@ -51,358 +96,520 @@ bin/rails generate maglev:install --embedding-dimensions=1536
|
|
|
51
96
|
bin/rails db:migrate
|
|
52
97
|
```
|
|
53
98
|
|
|
54
|
-
|
|
99
|
+
Generator 会创建 initializer、`maglev_chunks`、来源/租户元数据、HNSW 余弦索引和 `maglev_index_states` 诊断表。Owner 使用 UUID 主键时请检查并调整生成的迁移。
|
|
100
|
+
|
|
101
|
+
## 配置
|
|
55
102
|
|
|
56
|
-
|
|
103
|
+
内置 embedding 和 generation 客户端使用 OpenAI-compatible HTTP 协议。Planner 默认使用 OpenAI 的 `json_schema` response format;不支持该能力的服务商可以改用约束较弱的 `json_object` format。Embedding 与 generation 可以使用不同服务商。
|
|
57
104
|
|
|
58
105
|
```ruby
|
|
59
|
-
# config/initializers/maglev.rb
|
|
60
106
|
Maglev.configure do |config|
|
|
61
107
|
config.embedding_provider do |provider|
|
|
62
|
-
provider.url = "
|
|
63
|
-
provider.api_key = ENV["
|
|
64
|
-
provider.model = "
|
|
65
|
-
provider.dimensions =
|
|
108
|
+
provider.url = ENV.fetch("MAGLEV_EMBEDDING_URL", "https://api.openai.com/v1")
|
|
109
|
+
provider.api_key = ENV["MAGLEV_EMBEDDING_API_KEY"]
|
|
110
|
+
provider.model = "text-embedding-3-small"
|
|
111
|
+
provider.dimensions = 1536
|
|
66
112
|
end
|
|
67
113
|
|
|
68
114
|
config.generation_provider do |provider|
|
|
69
|
-
provider.url = "https://api.
|
|
70
|
-
provider.api_key =
|
|
71
|
-
provider.model = "
|
|
115
|
+
provider.url = ENV.fetch("MAGLEV_GENERATION_URL", "https://api.openai.com/v1")
|
|
116
|
+
provider.api_key = ENV["MAGLEV_GENERATION_API_KEY"]
|
|
117
|
+
provider.model = "gpt-4.1-mini"
|
|
72
118
|
end
|
|
73
119
|
|
|
120
|
+
config.planner_adapter = Maglev::Adapters::FaradayPlanner.new
|
|
121
|
+
# 用于不支持 json_schema 的服务商:
|
|
122
|
+
# config.planner_adapter = Maglev::Adapters::FaradayPlanner.new(response_format: :json_object)
|
|
123
|
+
config.routing_adapter = MyRoutingAdapter.new # 仅 mode: :auto 需要
|
|
124
|
+
|
|
74
125
|
config.chunk_size = 1000
|
|
126
|
+
config.minimum_similarity = nil
|
|
127
|
+
config.retrieval_max_candidates = 1000
|
|
128
|
+
config.context_max_characters = 4000
|
|
129
|
+
config.context_per_owner_characters = 1200
|
|
130
|
+
|
|
131
|
+
config.snapshot_attribute_max_characters = 20_000
|
|
132
|
+
config.snapshot_related_record_max_characters = 50_000
|
|
133
|
+
config.snapshot_max_characters = 100_000
|
|
134
|
+
config.snapshot_max_chunks = 100
|
|
135
|
+
|
|
136
|
+
config.structured_query_timeout = 5
|
|
137
|
+
config.structured_evidence_max_rows = 100
|
|
138
|
+
config.structured_evidence_max_bytes = 32_768
|
|
75
139
|
end
|
|
76
140
|
```
|
|
77
141
|
|
|
78
|
-
|
|
142
|
+
若内置协议不适用,可以注入自定义 `embedding_adapter`、`generation_adapter`、`planner_adapter`、`routing_adapter`、`attachment_extractor` 或 `authorization_adapter`。
|
|
143
|
+
|
|
144
|
+
## 注册资源
|
|
79
145
|
|
|
80
|
-
|
|
146
|
+
`maglev_resource` 是 v0.2 的主要 DSL。结构化查询能力与知识能力彼此独立,可以同时声明,也可以只声明一种。
|
|
81
147
|
|
|
82
|
-
###
|
|
148
|
+
### 一个带完整注释的结构化资源
|
|
83
149
|
|
|
84
150
|
```ruby
|
|
85
|
-
class
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
151
|
+
class Invoice < ApplicationRecord
|
|
152
|
+
# Rails 关联和 scope 仍然是业务逻辑的事实来源。
|
|
153
|
+
belongs_to :account
|
|
154
|
+
scope :due_before, ->(date) { where(due_on: ..date) }
|
|
155
|
+
|
|
156
|
+
# :invoices 是 Maglev plan/request 使用的稳定资源标识符。
|
|
157
|
+
maglev_resource :invoices do
|
|
158
|
+
# 帮助 planner 理解这个资源代表什么。
|
|
159
|
+
description "属于已授权账户的发票"
|
|
160
|
+
synonyms "bills"
|
|
161
|
+
|
|
162
|
+
# 这个 block 只控制结构化 ActiveRecord 查询。
|
|
163
|
+
queryable do
|
|
164
|
+
# 允许对这些真实数据库字段执行精确筛选和排序。
|
|
165
|
+
# enum 同时限定 planner 可以使用的状态值。
|
|
166
|
+
field :status, enum: %w[draft open paid void]
|
|
167
|
+
field :amount, description: "以账户币种表示的发票总额"
|
|
168
|
+
field :due_on, synonyms: ["deadline", "截止日期"]
|
|
169
|
+
field :paid_at
|
|
170
|
+
|
|
171
|
+
# 即使 planner 请求,也明确禁止这些敏感字段。
|
|
172
|
+
prohibit :number, :internal_note
|
|
173
|
+
|
|
174
|
+
# 允许通过已注册关联连接到另一个 :accounts 资源。
|
|
175
|
+
association :account, resource: :accounts
|
|
176
|
+
|
|
177
|
+
# 允许调用这个已有 Rails scope,并定义其参数类型。
|
|
178
|
+
scope :due_before,
|
|
179
|
+
parameters: {date: {type: :date, required: true}}
|
|
180
|
+
|
|
181
|
+
# 只开放这些聚合函数和字段。
|
|
182
|
+
aggregates count: true, sum: [:amount], average: [:amount]
|
|
183
|
+
|
|
184
|
+
# 资源级上限,会与全局/请求级限制取最严格值。
|
|
185
|
+
limits rows: 50, operations: 8, joins: 1
|
|
186
|
+
|
|
187
|
+
# 每次结构化规划都必须由调用方授权该资源。
|
|
188
|
+
authorization :required
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# 这个独立 block 控制语义索引和 RAG。
|
|
192
|
+
# 字段在这里重复出现,不会扩大结构化查询权限。
|
|
193
|
+
knowledge do
|
|
194
|
+
expose :status, :amount, :due_on, :paid_at
|
|
195
|
+
end
|
|
101
196
|
end
|
|
102
197
|
end
|
|
198
|
+
```
|
|
103
199
|
|
|
104
|
-
|
|
105
|
-
belongs_to :product, inverse_of: :reviews
|
|
200
|
+
Maglev 不会隐式暴露任何内容。`authorization :required` 表示:除非调用方明确授权,否则该资源不会进入请求的 schema snapshot。`allow_unscoped_model_queries` 必须显式启用,且只应供真正公开的数据使用。
|
|
106
201
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```
|
|
202
|
+
`queryable` 只定义受约束的 ActiveRecord 查询契约;`knowledge` 只定义 RAG
|
|
203
|
+
索引和检索来源;`maglev_resource` 是统一资源声明,可以只包含其中一个 block,
|
|
204
|
+
也可以同时包含两者。未声明 `knowledge` 的模型不能使用 `search`、`retrieve`、
|
|
205
|
+
`ask`、snapshot 或索引 callback。
|
|
112
206
|
|
|
113
|
-
|
|
207
|
+
请把声明理解为 allowlist,而不是数据库 schema 的复制。未出现在 `field` 中的列
|
|
208
|
+
不能进入 Query IR;未出现在 `expose` 或其他 knowledge source 中的值不会进入语义
|
|
209
|
+
snapshot。
|
|
114
210
|
|
|
115
|
-
###
|
|
211
|
+
### 为什么一个字段可以同时出现在两个 block 中
|
|
116
212
|
|
|
117
|
-
|
|
213
|
+
同一字段可以承担两种不同职责:
|
|
118
214
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# 或重建所有声明了 has_knowledge 的模型
|
|
122
|
-
bin/rails maglev:reindex_all
|
|
123
|
-
```
|
|
215
|
+
- `field :status` 允许结构化查询生成 `status = "open"` 这样的精确条件。
|
|
216
|
+
- `expose :status` 会把 `status: open` 写入索引 snapshot,让检索证据保留上下文。
|
|
124
217
|
|
|
125
|
-
|
|
218
|
+
声明一边不会自动声明另一边。标识符、日期、枚举和金额适合放入 `queryable`;
|
|
219
|
+
描述、正文、评论、解决记录和附件文本适合放入 `knowledge`;状态、优先级、产品区域
|
|
220
|
+
这类上下文字段则经常需要同时声明。
|
|
126
221
|
|
|
127
|
-
###
|
|
222
|
+
### 一个真正体现 RAG 价值的资源
|
|
128
223
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
limit: 10,
|
|
133
|
-
user: current_user
|
|
134
|
-
)
|
|
224
|
+
当答案存在于人写的语言中,而不是某个精确列里时,RAG 才真正有价值。下面的结构化
|
|
225
|
+
查询可以找出 open/high-priority 工单,而 RAG 可以从工单正文、评论、解决记录和日志
|
|
226
|
+
附件中理解“重复扣款”这类不同措辞。
|
|
135
227
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
228
|
+
```ruby
|
|
229
|
+
class SupportTicket < ApplicationRecord
|
|
230
|
+
belongs_to :account
|
|
231
|
+
has_many :comments
|
|
232
|
+
has_many_attached :files
|
|
233
|
+
has_rich_text :resolution
|
|
234
|
+
|
|
235
|
+
maglev_resource :support_tickets do
|
|
236
|
+
description "客户支持请求及其调查证据"
|
|
237
|
+
|
|
238
|
+
queryable do
|
|
239
|
+
# 适合结构化查询:精确、有类型、便于筛选。
|
|
240
|
+
field :status, enum: %w[open pending resolved closed]
|
|
241
|
+
field :priority, enum: %w[low normal high urgent]
|
|
242
|
+
field :product_area
|
|
243
|
+
field :created_at
|
|
244
|
+
prohibit :requester_email, :internal_notes
|
|
245
|
+
limits rows: 100, operations: 8, joins: 1
|
|
246
|
+
authorization :required
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
knowledge do
|
|
250
|
+
# 正文包含精确筛选无法表达的语义。
|
|
251
|
+
expose :subject, :body
|
|
252
|
+
|
|
253
|
+
# 与 queryable 重叠,使证据保留业务上下文。
|
|
254
|
+
expose :status, :priority, :product_area
|
|
255
|
+
|
|
256
|
+
# 只纳入数量和顺序都确定的关联对话。
|
|
257
|
+
include_related :comments, depth: 1, limit: 20,
|
|
258
|
+
order: {created_at: :desc}
|
|
259
|
+
|
|
260
|
+
# 纳入受支持的附件文本和 Action Text 内容。
|
|
261
|
+
expose_attached :files
|
|
262
|
+
expose_rich_text :resolution
|
|
263
|
+
end
|
|
264
|
+
end
|
|
142
265
|
end
|
|
143
266
|
```
|
|
144
267
|
|
|
145
|
-
|
|
268
|
+
被 `include_related` 使用的关联模型也必须声明自己的
|
|
269
|
+
`maglev_resource ... knowledge`。只需要 RAG 时省略 `queryable`;只需要结构化查询时
|
|
270
|
+
省略 `knowledge`。
|
|
271
|
+
|
|
272
|
+
记录完成索引后,下面三个调用解决不同问题:
|
|
146
273
|
|
|
147
274
|
```ruby
|
|
148
|
-
|
|
149
|
-
|
|
275
|
+
# 只返回语义证据,不调用 generation provider。
|
|
276
|
+
evidence = SupportTicket.retrieve(
|
|
277
|
+
"客户说取消服务后仍被重复扣款",
|
|
278
|
+
limit: 10,
|
|
279
|
+
user: current_user
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# 根据选中的工单证据生成有据可查的自然语言回答。
|
|
283
|
+
answer = SupportTicket.ask(
|
|
284
|
+
"取消服务后反复出现了哪些重复扣款模式?",
|
|
150
285
|
limit: 5,
|
|
151
286
|
user: current_user
|
|
152
287
|
)
|
|
153
288
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
289
|
+
# 先按数据库字段精确筛选,再只在结果集合中做语义检索。
|
|
290
|
+
result = current_account.support_tickets.maglev_request(
|
|
291
|
+
"客户描述被重复扣款的未关闭紧急工单",
|
|
292
|
+
mode: :hybrid,
|
|
293
|
+
hybrid_plan: :structured_first,
|
|
294
|
+
planner_adapter: planner,
|
|
295
|
+
authorizer: resource_authorizer,
|
|
296
|
+
user: current_user
|
|
297
|
+
)
|
|
157
298
|
```
|
|
158
299
|
|
|
159
|
-
|
|
300
|
+
默认附件提取器支持纯文本、Markdown、HTML 和 XHTML。PDF、Office、OCR、图片、音视频解析需要应用自定义 extractor。Snapshot、relation、附件和 chunk 都有硬预算。
|
|
160
301
|
|
|
161
|
-
|
|
302
|
+
无需调用 provider 即可检查暴露内容:
|
|
162
303
|
|
|
163
304
|
```ruby
|
|
164
|
-
|
|
305
|
+
SupportTicket.maglev_schema
|
|
306
|
+
ticket.maglev_snapshot
|
|
307
|
+
ticket.maglev_context_preview(question: "为什么还未解决?")
|
|
308
|
+
ticket.maglev_index_status
|
|
165
309
|
```
|
|
166
310
|
|
|
167
|
-
|
|
311
|
+
### DSL API 参考
|
|
168
312
|
|
|
169
|
-
|
|
170
|
-
review.update!(product: replacement_product)
|
|
171
|
-
# 两个受影响的产品都会加入 Maglev::ReindexJob 队列。
|
|
172
|
-
```
|
|
313
|
+
资源级 DSL:
|
|
173
314
|
|
|
174
|
-
|
|
315
|
+
| DSL | 用途 |
|
|
316
|
+
| --- | --- |
|
|
317
|
+
| `maglev_resource :identifier` | 为模型注册一个稳定的资源标识符。 |
|
|
318
|
+
| `description "..."` | 提供给 planner 的资源说明;不会包含记录值。 |
|
|
319
|
+
| `synonyms "...", "..."` | 资源可能使用的其他名称。 |
|
|
320
|
+
| `queryable { ... }` | 声明结构化 ActiveRecord 能力;只能出现一次。 |
|
|
321
|
+
| `knowledge { ... }` | 声明 RAG/索引能力;只能出现一次。 |
|
|
175
322
|
|
|
176
|
-
|
|
323
|
+
`queryable` DSL:
|
|
177
324
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
325
|
+
| DSL | 用途与选项 |
|
|
326
|
+
| --- | --- |
|
|
327
|
+
| `field :name` | 允许一个真实字段。选项:`description:`、`synonyms:`、`enum:`、`sensitive:`。敏感字段不会进入 planner schema。 |
|
|
328
|
+
| `prohibit :a, :b` | 明确禁止真实字段;同一字段不能既允许又禁止。 |
|
|
329
|
+
| `association :account, resource: :accounts` | 允许一个已注册 ActiveRecord 关联路径。支持 `description:`、`synonyms:`;目标资源也必须注册。 |
|
|
330
|
+
| `scope :due_before, parameters: {...}` | 允许一个已有 model scope。参数 metadata 支持 `type`、`required`、`nullable`、`enum_values`、`minimum`、`maximum`。其他 scope 不能调用。 |
|
|
331
|
+
| `aggregates count: true, sum: [:amount]` | 允许 `count`、`sum`、`average`、`minimum`、`maximum`;字段列表进一步限制聚合目标。 |
|
|
332
|
+
| `limits rows:, operations:, joins:` | 设置正整数资源上限;最终使用所有配置中最严格的限制。 |
|
|
333
|
+
| `authorization :required` | 默认值。只有 `authorizer` 批准后,资源才进入 schema snapshot。 |
|
|
334
|
+
| `authorization :public` | 资源 schema 公开;记录访问仍受传入 relation 约束。 |
|
|
335
|
+
| `allow_unscoped_model_queries true` | 允许没有 base relation 的结构化请求。默认关闭,只应用于真正公开的数据。 |
|
|
336
|
+
|
|
337
|
+
Scope 参数 `type` 支持 `:string`、`:integer`、`:float`、`:decimal`、
|
|
338
|
+
`:boolean`、`:date`、`:datetime`、`:timestamp` 和 `:time`。注册资源时会拒绝不支持的类型。
|
|
192
339
|
|
|
193
|
-
|
|
194
|
-
2. Maglev 根据允许的属性、关联记录、附件、富文本和标签生成确定性的文本快照。
|
|
195
|
-
3. 快照被拆成大小受限的分块,并通过配置的适配器生成嵌入向量。
|
|
196
|
-
4. 分块被写入向量存储;默认存储使用 PostgreSQL 和 pgvector。
|
|
197
|
-
5. `search` 为查询生成嵌入,并执行基于余弦距离的近邻检索。
|
|
198
|
-
6. `ask` 在上下文预算内组装分块,构建有依据的提示词,并返回包含来源元数据的答案。
|
|
199
|
-
7. Rails 回调会沿对象图传播已声明记录的变化,并为受影响的所有者安排重新索引。
|
|
340
|
+
`knowledge` DSL:
|
|
200
341
|
|
|
201
|
-
|
|
342
|
+
| DSL | 用途与选项 |
|
|
343
|
+
| --- | --- |
|
|
344
|
+
| `expose :subject, :body` | 把指定的非 nil 模型字段加入可搜索 snapshot。 |
|
|
345
|
+
| `hide :internal_notes` | 明确记录不得暴露的字段;同一字段不能同时 expose 和 hide。 |
|
|
346
|
+
| `tags :support, :customer` | 给该模型的每个 snapshot 添加固定分类标签。 |
|
|
347
|
+
| `include_related :comments, depth:, limit:` | 加入有界关联记录 snapshot。`inverse:` 可指定不明显的反向关联;`order:` 接受字段或 `{field: :asc/:desc}`。 |
|
|
348
|
+
| `expose_attached :files` | 加入指定 Active Storage 附件中提取出的文本。 |
|
|
349
|
+
| `expose_rich_text :resolution` | 加入指定 Action Text 字段的纯文本。 |
|
|
202
350
|
|
|
203
|
-
|
|
351
|
+
Maglev 不会根据数据库可见性自动推断权限。DSL 会在注册时验证;未知字段、关联、
|
|
352
|
+
scope 或附件会立即抛出 `Maglev::ConfigurationError`。
|
|
204
353
|
|
|
205
|
-
|
|
354
|
+
## 结构化查询
|
|
206
355
|
|
|
207
|
-
|
|
356
|
+
规划与执行刻意分离。
|
|
208
357
|
|
|
209
358
|
```ruby
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
359
|
+
base = current_account.invoices.where(archived: false)
|
|
360
|
+
|
|
361
|
+
plan = Maglev.plan(
|
|
362
|
+
"本月到期、金额超过 500 美元的未结发票",
|
|
363
|
+
resource: :invoices,
|
|
364
|
+
base_relation: base,
|
|
365
|
+
authorizer: ->(entry, user) { user.account_id == current_account.id },
|
|
366
|
+
user: current_user,
|
|
367
|
+
constraints: {rows: 25, operations: 8, joins: 1},
|
|
368
|
+
adapter: planner
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
plan.status # :ready / :clarification_required / :unsupported / :invalid
|
|
372
|
+
plan.ir # ready 时为不可变 Maglev::QueryIR::Query
|
|
373
|
+
plan.explanation
|
|
374
|
+
plan.policy_limits # 实际生效的 rows/operations/joins/complexity
|
|
375
|
+
plan.evidence_requirements
|
|
376
|
+
plan.trace_id
|
|
377
|
+
|
|
378
|
+
result = Maglev.execute(plan)
|
|
379
|
+
result.status # :succeeded
|
|
380
|
+
result.kind # :relation 或 :aggregate
|
|
381
|
+
result.value # 受保护的 relation 或有界 scalar
|
|
382
|
+
result.evidence
|
|
383
|
+
result.render
|
|
214
384
|
```
|
|
215
385
|
|
|
216
|
-
|
|
386
|
+
记录 relation 在读取前保持 lazy 和可组合。读取发生在带 `statement_timeout` 的 PostgreSQL 只读事务中;返回记录只读,批量写操作会被拒绝。
|
|
387
|
+
|
|
388
|
+
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、任意方法、写操作、锁、嵌套布尔组、窗口函数、子查询、`HAVING` 或规划器自定义工具。
|
|
217
389
|
|
|
218
|
-
|
|
390
|
+
## RAG:search、retrieve 与 ask
|
|
219
391
|
|
|
220
|
-
|
|
392
|
+
知识资源仍然可以直接使用模型 API。
|
|
221
393
|
|
|
222
394
|
```ruby
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
395
|
+
matches = SupportTicket.search(
|
|
396
|
+
"取消流程故障",
|
|
397
|
+
limit: 10,
|
|
398
|
+
minimum_similarity: 0.65,
|
|
399
|
+
user: current_user
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
matches.first.owner
|
|
403
|
+
matches.first.source_identity
|
|
404
|
+
matches.first.source_type
|
|
405
|
+
matches.first.similarity
|
|
227
406
|
```
|
|
228
407
|
|
|
229
|
-
|
|
408
|
+
`search` 每个 owner 最多返回一个 `SearchResult`。需要完整、无生成的检索诊断时使用 `retrieve`:
|
|
409
|
+
|
|
410
|
+
```ruby
|
|
411
|
+
retrieval = SupportTicket.retrieve(
|
|
412
|
+
"取消流程故障",
|
|
413
|
+
limit: 10,
|
|
414
|
+
chunks_per_owner: 2,
|
|
415
|
+
user: current_user
|
|
416
|
+
)
|
|
230
417
|
|
|
231
|
-
|
|
418
|
+
retrieval.considered
|
|
419
|
+
retrieval.selected
|
|
420
|
+
retrieval.rejected
|
|
421
|
+
retrieval.context
|
|
422
|
+
retrieval.budgets
|
|
423
|
+
retrieval.reasons
|
|
424
|
+
retrieval.timings
|
|
425
|
+
retrieval.trace_id
|
|
426
|
+
```
|
|
232
427
|
|
|
233
|
-
|
|
428
|
+
只有需要自然语言答案时才调用生成:
|
|
234
429
|
|
|
235
430
|
```ruby
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
431
|
+
answer = SupportTicket.ask(
|
|
432
|
+
"哪些取消故障反复出现?",
|
|
433
|
+
limit: 5,
|
|
434
|
+
chunks_per_owner: 2,
|
|
435
|
+
minimum_similarity: 0.65,
|
|
436
|
+
user: current_user
|
|
241
437
|
)
|
|
242
|
-
|
|
243
|
-
|
|
438
|
+
|
|
439
|
+
answer.text
|
|
440
|
+
answer.sources
|
|
441
|
+
answer.metadata
|
|
244
442
|
```
|
|
245
443
|
|
|
246
|
-
|
|
444
|
+
若授权、相似度或上下文预算过滤掉全部证据,`ask` 会返回确定性的 insufficient context,且不调用 generation provider。
|
|
247
445
|
|
|
248
|
-
|
|
446
|
+
## 统一请求与路由
|
|
249
447
|
|
|
250
|
-
|
|
251
|
-
class MaglevAuthorization
|
|
252
|
-
def scope(model:, user:)
|
|
253
|
-
model.accessible_by(user)
|
|
254
|
-
end
|
|
448
|
+
需要统一 Result envelope 或路线选择时,使用 `Maglev.request`、`Model.maglev_request` 或 `relation.maglev_request`。
|
|
255
449
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
450
|
+
```ruby
|
|
451
|
+
result = current_account.invoices.maglev_request(
|
|
452
|
+
"有多少未结发票已经逾期?",
|
|
453
|
+
mode: :structured,
|
|
454
|
+
planner_adapter: planner,
|
|
455
|
+
authorizer: resource_authorizer,
|
|
456
|
+
user: current_user
|
|
457
|
+
)
|
|
260
458
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
459
|
+
result.status
|
|
460
|
+
result.route
|
|
461
|
+
result.kind
|
|
462
|
+
result.value
|
|
463
|
+
result.evidence
|
|
464
|
+
result.warnings
|
|
465
|
+
result.trace_id
|
|
466
|
+
result.confidence
|
|
467
|
+
result.reasons
|
|
468
|
+
result.metadata
|
|
264
469
|
```
|
|
265
470
|
|
|
266
|
-
|
|
471
|
+
Mode 包括 `:structured`、`:rag`、`:hybrid`、`:auto`。显式 mode 永不调用路由 classifier。自动路由需要 `routing_adapter`,并且只会收到有界 capability summary,不会收到记录值或来源正文。应用级请求必须提供 resources/models 或 base relation;Maglev 永远不会扫描所有应用模型。
|
|
267
472
|
|
|
268
|
-
|
|
269
|
-
|
|
473
|
+
Routing adapter 实现 `classify(question:, capabilities:)`,并返回例如
|
|
474
|
+
`{"route" => "structured", "confidence" => 0.9, "reasons" => ["exact fields"]}`
|
|
475
|
+
的结果。Confidence 仅供参考,绝不授予权限。
|
|
270
476
|
|
|
271
|
-
|
|
477
|
+
通过统一 API 获得生成式 RAG 答案时传入 `answer: true`;否则 RAG 路线返回 `kind: :semantic_matches`。
|
|
272
478
|
|
|
273
|
-
|
|
479
|
+
## 混合流程
|
|
274
480
|
|
|
275
|
-
|
|
481
|
+
Hybrid 只支持两种固定 shape,并要求一个同时声明 queryable 与 knowledge 的资源。
|
|
276
482
|
|
|
277
483
|
```ruby
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
484
|
+
result = current_account.support_tickets.maglev_request(
|
|
485
|
+
"提到取消问题的未结工单",
|
|
486
|
+
mode: :hybrid,
|
|
487
|
+
hybrid_plan: :structured_first, # 或 :rag_first
|
|
488
|
+
planner_adapter: planner,
|
|
489
|
+
authorizer: resource_authorizer,
|
|
490
|
+
candidate_limit: 100,
|
|
491
|
+
user: current_user
|
|
492
|
+
)
|
|
293
493
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
494
|
+
result.kind # :hybrid_answer
|
|
495
|
+
result.value.records
|
|
496
|
+
result.evidence # 带 structured/RAG provenance
|
|
497
|
+
result.metadata[:plan_shape]
|
|
498
|
+
result.metadata[:operations]
|
|
310
499
|
```
|
|
311
500
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
当应用需要不同的模型提供商或策略行为时,可注入自定义的 `embedding_adapter`、`generation_adapter`、`attachment_extractor`、`authorization_adapter` 或 `source_redactor`。测试环境可以使用确定性适配器,全程不发起网络请求。
|
|
501
|
+
Structured-first 先筛选记录,再在候选中检索;RAG-first 先检索 owner,再通过授权 relation 验证。候选项只传递有界且经过类型转换的主键;每个阶段都会重新应用注册、租户、base relation 和授权约束。系统不会循环或自主调用工具。
|
|
315
502
|
|
|
316
|
-
##
|
|
503
|
+
## 授权与租户
|
|
317
504
|
|
|
318
|
-
|
|
505
|
+
Base relation 是 structured 和 hybrid 的权限边界:
|
|
319
506
|
|
|
320
507
|
```ruby
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
# 删除具有这些稳定 ID 的文档
|
|
332
|
-
end
|
|
508
|
+
current_account.invoices.maglev_request(
|
|
509
|
+
question, mode: :structured, authorizer: resource_authorizer, user: current_user
|
|
510
|
+
)
|
|
511
|
+
policy_scope(Invoice).maglev_request(
|
|
512
|
+
question, mode: :structured, authorizer: resource_authorizer, user: current_user
|
|
513
|
+
) # Pundit
|
|
514
|
+
Invoice.accessible_by(current_ability).maglev_request(
|
|
515
|
+
question, mode: :structured, authorizer: resource_authorizer, user: current_user
|
|
516
|
+
) # CanCanCan
|
|
517
|
+
```
|
|
333
518
|
|
|
334
|
-
|
|
335
|
-
# 删除属于该所有者的所有文档
|
|
336
|
-
end
|
|
519
|
+
RAG 授权使用可选 adapter:
|
|
337
520
|
|
|
338
|
-
|
|
339
|
-
|
|
521
|
+
```ruby
|
|
522
|
+
class MaglevAuthorization
|
|
523
|
+
def scope(model:, user:) = model.where(account_id: user.account_id)
|
|
524
|
+
def authorize(record:, user:) = record.account_id == user.account_id
|
|
340
525
|
end
|
|
341
526
|
|
|
342
527
|
Maglev.configure do |config|
|
|
343
|
-
config.
|
|
528
|
+
config.authorization_adapter = MaglevAuthorization.new
|
|
529
|
+
config.tenant_id_resolver = lambda do |record: nil, user: nil|
|
|
530
|
+
(record || user)&.account_id&.to_s
|
|
531
|
+
end
|
|
344
532
|
end
|
|
345
533
|
```
|
|
346
534
|
|
|
347
|
-
`
|
|
535
|
+
没有 RAG authorization adapter 时,默认允许所有记录。所有用户范围内的检索都应配置 adapter 并传入 `user:`。
|
|
348
536
|
|
|
349
|
-
|
|
537
|
+
存储支持时会下推授权过滤;hydrate 后仍会重新检查每条记录。授权 scope 超过 1,000 个 owner ID 时会 fail closed。
|
|
350
538
|
|
|
351
|
-
##
|
|
539
|
+
## 索引、升级与运维
|
|
352
540
|
|
|
353
541
|
```bash
|
|
354
542
|
bin/rails maglev:status
|
|
355
|
-
bin/rails maglev:reindex[
|
|
543
|
+
bin/rails maglev:reindex[SupportTicket]
|
|
356
544
|
bin/rails maglev:reindex_all
|
|
545
|
+
bin/rails maglev:evaluate_planner
|
|
357
546
|
```
|
|
358
547
|
|
|
359
|
-
|
|
548
|
+
相关事务提交后 callback 会入队 `Maglev::ReindexJob`。索引操作幂等、复用未变化 chunk、原子替换单个 owner 的完整可搜索代际,并记录安全的状态和失败诊断。
|
|
360
549
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
end
|
|
550
|
+
从 0.1.x 升级请遵循 [CHANGELOG.md](CHANGELOG.md):
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
bin/rails generate maglev:upgrade_index_version
|
|
554
|
+
bin/rails generate maglev:upgrade_source_identity
|
|
555
|
+
bin/rails db:migrate
|
|
556
|
+
bin/rails maglev:reindex_all
|
|
369
557
|
```
|
|
370
558
|
|
|
371
|
-
|
|
559
|
+
请检查生成的迁移。Embedding 维度变化时需要单独修改 vector 列、重建 HNSW 索引,再完成全量 reindex。旧记录在具有当前 index identity 前不会参与检索。
|
|
372
560
|
|
|
373
|
-
|
|
374
|
-
- 只公开适合发送给所配置模型提供商的字段和内容源。
|
|
375
|
-
- 默认附件限制为 5 MiB 和 20,000 个提取字符;默认允许纯文本、Markdown、HTML 和 XHTML。
|
|
376
|
-
- v1 生成迁移使用 `bigint` 所有者 ID。使用 UUID 的模型需要自定义迁移。
|
|
377
|
-
- Maglev 只负责 RAG。它不会生成 SQL、通过数据库计算回答聚合问题、公开 REST 端点、提供管理界面、运行 Agent 或管理对话记忆。
|
|
561
|
+
### Index identity 与安全替换
|
|
378
562
|
|
|
379
|
-
|
|
563
|
+
每个 chunk 都保存 `index_version`。Fingerprint 格式版本 1 使用
|
|
564
|
+
`maglev-index` namespace,并覆盖 embedding 模型/维度、adapter ID/版本、
|
|
565
|
+
chunking 算法/大小和 `application_index_version`。自定义 embedding adapter
|
|
566
|
+
应实现 `maglev_adapter_id` 与 `maglev_adapter_version`,或配置
|
|
567
|
+
`embedding_adapter_id` 与 `embedding_adapter_version`。
|
|
380
568
|
|
|
381
|
-
|
|
569
|
+
`upgrade_index_version` 迁移会有意添加可空的 `index_version`。Legacy row
|
|
570
|
+
在全量 reindex 获得当前 identity 前不可检索。维度变化时必须先迁移 vector
|
|
571
|
+
列,再执行 reindex。Owner 替换失败时必须保留上一代完整内容。
|
|
382
572
|
|
|
383
|
-
|
|
384
|
-
|---|---|
|
|
385
|
-
| Ruby | 3.2、3.3 |
|
|
386
|
-
| Rails | 7.1、8.0 |
|
|
387
|
-
| 数据库 | PostgreSQL + pgvector |
|
|
573
|
+
## Vector store 契约
|
|
388
574
|
|
|
389
|
-
|
|
575
|
+
PostgreSQL/pgvector 是生产默认方案;`Maglev::VectorStores::Memory` 适合测试和本地实验。自定义 store 实现 `fetch(ids:)`、`upsert(documents:)`、`search(vector:, filters:, limit:)`、`delete(ids:)`、`delete_by_owner(owner_type:, owner_id:)`、原子的 `replace_owner(owner_type:, owner_id:, documents:)`、`healthcheck` 和 `capabilities`。
|
|
390
576
|
|
|
391
|
-
|
|
577
|
+
`delete_by_owner` 后接 `upsert` 不是原子替换。同一 owner 的并发替换/删除必须线性化;替换失败必须保留上一代完整内容。
|
|
392
578
|
|
|
393
|
-
|
|
394
|
-
bundle install
|
|
579
|
+
## Trace、证据与安全边界
|
|
395
580
|
|
|
396
|
-
|
|
397
|
-
MAGLEV_REQUIRE_POSTGRESQL=true MAGLEV_DATABASE=maglev_test bundle exec rspec
|
|
581
|
+
Maglev Result 包含有界证据和 trace ID。Trace 记录标识符、决策、操作名、限制、安全计时、警告和错误类;默认排除记录值、来源正文、prompt、密钥和原始 provider payload。持久化和保留策略由宿主应用负责。
|
|
398
582
|
|
|
583
|
+
Maglev 0.2 **不提供**:
|
|
584
|
+
|
|
585
|
+
- 自然语言写操作或 mutation;
|
|
586
|
+
- 不受限 SQL、Ruby、Arel、scope 或代码执行;
|
|
587
|
+
- 自主/迭代 Agent;
|
|
588
|
+
- Rails Engine、REST API、管理 UI 或强制前端;
|
|
589
|
+
- 内置 PDF/Office/OCR/音频解析;
|
|
590
|
+
- streaming 或对话记忆;
|
|
591
|
+
- Qdrant 或其他强制外部向量服务。
|
|
592
|
+
|
|
593
|
+
检索文档只是证据,绝不是能够改变路线、权限、Query IR 或执行策略的指令。
|
|
594
|
+
|
|
595
|
+
## 运行环境支持与开发
|
|
596
|
+
|
|
597
|
+
| 组件 | 支持版本 |
|
|
598
|
+
| --- | --- |
|
|
599
|
+
| Ruby | 3.3、4.0 |
|
|
600
|
+
| Rails | 7.1、8.0 |
|
|
601
|
+
| 数据库 | PostgreSQL + pgvector |
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
bundle exec rspec
|
|
399
605
|
bundle exec standardrb
|
|
400
606
|
bundle exec rubocop
|
|
401
607
|
bundle exec rake build
|
|
608
|
+
bundle exec rake maglev:release_audit
|
|
402
609
|
```
|
|
403
610
|
|
|
404
|
-
|
|
611
|
+
默认测试使用确定性 fake,不调用线上 LLM 或 embedding provider。
|
|
405
612
|
|
|
406
613
|
## 许可证
|
|
407
614
|
|
|
408
|
-
Maglev
|
|
615
|
+
Maglev 使用 [MIT License](LICENSE.txt)。
|