smart_rag 0.1.0 → 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/.env.example +252 -0
- data/.rspec +2 -0
- data/AGENTS.md +33 -0
- data/API_DOCUMENTATION.md +828 -0
- data/CHANGELOG.md +16 -1
- data/ER-diagram.mmd +144 -0
- data/Gemfile +50 -0
- data/Gemfile.lock +398 -0
- data/Hybrid_Reranking.md +171 -0
- data/README.en.md +420 -28
- data/README.md +534 -63
- data/Rakefile +268 -0
- data/SETUP_GUIDE.md +650 -0
- data/SmartChunking.md +180 -0
- data/USAGE_EXAMPLES.md +1002 -0
- data/config/llm_config.yml +4 -2
- data/config/smart_rag.yml +45 -1
- data/config.ru +15 -0
- data/db/migrations/006_create_text_search_configs.rb +3 -2
- data/db/migrations/008_create_embeddings.rb +5 -4
- data/db/migrations/012_add_metadata_to_source_sections.rb +11 -0
- data/db/migrations/013_create_media_jobs.rb +25 -0
- data/db/migrations/014_add_media_job_operations_indexes.rb +11 -0
- data/db/migrations/015_add_media_leases_and_objects.rb +80 -0
- data/db/migrations/016_add_document_principals_and_staging_references.rb +38 -0
- data/db/migrations/017_add_media_job_request_fingerprint.rb +48 -0
- data/db/seeds/text_search_configs.sql +3 -3
- data/design.md +1057 -0
- data/docs/API_DOCUMENTATION.md +838 -0
- data/docs/DOCUMENTATION_INDEX.en.md +60 -0
- data/docs/DOCUMENTATION_INDEX.md +65 -0
- data/docs/FIX_SUMMARY.md +256 -0
- data/docs/FIX_SUMMARY_COMPLETE.md +273 -0
- data/docs/Hybrid_Reranking.md +171 -0
- data/docs/MIGRATION_GUIDE.md +151 -0
- data/docs/PERFORMANCE_GUIDE.md +58 -0
- data/docs/SETUP_GUIDE.md +659 -0
- data/docs/SmartChunking.md +180 -0
- data/docs/USAGE_EXAMPLES.md +1008 -0
- data/docs/design.md +1057 -0
- data/docs/evidence_pack.md +211 -0
- data/docs/requirements.md +376 -0
- data/docs/retrieval_plan.md +251 -0
- data/docs/smartrag_improvement_plan.md +201 -0
- data/docs/smartrag_refactor.md +216 -0
- data/docs/todo.md +931 -0
- data/examples/common.rb +1 -1
- data/exe/smart-rag-db +163 -0
- data/exe/smart-rag-media-worker +34 -0
- data/lib/smart_rag/config.rb +12 -0
- data/lib/smart_rag/core/document_processor.rb +80 -16
- data/lib/smart_rag/core/local_content_store.rb +51 -0
- data/lib/smart_rag/core/media_extractors.rb +140 -0
- data/lib/smart_rag/core/media_job_queue.rb +353 -0
- data/lib/smart_rag/core/media_metadata_extractor.rb +188 -0
- data/lib/smart_rag/core/media_object_registry.rb +79 -0
- data/lib/smart_rag/core/media_processor.rb +228 -0
- data/lib/smart_rag/core/media_safety_policy.rb +61 -0
- data/lib/smart_rag/core/s3_content_store.rb +78 -0
- data/lib/smart_rag/core/transcript_normalizer.rb +44 -0
- data/lib/smart_rag/core/video_semantic_extractor.rb +130 -0
- data/lib/smart_rag/http_access_policy.rb +86 -0
- data/lib/smart_rag/http_app.rb +188 -0
- data/lib/smart_rag/models/embedding.rb +1 -1
- data/lib/smart_rag/models/research_topic.rb +1 -1
- data/lib/smart_rag/models/research_topic_section.rb +5 -0
- data/lib/smart_rag/models/research_topic_tag.rb +5 -0
- data/lib/smart_rag/models/search_log.rb +1 -1
- data/lib/smart_rag/models/section_fts.rb +5 -0
- data/lib/smart_rag/models/section_tag.rb +5 -0
- data/lib/smart_rag/models/source_document.rb +1 -1
- data/lib/smart_rag/models/source_section.rb +1 -1
- data/lib/smart_rag/models/tag.rb +1 -1
- data/lib/smart_rag/models/text_search_config.rb +5 -0
- data/lib/smart_rag/retrieve.rb +72 -1
- data/lib/smart_rag/services/embedding_service.rb +1 -1
- data/lib/smart_rag/services/fulltext_search_service.rb +11 -13
- data/lib/smart_rag/services/hybrid_search_service.rb +15 -11
- data/lib/smart_rag/services/summarization_service.rb +1 -1
- data/lib/smart_rag/services/tag_service.rb +1 -1
- data/lib/smart_rag/version.rb +1 -1
- data/lib/smart_rag.rb +264 -30
- data/patch_language.rb +27 -0
- data/requirements.md +376 -0
- data/source_documents_export.json +11072 -0
- data/todo.md +931 -0
- data/workers/analyze_content.rb +6 -2
- data/workers/get_embedding.rb +1 -1
- metadata +151 -38
data/design.md
ADDED
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
# SmartRAG 设计文档
|
|
2
|
+
|
|
3
|
+
## 1. 架构概述
|
|
4
|
+
|
|
5
|
+
SmartRAG 被设计为提供检索增强生成功能的模块化 Ruby gem。架构采用分层方法,关注点分离清晰。
|
|
6
|
+
|
|
7
|
+
系统采用混合检索架构,融合向量检索(语义理解)和全文检索(精确匹配)两种搜索方式,利用 RRF(Reciprocal Rank Fusion)算法优化搜索结果。
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
smart_rag/
|
|
11
|
+
├── lib/
|
|
12
|
+
│ ├── smart_rag/
|
|
13
|
+
│ │ ├── core/ # 核心功能
|
|
14
|
+
│ │ │ ├── embedding.rb # 向量嵌入管理
|
|
15
|
+
│ │ │ ├── query_processor.rb # 查询处理器(支持混合检索)
|
|
16
|
+
│ │ │ └── document_processor.rb # 文档处理器
|
|
17
|
+
│ │ ├── models/ # 数据库模型
|
|
18
|
+
│ │ │ ├── embedding.rb
|
|
19
|
+
│ │ │ ├── source_document.rb # 源文档模型
|
|
20
|
+
│ │ │ ├── source_section.rb # 文档片段模型
|
|
21
|
+
│ │ │ ├── tag.rb # 标签模型
|
|
22
|
+
│ │ │ ├── section_fts.rb # 全文检索专用表模型
|
|
23
|
+
│ │ │ └── text_search_config.rb # 语言配置模型
|
|
24
|
+
│ │ ├── services/ # 服务层
|
|
25
|
+
│ │ │ ├── embedding_service.rb # 嵌入生成服务
|
|
26
|
+
│ │ │ ├── vector_search_service.rb # 向量检索服务
|
|
27
|
+
│ │ │ ├── fulltext_search_service.rb # 全文检索服务
|
|
28
|
+
│ │ │ ├── hybrid_search_service.rb # 混合检索服务
|
|
29
|
+
│ │ │ ├── tag_service.rb # 标签生成服务
|
|
30
|
+
│ │ │ └── summarization_service.rb # 摘要生成服务
|
|
31
|
+
│ │ ├── chunker/ # 文档分块
|
|
32
|
+
│ │ │ └── markdown_chunker.rb
|
|
33
|
+
│ │ ├── parsers/ # 查询解析器
|
|
34
|
+
│ │ │ └── query_parser.rb # 查询解析(语言检测、tsquery 构建)
|
|
35
|
+
│ │ └── config.rb
|
|
36
|
+
│ └── smart_rag.rb # 主入口
|
|
37
|
+
├── db/
|
|
38
|
+
│ ├── migrations/ # 数据库迁移
|
|
39
|
+
│ ├── schema.sql
|
|
40
|
+
│ └── seeds/ # 初始化数据
|
|
41
|
+
│ └── text_search_configs.sql
|
|
42
|
+
└── config/
|
|
43
|
+
├── smart_rag.yml # 主配置
|
|
44
|
+
└── fulltext_search.yml # 全文检索配置
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 2. 核心组件
|
|
48
|
+
|
|
49
|
+
### 2.1 嵌入管理 (`core/embedding.rb`)
|
|
50
|
+
|
|
51
|
+
**职责:**
|
|
52
|
+
- 存储和检索向量嵌入
|
|
53
|
+
- 执行相似度搜索
|
|
54
|
+
- 管理基于标签的结果增强
|
|
55
|
+
|
|
56
|
+
**关键方法:**
|
|
57
|
+
```ruby
|
|
58
|
+
class SmartRAG::Core::Embedding
|
|
59
|
+
def initialize(db_connection)
|
|
60
|
+
# 使用数据库连接初始化
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# 为源内容存储嵌入
|
|
64
|
+
def store_embedding(source_id, vector)
|
|
65
|
+
# 实现
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 按向量相似度搜索
|
|
69
|
+
def search_by_vector(query_vector, limit = 5)
|
|
70
|
+
# 使用 PostgreSQL <-> 操作符计算余弦距离
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# 带标签增强的搜索
|
|
74
|
+
def search_by_vector_with_tags(query_vector, tags, limit = 5)
|
|
75
|
+
# 增强搜索,基于标签的评分
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**实现细节:**
|
|
81
|
+
- 使用 PostgreSQL 与 pgvector 扩展
|
|
82
|
+
- 将向量存储为 `vector` 类型列
|
|
83
|
+
- 与 source_sections 和 source_documents 连接以获取完整结果
|
|
84
|
+
- 为标签匹配实现加权评分
|
|
85
|
+
|
|
86
|
+
### 2.2 查询处理器 (`core/query_processor.rb`)
|
|
87
|
+
|
|
88
|
+
**职责:**
|
|
89
|
+
- 将自然语言转换为向量表示
|
|
90
|
+
- 生成搜索关键词/标签
|
|
91
|
+
- 格式化和排序搜索结果
|
|
92
|
+
- 生成自然语言响应
|
|
93
|
+
|
|
94
|
+
**关键方法:**
|
|
95
|
+
```ruby
|
|
96
|
+
class SmartRAG::Core::QueryProcessor
|
|
97
|
+
def initialize(embedding_client, tag_generator, summarizer)
|
|
98
|
+
@embedding_client = embedding_client
|
|
99
|
+
@tag_generator = tag_generator
|
|
100
|
+
@summarizer = summarizer
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# 处理自然语言查询
|
|
104
|
+
def process_query(query_text, language = :zh_cn, limit = 5)
|
|
105
|
+
# 1. 从查询生成标签
|
|
106
|
+
# 2. 将查询转换为向量
|
|
107
|
+
# 3. 执行向量搜索
|
|
108
|
+
# 4. 排序和格式化结果
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# 生成自然语言响应
|
|
112
|
+
def generate_response(question, search_results)
|
|
113
|
+
# 使用摘要器创建自然答案
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**多语言支持:**
|
|
119
|
+
- 支持中文(简体/繁体)、英语、日语
|
|
120
|
+
- 为每种语言独立生成标签
|
|
121
|
+
- 通过 LLM 生成分类标签和内容标签
|
|
122
|
+
|
|
123
|
+
### 2.3 文档处理器 (`core/document_processor.rb`)
|
|
124
|
+
|
|
125
|
+
**职责:**
|
|
126
|
+
- 下载和处理文档
|
|
127
|
+
- 将各种格式转换为 Markdown
|
|
128
|
+
- 智能分块文档
|
|
129
|
+
- 管理文档生命周期
|
|
130
|
+
|
|
131
|
+
**关键方法:**
|
|
132
|
+
```ruby
|
|
133
|
+
class SmartRAG::Core::DocumentProcessor
|
|
134
|
+
def process_url(url, options = {})
|
|
135
|
+
# 1. 下载内容
|
|
136
|
+
# 2. 提取元数据
|
|
137
|
+
# 3. 转换为 Markdown
|
|
138
|
+
# 4. 分块内容
|
|
139
|
+
# 5. 生成嵌入
|
|
140
|
+
# 6. 生成标签
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 2.4 全文检索管理 (`core/fulltext_manager.rb`)
|
|
146
|
+
|
|
147
|
+
**职责:**
|
|
148
|
+
- 管理全文检索功能和 tsvector 索引
|
|
149
|
+
- 执行关键词搜索和查询解析
|
|
150
|
+
- 支持多语言分词和语言检测
|
|
151
|
+
|
|
152
|
+
**关键方法:**
|
|
153
|
+
```ruby
|
|
154
|
+
class SmartRAG::Core::FulltextManager
|
|
155
|
+
def initialize(db_connection)
|
|
156
|
+
@db = db_connection
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# 存储或更新全文索引
|
|
160
|
+
def update_fulltext_index(section_id, title, content, language = 'en')
|
|
161
|
+
# 1. 检测语言
|
|
162
|
+
# 2. 获取分词器配置
|
|
163
|
+
# 3. 生成 tsvector
|
|
164
|
+
# 4. 存储到 section_fts 表
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# 基础全文检索
|
|
168
|
+
def search_by_text(query, language = nil, limit = 20)
|
|
169
|
+
# 1. 检测查询语言
|
|
170
|
+
# 2. 构建 tsquery
|
|
171
|
+
# 3. 执行数据库查询
|
|
172
|
+
# 4. 返回排序结果
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# 混合检索(与向量检索结合)
|
|
176
|
+
def hybrid_search(text_query, vector_query, options = {})
|
|
177
|
+
# 1. 并行执行全文检索和向量检索
|
|
178
|
+
# 2. 使用 RRF 算法融合结果
|
|
179
|
+
# 3. 返回最终排序结果
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# 语言检测
|
|
183
|
+
def detect_language(text)
|
|
184
|
+
# 检查字符分布
|
|
185
|
+
# 返回语言代码(en/zh/ja 等)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# 构建 tsquery
|
|
189
|
+
def build_tsquery(text, language = 'en')
|
|
190
|
+
# 支持自然语言查询解析
|
|
191
|
+
# 支持高级查询语法(引号、AND、OR)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**多语言支持实现:**
|
|
197
|
+
- 语言检测:基于字符范围的正则表达式检测
|
|
198
|
+
- 配置映射:从 `text_search_configs` 表获取分词器配置
|
|
199
|
+
- 中文分词:使用 pg_jieba 扩展,支持自定义词典
|
|
200
|
+
- 动态配置:根据文档元数据自动选择分词器
|
|
201
|
+
|
|
202
|
+
## 3. 数据模型
|
|
203
|
+
|
|
204
|
+
### 3.1 嵌入模型 (`models/embedding_model.rb`)
|
|
205
|
+
|
|
206
|
+
```sql
|
|
207
|
+
CREATE TABLE embeddings (
|
|
208
|
+
id SERIAL PRIMARY KEY,
|
|
209
|
+
source_id INTEGER NOT NULL REFERENCES source_sections(id),
|
|
210
|
+
vector VECTOR(1024) NOT NULL,
|
|
211
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
CREATE INDEX idx_embedding_vector ON embeddings USING ivfflat (vector vector_cosine_ops);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 3.2 源文档模型 (`models/source_document.rb`)
|
|
218
|
+
|
|
219
|
+
```sql
|
|
220
|
+
CREATE TABLE source_documents (
|
|
221
|
+
id SERIAL PRIMARY KEY,
|
|
222
|
+
title VARCHAR(255) NOT NULL,
|
|
223
|
+
url TEXT,
|
|
224
|
+
author VARCHAR(255),
|
|
225
|
+
publication_date DATE,
|
|
226
|
+
language VARCHAR(10),
|
|
227
|
+
description TEXT,
|
|
228
|
+
download_state SMALLINT DEFAULT 0, -- 0: 待处理, 1: 已完成, 2: 失败
|
|
229
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
230
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
231
|
+
);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 3.3 源片段模型 (`models/source_section.rb`)
|
|
235
|
+
|
|
236
|
+
```sql
|
|
237
|
+
CREATE TABLE source_sections (
|
|
238
|
+
id SERIAL PRIMARY KEY,
|
|
239
|
+
document_id INTEGER NOT NULL REFERENCES source_documents(id),
|
|
240
|
+
content TEXT NOT NULL,
|
|
241
|
+
section_title VARCHAR(500),
|
|
242
|
+
section_number INTEGER,
|
|
243
|
+
tag_id INTEGER REFERENCES tags(id),
|
|
244
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
245
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 3.4 相关表
|
|
250
|
+
|
|
251
|
+
```sql
|
|
252
|
+
-- 层级标签系统
|
|
253
|
+
CREATE TABLE tags (
|
|
254
|
+
id SERIAL PRIMARY KEY,
|
|
255
|
+
name VARCHAR(255) NOT NULL UNIQUE,
|
|
256
|
+
parent_id INTEGER REFERENCES tags(id),
|
|
257
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
-- 研究主题分类
|
|
261
|
+
CREATE TABLE research_topics (
|
|
262
|
+
id SERIAL PRIMARY KEY,
|
|
263
|
+
name VARCHAR(255) NOT NULL UNIQUE,
|
|
264
|
+
description TEXT,
|
|
265
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
-- 多对多关系
|
|
269
|
+
CREATE TABLE section_tags (
|
|
270
|
+
section_id INTEGER REFERENCES source_sections(id),
|
|
271
|
+
tag_id INTEGER REFERENCES tags(id),
|
|
272
|
+
PRIMARY KEY (section_id, tag_id)
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
CREATE TABLE research_topic_sections (
|
|
276
|
+
research_topic_id INTEGER REFERENCES research_topics(id),
|
|
277
|
+
section_id INTEGER REFERENCES source_sections(id),
|
|
278
|
+
PRIMARY KEY (research_topic_id, section_id)
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
CREATE TABLE research_topic_tags (
|
|
282
|
+
research_topic_id INTEGER REFERENCES research_topics(id),
|
|
283
|
+
tag_id INTEGER REFERENCES tags(id),
|
|
284
|
+
PRIMARY KEY (research_topic_id, tag_id)
|
|
285
|
+
);
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 3.5 全文检索数据模型
|
|
289
|
+
|
|
290
|
+
#### 3.5.1 语言配置表 (`models/text_search_config.rb`)
|
|
291
|
+
|
|
292
|
+
```sql
|
|
293
|
+
-- 语言配置映射表
|
|
294
|
+
CREATE TABLE text_search_configs (
|
|
295
|
+
language_code TEXT PRIMARY KEY, -- 'en', 'zh', 'ja', etc.
|
|
296
|
+
config_name TEXT NOT NULL, -- 'pg_catalog.english', 'jieba', etc.
|
|
297
|
+
is_installed BOOLEAN DEFAULT true
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
-- 初始化数据
|
|
301
|
+
INSERT INTO text_search_configs VALUES
|
|
302
|
+
('en', 'pg_catalog.english'),
|
|
303
|
+
('zh', 'jieba'),
|
|
304
|
+
('ja', 'pg_catalog.simple'),
|
|
305
|
+
('ko', 'pg_catalog.simple'),
|
|
306
|
+
('default', 'pg_catalog.simple');
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### 3.5.2 全文检索专用表 (`models/section_fts.rb`)
|
|
310
|
+
|
|
311
|
+
```sql
|
|
312
|
+
-- 全文检索专用表(方案 B:独立表设计)
|
|
313
|
+
CREATE TABLE section_fts (
|
|
314
|
+
section_id INTEGER PRIMARY KEY REFERENCES source_sections(id) ON DELETE CASCADE,
|
|
315
|
+
language TEXT NOT NULL,
|
|
316
|
+
fts_title tsvector, -- 标题字段(更高权重 A)
|
|
317
|
+
fts_content tsvector, -- 内容字段(权重 B)
|
|
318
|
+
fts_combined tsvector, -- 合并字段(标题+内容)
|
|
319
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
320
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
-- 创建 GIN 索引以提高查询性能
|
|
324
|
+
CREATE INDEX section_fts_gin_idx ON section_fts USING GIN (fts_combined);
|
|
325
|
+
CREATE INDEX section_fts_language_idx ON section_fts (language);
|
|
326
|
+
CREATE INDEX section_fts_title_idx ON section_fts USING GIN (fts_title);
|
|
327
|
+
|
|
328
|
+
-- 分区索引(按语言)
|
|
329
|
+
CREATE INDEX section_fts_gin_zh ON section_fts USING GIN (fts_combined) WHERE language = 'zh';
|
|
330
|
+
CREATE INDEX section_fts_gin_en ON section_fts USING GIN (fts_combined) WHERE language = 'en';
|
|
331
|
+
|
|
332
|
+
-- 数据库触发器自动维护全文索引
|
|
333
|
+
CREATE OR REPLACE FUNCTION update_section_fts()
|
|
334
|
+
RETURNS TRIGGER AS $$
|
|
335
|
+
DECLARE
|
|
336
|
+
v_language TEXT;
|
|
337
|
+
v_config TEXT;
|
|
338
|
+
BEGIN
|
|
339
|
+
-- 获取文档语言
|
|
340
|
+
SELECT COALESCE(sd.language, 'en') INTO v_language
|
|
341
|
+
FROM source_documents sd
|
|
342
|
+
WHERE sd.id = NEW.document_id;
|
|
343
|
+
|
|
344
|
+
-- 获取对应的配置
|
|
345
|
+
SELECT COALESCE(tsc.config_name, 'pg_catalog.simple') INTO v_config
|
|
346
|
+
FROM text_search_configs tsc
|
|
347
|
+
WHERE tsc.language_code = v_language;
|
|
348
|
+
|
|
349
|
+
-- 维护全文检索数据
|
|
350
|
+
INSERT INTO section_fts (section_id, language, fts_title, fts_content, fts_combined)
|
|
351
|
+
VALUES (
|
|
352
|
+
NEW.id,
|
|
353
|
+
v_language,
|
|
354
|
+
setweight(to_tsvector(v_config, coalesce(NEW.section_title,'')), 'A'),
|
|
355
|
+
setweight(to_tsvector(v_config, coalesce(NEW.content,'')), 'B'),
|
|
356
|
+
setweight(to_tsvector(v_config, coalesce(NEW.section_title,'')), 'A') ||
|
|
357
|
+
setweight(to_tsvector(v_config, coalesce(NEW.content,'')), 'B')
|
|
358
|
+
)
|
|
359
|
+
ON CONFLICT (section_id) DO UPDATE SET
|
|
360
|
+
language = v_language,
|
|
361
|
+
fts_title = setweight(to_tsvector(v_config, coalesce(NEW.section_title,'')), 'A'),
|
|
362
|
+
fts_content = setweight(to_tsvector(v_config, coalesce(NEW.content,'')), 'B'),
|
|
363
|
+
fts_combined = setweight(to_tsvector(v_config, coalesce(NEW.section_title,'')), 'A') ||
|
|
364
|
+
setweight(to_tsvector(v_config, coalesce(NEW.content,'')), 'B'),
|
|
365
|
+
updated_at = CURRENT_TIMESTAMP;
|
|
366
|
+
|
|
367
|
+
RETURN NEW;
|
|
368
|
+
END;
|
|
369
|
+
$$ LANGUAGE plpgsql;
|
|
370
|
+
|
|
371
|
+
-- 创建触发器
|
|
372
|
+
CREATE TRIGGER trigger_update_section_fts
|
|
373
|
+
AFTER INSERT OR UPDATE ON source_sections
|
|
374
|
+
FOR EACH ROW EXECUTE FUNCTION update_section_fts();
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### 3.5.3 查询日志表(用于监控和优化)
|
|
378
|
+
|
|
379
|
+
```sql
|
|
380
|
+
-- 搜索查询性能监控表
|
|
381
|
+
CREATE TABLE search_logs (
|
|
382
|
+
id SERIAL PRIMARY KEY,
|
|
383
|
+
query TEXT NOT NULL,
|
|
384
|
+
search_type VARCHAR(20), -- 'vector', 'fulltext', 'hybrid'
|
|
385
|
+
execution_time_ms INTEGER,
|
|
386
|
+
results_count INTEGER,
|
|
387
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
CREATE INDEX search_logs_created_at_idx ON search_logs (created_at);
|
|
391
|
+
CREATE INDEX search_logs_query_idx ON search_logs USING gin (to_tsvector('simple', query));
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## 4. 文档分块系统
|
|
395
|
+
|
|
396
|
+
### 4.1 MarkdownChunker (`chunker/markdown_chunker.rb`)
|
|
397
|
+
|
|
398
|
+
**分块策略:**
|
|
399
|
+
1. 首先,按 Markdown 标题(#、##、###、####)拆分
|
|
400
|
+
2. 如果分块超过 max_chars,进一步按字符限制拆分
|
|
401
|
+
3. 保持分块之间的重叠以保留上下文
|
|
402
|
+
|
|
403
|
+
**算法:**
|
|
404
|
+
```ruby
|
|
405
|
+
class SmartRAG::Chunker::MarkdownChunker
|
|
406
|
+
MAX_CHARS = 4000
|
|
407
|
+
OVERLAP = 100
|
|
408
|
+
|
|
409
|
+
def chunk(content)
|
|
410
|
+
chunks = split_by_headers(content)
|
|
411
|
+
chunks.flat_map { |chunk| enforce_size_limit(chunk) }
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
private
|
|
415
|
+
|
|
416
|
+
def split_by_headers(content)
|
|
417
|
+
# 使用正则表达式查找标题并相应拆分
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def enforce_size_limit(chunk)
|
|
421
|
+
if chunk.length <= MAX_CHARS
|
|
422
|
+
[chunk]
|
|
423
|
+
else
|
|
424
|
+
# 带重叠拆分
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**标题解析:**
|
|
431
|
+
- 使用正则表达式模式:`/^(#{headers.join("|")})\s+.+$/m`
|
|
432
|
+
- 保留标题层级
|
|
433
|
+
- 维护章节关系
|
|
434
|
+
|
|
435
|
+
## 5. 服务层
|
|
436
|
+
|
|
437
|
+
### 5.1 嵌入服务 (`services/embedding_service.rb`)
|
|
438
|
+
|
|
439
|
+
**用途:** 与外部嵌入 API 接口
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
class SmartRAG::Services::EmbeddingService
|
|
443
|
+
def initialize(provider_config)
|
|
444
|
+
@provider = provider_config[:provider]
|
|
445
|
+
@api_key = provider_config[:api_key]
|
|
446
|
+
@endpoint = provider_config[:endpoint]
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def generate_embedding(text, dimensions = 1024)
|
|
450
|
+
# 调用外部嵌入 API
|
|
451
|
+
# 返回向量数组
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
private
|
|
455
|
+
|
|
456
|
+
def call_embedding_api(text)
|
|
457
|
+
# HTTP 请求到嵌入服务
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### 5.2 向量搜索服务 (`services/vector_search_service.rb`)
|
|
463
|
+
|
|
464
|
+
**用途:** 执行向量相似度搜索
|
|
465
|
+
|
|
466
|
+
```ruby
|
|
467
|
+
class SmartRAG::Services::VectorSearchService
|
|
468
|
+
def initialize(embedding_model)
|
|
469
|
+
@embedding_model = embedding_model
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def search(query, options = {})
|
|
473
|
+
# 1. 生成查询向量(调用外部嵌入服务或缓存)
|
|
474
|
+
# 2. 执行向量相似度搜索
|
|
475
|
+
# 3. 返回排序结果
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def search_by_vector(vector, options = {})
|
|
479
|
+
# 直接使用预计算向量搜索
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### 5.3 全文检索服务 (`services/fulltext_search_service.rb`)
|
|
485
|
+
|
|
486
|
+
**用途:** 执行全文关键词搜索,支持多语言
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
class SmartRAG::Services::FulltextSearchService
|
|
490
|
+
def initialize(fulltext_manager, query_parser)
|
|
491
|
+
@fulltext_manager = fulltext_manager
|
|
492
|
+
@query_parser = query_parser
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def search(query, options = {})
|
|
496
|
+
# 1. 检测查询语言(自动或基于选项)
|
|
497
|
+
# 2. 构建 tsquery
|
|
498
|
+
# 3. 执行数据库查询
|
|
499
|
+
# 4. 返回排序结果(含 BM25 分数、高亮片段)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def search_with_filters(query, filters, options = {})
|
|
503
|
+
# 带过滤条件的全文检索
|
|
504
|
+
# 支持按文档 ID、标签、日期范围过滤
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 5.4 混合检索服务 (`services/hybrid_search_service.rb`)
|
|
510
|
+
|
|
511
|
+
**用途:** 融合全文检索和向量检索结果,提供最优搜索体验
|
|
512
|
+
|
|
513
|
+
```ruby
|
|
514
|
+
class SmartRAG::Services::HybridSearchService
|
|
515
|
+
def initialize(vector_search_service, fulltext_search_service)
|
|
516
|
+
@vector_search_service = vector_search_service
|
|
517
|
+
@fulltext_search_service = fulltext_search_service
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
def search(query, options = {})
|
|
521
|
+
# 1. 构建全文和向量查询
|
|
522
|
+
# 2. 并行执行两种检索(使用线程池)
|
|
523
|
+
# 3. 使用 RRF 算法融合结果
|
|
524
|
+
# 4. 返回最终排序结果
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
private
|
|
528
|
+
|
|
529
|
+
# RRF(Reciprocal Rank Fusion)算法实现
|
|
530
|
+
def combine_results(fts_results, vector_results, k: 60)
|
|
531
|
+
scores = {}
|
|
532
|
+
|
|
533
|
+
# 全文检索得分
|
|
534
|
+
fts_results.each_with_index do |result, index|
|
|
535
|
+
rank = index + 1
|
|
536
|
+
scores[result[:id]] = { fts_score: 1.0 / (k + rank), vector_score: 0, data: result }
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# 向量检索得分
|
|
540
|
+
vector_results.each_with_index do |result, index|
|
|
541
|
+
rank = index + 1
|
|
542
|
+
if scores[result[:id]]
|
|
543
|
+
scores[result[:id]][:vector_score] = 1.0 / (k + rank)
|
|
544
|
+
else
|
|
545
|
+
scores[result[:id]] = { fts_score: 0, vector_score: 1.0 / (k + rank), data: result }
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# 排序并返回(按总分降序)
|
|
550
|
+
scores.values.sort_by { |s| -(s[:fts_score] + s[:vector_score]) }.map { |s| s[:data] }
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**混合检索优势:**
|
|
556
|
+
- 结合全文检索的精确匹配和向量检索的语义理解
|
|
557
|
+
- 相比单一检索模式,召回率提升 15-25%
|
|
558
|
+
- 并行执行两种检索,性能开销最小化
|
|
559
|
+
- 支持配置权重,适应不同场景需求
|
|
560
|
+
|
|
561
|
+
### 5.3 标签服务 (`services/tag_service.rb`)
|
|
562
|
+
|
|
563
|
+
**用途:** 生成和管理标签
|
|
564
|
+
|
|
565
|
+
```ruby
|
|
566
|
+
class SmartRAG::Services::TagService
|
|
567
|
+
def initialize(llm_client)
|
|
568
|
+
@llm_client = llm_client
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def generate_tags(text, topic, languages = [:zh_cn, :en])
|
|
572
|
+
# 通过 LLM 生成分类标签和内容标签
|
|
573
|
+
# 返回结构化标签数据
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def find_or_create_tags(tag_names)
|
|
577
|
+
# 确保标签存在于数据库
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### 5.4 摘要服务 (`services/summarization_service.rb`)
|
|
583
|
+
|
|
584
|
+
**用途:** 生成自然语言摘要
|
|
585
|
+
|
|
586
|
+
```ruby
|
|
587
|
+
class SmartRAG::Services::SummarizationService
|
|
588
|
+
def initialize(llm_client)
|
|
589
|
+
@llm_client = llm_client
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def summarize_search_results(question, results)
|
|
593
|
+
# 基于搜索结果生成连贯答案
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### 5.5 查询解析器 (`parsers/query_parser.rb`)
|
|
599
|
+
|
|
600
|
+
**用途:** 解析用户查询,检测语言并构建 tsquery
|
|
601
|
+
|
|
602
|
+
```ruby
|
|
603
|
+
class SmartRAG::Parsers::QueryParser
|
|
604
|
+
def detect_language(text)
|
|
605
|
+
# 基于字符范围的简单语言检测
|
|
606
|
+
# 返回语言代码:en/zh/ja/ko
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def build_tsquery(text, language = 'en')
|
|
610
|
+
# 查询预处理:转义特殊字符、标准化
|
|
611
|
+
# 判断查询类型(自然语言 / 高级语法)
|
|
612
|
+
# 调用合适的转换函数:
|
|
613
|
+
# - plainto_tsquery: 自然语言查询
|
|
614
|
+
# - websearch_to_tsquery: 高级查询(引号、AND、OR)
|
|
615
|
+
# - phraseto_tsquery: 短语查询
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def parse_advanced_query(text)
|
|
619
|
+
# 解析引号、布尔操作符
|
|
620
|
+
# 构建复杂的 tsquery 表达式
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
## 6. 错误处理和日志记录
|
|
626
|
+
|
|
627
|
+
### 6.1 错误层级
|
|
628
|
+
|
|
629
|
+
```ruby
|
|
630
|
+
module SmartRAG
|
|
631
|
+
module Errors
|
|
632
|
+
class BaseError < StandardError; end
|
|
633
|
+
|
|
634
|
+
class EmbeddingGenerationError < BaseError; end
|
|
635
|
+
class VectorSearchError < BaseError; end
|
|
636
|
+
class FulltextSearchError < BaseError; end
|
|
637
|
+
class HybridSearchError < BaseError; end
|
|
638
|
+
class DocumentProcessingError < BaseError; end
|
|
639
|
+
class TagGenerationError < BaseError; end
|
|
640
|
+
class DatabaseError < BaseError; end
|
|
641
|
+
class LanguageDetectionError < BaseError; end
|
|
642
|
+
class QueryParseError < BaseError; end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### 6.2 日志系统
|
|
648
|
+
|
|
649
|
+
```ruby
|
|
650
|
+
module SmartRAG
|
|
651
|
+
class Logger
|
|
652
|
+
LEVELS = %i[debug info warn error fatal]
|
|
653
|
+
|
|
654
|
+
def initialize(log_level = :info)
|
|
655
|
+
@log_level = log_level
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def log(level, message, context = {})
|
|
659
|
+
return unless LEVELS.index(level) >= LEVELS.index(@log_level)
|
|
660
|
+
|
|
661
|
+
# 结构化日志
|
|
662
|
+
entry = {
|
|
663
|
+
timestamp: Time.now.iso8601,
|
|
664
|
+
level: level,
|
|
665
|
+
message: message,
|
|
666
|
+
context: context
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
puts entry.to_json
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
end
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
**日志类别:**
|
|
676
|
+
- 嵌入操作
|
|
677
|
+
- 搜索查询和结果
|
|
678
|
+
- 文档处理步骤
|
|
679
|
+
- 标签生成
|
|
680
|
+
- 数据库操作
|
|
681
|
+
|
|
682
|
+
## 7. 配置管理
|
|
683
|
+
|
|
684
|
+
### 7.1 配置结构 (`config/smart_rag.yml`)
|
|
685
|
+
|
|
686
|
+
```yaml
|
|
687
|
+
# 数据库配置
|
|
688
|
+
database:
|
|
689
|
+
adapter: postgresql
|
|
690
|
+
host: localhost
|
|
691
|
+
port: 5432
|
|
692
|
+
database: smart_rag_development
|
|
693
|
+
username: username
|
|
694
|
+
password: password
|
|
695
|
+
pool: 5
|
|
696
|
+
|
|
697
|
+
# 嵌入配置
|
|
698
|
+
embedding:
|
|
699
|
+
provider: openai # 或其他提供商
|
|
700
|
+
api_key: <%= ENV['EMBEDDING_API_KEY'] %>
|
|
701
|
+
endpoint: https://api.openai.com/v1/embeddings
|
|
702
|
+
model: text-embedding-ada-002
|
|
703
|
+
dimensions: 1024
|
|
704
|
+
|
|
705
|
+
# 全文检索配置
|
|
706
|
+
fulltext_search:
|
|
707
|
+
default_language: en # 默认语言
|
|
708
|
+
max_results: 100 # 最大结果数
|
|
709
|
+
enable_jieba: true # 启用中文分词
|
|
710
|
+
custom_dict_path: null # 自定义词典路径
|
|
711
|
+
|
|
712
|
+
# 混合检索权重配置
|
|
713
|
+
hybrid_weight:
|
|
714
|
+
fulltext: 0.4 # 全文检索权重
|
|
715
|
+
vector: 0.6 # 向量检索权重
|
|
716
|
+
|
|
717
|
+
# RRF 算法参数
|
|
718
|
+
rrf_k: 60 # RRF 算法的 k 参数(通常 50-100)
|
|
719
|
+
|
|
720
|
+
# 缓存配置
|
|
721
|
+
cache:
|
|
722
|
+
enabled: true
|
|
723
|
+
ttl: 3600 # 缓存 TTL(秒)
|
|
724
|
+
|
|
725
|
+
# 监控配置
|
|
726
|
+
monitoring:
|
|
727
|
+
log_slow_queries: true
|
|
728
|
+
slow_query_threshold_ms: 100 # 慢查询阈值
|
|
729
|
+
|
|
730
|
+
# 索引配置
|
|
731
|
+
index:
|
|
732
|
+
enable_partition: true # 启用分区索引
|
|
733
|
+
auto_vacuum: true # 自动清理
|
|
734
|
+
|
|
735
|
+
# 分块配置
|
|
736
|
+
chunking:
|
|
737
|
+
max_chars: 4000
|
|
738
|
+
overlap: 100
|
|
739
|
+
split_by_headers: true
|
|
740
|
+
|
|
741
|
+
# 搜索配置
|
|
742
|
+
search:
|
|
743
|
+
default_limit: 5
|
|
744
|
+
tag_boost_weight: 0.5 # 标签匹配的距离减少因子
|
|
745
|
+
|
|
746
|
+
# 标签/摘要生成的 LLM 配置
|
|
747
|
+
llm:
|
|
748
|
+
provider: openai
|
|
749
|
+
api_key: <%= ENV['LLM_API_KEY'] %>
|
|
750
|
+
endpoint: https://api.openai.com/v1/chat/completions
|
|
751
|
+
model: gpt-4
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### 7.2 配置加载
|
|
755
|
+
|
|
756
|
+
```ruby
|
|
757
|
+
module SmartRAG
|
|
758
|
+
class Config
|
|
759
|
+
def self.load(file_path = nil)
|
|
760
|
+
file_path ||= File.join(__dir__, '..', 'config', 'smart_rag.yml')
|
|
761
|
+
yaml_content = File.read(file_path)
|
|
762
|
+
YAML.safe_load(ERB.new(yaml_content).result, permitted_classes: [Symbol])
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
end
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
## 8. 主入口点
|
|
769
|
+
|
|
770
|
+
### 8.1 SmartRAG 模块
|
|
771
|
+
|
|
772
|
+
```ruby
|
|
773
|
+
require 'smart_rag/version'
|
|
774
|
+
require 'smart_rag/config'
|
|
775
|
+
require 'smart_rag/core/embedding'
|
|
776
|
+
require 'smart_rag/core/fulltext_manager'
|
|
777
|
+
require 'smart_rag/core/query_processor'
|
|
778
|
+
require 'smart_rag/core/document_processor'
|
|
779
|
+
require 'smart_rag/parsers/query_parser'
|
|
780
|
+
require 'smart_rag/models/*'
|
|
781
|
+
require 'smart_rag/services/*'
|
|
782
|
+
require 'smart_rag/chunker/markdown_chunker'
|
|
783
|
+
|
|
784
|
+
module SmartRAG
|
|
785
|
+
class << self
|
|
786
|
+
attr_accessor :config, :db, :logger
|
|
787
|
+
|
|
788
|
+
def configure
|
|
789
|
+
yield(config) if block_given?
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
def init_db
|
|
793
|
+
@db = Sequel.connect(config[:database])
|
|
794
|
+
load_models
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
def embedding_manager
|
|
798
|
+
@embedding_manager ||= Core::Embedding.new(db)
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def fulltext_manager
|
|
802
|
+
@fulltext_manager ||= Core::FulltextManager.new(db)
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
def query_parser
|
|
806
|
+
@query_parser ||= Parsers::QueryParser.new
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
def vector_search_service
|
|
810
|
+
@vector_search_service ||= Services::VectorSearchService.new(embedding_manager)
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
def fulltext_search_service
|
|
814
|
+
@fulltext_search_service ||= Services::FulltextSearchService.new(fulltext_manager, query_parser)
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
def hybrid_search_service
|
|
818
|
+
@hybrid_search_service ||= Services::HybridSearchService.new(vector_search_service, fulltext_search_service)
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
def document_processor
|
|
822
|
+
@document_processor ||= Core::DocumentProcessor.new(
|
|
823
|
+
embedding_service,
|
|
824
|
+
tag_service,
|
|
825
|
+
chunker
|
|
826
|
+
)
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
private
|
|
830
|
+
|
|
831
|
+
def load_models
|
|
832
|
+
Dir[File.join(__dir__, 'smart_rag', 'models', '*.rb')].each { |f| require f }
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
end
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
## 9. 使用示例
|
|
839
|
+
|
|
840
|
+
### 9.1 基础设置
|
|
841
|
+
|
|
842
|
+
```ruby
|
|
843
|
+
require 'smart_rag'
|
|
844
|
+
|
|
845
|
+
SmartRAG.config = SmartRAG::Config.load
|
|
846
|
+
SmartRAG.init_db
|
|
847
|
+
SmartRAG.logger = SmartRAG::Logger.new(:debug)
|
|
848
|
+
|
|
849
|
+
# 初始化服务
|
|
850
|
+
embedding_service = SmartRAG::Services::EmbeddingService.new(
|
|
851
|
+
provider: :openai,
|
|
852
|
+
api_key: ENV['OPENAI_API_KEY']
|
|
853
|
+
)
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### 9.2 文档处理
|
|
857
|
+
|
|
858
|
+
```ruby
|
|
859
|
+
processor = SmartRAG.document_processor
|
|
860
|
+
|
|
861
|
+
# 处理网页文档
|
|
862
|
+
doc = processor.process_url(
|
|
863
|
+
'https://example.com/article',
|
|
864
|
+
topic_ids: [1, 2],
|
|
865
|
+
generate_embeddings: true,
|
|
866
|
+
generate_tags: true
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
puts "Document ID: #{doc.id}"
|
|
870
|
+
puts "Sections created: #{doc.sections.count}"
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### 9.3 向量搜索
|
|
874
|
+
|
|
875
|
+
```ruby
|
|
876
|
+
# 简单向量搜索
|
|
877
|
+
results = SmartRAG.embedding_manager.search_by_vector(query_vector, limit: 5)
|
|
878
|
+
|
|
879
|
+
# 带标签增强的搜索
|
|
880
|
+
tags = ['machine learning', 'neural networks']
|
|
881
|
+
results = SmartRAG.embedding_manager.search_by_vector_with_tags(
|
|
882
|
+
query_vector,
|
|
883
|
+
tags,
|
|
884
|
+
limit: 10
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
results.each do |result|
|
|
888
|
+
puts "#{result[:document_title]} - Distance: #{result[:distance]}"
|
|
889
|
+
end
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### 9.4 自然语言查询
|
|
893
|
+
|
|
894
|
+
```ruby
|
|
895
|
+
query_processor = SmartRAG::QueryProcessor.new(
|
|
896
|
+
embedding_service,
|
|
897
|
+
tag_service,
|
|
898
|
+
summarization_service
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
# 处理自然语言问题
|
|
902
|
+
results = query_processor.process_query(
|
|
903
|
+
"What are the latest developments in AI?",
|
|
904
|
+
language: :en,
|
|
905
|
+
limit: 5
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
# 生成自然语言答案
|
|
909
|
+
response = query_processor.generate_response(
|
|
910
|
+
"What are the latest developments in AI?",
|
|
911
|
+
results
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
puts response
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
## 10. 测试策略
|
|
918
|
+
|
|
919
|
+
### 10.1 测试结构
|
|
920
|
+
|
|
921
|
+
```
|
|
922
|
+
test/
|
|
923
|
+
├── fixtures/
|
|
924
|
+
│ ├── sample_documents/
|
|
925
|
+
│ └── sample_embeddings/
|
|
926
|
+
├── models/
|
|
927
|
+
│ ├── embedding_model_test.rb
|
|
928
|
+
│ ├── source_document_test.rb
|
|
929
|
+
│ └── source_section_test.rb
|
|
930
|
+
├── integration/
|
|
931
|
+
│ ├── document_processing_test.rb
|
|
932
|
+
│ └── search_flow_test.rb
|
|
933
|
+
├── chunker/
|
|
934
|
+
│ └── markdown_chunker_test.rb
|
|
935
|
+
└── test_helper.rb
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
### 10.2 关键测试场景
|
|
939
|
+
|
|
940
|
+
1. **向量搜索准确性**
|
|
941
|
+
- 测试余弦距离计算
|
|
942
|
+
- 验证标签增强逻辑
|
|
943
|
+
- 基准搜索性能
|
|
944
|
+
|
|
945
|
+
2. **文档分块**
|
|
946
|
+
- 测试 Markdown 标题拆分
|
|
947
|
+
- 验证重叠处理
|
|
948
|
+
- 测试边缘情况(无标题、超长章节)
|
|
949
|
+
|
|
950
|
+
3. **标签生成**
|
|
951
|
+
- 模拟 LLM 响应
|
|
952
|
+
- 验证标签链接逻辑
|
|
953
|
+
- 测试多语言支持
|
|
954
|
+
|
|
955
|
+
4. **集成测试**
|
|
956
|
+
- 端到端文档处理
|
|
957
|
+
- 完整搜索流程
|
|
958
|
+
- 错误恢复场景
|
|
959
|
+
|
|
960
|
+
## 11. 性能考虑
|
|
961
|
+
|
|
962
|
+
### 11.1 数据库优化
|
|
963
|
+
|
|
964
|
+
- **索引:**
|
|
965
|
+
- 标签数组上的 GIN 索引以实现高效筛选
|
|
966
|
+
- 向量列上的 IVFFLAT 索引以进行近似最近邻搜索
|
|
967
|
+
- 外键和频繁查询列上的 B-tree 索引
|
|
968
|
+
|
|
969
|
+
- **查询优化:**
|
|
970
|
+
- 使用 `SELECT ... LIMIT` 进行分页
|
|
971
|
+
- 批量插入操作
|
|
972
|
+
- 使用连接池(Sequel 的连接池)
|
|
973
|
+
|
|
974
|
+
### 11.2 缓存策略
|
|
975
|
+
|
|
976
|
+
```ruby
|
|
977
|
+
class SmartRAG::Cache
|
|
978
|
+
def initialize(redis_client = nil)
|
|
979
|
+
@redis = redis_client
|
|
980
|
+
@memory_cache = {}
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
def fetch_embedding(key, &block)
|
|
984
|
+
# 首先检查内存缓存
|
|
985
|
+
# 然后检查 Redis(如果可用)
|
|
986
|
+
# 最后计算并缓存
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
### 11.3 后台处理
|
|
992
|
+
|
|
993
|
+
对于大型文档处理,考虑后台作业集成:
|
|
994
|
+
|
|
995
|
+
```ruby
|
|
996
|
+
# 未来增强
|
|
997
|
+
class DocumentProcessingJob
|
|
998
|
+
def perform(url, options = {})
|
|
999
|
+
SmartRAG.document_processor.process_url(url, options)
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
## 12. 未来增强
|
|
1005
|
+
|
|
1006
|
+
### 12.1 高级功能
|
|
1007
|
+
|
|
1008
|
+
1. **混合搜索**
|
|
1009
|
+
- 结合向量搜索与全文搜索
|
|
1010
|
+
- 语义和关键词相关性之间的加权评分
|
|
1011
|
+
|
|
1012
|
+
2. **相关度反馈**
|
|
1013
|
+
- 从用户交互中学习
|
|
1014
|
+
- 基于反馈调整嵌入
|
|
1015
|
+
|
|
1016
|
+
3. **多模态嵌入**
|
|
1017
|
+
- 支持图像嵌入
|
|
1018
|
+
- 音频/视频内容索引
|
|
1019
|
+
|
|
1020
|
+
4. **语义聚类**
|
|
1021
|
+
- 自动分类文档
|
|
1022
|
+
- 主题建模集成
|
|
1023
|
+
|
|
1024
|
+
5. **查询扩展**
|
|
1025
|
+
- 自动用相关术语扩展查询
|
|
1026
|
+
- 使用 LLM 理解查询
|
|
1027
|
+
|
|
1028
|
+
### 12.2 可扩展性改进
|
|
1029
|
+
|
|
1030
|
+
1. **分布式搜索**
|
|
1031
|
+
- 跨多个数据库分片嵌入
|
|
1032
|
+
- 跨分片的联合搜索
|
|
1033
|
+
|
|
1034
|
+
2. **流式更新**
|
|
1035
|
+
- 实时嵌入更新
|
|
1036
|
+
- 增量索引
|
|
1037
|
+
|
|
1038
|
+
3. **存档系统**
|
|
1039
|
+
- 将旧嵌入移至冷存储
|
|
1040
|
+
- 可配置的保留策略
|
|
1041
|
+
|
|
1042
|
+
## 13. 安全考虑
|
|
1043
|
+
|
|
1044
|
+
1. **API 密钥管理**
|
|
1045
|
+
- 对凭证使用环境变量
|
|
1046
|
+
- 支持密钥轮换
|
|
1047
|
+
- 审计日志
|
|
1048
|
+
|
|
1049
|
+
2. **输入验证**
|
|
1050
|
+
- 清理所有文本输入
|
|
1051
|
+
- 下载前验证 URL
|
|
1052
|
+
- 文档大小限制
|
|
1053
|
+
|
|
1054
|
+
3. **数据库安全**
|
|
1055
|
+
- 使用预处理语句(Sequel 已处理)
|
|
1056
|
+
- 远程数据库的连接加密
|
|
1057
|
+
- 对数据库用户应用最小权限原则
|