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/Hybrid_Reranking.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# RAGFlow 多路召回与重排序(Hybrid Search & Reranking)技术介绍
|
|
2
|
+
|
|
3
|
+
在 RAGFlow 中,多路召回(Multi-path Retrieval)与重排序(Reranking)技术共同构成了其高精度检索的核心架构,旨在解决单一检索方式在复杂查询下容易出现的“搜不准、搜不全”问题。以下是 2026 年初该技术的详细解读:
|
|
4
|
+
|
|
5
|
+
1. 多路召回技术:确保“广而全”
|
|
6
|
+
|
|
7
|
+
RAGFlow 采用双路混合检索架构,利用不同维度的“筛子”同时在海量文档中筛选候选结果。
|
|
8
|
+
全文检索(关键词通路):
|
|
9
|
+
核心算法:通常基于 BM25 算法。
|
|
10
|
+
优势:擅长精准匹配缩写、产品型号、专业术语或姓名。例如搜索“R1-750”,全文检索能精准锁定包含该特定编号的文档。
|
|
11
|
+
向量检索(语义通路):
|
|
12
|
+
核心算法:利用 Embedding 模型(如 BGE 或 OpenAI 兼容模型)将文本转化为高维向量。
|
|
13
|
+
优势:理解用户意图,即使问题中没有原词,也能找到意思相近的内容。例如用户问“如何理财”,系统能检索到包含“资产配置”或“储蓄方案”的片段。
|
|
14
|
+
混合融合(RRF):
|
|
15
|
+
系统使用 倒数排名融合(Reciprocal Rank Fusion, RRF) 等算法将两路结果合并,初步平衡关键词匹配和语义相关性的得分。
|
|
16
|
+
|
|
17
|
+
2. 重排序技术:确保“精而准”
|
|
18
|
+
|
|
19
|
+
多路召回虽然覆盖面广,但往往会混入不相关的噪音。RAGFlow 引入重排序(Reranking)阶段,对初步选出的 Top-K 候选片断进行“二次打分”。
|
|
20
|
+
级联式重排序策略:
|
|
21
|
+
交叉编码器(Cross-Encoder):这是重排序的核心模型(如 bge-reranker-v2-m3)。与简单的向量相似度不同,它会将查询(Query)和文档(Document)同时输入模型,捕捉更细微的语义匹配关系。
|
|
22
|
+
上下文长度优势:截至 2026 年,其主流重排序模型已支持最高 8192 tokens 的上下文,能够处理更长的文档片段而不会丢失关键信息。
|
|
23
|
+
多模型集成:
|
|
24
|
+
RAGFlow 支持集成多种顶级重排序器,包括 Cohere Rerank、Jina Reranker 以及开源的 BGE 系列。2026 年甚至支持通过 vLLM 托管这些重排序模型以获得更高的推理效率。
|
|
25
|
+
|
|
26
|
+
3. 技术核心优势
|
|
27
|
+
|
|
28
|
+
首条命中率提升:通过先“广搜”再“精排”,显著提高了首个召回片段的相关性,这对于 LLM 减少幻觉至关重要。
|
|
29
|
+
结构化数据亲和:针对 CSV/JSON 等缺乏自然语言语义的结构化数据,多路召回中的关键词通路能补足传统向量检索的短板。
|
|
30
|
+
可追溯性:重排序后的高分片段会与原文位置绑定,在 UI 界面上直接展示为高亮引用,保证了 AI 回答的“有据可查”。
|
|
31
|
+
通过这种双引擎驱动 + 深度精排的模式,RAGFlow 能够将检索准确率从初级 RAG 的约 60% 提升至 90% 以上。
|
|
32
|
+
|
|
33
|
+
# 多路召回与重排序设计方案
|
|
34
|
+
|
|
35
|
+
1) Hybrid Search:多路召回 + 融合
|
|
36
|
+
|
|
37
|
+
- 入口:rag\nlp\search.py 的 Dealer.search()
|
|
38
|
+
- 文本召回:FulltextQueryer.question() 生成 MatchTextExpr(BM25/查询字符串)并扩展同义词与细粒度 token。
|
|
39
|
+
- 参考:rag\nlp\query.py
|
|
40
|
+
- 向量召回:get_vector() 生成 MatchDenseExpr,向量字段命名 q_{dim}_vec。
|
|
41
|
+
- 参考:rag\nlp\search.py
|
|
42
|
+
- 融合:FusionExpr("weighted_sum", {"weights":"0.05,0.95"}),文本/向量权重融合。
|
|
43
|
+
- 参考:rag\nlp\search.py
|
|
44
|
+
- 数据库层执行:
|
|
45
|
+
- OpenSearch:使用 query_string + knn,并用 FusionExpr 的权重调整 boost。
|
|
46
|
+
- 参考:rag\utils\opensearch_conn.py
|
|
47
|
+
- OB/Infinity 等:由连接器实现融合查询与归一化,Infinity 会归一化两路得分,后续无需再 rerank。
|
|
48
|
+
- 参考:rag\nlp\search.py 中 settings.DOC_ENGINE_INFINITY 分支
|
|
49
|
+
|
|
50
|
+
2) Rerank:两种路径
|
|
51
|
+
|
|
52
|
+
- 内置 rerank(无外部模型):
|
|
53
|
+
- Dealer.rerank() 调用 FulltextQueryer.hybrid_similarity()
|
|
54
|
+
- 得分 = token_similarity * tkweight + vector_similarity * vtweight + rank_feature
|
|
55
|
+
- token_similarity 对内容 tokens + 标题/重要关键词加权(title2,important5,question*6)。
|
|
56
|
+
- 参考:rag\nlp\search.py, rag\nlp\query.py
|
|
57
|
+
- 外部 rerank 模型:
|
|
58
|
+
- Dealer.rerank_by_model() 使用 reranker 输出向量得分,混合 token 相似度。
|
|
59
|
+
- rerank 模型适配统一接口:similarity(query, texts)。
|
|
60
|
+
- 参考:rag\nlp\search.py, rag\llm\rerank_model.py
|
|
61
|
+
|
|
62
|
+
3) 排序增强:rank_feature(Pagerank / 标签)
|
|
63
|
+
|
|
64
|
+
- rank_feature 引入 pagerank 与标签相关性加权。
|
|
65
|
+
- _rank_feature_scores() 对标签向量与 query 标签做相似度,叠加 pagerank。
|
|
66
|
+
- 参考:rag\nlp\search.py, rag\utils\opensearch_conn.py
|
|
67
|
+
|
|
68
|
+
4) 流程细节
|
|
69
|
+
|
|
70
|
+
- 再排序池大小:RERANK_LIMIT 固定到 64 的倍数分页,以扩大 rerank 范围。
|
|
71
|
+
- 参考:rag\nlp\search.py
|
|
72
|
+
- 失败回退:如果融合查询结果为空,降低 min_match、提高 similarity threshold 再试。
|
|
73
|
+
- 参考:rag\nlp\search.py
|
|
74
|
+
|
|
75
|
+
Ruby 复刻设计方案(详细)
|
|
76
|
+
|
|
77
|
+
A. 模块划分
|
|
78
|
+
|
|
79
|
+
- HybridSearch::QueryBuilder
|
|
80
|
+
- 构造全文检索查询 + 同义词扩展 + token 权重。
|
|
81
|
+
- 接口:build_text_query(question, min_match) → MatchTextExpr + keywords。
|
|
82
|
+
- 参考:rag\nlp\query.py
|
|
83
|
+
- HybridSearch::Embedding
|
|
84
|
+
- encode_queries(text) → vector
|
|
85
|
+
- 统一向量字段名:q_#{dim}_vec。
|
|
86
|
+
- HybridSearch::Fusion
|
|
87
|
+
- 表示融合策略:weighted_sum,保存权重。
|
|
88
|
+
- 参考:common\doc_store\doc_store_base.py
|
|
89
|
+
- HybridSearch::DocStoreAdapter
|
|
90
|
+
- search(select_fields, filters, match_exprs, order_by, limit, offset, rank_feature)
|
|
91
|
+
- 提供 OpenSearch/PG/OB/自研引擎适配。
|
|
92
|
+
- HybridSearch::Reranker
|
|
93
|
+
- rerank_by_model:外部模型返回相似度。
|
|
94
|
+
- rerank_by_hybrid:token+vector 混合。
|
|
95
|
+
- HybridSearch::Retriever
|
|
96
|
+
- orchestrator:负责 recall → rerank → filtering → pagination。
|
|
97
|
+
|
|
98
|
+
B. 核心数据结构
|
|
99
|
+
|
|
100
|
+
- MatchTextExpr, MatchDenseExpr, FusionExpr(对齐 common\doc_store\doc_store_base.py)
|
|
101
|
+
- SearchResult:total, ids, fields, query_vector, highlight, aggs
|
|
102
|
+
|
|
103
|
+
C. 召回策略(Hybrid Search)
|
|
104
|
+
|
|
105
|
+
1. 构建全文查询:
|
|
106
|
+
- 英文:词权重 + 词邻近短语(bigram boost)。
|
|
107
|
+
- 中文:分词 + 同义词 + fine-grained token。
|
|
108
|
+
2. 构建向量查询:q_{dim}_vec + topk + similarity_threshold
|
|
109
|
+
3. 组装 FusionExpr:默认 "0.05,0.95"(文本/向量)
|
|
110
|
+
4. 交给 DocStoreAdapter 执行。
|
|
111
|
+
|
|
112
|
+
D. Rerank 策略
|
|
113
|
+
|
|
114
|
+
- 如果配置 rerank_model:
|
|
115
|
+
- score = tkweight * token_similarity + vtweight * model_score + rank_feature
|
|
116
|
+
- 否则:
|
|
117
|
+
- score = tkweight * token_similarity + vtweight * vector_similarity + rank_feature
|
|
118
|
+
- tkweight = 1 - vector_similarity_weight(配置默认 0.3)
|
|
119
|
+
- rank_feature 依赖 pagerank / tag_vector(若存在)。
|
|
120
|
+
|
|
121
|
+
E. 评分细节复刻
|
|
122
|
+
|
|
123
|
+
- token_similarity:
|
|
124
|
+
- 使用 term-weight 计算 query tokens 与 doc tokens 的重合度。
|
|
125
|
+
- doc tokens= content_ltks + title_tks*2 + important_kwd*5 + question_tks*6
|
|
126
|
+
- vector_similarity:
|
|
127
|
+
- cosine similarity of query_vector vs doc_vector。
|
|
128
|
+
- rank_feature:
|
|
129
|
+
- pagerank + 标签向量相似度(可按需求保留)。
|
|
130
|
+
|
|
131
|
+
F. 分页与 rerank pool
|
|
132
|
+
|
|
133
|
+
- 先取大范围 RERANK_LIMIT(推荐 64 的倍数)
|
|
134
|
+
- rerank 后再分页
|
|
135
|
+
- 避免直接分页导致 rerank “局部最优”。
|
|
136
|
+
|
|
137
|
+
G. Ruby 伪代码
|
|
138
|
+
|
|
139
|
+
def retrieve(question, page, page_size, topk:, similarity:, vec_weight:, rerank_model: nil)
|
|
140
|
+
text_expr, keywords = QueryBuilder.build_text_query(question, min_match: 0.3)
|
|
141
|
+
dense_expr = Embedding.match_dense(question, topk: topk, similarity: similarity)
|
|
142
|
+
|
|
143
|
+
fusion = FusionExpr.new("weighted_sum", topk, weights: "0.05,0.95")
|
|
144
|
+
match_exprs = [text_expr, dense_expr, fusion]
|
|
145
|
+
|
|
146
|
+
pool = docstore.search(fields, filters, match_exprs, limit: rerank_limit, offset: page_offset)
|
|
147
|
+
|
|
148
|
+
scores = if rerank_model
|
|
149
|
+
Reranker.rerank_by_model(rerank_model, pool, question, tkweight: 1-vec_weight, vtweight: vec_weight)
|
|
150
|
+
else
|
|
151
|
+
Reranker.rerank_by_hybrid(pool, question, tkweight: 1-vec_weight, vtweight: vec_weight)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
ranked = pool.sort_by { |doc| -scores[doc.id] }
|
|
155
|
+
paginate(ranked, page, page_size)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
H. 配置参数建议
|
|
159
|
+
|
|
160
|
+
- vector_similarity_weight(默认 0.3)
|
|
161
|
+
- topk(召回池大小)
|
|
162
|
+
- similarity_threshold(向量召回阈值)
|
|
163
|
+
- rerank_model_id(可选)
|
|
164
|
+
- rank_feature(pagerank/tag_fea 权重)
|
|
165
|
+
|
|
166
|
+
复刻重点与注意事项
|
|
167
|
+
|
|
168
|
+
- 若底层引擎能做融合归一化(类似 Infinity),可跳过自定义 rerank。
|
|
169
|
+
- 参考:rag\nlp\search.py 对 Infinity 的分支判断。
|
|
170
|
+
- 不同引擎的融合实现差异较大(OpenSearch 用 knn + query_string + boost),建议先实现一个“逻辑融合 + 本地 rerank”的通用路径,再做引擎级融合优化。
|
|
171
|
+
- rank_feature(pagerank/tag_fea)是 RAGFlow 的额外增益项,若没有对应特征可直接忽略或留接口。
|
data/README.en.md
CHANGED
|
@@ -2,21 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
[中文 README](README.md)
|
|
4
4
|
|
|
5
|
-
SmartRAG is a Ruby-based hybrid RAG library that combines vector retrieval, full-text search, and topic/tag
|
|
5
|
+
SmartRAG is a Ruby-based hybrid Retrieval-Augmented Generation (RAG) library that combines vector retrieval, full-text search, LLM-powered Q&A summarization, and topic/tag management — covering the full pipeline from document ingestion to intelligent question answering.
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
9
|
+
SmartRAG covers the complete RAG lifecycle:
|
|
10
|
+
|
|
11
|
+
- **Document Processing**: local files and URL import with automatic format conversion (PDF/DOCX/HTML → Markdown)
|
|
12
|
+
- **Smart Chunking**: Markdown heading-based splitting + adaptive size splitting + structure detection
|
|
13
|
+
- **Vector Embedding**: text embeddings via Ollama, stored in PostgreSQL pgvector
|
|
14
|
+
- **Full-Text Indexing**: PostgreSQL tsvector search, Chinese segmented with pg_jieba
|
|
15
|
+
- **Hybrid Retrieval**: vector semantic search + full-text keyword search → RRF fusion → reranking
|
|
16
|
+
- **LLM Q&A**: structured answers from search results (supports EN/ZH/JA/Traditional Chinese)
|
|
17
|
+
- **Topics & Tags**: LLM-generated tags, topic organization and association recommendations
|
|
18
|
+
- **Operations Tooling**: index rebuild, deduplication, backfill, search logs, system statistics
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────────────────┐
|
|
24
|
+
│ Public API │
|
|
25
|
+
│ SmartRAG::SmartRAG (lib/smart_rag.rb) │
|
|
26
|
+
├─────────────────────────────────────────────┤
|
|
27
|
+
│ Retrieve Structured Retrieval │
|
|
28
|
+
│ SmartRAG::Retrieve (retrieve.rb) │
|
|
29
|
+
├──────────────┬──────────────┬────────────────┤
|
|
30
|
+
│ Core Layer │ Services Layer│ Chunking Layer │
|
|
31
|
+
│ │ │ │
|
|
32
|
+
│ Query │ Hybrid │ Markdown │
|
|
33
|
+
│ Processor │ Search Svc │ Chunker │
|
|
34
|
+
│ │ │ │
|
|
35
|
+
│ Document │ Embedding │ Smart │
|
|
36
|
+
│ Processor │ Service │ Chunking │
|
|
37
|
+
│ │ │ │
|
|
38
|
+
│ Embedding │ Fulltext │ │
|
|
39
|
+
│ (Core) │ Search Svc │ │
|
|
40
|
+
│ │ │ │
|
|
41
|
+
│ Fulltext │ Tag │ │
|
|
42
|
+
│ Manager │ Service │ │
|
|
43
|
+
│ │ │ │
|
|
44
|
+
│ │ Summarization │ │
|
|
45
|
+
│ │ Service │ │
|
|
46
|
+
├──────────────┴──────────────┴────────────────┤
|
|
47
|
+
│ Models Layer (Sequel ORM) │
|
|
48
|
+
│ SourceDocument / SourceSection / Embedding │
|
|
49
|
+
│ Tag / ResearchTopic / SearchLog / SectionFts │
|
|
50
|
+
├─────────────────────────────────────────────┤
|
|
51
|
+
│ Config Layer (YAML + ERB) │
|
|
52
|
+
│ smart_rag.yml / database.yml / llm_config │
|
|
53
|
+
├─────────────────────────────────────────────┤
|
|
54
|
+
│ Workers Layer (SmartPrompt) │
|
|
55
|
+
│ get_embedding / analyze_content │
|
|
56
|
+
└─────────────────────────────────────────────┘
|
|
57
|
+
```
|
|
14
58
|
|
|
15
59
|
## Default Model Setup
|
|
16
60
|
|
|
17
61
|
Current defaults use local Ollama-compatible endpoints:
|
|
18
62
|
|
|
19
|
-
- Embedding model: `qwen3-embedding`
|
|
63
|
+
- Embedding model: `qwen3-embedding` (1024 dimensions)
|
|
20
64
|
- Text LLM model: `qwen3`
|
|
21
65
|
- Embedding endpoint: `http://localhost:11434/v1/embeddings`
|
|
22
66
|
- LLM endpoint: `http://localhost:11434/v1/chat/completions`
|
|
@@ -66,49 +110,397 @@ ruby examples/01_quick_start.rb
|
|
|
66
110
|
ruby examples/03_search_operations.rb
|
|
67
111
|
```
|
|
68
112
|
|
|
69
|
-
##
|
|
113
|
+
## API Reference
|
|
114
|
+
|
|
115
|
+
### Initialization
|
|
70
116
|
|
|
71
117
|
```ruby
|
|
72
118
|
require "smart_rag"
|
|
73
119
|
|
|
74
|
-
|
|
75
|
-
client = SmartRAG::SmartRAG.new(config)
|
|
120
|
+
# From a config file
|
|
121
|
+
client = SmartRAG::SmartRAG.new("config/smart_rag.yml")
|
|
122
|
+
|
|
123
|
+
# Or from a Hash
|
|
124
|
+
client = SmartRAG::SmartRAG.new({
|
|
125
|
+
database: {
|
|
126
|
+
adapter: "postgresql",
|
|
127
|
+
host: "localhost",
|
|
128
|
+
database: "smart_rag_development",
|
|
129
|
+
user: "rag_user",
|
|
130
|
+
password: "your_password"
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Knowledge Base Management
|
|
136
|
+
|
|
137
|
+
| Method | Description | Returns |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| `add_document(path, options)` | Import a document (local file or URL) | `{ document_id:, section_count:, status: }` |
|
|
140
|
+
| `remove_document(id)` | Delete a document with all sections/embeddings | `{ success:, deleted_sections:, deleted_embeddings: }` |
|
|
141
|
+
| `get_document(id)` | Get document details | `{ id:, title:, description:, section_count:, metadata: }` |
|
|
142
|
+
| `list_documents(options)` | Paginated listing with title search | `{ documents:, total_count:, page:, per_page:, total_pages: }` |
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
# Import and auto-generate embeddings
|
|
146
|
+
client.add_document("docs/report.md", generate_embeddings: true)
|
|
76
147
|
|
|
77
|
-
|
|
78
|
-
|
|
148
|
+
# Import from URL
|
|
149
|
+
client.add_document("https://example.com/article.pdf")
|
|
79
150
|
|
|
80
|
-
|
|
151
|
+
# Paginated listing
|
|
152
|
+
client.list_documents(page: 1, per_page: 10, search: "Python")
|
|
81
153
|
```
|
|
82
154
|
|
|
83
|
-
|
|
155
|
+
### Search (Core)
|
|
156
|
+
|
|
157
|
+
SmartRAG provides three search modes: **hybrid** (default), **vector**, and **fulltext**.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# Unified search entry point
|
|
161
|
+
results = client.search("What is machine learning?",
|
|
162
|
+
search_type: "hybrid", # hybrid | vector | fulltext
|
|
163
|
+
limit: 5,
|
|
164
|
+
language: :en, # :en | :zh_cn | :ja | auto-detect
|
|
165
|
+
alpha: 0.7, # vector weight (0.0-1.0), hybrid only
|
|
166
|
+
include_content: true,
|
|
167
|
+
include_metadata: true,
|
|
168
|
+
generate_tags: false, # use LLM to generate tags from query
|
|
169
|
+
document_ids: [1, 2], # restrict to specific documents
|
|
170
|
+
tags: ["AI", "ML"] # tag filtering/boosting
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Individual search modes
|
|
174
|
+
client.vector_search("neural network architectures", limit: 5)
|
|
175
|
+
client.fulltext_search('"deep reinforcement learning"', limit: 5)
|
|
176
|
+
client.hybrid_search("AI applications", language: :en)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Hybrid Search Pipeline
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
User Query
|
|
183
|
+
↓
|
|
184
|
+
① Language Detection (EN/ZH/JA)
|
|
185
|
+
↓
|
|
186
|
+
② Optional: LLM query tag generation
|
|
187
|
+
↓
|
|
188
|
+
③ Query vector embedding
|
|
189
|
+
↓
|
|
190
|
+
④ Parallel: vector search + full-text search
|
|
191
|
+
↓
|
|
192
|
+
⑤ RRF Fusion (Reciprocal Rank Fusion, k=60)
|
|
193
|
+
↓
|
|
194
|
+
⑥ Reranking (rerank_limit=64)
|
|
195
|
+
↓
|
|
196
|
+
⑦ Domain boosting + category diversity
|
|
197
|
+
↓
|
|
198
|
+
⑧ Return results
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**RRF weights**: vector 0.6 / fulltext 0.4 (adjustable in `config/fulltext_search.yml`).
|
|
202
|
+
|
|
203
|
+
#### Multilingual Support
|
|
204
|
+
|
|
205
|
+
| Language | FTS Configuration | Tokenizer |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| English | `pg_catalog.english` | stemming |
|
|
208
|
+
| Chinese | `jieba` | pg_jieba segmentation |
|
|
209
|
+
| Japanese | `pg_catalog.simple` | basic |
|
|
210
|
+
| Korean | `pg_catalog.simple` | basic |
|
|
211
|
+
|
|
212
|
+
### Structured Retrieval (SmartBrain Integration)
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
plan = {
|
|
216
|
+
queries: [
|
|
217
|
+
{ text: "machine learning basics", mode: "semantic", weight: 1.0 },
|
|
218
|
+
{ text: "deep neural networks", mode: "keyword", weight: 0.8 }
|
|
219
|
+
],
|
|
220
|
+
budget: {
|
|
221
|
+
candidate_k: 200,
|
|
222
|
+
per_mode_k: { semantic: 30, keyword: 20 }
|
|
223
|
+
},
|
|
224
|
+
ranking: {
|
|
225
|
+
rerank: { enabled: true }
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
evidence_pack = client.retrieve(plan: plan)
|
|
230
|
+
# Returns EvidencePack: { evidences:, stats:, explain:, warnings: }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Tag Management
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
# LLM auto-generate tags
|
|
237
|
+
result = client.generate_tags("This is a text about deep learning and neural networks...",
|
|
238
|
+
max_tags: 10
|
|
239
|
+
)
|
|
240
|
+
# => { content_tags: ["deep learning", "neural networks"], category_tags: ["AI"] }
|
|
241
|
+
|
|
242
|
+
# Paginated tag listing
|
|
243
|
+
client.list_tags(page: 1, per_page: 20, search: "AI")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Topic Management
|
|
247
|
+
|
|
248
|
+
```ruby
|
|
249
|
+
# Create topic
|
|
250
|
+
client.create_topic("AI Research",
|
|
251
|
+
description: "Artificial intelligence research topics",
|
|
252
|
+
tags: ["AI", "Machine Learning"],
|
|
253
|
+
document_ids: [1, 3]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Query topics
|
|
257
|
+
client.get_topic(1)
|
|
258
|
+
client.list_topics(page: 1, per_page: 20, search: "AI")
|
|
259
|
+
|
|
260
|
+
# Update topic
|
|
261
|
+
client.update_topic(1, title: "Artificial Intelligence Research", tags: ["AI", "DL"])
|
|
262
|
+
|
|
263
|
+
# Delete topic
|
|
264
|
+
client.delete_topic(1)
|
|
265
|
+
|
|
266
|
+
# Document-topic association
|
|
267
|
+
client.add_document_to_topic(topic_id: 1, document_id: 5)
|
|
268
|
+
client.remove_document_from_topic(topic_id: 1, document_id: 5)
|
|
269
|
+
|
|
270
|
+
# Topic recommendations (based on tag co-occurrence)
|
|
271
|
+
client.get_topic_recommendations(1, limit: 5)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### System Operations
|
|
275
|
+
|
|
276
|
+
| Method | Description |
|
|
277
|
+
|---|---|
|
|
278
|
+
| `statistics` | System stats (documents/sections/topics/tags/embeddings count) |
|
|
279
|
+
| `search_logs(limit:, search_type:)` | Query search history |
|
|
280
|
+
| `rebuild_fts(document_id)` | Rebuild full-text indexes (omit for all) |
|
|
281
|
+
| `rebuild_embeddings(document_id)` | Rebuild vector embeddings (omit for all) |
|
|
282
|
+
| `reindex(document_id)` | Rebuild both FTS + embeddings |
|
|
283
|
+
| `dedupe_by_content_hash` | Deduplicate documents by content hash |
|
|
284
|
+
| `backfill_source_fields(dry_run:)` | Backfill source_uri/source_type/content_hash fields |
|
|
285
|
+
| `prepare_release_indexes(dry_run:)` | Pre-release pipeline: backfill → dedupe → reindex |
|
|
84
286
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
287
|
+
```ruby
|
|
288
|
+
# System statistics
|
|
289
|
+
stats = client.statistics
|
|
290
|
+
# => { document_count:, section_count:, topic_count:, tag_count:, embedding_count: }
|
|
291
|
+
|
|
292
|
+
# Search logs
|
|
293
|
+
client.search_logs(limit: 50, search_type: "hybrid")
|
|
294
|
+
|
|
295
|
+
# Dry run release prep
|
|
296
|
+
client.prepare_release_indexes(dry_run: true)
|
|
297
|
+
|
|
298
|
+
# Execute release prep
|
|
299
|
+
client.prepare_release_indexes
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Document Processing Pipeline
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
URL / file path
|
|
306
|
+
↓
|
|
307
|
+
① Download (with 301/302 redirect support)
|
|
308
|
+
↓
|
|
309
|
+
② Extract metadata (size, type, timestamp)
|
|
310
|
+
↓
|
|
311
|
+
③ Format conversion (Markitdown: PDF/DOCX/HTML → Markdown)
|
|
312
|
+
↓
|
|
313
|
+
④ Create SourceDocument record
|
|
314
|
+
↓
|
|
315
|
+
⑤ Smart chunking (heading-based → size-based fallback)
|
|
316
|
+
↓
|
|
317
|
+
⑥ Store SourceSection records
|
|
318
|
+
↓
|
|
319
|
+
⑦ Optional: generate Embedding + Tag
|
|
320
|
+
↓
|
|
321
|
+
⑧ Mark document status as completed
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Supported input formats: `.md` / `.txt` / `.pdf` / `.docx` / `.html`
|
|
325
|
+
|
|
326
|
+
### Chunking Strategies
|
|
327
|
+
|
|
328
|
+
- **MarkdownChunker** (default): split by H1-H3 headings, oversized chunks split further by character count
|
|
329
|
+
- **SmartChunking** (advanced): structure detection + document type awareness (laws/books/papers/manuals), token-based merging
|
|
330
|
+
|
|
331
|
+
Configuration (`config/smart_rag.yml`):
|
|
332
|
+
|
|
333
|
+
```yaml
|
|
334
|
+
chunking:
|
|
335
|
+
max_chars: 4000 # max characters per chunk
|
|
336
|
+
overlap: 100 # character overlap between chunks
|
|
337
|
+
split_by_headers: true # split by markdown headers first
|
|
338
|
+
min_chunk_size: 100 # discard chunks smaller than this
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Data Model
|
|
342
|
+
|
|
343
|
+
| Model | Table | Purpose |
|
|
344
|
+
|---|---|---|
|
|
345
|
+
| `SourceDocument` | `source_documents` | Document metadata (title, author, source type, state) |
|
|
346
|
+
| `SourceSection` | `source_sections` | Document sections (title, number, content, language) |
|
|
347
|
+
| `Embedding` | `embeddings` | pgvector vector storage |
|
|
348
|
+
| `Tag` | `tags` | Tags (supports hierarchy via parent_id) |
|
|
349
|
+
| `SectionTag` | `section_tags` | Many-to-many: section ↔ tag |
|
|
350
|
+
| `ResearchTopic` | `research_topics` | Research topics |
|
|
351
|
+
| `ResearchTopicSection` | `research_topic_sections` | Topic ↔ section association |
|
|
352
|
+
| `ResearchTopicTag` | `research_topic_tags` | Topic ↔ tag association |
|
|
353
|
+
| `SearchLog` | `search_logs` | Search records (query, duration, result count) |
|
|
354
|
+
| `SectionFts` | `section_fts` | Full-text search materialized view |
|
|
355
|
+
|
|
356
|
+
## Dependencies
|
|
357
|
+
|
|
358
|
+
- **Ruby** >= 2.7
|
|
359
|
+
- **PostgreSQL** + `pgvector` extension + `pg_jieba` extension
|
|
360
|
+
- **Sequel** ORM
|
|
361
|
+
- **SmartPrompt** gem (LLM abstraction layer)
|
|
362
|
+
- **Nokogiri** / **Markitdown** (document format conversion)
|
|
363
|
+
|
|
364
|
+
## Configuration Reference
|
|
365
|
+
|
|
366
|
+
Main configuration files:
|
|
367
|
+
|
|
368
|
+
| File | Purpose |
|
|
369
|
+
|---|---|
|
|
370
|
+
| `config/smart_rag.yml` | Main config (database, embedding, search, chunking, LLM, logging) |
|
|
371
|
+
| `config/database.yml` | Multi-environment database config |
|
|
372
|
+
| `config/llm_config.yml` | LLM adapter config (Ollama / SiliconFlow etc.) |
|
|
373
|
+
| `config/fulltext_search.yml` | Full-text search details (languages, indexes, performance) |
|
|
374
|
+
|
|
375
|
+
Key environment variables:
|
|
376
|
+
|
|
377
|
+
| Variable | Default | Description |
|
|
378
|
+
|---|---|---|
|
|
379
|
+
| `SMARTRAG_DB_NAME` | `smart_rag_development` | Database name |
|
|
380
|
+
| `SMARTRAG_DB_USER` | `rag_user` | Database user |
|
|
381
|
+
| `SMARTRAG_DB_PASSWORD` | - | Database password |
|
|
382
|
+
| `EMBEDDING_MODEL` | `qwen3-embedding` | Embedding model |
|
|
383
|
+
| `EMBEDDING_DIMENSIONS` | `1024` | Vector dimensions |
|
|
384
|
+
| `LLM_MODEL` | `qwen3` | LLM model |
|
|
385
|
+
| `DEFAULT_LANGUAGE` | `en` | Default language |
|
|
386
|
+
| `ENABLE_JIEBA` | `true` | Enable Chinese segmentation |
|
|
89
387
|
|
|
90
388
|
## Project Structure
|
|
91
389
|
|
|
92
390
|
```text
|
|
93
391
|
lib/
|
|
94
|
-
smart_rag.rb
|
|
95
|
-
smart_rag/
|
|
96
|
-
smart_rag/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
392
|
+
smart_rag.rb # Main API entry (SmartRAG::SmartRAG class)
|
|
393
|
+
smart_rag/config.rb # Config loading (YAML + ERB)
|
|
394
|
+
smart_rag/version.rb # Version
|
|
395
|
+
smart_rag/errors.rb # Custom error classes
|
|
396
|
+
smart_rag/retrieve.rb # Structured retrieval (RetrievalPlan → EvidencePack)
|
|
397
|
+
smart_rag/models.rb # Model loading and connection management
|
|
398
|
+
smart_rag/models/ # Sequel ORM models (10 tables)
|
|
399
|
+
smart_rag/core/ # Core processing logic
|
|
400
|
+
query_processor.rb # Query processor (search + Q&A)
|
|
401
|
+
document_processor.rb # Document processor (import + chunk + store)
|
|
402
|
+
embedding.rb # Low-level embedding operations
|
|
403
|
+
fulltext_manager.rb # Full-text index management
|
|
404
|
+
markitdown_bridge.rb # Document format conversion bridge
|
|
405
|
+
smart_rag/services/ # Service layer
|
|
406
|
+
embedding_service.rb # Embedding service (CRUD + batch)
|
|
407
|
+
vector_search_service.rb # Vector search service
|
|
408
|
+
fulltext_search_service.rb # Full-text search service
|
|
409
|
+
hybrid_search_service.rb # Hybrid search service (RRF + reranking)
|
|
410
|
+
summarization_service.rb # LLM Q&A summarization service
|
|
411
|
+
tag_service.rb # Tag generation and management service
|
|
412
|
+
smart_rag/chunker/ # Chunkers
|
|
413
|
+
markdown_chunker.rb # Markdown heading-based chunker
|
|
414
|
+
smart_rag/smart_chunking/ # Advanced smart chunking
|
|
415
|
+
pipeline.rb / parser.rb # Structure detection + type-aware chunking
|
|
416
|
+
merger.rb / tokenizer.rb # Token merging strategies
|
|
417
|
+
smart_rag/parsers/ # Parsers
|
|
418
|
+
query_parser.rb # Query parser
|
|
419
|
+
config/ # Runtime configuration
|
|
420
|
+
db/ # Database migrations and seed SQL
|
|
421
|
+
examples/ # Example scripts (6 scenarios)
|
|
422
|
+
test/ # E2E test scripts + sample documents
|
|
423
|
+
spec/ # RSpec tests
|
|
424
|
+
workers/ # SmartPrompt worker definitions
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Development Commands
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
# Run tests
|
|
431
|
+
bundle exec rspec # RSpec unit/integration tests
|
|
432
|
+
SMARTRAG_LIVE_SPECS=1 bundle exec rspec spec/documentation # Live-model documentation examples
|
|
433
|
+
ruby test/test_rag.rb # E2E test script
|
|
434
|
+
|
|
435
|
+
# Database operations
|
|
436
|
+
bundle exec rake db:create # Create database
|
|
437
|
+
bundle exec rake db:migrate # Run migrations
|
|
438
|
+
bundle exec rake db:seed # Seed data
|
|
439
|
+
bundle exec rake db:reset # Recreate database
|
|
440
|
+
|
|
441
|
+
# Operations
|
|
442
|
+
bundle exec rake db:backfill_source_fields # Backfill fields
|
|
443
|
+
bundle exec rake db:prepare_release # Pre-release pipeline
|
|
444
|
+
|
|
445
|
+
# Build
|
|
446
|
+
gem build smart_rag.gemspec # Build gem package
|
|
447
|
+
|
|
448
|
+
# Import test documents
|
|
449
|
+
ruby test/import_doc.rb import
|
|
450
|
+
|
|
451
|
+
# Rebuild embeddings
|
|
452
|
+
ruby test/reembed_all.rb
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Media Storage, Tenant Isolation, and Idempotency
|
|
456
|
+
|
|
457
|
+
Run database migrations through `017_add_media_job_request_fingerprint` before deploying the current asynchronous media queue. Migrations 015 and 016 add leases, object references, authenticated document ownership, and explicit staging-object protection. Migration 017 backfills and requires a canonical SHA-256 fingerprint for every queued request.
|
|
458
|
+
|
|
459
|
+
For asynchronous `POST /v1/media` requests, send `Idempotency-Key`. Repeating the same operation, source, and canonicalized options for the same authenticated principal returns the original job with `deduplicated: true`. Reusing the key with a different payload returns HTTP `409` and `code: "idempotency_conflict"`. Different principals may use the same key independently.
|
|
460
|
+
|
|
461
|
+
Configure AWS S3 or MinIO with the `MEDIA_CONTENT_STORE_*` and `MEDIA_S3_*` variables documented in `.env.example`. MinIO normally requires `MEDIA_S3_FORCE_PATH_STYLE=true`. The `aws-sdk-s3` runtime dependency is loaded only when the S3 provider is selected. Retained queued, processing, and failed jobs protect their staging objects from garbage collection.
|
|
462
|
+
|
|
463
|
+
When HTTP authentication is enabled, every endpoint except `/healthz` requires a Bearer token. Retrieval is scoped by the authenticated principal at both boundaries: owned document IDs are pushed into search, and returned candidates are rechecked against PostgreSQL before evidence is emitted. Job reads and mutations are scoped the same way.
|
|
464
|
+
|
|
465
|
+
The real MinIO suite is opt-in because it writes and deletes actual objects:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
SMARTRAG_MINIO_E2E=1 \
|
|
469
|
+
SMARTRAG_MINIO_ENDPOINT=http://127.0.0.1:19000 \
|
|
470
|
+
SMARTRAG_MINIO_BUCKET=smart-rag-e2e \
|
|
471
|
+
SMARTRAG_MINIO_ACCESS_KEY=smart-rag-e2e \
|
|
472
|
+
SMARTRAG_MINIO_SECRET_KEY=smart-rag-e2e-secret \
|
|
473
|
+
bundle exec rspec spec/integration/minio_content_store_spec.rb
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
It verifies cross-instance storage, asynchronous worker materialization, reference lifecycle, real deletion, and failed-job GC protection. Run the real PostgreSQL HTTP isolation and idempotency checks with:
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
bundle exec rspec \
|
|
480
|
+
spec/integration/media_tenant_isolation_spec.rb \
|
|
481
|
+
spec/integration/media_p3_spec.rb
|
|
102
482
|
```
|
|
103
483
|
|
|
104
484
|
## Documentation Map
|
|
105
485
|
|
|
106
|
-
See `docs/DOCUMENTATION_INDEX.en.md` for a curated map of all docs, reading order, and maintenance notes.
|
|
486
|
+
See `docs/DOCUMENTATION_INDEX.en.md` for a curated map of all docs, reading order, and maintenance notes.
|
|
107
487
|
Chinese version: `docs/DOCUMENTATION_INDEX.md`.
|
|
108
488
|
|
|
489
|
+
Other key documents:
|
|
490
|
+
|
|
491
|
+
- `docs/design.md` — System design
|
|
492
|
+
- `docs/API_DOCUMENTATION.md` — Detailed API docs
|
|
493
|
+
- `docs/SETUP_GUIDE.md` — Environment setup guide
|
|
494
|
+
- `docs/USAGE_EXAMPLES.md` — Usage examples
|
|
495
|
+
- `docs/Hybrid_Reranking.md` — Hybrid search & reranking details
|
|
496
|
+
- `docs/SmartChunking.md` — Smart chunking details
|
|
497
|
+
- `docs/MIGRATION_GUIDE.md` — Migration guide
|
|
498
|
+
- `docs/PERFORMANCE_GUIDE.md` — Performance optimization guide
|
|
499
|
+
- `ER-diagram.mmd` — ER diagram
|
|
500
|
+
|
|
109
501
|
## Notes
|
|
110
502
|
|
|
111
|
-
- Some legacy docs still contain older defaults (
|
|
503
|
+
- Some legacy docs still contain older defaults (e.g. OpenAI references). Runtime truth is `config/smart_rag.yml`.
|
|
112
504
|
|
|
113
505
|
## License
|
|
114
506
|
|