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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +252 -0
  3. data/.rspec +2 -0
  4. data/AGENTS.md +33 -0
  5. data/API_DOCUMENTATION.md +828 -0
  6. data/CHANGELOG.md +16 -1
  7. data/ER-diagram.mmd +144 -0
  8. data/Gemfile +50 -0
  9. data/Gemfile.lock +398 -0
  10. data/Hybrid_Reranking.md +171 -0
  11. data/README.en.md +420 -28
  12. data/README.md +534 -63
  13. data/Rakefile +268 -0
  14. data/SETUP_GUIDE.md +650 -0
  15. data/SmartChunking.md +180 -0
  16. data/USAGE_EXAMPLES.md +1002 -0
  17. data/config/llm_config.yml +4 -2
  18. data/config/smart_rag.yml +45 -1
  19. data/config.ru +15 -0
  20. data/db/migrations/006_create_text_search_configs.rb +3 -2
  21. data/db/migrations/008_create_embeddings.rb +5 -4
  22. data/db/migrations/012_add_metadata_to_source_sections.rb +11 -0
  23. data/db/migrations/013_create_media_jobs.rb +25 -0
  24. data/db/migrations/014_add_media_job_operations_indexes.rb +11 -0
  25. data/db/migrations/015_add_media_leases_and_objects.rb +80 -0
  26. data/db/migrations/016_add_document_principals_and_staging_references.rb +38 -0
  27. data/db/migrations/017_add_media_job_request_fingerprint.rb +48 -0
  28. data/db/seeds/text_search_configs.sql +3 -3
  29. data/design.md +1057 -0
  30. data/docs/API_DOCUMENTATION.md +838 -0
  31. data/docs/DOCUMENTATION_INDEX.en.md +60 -0
  32. data/docs/DOCUMENTATION_INDEX.md +65 -0
  33. data/docs/FIX_SUMMARY.md +256 -0
  34. data/docs/FIX_SUMMARY_COMPLETE.md +273 -0
  35. data/docs/Hybrid_Reranking.md +171 -0
  36. data/docs/MIGRATION_GUIDE.md +151 -0
  37. data/docs/PERFORMANCE_GUIDE.md +58 -0
  38. data/docs/SETUP_GUIDE.md +659 -0
  39. data/docs/SmartChunking.md +180 -0
  40. data/docs/USAGE_EXAMPLES.md +1008 -0
  41. data/docs/design.md +1057 -0
  42. data/docs/evidence_pack.md +211 -0
  43. data/docs/requirements.md +376 -0
  44. data/docs/retrieval_plan.md +251 -0
  45. data/docs/smartrag_improvement_plan.md +201 -0
  46. data/docs/smartrag_refactor.md +216 -0
  47. data/docs/todo.md +931 -0
  48. data/examples/common.rb +1 -1
  49. data/exe/smart-rag-db +163 -0
  50. data/exe/smart-rag-media-worker +34 -0
  51. data/lib/smart_rag/config.rb +12 -0
  52. data/lib/smart_rag/core/document_processor.rb +80 -16
  53. data/lib/smart_rag/core/local_content_store.rb +51 -0
  54. data/lib/smart_rag/core/media_extractors.rb +140 -0
  55. data/lib/smart_rag/core/media_job_queue.rb +353 -0
  56. data/lib/smart_rag/core/media_metadata_extractor.rb +188 -0
  57. data/lib/smart_rag/core/media_object_registry.rb +79 -0
  58. data/lib/smart_rag/core/media_processor.rb +228 -0
  59. data/lib/smart_rag/core/media_safety_policy.rb +61 -0
  60. data/lib/smart_rag/core/s3_content_store.rb +78 -0
  61. data/lib/smart_rag/core/transcript_normalizer.rb +44 -0
  62. data/lib/smart_rag/core/video_semantic_extractor.rb +130 -0
  63. data/lib/smart_rag/http_access_policy.rb +86 -0
  64. data/lib/smart_rag/http_app.rb +188 -0
  65. data/lib/smart_rag/models/embedding.rb +1 -1
  66. data/lib/smart_rag/models/research_topic.rb +1 -1
  67. data/lib/smart_rag/models/research_topic_section.rb +5 -0
  68. data/lib/smart_rag/models/research_topic_tag.rb +5 -0
  69. data/lib/smart_rag/models/search_log.rb +1 -1
  70. data/lib/smart_rag/models/section_fts.rb +5 -0
  71. data/lib/smart_rag/models/section_tag.rb +5 -0
  72. data/lib/smart_rag/models/source_document.rb +1 -1
  73. data/lib/smart_rag/models/source_section.rb +1 -1
  74. data/lib/smart_rag/models/tag.rb +1 -1
  75. data/lib/smart_rag/models/text_search_config.rb +5 -0
  76. data/lib/smart_rag/retrieve.rb +72 -1
  77. data/lib/smart_rag/services/embedding_service.rb +1 -1
  78. data/lib/smart_rag/services/fulltext_search_service.rb +11 -13
  79. data/lib/smart_rag/services/hybrid_search_service.rb +15 -11
  80. data/lib/smart_rag/services/summarization_service.rb +1 -1
  81. data/lib/smart_rag/services/tag_service.rb +1 -1
  82. data/lib/smart_rag/version.rb +1 -1
  83. data/lib/smart_rag.rb +264 -30
  84. data/patch_language.rb +27 -0
  85. data/requirements.md +376 -0
  86. data/source_documents_export.json +11072 -0
  87. data/todo.md +931 -0
  88. data/workers/analyze_content.rb +6 -2
  89. data/workers/get_embedding.rb +1 -1
  90. 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
+ - 对数据库用户应用最小权限原则