demon-slaying-rag 0.1.0__tar.gz
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.
- demon_slaying_rag-0.1.0/PKG-INFO +290 -0
- demon_slaying_rag-0.1.0/README.md +266 -0
- demon_slaying_rag-0.1.0/demon_slaying_rag.egg-info/PKG-INFO +290 -0
- demon_slaying_rag-0.1.0/demon_slaying_rag.egg-info/SOURCES.txt +8 -0
- demon_slaying_rag-0.1.0/demon_slaying_rag.egg-info/dependency_links.txt +1 -0
- demon_slaying_rag-0.1.0/demon_slaying_rag.egg-info/requires.txt +14 -0
- demon_slaying_rag-0.1.0/demon_slaying_rag.egg-info/top_level.txt +1 -0
- demon_slaying_rag-0.1.0/demon_slaying_rag.py +301 -0
- demon_slaying_rag-0.1.0/pyproject.toml +34 -0
- demon_slaying_rag-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: demon-slaying-rag
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modular RAG retrieval class based on LangChain and Chroma
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: chromadb>=1.0
|
|
12
|
+
Requires-Dist: langchain-chroma>=1.0
|
|
13
|
+
Requires-Dist: langchain-core>=1.0
|
|
14
|
+
Requires-Dist: langchain-community>=0.4
|
|
15
|
+
Requires-Dist: langchain-openai>=1.0
|
|
16
|
+
Requires-Dist: langchain-classic>=1.0
|
|
17
|
+
Requires-Dist: dashscope>=2.0
|
|
18
|
+
Requires-Dist: python-dotenv>=1.0
|
|
19
|
+
Requires-Dist: jieba>=0.42
|
|
20
|
+
Requires-Dist: rank-bm25>=0.2
|
|
21
|
+
Provides-Extra: publish
|
|
22
|
+
Requires-Dist: build; extra == "publish"
|
|
23
|
+
Requires-Dist: twine; extra == "publish"
|
|
24
|
+
|
|
25
|
+
# demon-slaying-rag
|
|
26
|
+
|
|
27
|
+
一个可配置的**模块化 RAG 检索器**:只负责"从已有 Chroma 知识库里把最相关的片段取回来",不包含入库、切分与答案生成。
|
|
28
|
+
|
|
29
|
+
核心是 `ModularRAG` 类,把早期脚本里散落的检索参数集中成一份 `config` 字典,并按三阶段流水线执行:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
问题
|
|
33
|
+
│
|
|
34
|
+
├─ 检索前优化 pre_retrieval 归一化 → (可选)查询改写 → (可选)HyDE 假设答案
|
|
35
|
+
│
|
|
36
|
+
├─ 混合召回 hybrid_retrieval 纯向量检索 或 向量+BM25 经 RRF 融合
|
|
37
|
+
│
|
|
38
|
+
└─ 检索后处理 post_retrieval 去重 → (可选)重排 → (可选)相似度过滤
|
|
39
|
+
→ 截断 top_k → (可选)上下文压缩 → (可选)重排顺序
|
|
40
|
+
│
|
|
41
|
+
返回 List[Document](带 retrieval_rank / context_position 等元数据)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 环境要求
|
|
47
|
+
|
|
48
|
+
- Python >= 3.11(已在 3.13 上验证)
|
|
49
|
+
- 依赖(`pip install .` 会自动安装):
|
|
50
|
+
|
|
51
|
+
| 依赖 | 用途 |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `chromadb` / `langchain-chroma` | 向量库连接(`Chroma`) |
|
|
54
|
+
| `langchain-core` | `Document`、`RunnableLambda` |
|
|
55
|
+
| `langchain-community` | `DashScopeEmbeddings`、`BM25Retriever`、`DashScopeRerank`、`LongContextReorder` |
|
|
56
|
+
| `langchain-openai` | 通过 DashScope 兼容接口调用 `qwen-plus` 做改写 / HyDE / 压缩 |
|
|
57
|
+
| `langchain-classic` | `EnsembleRetriever`(RRF 融合)、`LLMChainExtractor`(上下文压缩) |
|
|
58
|
+
| `dashscope` | 通义 Embedding / Rerank 的 SDK |
|
|
59
|
+
| `jieba` + `rank-bm25` | 混合检索的 BM25 分词与实现(仅混合检索需要) |
|
|
60
|
+
| `python-dotenv` | 读取 `.env` 中的 `DASHSCOPE_API_KEY` |
|
|
61
|
+
|
|
62
|
+
## 安装
|
|
63
|
+
|
|
64
|
+
本项目未发布到 PyPI,使用本地安装:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 可编辑安装(推荐本地开发)
|
|
68
|
+
pip install -e .
|
|
69
|
+
|
|
70
|
+
# 或普通安装
|
|
71
|
+
pip install .
|
|
72
|
+
|
|
73
|
+
# 额外安装打包发布工具
|
|
74
|
+
pip install -e ".[publish]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 前置条件
|
|
78
|
+
|
|
79
|
+
1. **知识库必须已存在**:`persist_directory` 指向的目录下必须有 `chroma.sqlite3`,否则构造函数直接抛 `FileNotFoundError`。
|
|
80
|
+
2. **不会自动建集合**:内部使用 `create_collection_if_not_exists=False`,集合不存在时报错,而不是静默创建一个空库(避免"检索结果永远为空"的排查黑洞)。
|
|
81
|
+
3. **API Key**:在项目根目录放一个 `.env`,或直接设置环境变量。
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Linux / macOS
|
|
85
|
+
export DASHSCOPE_API_KEY="sk-xxxx"
|
|
86
|
+
|
|
87
|
+
# Windows PowerShell
|
|
88
|
+
setx DASHSCOPE_API_KEY "sk-xxxx"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```ini
|
|
92
|
+
# .env
|
|
93
|
+
DASHSCOPE_API_KEY=sk-xxxx
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> 入库(切分 + 向量化 + 写 Chroma)不在本模块职责内,请用你已有的离线建库脚本生成知识库。
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 快速开始
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from demon_slaying_rag import ModularRAG
|
|
104
|
+
|
|
105
|
+
rag = ModularRAG({
|
|
106
|
+
# ---- 基础连接 ----
|
|
107
|
+
"persist_directory": "./chroma_db", # Chroma 目录(含 chroma.sqlite3)
|
|
108
|
+
"collection_name": "study",
|
|
109
|
+
"embedding_model": "text-embedding-v4",
|
|
110
|
+
# ---- 召回 ----
|
|
111
|
+
"top_k": 5,
|
|
112
|
+
"recall_top_k": 20,
|
|
113
|
+
# ---- 检索前优化 ----
|
|
114
|
+
"pre_retrieval": {
|
|
115
|
+
"enabled": False,
|
|
116
|
+
"rewrite_query": False,
|
|
117
|
+
"hyde": False,
|
|
118
|
+
"model": "qwen-plus",
|
|
119
|
+
},
|
|
120
|
+
# ---- 混合召回 ----
|
|
121
|
+
"hybrid_retrieval": {
|
|
122
|
+
"enabled": True,
|
|
123
|
+
"weights": [0.5, 0.5], # [向量权重, BM25 权重]
|
|
124
|
+
},
|
|
125
|
+
# ---- 检索后处理 ----
|
|
126
|
+
"post_retrieval": {
|
|
127
|
+
"enabled": True,
|
|
128
|
+
"deduplicate": True,
|
|
129
|
+
"rerank": False,
|
|
130
|
+
"rerank_model": "qwen3-rerank",
|
|
131
|
+
"similarity_filter": False,
|
|
132
|
+
"similarity_threshold": 0.4,
|
|
133
|
+
"compress_context": False,
|
|
134
|
+
"llm_model": "qwen-plus",
|
|
135
|
+
"reorder": False,
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
documents = rag.search("公司打卡时间?")
|
|
140
|
+
for document in documents:
|
|
141
|
+
print(document.metadata["retrieval_rank"], document.metadata["context_position"])
|
|
142
|
+
print(document.page_content)
|
|
143
|
+
print("-" * 40)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
带元数据过滤与临时 top_k:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
documents = rag.search(
|
|
150
|
+
"试用期规定",
|
|
151
|
+
top_k=8,
|
|
152
|
+
filters={"source": "员工手册.pdf"}, # 与 config["filters"] 以 $and 合并
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 配置项
|
|
159
|
+
|
|
160
|
+
### 基础连接与召回
|
|
161
|
+
|
|
162
|
+
| 键 | 默认值 | 说明 |
|
|
163
|
+
| --- | --- | --- |
|
|
164
|
+
| `persist_directory` | `./chroma_db` | Chroma 目录,**相对当前工作目录**解析(支持 `~`) |
|
|
165
|
+
| `collection_name` | `study` | 集合名 |
|
|
166
|
+
| `embedding_model` | `text-embedding-v4` | DashScope Embedding 模型,必须与建库时一致 |
|
|
167
|
+
| `top_k` | `5` | 最终返回条数 |
|
|
168
|
+
| `recall_top_k` | `20` | 召回阶段条数;实际取 `max(recall_top_k, top_k)` |
|
|
169
|
+
| `filters` | 无 | Chroma `where` 过滤条件,如 `{"source": "a.pdf"}` |
|
|
170
|
+
|
|
171
|
+
### 检索前优化 `pre_retrieval`
|
|
172
|
+
|
|
173
|
+
| 键 | 默认值 | 说明 |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| `enabled` | `False` | 总开关,关闭时下列子项**全部不生效** |
|
|
176
|
+
| `rewrite_query` | `False` | 用 `qwen-plus` 把问题改写为独立查询,向量与 BM25 都用改写后结果 |
|
|
177
|
+
| `hyde` | `False` | 用 `qwen-plus` 生成约 100 字假设答案,**仅替换向量检索的查询** |
|
|
178
|
+
| `model` | `qwen-plus` | 改写 / HyDE 使用的 LLM |
|
|
179
|
+
|
|
180
|
+
开启 `enabled` 后,`normalize_query`(合并多余空白)总是执行,没有单独开关。
|
|
181
|
+
|
|
182
|
+
### 混合召回 `hybrid_retrieval`
|
|
183
|
+
|
|
184
|
+
| 键 | 默认值 | 说明 |
|
|
185
|
+
| --- | --- | --- |
|
|
186
|
+
| `enabled` | `False` | 开启后走 向量 + BM25 + RRF 融合;关闭则纯向量检索 |
|
|
187
|
+
| `weights` | `[0.5, 0.5]` | `EnsembleRetriever` 权重,顺序为 `[向量, BM25]` |
|
|
188
|
+
|
|
189
|
+
RRF 的 `c` 参数固定为 `60`(`rrf_fusion` 的 `rrf_k`)。
|
|
190
|
+
|
|
191
|
+
### 检索后处理 `post_retrieval`
|
|
192
|
+
|
|
193
|
+
| 键 | 默认值 | 说明 |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| `enabled` | `False` | 总开关,关闭时下列子项全部不生效 |
|
|
196
|
+
| `deduplicate` | `True` | 按 `page_content` 去重 |
|
|
197
|
+
| `rerank` | `False` | 调用 DashScope Rerank 重排 |
|
|
198
|
+
| `rerank_model` | `qwen3-rerank` | 重排模型 |
|
|
199
|
+
| `similarity_filter` | `False` | 按余弦相似度过滤 |
|
|
200
|
+
| `similarity_threshold` | `0.4` | 相似度阈值,低于该值丢弃 |
|
|
201
|
+
| `compress_context` | `False` | 用 `LLMChainExtractor` 抽取与问题相关的句子(表格类文档跳过) |
|
|
202
|
+
| `llm_model` | `qwen-plus` | 压缩使用的 LLM |
|
|
203
|
+
| `reorder` | `False` | `LongContextReorder`:把关键内容放到上下文首尾("中间遗忘"缓解) |
|
|
204
|
+
|
|
205
|
+
### 执行顺序(`search` 内部)
|
|
206
|
+
|
|
207
|
+
1. 校验:问题非空、`top_k` 为正整数,否则抛 `ValueError`
|
|
208
|
+
2. `count = max(recall_top_k, top_k)`
|
|
209
|
+
3. 检索前:归一化 → 改写 →(可选)HyDE 生成 `dense_query`
|
|
210
|
+
4. 过滤条件:`config["filters"]` 与 `search(filters=...)` 合并为 `$and`
|
|
211
|
+
5. 召回:
|
|
212
|
+
- `hybrid.enabled`:`db.get(where=...)` 拉全集 → 建 `BM25Retriever`(jieba 分词)+ 向量检索器 → `EnsembleRetriever` RRF 融合
|
|
213
|
+
- 否则:`vector_search`
|
|
214
|
+
6. 后处理(`post.enabled`):去重 → 重排 `top_n=len(documents)` → 相似度过滤
|
|
215
|
+
7. 截断到 `top_k`,写入 `metadata["retrieval_rank"]`(1 起)
|
|
216
|
+
8. 后处理第二阶段:上下文压缩 → 重排顺序,写入 `metadata["context_position"]`
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## API
|
|
221
|
+
|
|
222
|
+
### `ModularRAG(config: dict)`
|
|
223
|
+
|
|
224
|
+
连接知识库并初始化 Embedding 模型。`config` 中缺失的键一律走默认值。
|
|
225
|
+
|
|
226
|
+
### `search(question: str, top_k: int | None = None, filters: dict | None = None) -> list[Document]`
|
|
227
|
+
|
|
228
|
+
在线检索,返回按相关性排好序的 `Document` 列表。返回的 `metadata` 除知识库原始字段外,可能包含:
|
|
229
|
+
|
|
230
|
+
| 字段 | 出现条件 | 含义 |
|
|
231
|
+
| --- | --- | --- |
|
|
232
|
+
| `chunk_id` | 纯向量检索分支 | 文档在 Chroma 中的 id |
|
|
233
|
+
| `retrieval_rank` | 总是 | 截断后的最终名次(1 起) |
|
|
234
|
+
| `context_position` | 总是 | 送入上下文时的位置(1 起) |
|
|
235
|
+
| `cosine_similarity` | 开启 `similarity_filter` | 与问题的余弦相似度 |
|
|
236
|
+
|
|
237
|
+
### 可独立复用的静态方法
|
|
238
|
+
|
|
239
|
+
| 方法 | 说明 |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `normalize_query(question)` | 合并空白并去首尾空格 |
|
|
242
|
+
| `rewrite_query(question, model)` | 改写为适合检索的独立查询 |
|
|
243
|
+
| `generate_hyde(question, model)` | 生成假设性答案(约 100 字) |
|
|
244
|
+
| `vector_search(question, db, top_k, filters)` | 稠密检索 |
|
|
245
|
+
| `bm25_search(question, documents, top_k)` | 稀疏检索(需自行提供语料) |
|
|
246
|
+
| `rrf_fusion(question, vector_retriever, bm25_retriever, weights, rrf_k)` | RRF 融合两路召回 |
|
|
247
|
+
| `deduplicate(documents)` | 按内容去重 |
|
|
248
|
+
| `rerank_documents(question, documents, top_k, model)` | DashScope 重排 |
|
|
249
|
+
| `similarity_filter(question, documents, embeddings, threshold)` | 余弦相似度过滤 |
|
|
250
|
+
| `compress_context(question, documents, model)` | LLM 抽取式压缩 |
|
|
251
|
+
| `reorder_documents(documents)` | 长上下文重排(首尾优先) |
|
|
252
|
+
|
|
253
|
+
### 异常
|
|
254
|
+
|
|
255
|
+
| 场景 | 异常 |
|
|
256
|
+
| --- | --- |
|
|
257
|
+
| 目录下没有 `chroma.sqlite3` | `FileNotFoundError` |
|
|
258
|
+
| 问题为空 / `top_k` 非正整数 | `ValueError` |
|
|
259
|
+
| 集合名不存在 | `chromadb` 抛出的 `NotFoundError` 类异常 |
|
|
260
|
+
| 未安装 `jieba` / `rank-bm25` 却开启混合检索 | `ImportError` |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 已知限制与注意事项
|
|
265
|
+
|
|
266
|
+
- **键名拼错不会报错**:全篇使用 `config.get(k, default)`,写错键名只会静默走默认值,排障时优先怀疑键名。
|
|
267
|
+
- **总开关优先**:`pre_retrieval.enabled=False` / `post_retrieval.enabled=False` 时,其下所有子项(含默认为 `True` 的 `deduplicate`)一律不生效。
|
|
268
|
+
- **路径与工作目录绑定**:`persist_directory` 相对当前工作目录解析,从不同目录启动会连到不同的库;工作区内根目录与 `day_1106/` 下各有一个 `chroma_db`。
|
|
269
|
+
- **混合检索的开销**:每次查询都会 `db.get()` 全量拉取文档并重建 BM25 索引,数据量大时延迟明显,且不支持大数据集分页。
|
|
270
|
+
- **重复的 Embedding 调用**:`similarity_filter` 会对每条候选文档重新调用 Embedding API(向量库里其实已存有向量),有额外耗时与费用。
|
|
271
|
+
- **压缩逐条调用 LLM**:`compress_context` 对每个文档单独请求一次 `qwen-plus`,条数越多越慢。
|
|
272
|
+
- **模型一致性**:`embedding_model` 必须与建库时相同,否则相似度与过滤结果都会失真。
|
|
273
|
+
|
|
274
|
+
## 目录结构
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
project/
|
|
278
|
+
├── demon_slaying_rag.py # ModularRAG 类(单模块)
|
|
279
|
+
├── pyproject.toml # 打包与依赖声明
|
|
280
|
+
└── README.md
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## 打包与发布
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
pip install -e ".[publish]"
|
|
287
|
+
python -m build
|
|
288
|
+
twine check dist/*
|
|
289
|
+
twine upload dist/* # 需要 PyPI Token
|
|
290
|
+
```
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# demon-slaying-rag
|
|
2
|
+
|
|
3
|
+
一个可配置的**模块化 RAG 检索器**:只负责"从已有 Chroma 知识库里把最相关的片段取回来",不包含入库、切分与答案生成。
|
|
4
|
+
|
|
5
|
+
核心是 `ModularRAG` 类,把早期脚本里散落的检索参数集中成一份 `config` 字典,并按三阶段流水线执行:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
问题
|
|
9
|
+
│
|
|
10
|
+
├─ 检索前优化 pre_retrieval 归一化 → (可选)查询改写 → (可选)HyDE 假设答案
|
|
11
|
+
│
|
|
12
|
+
├─ 混合召回 hybrid_retrieval 纯向量检索 或 向量+BM25 经 RRF 融合
|
|
13
|
+
│
|
|
14
|
+
└─ 检索后处理 post_retrieval 去重 → (可选)重排 → (可选)相似度过滤
|
|
15
|
+
→ 截断 top_k → (可选)上下文压缩 → (可选)重排顺序
|
|
16
|
+
│
|
|
17
|
+
返回 List[Document](带 retrieval_rank / context_position 等元数据)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 环境要求
|
|
23
|
+
|
|
24
|
+
- Python >= 3.11(已在 3.13 上验证)
|
|
25
|
+
- 依赖(`pip install .` 会自动安装):
|
|
26
|
+
|
|
27
|
+
| 依赖 | 用途 |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `chromadb` / `langchain-chroma` | 向量库连接(`Chroma`) |
|
|
30
|
+
| `langchain-core` | `Document`、`RunnableLambda` |
|
|
31
|
+
| `langchain-community` | `DashScopeEmbeddings`、`BM25Retriever`、`DashScopeRerank`、`LongContextReorder` |
|
|
32
|
+
| `langchain-openai` | 通过 DashScope 兼容接口调用 `qwen-plus` 做改写 / HyDE / 压缩 |
|
|
33
|
+
| `langchain-classic` | `EnsembleRetriever`(RRF 融合)、`LLMChainExtractor`(上下文压缩) |
|
|
34
|
+
| `dashscope` | 通义 Embedding / Rerank 的 SDK |
|
|
35
|
+
| `jieba` + `rank-bm25` | 混合检索的 BM25 分词与实现(仅混合检索需要) |
|
|
36
|
+
| `python-dotenv` | 读取 `.env` 中的 `DASHSCOPE_API_KEY` |
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
本项目未发布到 PyPI,使用本地安装:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 可编辑安装(推荐本地开发)
|
|
44
|
+
pip install -e .
|
|
45
|
+
|
|
46
|
+
# 或普通安装
|
|
47
|
+
pip install .
|
|
48
|
+
|
|
49
|
+
# 额外安装打包发布工具
|
|
50
|
+
pip install -e ".[publish]"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 前置条件
|
|
54
|
+
|
|
55
|
+
1. **知识库必须已存在**:`persist_directory` 指向的目录下必须有 `chroma.sqlite3`,否则构造函数直接抛 `FileNotFoundError`。
|
|
56
|
+
2. **不会自动建集合**:内部使用 `create_collection_if_not_exists=False`,集合不存在时报错,而不是静默创建一个空库(避免"检索结果永远为空"的排查黑洞)。
|
|
57
|
+
3. **API Key**:在项目根目录放一个 `.env`,或直接设置环境变量。
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Linux / macOS
|
|
61
|
+
export DASHSCOPE_API_KEY="sk-xxxx"
|
|
62
|
+
|
|
63
|
+
# Windows PowerShell
|
|
64
|
+
setx DASHSCOPE_API_KEY "sk-xxxx"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ini
|
|
68
|
+
# .env
|
|
69
|
+
DASHSCOPE_API_KEY=sk-xxxx
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> 入库(切分 + 向量化 + 写 Chroma)不在本模块职责内,请用你已有的离线建库脚本生成知识库。
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 快速开始
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from demon_slaying_rag import ModularRAG
|
|
80
|
+
|
|
81
|
+
rag = ModularRAG({
|
|
82
|
+
# ---- 基础连接 ----
|
|
83
|
+
"persist_directory": "./chroma_db", # Chroma 目录(含 chroma.sqlite3)
|
|
84
|
+
"collection_name": "study",
|
|
85
|
+
"embedding_model": "text-embedding-v4",
|
|
86
|
+
# ---- 召回 ----
|
|
87
|
+
"top_k": 5,
|
|
88
|
+
"recall_top_k": 20,
|
|
89
|
+
# ---- 检索前优化 ----
|
|
90
|
+
"pre_retrieval": {
|
|
91
|
+
"enabled": False,
|
|
92
|
+
"rewrite_query": False,
|
|
93
|
+
"hyde": False,
|
|
94
|
+
"model": "qwen-plus",
|
|
95
|
+
},
|
|
96
|
+
# ---- 混合召回 ----
|
|
97
|
+
"hybrid_retrieval": {
|
|
98
|
+
"enabled": True,
|
|
99
|
+
"weights": [0.5, 0.5], # [向量权重, BM25 权重]
|
|
100
|
+
},
|
|
101
|
+
# ---- 检索后处理 ----
|
|
102
|
+
"post_retrieval": {
|
|
103
|
+
"enabled": True,
|
|
104
|
+
"deduplicate": True,
|
|
105
|
+
"rerank": False,
|
|
106
|
+
"rerank_model": "qwen3-rerank",
|
|
107
|
+
"similarity_filter": False,
|
|
108
|
+
"similarity_threshold": 0.4,
|
|
109
|
+
"compress_context": False,
|
|
110
|
+
"llm_model": "qwen-plus",
|
|
111
|
+
"reorder": False,
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
documents = rag.search("公司打卡时间?")
|
|
116
|
+
for document in documents:
|
|
117
|
+
print(document.metadata["retrieval_rank"], document.metadata["context_position"])
|
|
118
|
+
print(document.page_content)
|
|
119
|
+
print("-" * 40)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
带元数据过滤与临时 top_k:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
documents = rag.search(
|
|
126
|
+
"试用期规定",
|
|
127
|
+
top_k=8,
|
|
128
|
+
filters={"source": "员工手册.pdf"}, # 与 config["filters"] 以 $and 合并
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 配置项
|
|
135
|
+
|
|
136
|
+
### 基础连接与召回
|
|
137
|
+
|
|
138
|
+
| 键 | 默认值 | 说明 |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `persist_directory` | `./chroma_db` | Chroma 目录,**相对当前工作目录**解析(支持 `~`) |
|
|
141
|
+
| `collection_name` | `study` | 集合名 |
|
|
142
|
+
| `embedding_model` | `text-embedding-v4` | DashScope Embedding 模型,必须与建库时一致 |
|
|
143
|
+
| `top_k` | `5` | 最终返回条数 |
|
|
144
|
+
| `recall_top_k` | `20` | 召回阶段条数;实际取 `max(recall_top_k, top_k)` |
|
|
145
|
+
| `filters` | 无 | Chroma `where` 过滤条件,如 `{"source": "a.pdf"}` |
|
|
146
|
+
|
|
147
|
+
### 检索前优化 `pre_retrieval`
|
|
148
|
+
|
|
149
|
+
| 键 | 默认值 | 说明 |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `enabled` | `False` | 总开关,关闭时下列子项**全部不生效** |
|
|
152
|
+
| `rewrite_query` | `False` | 用 `qwen-plus` 把问题改写为独立查询,向量与 BM25 都用改写后结果 |
|
|
153
|
+
| `hyde` | `False` | 用 `qwen-plus` 生成约 100 字假设答案,**仅替换向量检索的查询** |
|
|
154
|
+
| `model` | `qwen-plus` | 改写 / HyDE 使用的 LLM |
|
|
155
|
+
|
|
156
|
+
开启 `enabled` 后,`normalize_query`(合并多余空白)总是执行,没有单独开关。
|
|
157
|
+
|
|
158
|
+
### 混合召回 `hybrid_retrieval`
|
|
159
|
+
|
|
160
|
+
| 键 | 默认值 | 说明 |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| `enabled` | `False` | 开启后走 向量 + BM25 + RRF 融合;关闭则纯向量检索 |
|
|
163
|
+
| `weights` | `[0.5, 0.5]` | `EnsembleRetriever` 权重,顺序为 `[向量, BM25]` |
|
|
164
|
+
|
|
165
|
+
RRF 的 `c` 参数固定为 `60`(`rrf_fusion` 的 `rrf_k`)。
|
|
166
|
+
|
|
167
|
+
### 检索后处理 `post_retrieval`
|
|
168
|
+
|
|
169
|
+
| 键 | 默认值 | 说明 |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| `enabled` | `False` | 总开关,关闭时下列子项全部不生效 |
|
|
172
|
+
| `deduplicate` | `True` | 按 `page_content` 去重 |
|
|
173
|
+
| `rerank` | `False` | 调用 DashScope Rerank 重排 |
|
|
174
|
+
| `rerank_model` | `qwen3-rerank` | 重排模型 |
|
|
175
|
+
| `similarity_filter` | `False` | 按余弦相似度过滤 |
|
|
176
|
+
| `similarity_threshold` | `0.4` | 相似度阈值,低于该值丢弃 |
|
|
177
|
+
| `compress_context` | `False` | 用 `LLMChainExtractor` 抽取与问题相关的句子(表格类文档跳过) |
|
|
178
|
+
| `llm_model` | `qwen-plus` | 压缩使用的 LLM |
|
|
179
|
+
| `reorder` | `False` | `LongContextReorder`:把关键内容放到上下文首尾("中间遗忘"缓解) |
|
|
180
|
+
|
|
181
|
+
### 执行顺序(`search` 内部)
|
|
182
|
+
|
|
183
|
+
1. 校验:问题非空、`top_k` 为正整数,否则抛 `ValueError`
|
|
184
|
+
2. `count = max(recall_top_k, top_k)`
|
|
185
|
+
3. 检索前:归一化 → 改写 →(可选)HyDE 生成 `dense_query`
|
|
186
|
+
4. 过滤条件:`config["filters"]` 与 `search(filters=...)` 合并为 `$and`
|
|
187
|
+
5. 召回:
|
|
188
|
+
- `hybrid.enabled`:`db.get(where=...)` 拉全集 → 建 `BM25Retriever`(jieba 分词)+ 向量检索器 → `EnsembleRetriever` RRF 融合
|
|
189
|
+
- 否则:`vector_search`
|
|
190
|
+
6. 后处理(`post.enabled`):去重 → 重排 `top_n=len(documents)` → 相似度过滤
|
|
191
|
+
7. 截断到 `top_k`,写入 `metadata["retrieval_rank"]`(1 起)
|
|
192
|
+
8. 后处理第二阶段:上下文压缩 → 重排顺序,写入 `metadata["context_position"]`
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## API
|
|
197
|
+
|
|
198
|
+
### `ModularRAG(config: dict)`
|
|
199
|
+
|
|
200
|
+
连接知识库并初始化 Embedding 模型。`config` 中缺失的键一律走默认值。
|
|
201
|
+
|
|
202
|
+
### `search(question: str, top_k: int | None = None, filters: dict | None = None) -> list[Document]`
|
|
203
|
+
|
|
204
|
+
在线检索,返回按相关性排好序的 `Document` 列表。返回的 `metadata` 除知识库原始字段外,可能包含:
|
|
205
|
+
|
|
206
|
+
| 字段 | 出现条件 | 含义 |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| `chunk_id` | 纯向量检索分支 | 文档在 Chroma 中的 id |
|
|
209
|
+
| `retrieval_rank` | 总是 | 截断后的最终名次(1 起) |
|
|
210
|
+
| `context_position` | 总是 | 送入上下文时的位置(1 起) |
|
|
211
|
+
| `cosine_similarity` | 开启 `similarity_filter` | 与问题的余弦相似度 |
|
|
212
|
+
|
|
213
|
+
### 可独立复用的静态方法
|
|
214
|
+
|
|
215
|
+
| 方法 | 说明 |
|
|
216
|
+
| --- | --- |
|
|
217
|
+
| `normalize_query(question)` | 合并空白并去首尾空格 |
|
|
218
|
+
| `rewrite_query(question, model)` | 改写为适合检索的独立查询 |
|
|
219
|
+
| `generate_hyde(question, model)` | 生成假设性答案(约 100 字) |
|
|
220
|
+
| `vector_search(question, db, top_k, filters)` | 稠密检索 |
|
|
221
|
+
| `bm25_search(question, documents, top_k)` | 稀疏检索(需自行提供语料) |
|
|
222
|
+
| `rrf_fusion(question, vector_retriever, bm25_retriever, weights, rrf_k)` | RRF 融合两路召回 |
|
|
223
|
+
| `deduplicate(documents)` | 按内容去重 |
|
|
224
|
+
| `rerank_documents(question, documents, top_k, model)` | DashScope 重排 |
|
|
225
|
+
| `similarity_filter(question, documents, embeddings, threshold)` | 余弦相似度过滤 |
|
|
226
|
+
| `compress_context(question, documents, model)` | LLM 抽取式压缩 |
|
|
227
|
+
| `reorder_documents(documents)` | 长上下文重排(首尾优先) |
|
|
228
|
+
|
|
229
|
+
### 异常
|
|
230
|
+
|
|
231
|
+
| 场景 | 异常 |
|
|
232
|
+
| --- | --- |
|
|
233
|
+
| 目录下没有 `chroma.sqlite3` | `FileNotFoundError` |
|
|
234
|
+
| 问题为空 / `top_k` 非正整数 | `ValueError` |
|
|
235
|
+
| 集合名不存在 | `chromadb` 抛出的 `NotFoundError` 类异常 |
|
|
236
|
+
| 未安装 `jieba` / `rank-bm25` 却开启混合检索 | `ImportError` |
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 已知限制与注意事项
|
|
241
|
+
|
|
242
|
+
- **键名拼错不会报错**:全篇使用 `config.get(k, default)`,写错键名只会静默走默认值,排障时优先怀疑键名。
|
|
243
|
+
- **总开关优先**:`pre_retrieval.enabled=False` / `post_retrieval.enabled=False` 时,其下所有子项(含默认为 `True` 的 `deduplicate`)一律不生效。
|
|
244
|
+
- **路径与工作目录绑定**:`persist_directory` 相对当前工作目录解析,从不同目录启动会连到不同的库;工作区内根目录与 `day_1106/` 下各有一个 `chroma_db`。
|
|
245
|
+
- **混合检索的开销**:每次查询都会 `db.get()` 全量拉取文档并重建 BM25 索引,数据量大时延迟明显,且不支持大数据集分页。
|
|
246
|
+
- **重复的 Embedding 调用**:`similarity_filter` 会对每条候选文档重新调用 Embedding API(向量库里其实已存有向量),有额外耗时与费用。
|
|
247
|
+
- **压缩逐条调用 LLM**:`compress_context` 对每个文档单独请求一次 `qwen-plus`,条数越多越慢。
|
|
248
|
+
- **模型一致性**:`embedding_model` 必须与建库时相同,否则相似度与过滤结果都会失真。
|
|
249
|
+
|
|
250
|
+
## 目录结构
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
project/
|
|
254
|
+
├── demon_slaying_rag.py # ModularRAG 类(单模块)
|
|
255
|
+
├── pyproject.toml # 打包与依赖声明
|
|
256
|
+
└── README.md
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## 打包与发布
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
pip install -e ".[publish]"
|
|
263
|
+
python -m build
|
|
264
|
+
twine check dist/*
|
|
265
|
+
twine upload dist/* # 需要 PyPI Token
|
|
266
|
+
```
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: demon-slaying-rag
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modular RAG retrieval class based on LangChain and Chroma
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: chromadb>=1.0
|
|
12
|
+
Requires-Dist: langchain-chroma>=1.0
|
|
13
|
+
Requires-Dist: langchain-core>=1.0
|
|
14
|
+
Requires-Dist: langchain-community>=0.4
|
|
15
|
+
Requires-Dist: langchain-openai>=1.0
|
|
16
|
+
Requires-Dist: langchain-classic>=1.0
|
|
17
|
+
Requires-Dist: dashscope>=2.0
|
|
18
|
+
Requires-Dist: python-dotenv>=1.0
|
|
19
|
+
Requires-Dist: jieba>=0.42
|
|
20
|
+
Requires-Dist: rank-bm25>=0.2
|
|
21
|
+
Provides-Extra: publish
|
|
22
|
+
Requires-Dist: build; extra == "publish"
|
|
23
|
+
Requires-Dist: twine; extra == "publish"
|
|
24
|
+
|
|
25
|
+
# demon-slaying-rag
|
|
26
|
+
|
|
27
|
+
一个可配置的**模块化 RAG 检索器**:只负责"从已有 Chroma 知识库里把最相关的片段取回来",不包含入库、切分与答案生成。
|
|
28
|
+
|
|
29
|
+
核心是 `ModularRAG` 类,把早期脚本里散落的检索参数集中成一份 `config` 字典,并按三阶段流水线执行:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
问题
|
|
33
|
+
│
|
|
34
|
+
├─ 检索前优化 pre_retrieval 归一化 → (可选)查询改写 → (可选)HyDE 假设答案
|
|
35
|
+
│
|
|
36
|
+
├─ 混合召回 hybrid_retrieval 纯向量检索 或 向量+BM25 经 RRF 融合
|
|
37
|
+
│
|
|
38
|
+
└─ 检索后处理 post_retrieval 去重 → (可选)重排 → (可选)相似度过滤
|
|
39
|
+
→ 截断 top_k → (可选)上下文压缩 → (可选)重排顺序
|
|
40
|
+
│
|
|
41
|
+
返回 List[Document](带 retrieval_rank / context_position 等元数据)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 环境要求
|
|
47
|
+
|
|
48
|
+
- Python >= 3.11(已在 3.13 上验证)
|
|
49
|
+
- 依赖(`pip install .` 会自动安装):
|
|
50
|
+
|
|
51
|
+
| 依赖 | 用途 |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `chromadb` / `langchain-chroma` | 向量库连接(`Chroma`) |
|
|
54
|
+
| `langchain-core` | `Document`、`RunnableLambda` |
|
|
55
|
+
| `langchain-community` | `DashScopeEmbeddings`、`BM25Retriever`、`DashScopeRerank`、`LongContextReorder` |
|
|
56
|
+
| `langchain-openai` | 通过 DashScope 兼容接口调用 `qwen-plus` 做改写 / HyDE / 压缩 |
|
|
57
|
+
| `langchain-classic` | `EnsembleRetriever`(RRF 融合)、`LLMChainExtractor`(上下文压缩) |
|
|
58
|
+
| `dashscope` | 通义 Embedding / Rerank 的 SDK |
|
|
59
|
+
| `jieba` + `rank-bm25` | 混合检索的 BM25 分词与实现(仅混合检索需要) |
|
|
60
|
+
| `python-dotenv` | 读取 `.env` 中的 `DASHSCOPE_API_KEY` |
|
|
61
|
+
|
|
62
|
+
## 安装
|
|
63
|
+
|
|
64
|
+
本项目未发布到 PyPI,使用本地安装:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 可编辑安装(推荐本地开发)
|
|
68
|
+
pip install -e .
|
|
69
|
+
|
|
70
|
+
# 或普通安装
|
|
71
|
+
pip install .
|
|
72
|
+
|
|
73
|
+
# 额外安装打包发布工具
|
|
74
|
+
pip install -e ".[publish]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 前置条件
|
|
78
|
+
|
|
79
|
+
1. **知识库必须已存在**:`persist_directory` 指向的目录下必须有 `chroma.sqlite3`,否则构造函数直接抛 `FileNotFoundError`。
|
|
80
|
+
2. **不会自动建集合**:内部使用 `create_collection_if_not_exists=False`,集合不存在时报错,而不是静默创建一个空库(避免"检索结果永远为空"的排查黑洞)。
|
|
81
|
+
3. **API Key**:在项目根目录放一个 `.env`,或直接设置环境变量。
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Linux / macOS
|
|
85
|
+
export DASHSCOPE_API_KEY="sk-xxxx"
|
|
86
|
+
|
|
87
|
+
# Windows PowerShell
|
|
88
|
+
setx DASHSCOPE_API_KEY "sk-xxxx"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```ini
|
|
92
|
+
# .env
|
|
93
|
+
DASHSCOPE_API_KEY=sk-xxxx
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> 入库(切分 + 向量化 + 写 Chroma)不在本模块职责内,请用你已有的离线建库脚本生成知识库。
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 快速开始
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from demon_slaying_rag import ModularRAG
|
|
104
|
+
|
|
105
|
+
rag = ModularRAG({
|
|
106
|
+
# ---- 基础连接 ----
|
|
107
|
+
"persist_directory": "./chroma_db", # Chroma 目录(含 chroma.sqlite3)
|
|
108
|
+
"collection_name": "study",
|
|
109
|
+
"embedding_model": "text-embedding-v4",
|
|
110
|
+
# ---- 召回 ----
|
|
111
|
+
"top_k": 5,
|
|
112
|
+
"recall_top_k": 20,
|
|
113
|
+
# ---- 检索前优化 ----
|
|
114
|
+
"pre_retrieval": {
|
|
115
|
+
"enabled": False,
|
|
116
|
+
"rewrite_query": False,
|
|
117
|
+
"hyde": False,
|
|
118
|
+
"model": "qwen-plus",
|
|
119
|
+
},
|
|
120
|
+
# ---- 混合召回 ----
|
|
121
|
+
"hybrid_retrieval": {
|
|
122
|
+
"enabled": True,
|
|
123
|
+
"weights": [0.5, 0.5], # [向量权重, BM25 权重]
|
|
124
|
+
},
|
|
125
|
+
# ---- 检索后处理 ----
|
|
126
|
+
"post_retrieval": {
|
|
127
|
+
"enabled": True,
|
|
128
|
+
"deduplicate": True,
|
|
129
|
+
"rerank": False,
|
|
130
|
+
"rerank_model": "qwen3-rerank",
|
|
131
|
+
"similarity_filter": False,
|
|
132
|
+
"similarity_threshold": 0.4,
|
|
133
|
+
"compress_context": False,
|
|
134
|
+
"llm_model": "qwen-plus",
|
|
135
|
+
"reorder": False,
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
documents = rag.search("公司打卡时间?")
|
|
140
|
+
for document in documents:
|
|
141
|
+
print(document.metadata["retrieval_rank"], document.metadata["context_position"])
|
|
142
|
+
print(document.page_content)
|
|
143
|
+
print("-" * 40)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
带元数据过滤与临时 top_k:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
documents = rag.search(
|
|
150
|
+
"试用期规定",
|
|
151
|
+
top_k=8,
|
|
152
|
+
filters={"source": "员工手册.pdf"}, # 与 config["filters"] 以 $and 合并
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 配置项
|
|
159
|
+
|
|
160
|
+
### 基础连接与召回
|
|
161
|
+
|
|
162
|
+
| 键 | 默认值 | 说明 |
|
|
163
|
+
| --- | --- | --- |
|
|
164
|
+
| `persist_directory` | `./chroma_db` | Chroma 目录,**相对当前工作目录**解析(支持 `~`) |
|
|
165
|
+
| `collection_name` | `study` | 集合名 |
|
|
166
|
+
| `embedding_model` | `text-embedding-v4` | DashScope Embedding 模型,必须与建库时一致 |
|
|
167
|
+
| `top_k` | `5` | 最终返回条数 |
|
|
168
|
+
| `recall_top_k` | `20` | 召回阶段条数;实际取 `max(recall_top_k, top_k)` |
|
|
169
|
+
| `filters` | 无 | Chroma `where` 过滤条件,如 `{"source": "a.pdf"}` |
|
|
170
|
+
|
|
171
|
+
### 检索前优化 `pre_retrieval`
|
|
172
|
+
|
|
173
|
+
| 键 | 默认值 | 说明 |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| `enabled` | `False` | 总开关,关闭时下列子项**全部不生效** |
|
|
176
|
+
| `rewrite_query` | `False` | 用 `qwen-plus` 把问题改写为独立查询,向量与 BM25 都用改写后结果 |
|
|
177
|
+
| `hyde` | `False` | 用 `qwen-plus` 生成约 100 字假设答案,**仅替换向量检索的查询** |
|
|
178
|
+
| `model` | `qwen-plus` | 改写 / HyDE 使用的 LLM |
|
|
179
|
+
|
|
180
|
+
开启 `enabled` 后,`normalize_query`(合并多余空白)总是执行,没有单独开关。
|
|
181
|
+
|
|
182
|
+
### 混合召回 `hybrid_retrieval`
|
|
183
|
+
|
|
184
|
+
| 键 | 默认值 | 说明 |
|
|
185
|
+
| --- | --- | --- |
|
|
186
|
+
| `enabled` | `False` | 开启后走 向量 + BM25 + RRF 融合;关闭则纯向量检索 |
|
|
187
|
+
| `weights` | `[0.5, 0.5]` | `EnsembleRetriever` 权重,顺序为 `[向量, BM25]` |
|
|
188
|
+
|
|
189
|
+
RRF 的 `c` 参数固定为 `60`(`rrf_fusion` 的 `rrf_k`)。
|
|
190
|
+
|
|
191
|
+
### 检索后处理 `post_retrieval`
|
|
192
|
+
|
|
193
|
+
| 键 | 默认值 | 说明 |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| `enabled` | `False` | 总开关,关闭时下列子项全部不生效 |
|
|
196
|
+
| `deduplicate` | `True` | 按 `page_content` 去重 |
|
|
197
|
+
| `rerank` | `False` | 调用 DashScope Rerank 重排 |
|
|
198
|
+
| `rerank_model` | `qwen3-rerank` | 重排模型 |
|
|
199
|
+
| `similarity_filter` | `False` | 按余弦相似度过滤 |
|
|
200
|
+
| `similarity_threshold` | `0.4` | 相似度阈值,低于该值丢弃 |
|
|
201
|
+
| `compress_context` | `False` | 用 `LLMChainExtractor` 抽取与问题相关的句子(表格类文档跳过) |
|
|
202
|
+
| `llm_model` | `qwen-plus` | 压缩使用的 LLM |
|
|
203
|
+
| `reorder` | `False` | `LongContextReorder`:把关键内容放到上下文首尾("中间遗忘"缓解) |
|
|
204
|
+
|
|
205
|
+
### 执行顺序(`search` 内部)
|
|
206
|
+
|
|
207
|
+
1. 校验:问题非空、`top_k` 为正整数,否则抛 `ValueError`
|
|
208
|
+
2. `count = max(recall_top_k, top_k)`
|
|
209
|
+
3. 检索前:归一化 → 改写 →(可选)HyDE 生成 `dense_query`
|
|
210
|
+
4. 过滤条件:`config["filters"]` 与 `search(filters=...)` 合并为 `$and`
|
|
211
|
+
5. 召回:
|
|
212
|
+
- `hybrid.enabled`:`db.get(where=...)` 拉全集 → 建 `BM25Retriever`(jieba 分词)+ 向量检索器 → `EnsembleRetriever` RRF 融合
|
|
213
|
+
- 否则:`vector_search`
|
|
214
|
+
6. 后处理(`post.enabled`):去重 → 重排 `top_n=len(documents)` → 相似度过滤
|
|
215
|
+
7. 截断到 `top_k`,写入 `metadata["retrieval_rank"]`(1 起)
|
|
216
|
+
8. 后处理第二阶段:上下文压缩 → 重排顺序,写入 `metadata["context_position"]`
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## API
|
|
221
|
+
|
|
222
|
+
### `ModularRAG(config: dict)`
|
|
223
|
+
|
|
224
|
+
连接知识库并初始化 Embedding 模型。`config` 中缺失的键一律走默认值。
|
|
225
|
+
|
|
226
|
+
### `search(question: str, top_k: int | None = None, filters: dict | None = None) -> list[Document]`
|
|
227
|
+
|
|
228
|
+
在线检索,返回按相关性排好序的 `Document` 列表。返回的 `metadata` 除知识库原始字段外,可能包含:
|
|
229
|
+
|
|
230
|
+
| 字段 | 出现条件 | 含义 |
|
|
231
|
+
| --- | --- | --- |
|
|
232
|
+
| `chunk_id` | 纯向量检索分支 | 文档在 Chroma 中的 id |
|
|
233
|
+
| `retrieval_rank` | 总是 | 截断后的最终名次(1 起) |
|
|
234
|
+
| `context_position` | 总是 | 送入上下文时的位置(1 起) |
|
|
235
|
+
| `cosine_similarity` | 开启 `similarity_filter` | 与问题的余弦相似度 |
|
|
236
|
+
|
|
237
|
+
### 可独立复用的静态方法
|
|
238
|
+
|
|
239
|
+
| 方法 | 说明 |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `normalize_query(question)` | 合并空白并去首尾空格 |
|
|
242
|
+
| `rewrite_query(question, model)` | 改写为适合检索的独立查询 |
|
|
243
|
+
| `generate_hyde(question, model)` | 生成假设性答案(约 100 字) |
|
|
244
|
+
| `vector_search(question, db, top_k, filters)` | 稠密检索 |
|
|
245
|
+
| `bm25_search(question, documents, top_k)` | 稀疏检索(需自行提供语料) |
|
|
246
|
+
| `rrf_fusion(question, vector_retriever, bm25_retriever, weights, rrf_k)` | RRF 融合两路召回 |
|
|
247
|
+
| `deduplicate(documents)` | 按内容去重 |
|
|
248
|
+
| `rerank_documents(question, documents, top_k, model)` | DashScope 重排 |
|
|
249
|
+
| `similarity_filter(question, documents, embeddings, threshold)` | 余弦相似度过滤 |
|
|
250
|
+
| `compress_context(question, documents, model)` | LLM 抽取式压缩 |
|
|
251
|
+
| `reorder_documents(documents)` | 长上下文重排(首尾优先) |
|
|
252
|
+
|
|
253
|
+
### 异常
|
|
254
|
+
|
|
255
|
+
| 场景 | 异常 |
|
|
256
|
+
| --- | --- |
|
|
257
|
+
| 目录下没有 `chroma.sqlite3` | `FileNotFoundError` |
|
|
258
|
+
| 问题为空 / `top_k` 非正整数 | `ValueError` |
|
|
259
|
+
| 集合名不存在 | `chromadb` 抛出的 `NotFoundError` 类异常 |
|
|
260
|
+
| 未安装 `jieba` / `rank-bm25` 却开启混合检索 | `ImportError` |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 已知限制与注意事项
|
|
265
|
+
|
|
266
|
+
- **键名拼错不会报错**:全篇使用 `config.get(k, default)`,写错键名只会静默走默认值,排障时优先怀疑键名。
|
|
267
|
+
- **总开关优先**:`pre_retrieval.enabled=False` / `post_retrieval.enabled=False` 时,其下所有子项(含默认为 `True` 的 `deduplicate`)一律不生效。
|
|
268
|
+
- **路径与工作目录绑定**:`persist_directory` 相对当前工作目录解析,从不同目录启动会连到不同的库;工作区内根目录与 `day_1106/` 下各有一个 `chroma_db`。
|
|
269
|
+
- **混合检索的开销**:每次查询都会 `db.get()` 全量拉取文档并重建 BM25 索引,数据量大时延迟明显,且不支持大数据集分页。
|
|
270
|
+
- **重复的 Embedding 调用**:`similarity_filter` 会对每条候选文档重新调用 Embedding API(向量库里其实已存有向量),有额外耗时与费用。
|
|
271
|
+
- **压缩逐条调用 LLM**:`compress_context` 对每个文档单独请求一次 `qwen-plus`,条数越多越慢。
|
|
272
|
+
- **模型一致性**:`embedding_model` 必须与建库时相同,否则相似度与过滤结果都会失真。
|
|
273
|
+
|
|
274
|
+
## 目录结构
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
project/
|
|
278
|
+
├── demon_slaying_rag.py # ModularRAG 类(单模块)
|
|
279
|
+
├── pyproject.toml # 打包与依赖声明
|
|
280
|
+
└── README.md
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## 打包与发布
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
pip install -e ".[publish]"
|
|
287
|
+
python -m build
|
|
288
|
+
twine check dist/*
|
|
289
|
+
twine upload dist/* # 需要 PyPI Token
|
|
290
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
demon_slaying_rag
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
class ModularRAG:
|
|
2
|
+
def __init__(self, config: dict):
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
from langchain_chroma import Chroma
|
|
7
|
+
from langchain_community.embeddings import DashScopeEmbeddings
|
|
8
|
+
|
|
9
|
+
load_dotenv()
|
|
10
|
+
self.config = config # 配置参数(后续写)
|
|
11
|
+
directory = config.get("persist_directory", "./chroma_db") # 知识库目录
|
|
12
|
+
if not (Path(directory).expanduser() / "chroma.sqlite3").is_file(): # 知识库文件是否存在
|
|
13
|
+
raise FileNotFoundError(f"知识库不存在:{directory}")
|
|
14
|
+
|
|
15
|
+
self.embeddings = DashScopeEmbeddings( # 初始化嵌入模型
|
|
16
|
+
model=config.get("embedding_model", "text-embedding-v4"),
|
|
17
|
+
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY"),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
self.db = Chroma( # 初始化数据库
|
|
21
|
+
collection_name=config.get("collection_name", "study"),
|
|
22
|
+
persist_directory=str(Path(directory).expanduser()),
|
|
23
|
+
embedding_function=self.embeddings,
|
|
24
|
+
create_collection_if_not_exists=False,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def search(self, question: str, top_k=None, filters=None): # 搜索
|
|
28
|
+
from langchain_core.documents import Document
|
|
29
|
+
|
|
30
|
+
if not question.strip(): # 问题不能为空
|
|
31
|
+
raise ValueError("问题不能为空")
|
|
32
|
+
limit = self.config.get("top_k", 5) if top_k is None else top_k # top_k参数
|
|
33
|
+
if not isinstance(limit, int) or limit <= 0: # top_k 必须是正整数
|
|
34
|
+
raise ValueError("top_k 必须是正整数")
|
|
35
|
+
count = max(self.config.get("recall_top_k", 20), limit) # 回忆top_k参数
|
|
36
|
+
pre = self.config.get("pre_retrieval", {}) # 预检索参数
|
|
37
|
+
hybrid = self.config.get("hybrid_retrieval", {}) # 混合检索参数
|
|
38
|
+
post = self.config.get("post_retrieval", {}) # 后检索参数
|
|
39
|
+
|
|
40
|
+
query = question
|
|
41
|
+
if pre.get("enabled", False): # 预检索是否启用
|
|
42
|
+
query = self.normalize_query(query)
|
|
43
|
+
if pre.get("rewrite_query", False):
|
|
44
|
+
query = self.rewrite_query(query, model=pre.get("model", "qwen-plus"))
|
|
45
|
+
keyword_query = query
|
|
46
|
+
dense_query = query
|
|
47
|
+
if pre.get("enabled", False) and pre.get("hyde", False): # 预检索是否启用且使用HYDE
|
|
48
|
+
dense_query = self.generate_hyde(query, model=pre.get("model", "qwen-plus"))
|
|
49
|
+
|
|
50
|
+
conditions = []
|
|
51
|
+
for condition in (self.config.get("filters"), filters): # 过滤条件
|
|
52
|
+
if condition: # 过滤条件
|
|
53
|
+
conditions.extend({key: value} for key, value in condition.items()) # 过滤条件添加
|
|
54
|
+
where = None # 过滤条件
|
|
55
|
+
if len(conditions) == 1: # 过滤条件
|
|
56
|
+
where = conditions[0] # 过滤条件
|
|
57
|
+
elif len(conditions) > 1: # 过滤条件
|
|
58
|
+
where = {"$and": conditions} # 过滤条件
|
|
59
|
+
|
|
60
|
+
if hybrid.get("enabled", False): # 混合检索是否启用
|
|
61
|
+
import jieba
|
|
62
|
+
from langchain_community.retrievers import BM25Retriever
|
|
63
|
+
from langchain_core.runnables import RunnableLambda
|
|
64
|
+
|
|
65
|
+
stored = self.db.get(where=where, include=["documents", "metadatas"]) # 获取数据
|
|
66
|
+
corpus = []
|
|
67
|
+
for index, text in enumerate(stored["documents"] or []): # 文档遍历
|
|
68
|
+
corpus.append(Document(
|
|
69
|
+
id=stored["ids"][index], # ID
|
|
70
|
+
page_content=text, # 文本内容
|
|
71
|
+
metadata=stored["metadatas"][index] or {}, # 元数据
|
|
72
|
+
))
|
|
73
|
+
if not corpus: # 知识库为空
|
|
74
|
+
return []
|
|
75
|
+
vector_retriever = self.db.as_retriever( # 向量检索器
|
|
76
|
+
search_type="similarity",
|
|
77
|
+
search_kwargs={"k": count, "filter": where}, # 搜索参数
|
|
78
|
+
)
|
|
79
|
+
if dense_query != keyword_query:
|
|
80
|
+
vector_retriever = (
|
|
81
|
+
RunnableLambda(lambda incoming_query: dense_query) | vector_retriever
|
|
82
|
+
)
|
|
83
|
+
bm25_retriever = BM25Retriever.from_documents( # BM25检索器
|
|
84
|
+
corpus,
|
|
85
|
+
preprocess_func=lambda text: list(jieba.cut(text)), # 预处理函数
|
|
86
|
+
k=count, # top_k参数
|
|
87
|
+
)
|
|
88
|
+
documents = self.rrf_fusion( # RRF融合
|
|
89
|
+
keyword_query, vector_retriever, bm25_retriever,
|
|
90
|
+
weights=hybrid.get("weights", [0.5, 0.5]),
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
documents = self.vector_search(dense_query, self.db, count, where) # 向量检索
|
|
94
|
+
|
|
95
|
+
if post.get("enabled", False): # 后检索是否启用
|
|
96
|
+
if post.get("deduplicate", True): # 去重是否启用
|
|
97
|
+
documents = self.deduplicate(documents)
|
|
98
|
+
if post.get("rerank", False): # 重排序是否启用
|
|
99
|
+
documents = self.rerank_documents(
|
|
100
|
+
question, documents, top_k=len(documents),
|
|
101
|
+
model=post.get("rerank_model", "qwen3-rerank"),
|
|
102
|
+
)
|
|
103
|
+
if post.get("similarity_filter", False): # 相似度过滤是否启用
|
|
104
|
+
documents = self.similarity_filter(
|
|
105
|
+
question, documents, self.embeddings,
|
|
106
|
+
threshold=post.get("similarity_threshold", 0.4),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
documents = documents[:limit]
|
|
110
|
+
for rank, document in enumerate(documents, start=1):
|
|
111
|
+
document.metadata["retrieval_rank"] = rank
|
|
112
|
+
|
|
113
|
+
if post.get("enabled", False): # 后检索是否启用
|
|
114
|
+
if post.get("compress_context", False): # 上下文压缩是否启用
|
|
115
|
+
documents = self.compress_context(
|
|
116
|
+
question, documents, model=post.get("llm_model", "qwen-plus")
|
|
117
|
+
)
|
|
118
|
+
if post.get("reorder", False): # 重排序是否启用
|
|
119
|
+
documents = self.reorder_documents(documents)
|
|
120
|
+
for position, document in enumerate(documents, start=1):
|
|
121
|
+
document.metadata["context_position"] = position
|
|
122
|
+
return documents
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
# ==================== 1. 归一化查询词:移除所有空格并首尾去空格 ==================== # 归一化查询词
|
|
126
|
+
def normalize_query(question: str):
|
|
127
|
+
import re
|
|
128
|
+
return re.sub(r"\s+", " ", question).strip() # 移除所有空格并首尾去空格
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
# ==================== 2. 改写查询词:根据模型提示改写为适合知识库检索的独立查询 ==================== # 改写查询词
|
|
132
|
+
def rewrite_query(question: str, model="qwen-plus"):
|
|
133
|
+
import os
|
|
134
|
+
from dotenv import load_dotenv
|
|
135
|
+
from langchain_openai import ChatOpenAI
|
|
136
|
+
|
|
137
|
+
load_dotenv()
|
|
138
|
+
llm = ChatOpenAI(
|
|
139
|
+
model=model,
|
|
140
|
+
api_key=os.getenv("DASHSCOPE_API_KEY"),
|
|
141
|
+
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
142
|
+
temperature=0,
|
|
143
|
+
)
|
|
144
|
+
response = llm.invoke([
|
|
145
|
+
("system", "把问题改写为适合知识库检索的独立查询。保留原意,不编造缺失上下文,只输出查询。"),
|
|
146
|
+
("human", question),
|
|
147
|
+
])
|
|
148
|
+
return response.content.strip()
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
# ==================== 3. 生成假设性答案:根据模型提示生成约100字的假设性答案 ==================== # 生成假设性答案
|
|
152
|
+
def generate_hyde(question: str, model="qwen-plus"):
|
|
153
|
+
import os
|
|
154
|
+
from dotenv import load_dotenv
|
|
155
|
+
from langchain_openai import ChatOpenAI
|
|
156
|
+
|
|
157
|
+
load_dotenv()
|
|
158
|
+
llm = ChatOpenAI(
|
|
159
|
+
model=model,
|
|
160
|
+
api_key=os.getenv("DASHSCOPE_API_KEY"),
|
|
161
|
+
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
162
|
+
temperature=0,
|
|
163
|
+
)
|
|
164
|
+
response = llm.invoke([
|
|
165
|
+
("system", "为问题生成约100字的假设性答案,用于语义检索。只输出假设答案,不将其视为事实证据。"),
|
|
166
|
+
("human", question),
|
|
167
|
+
])
|
|
168
|
+
return response.content.strip()
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
# ==================== 4. 稠密:向量检索召回文档 ==================== # 稠密检索
|
|
172
|
+
def vector_search(question: str, db, top_k=5, filters=None):
|
|
173
|
+
retriever = db.as_retriever(
|
|
174
|
+
search_type="similarity",
|
|
175
|
+
search_kwargs={"k": top_k, "filter": filters},
|
|
176
|
+
)
|
|
177
|
+
documents = retriever.invoke(question)
|
|
178
|
+
for document in documents:
|
|
179
|
+
document.metadata["chunk_id"] = document.id
|
|
180
|
+
return documents
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
# ==================== 5. 稀疏:BM25检索召回文档 ==================== # 稀疏检索
|
|
184
|
+
def bm25_search(question: str, documents, top_k=5):
|
|
185
|
+
import jieba
|
|
186
|
+
from langchain_community.retrievers import BM25Retriever
|
|
187
|
+
|
|
188
|
+
if not documents:
|
|
189
|
+
return []
|
|
190
|
+
bm25_retriever = BM25Retriever.from_documents(
|
|
191
|
+
documents,
|
|
192
|
+
preprocess_func=lambda text: list(jieba.cut(text)),
|
|
193
|
+
k=top_k,
|
|
194
|
+
)
|
|
195
|
+
return bm25_retriever.invoke(question)
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
# ==================== 6. RRF融合召回文档 ==================== # RRF融合
|
|
199
|
+
def rrf_fusion(question: str, vector_retriever, bm25_retriever, weights=None, rrf_k=60):
|
|
200
|
+
from langchain_classic.retrievers import EnsembleRetriever
|
|
201
|
+
|
|
202
|
+
retriever = EnsembleRetriever(
|
|
203
|
+
retrievers=[vector_retriever, bm25_retriever],
|
|
204
|
+
weights=weights or [0.5, 0.5],
|
|
205
|
+
c=rrf_k,
|
|
206
|
+
)
|
|
207
|
+
return retriever.invoke(question)
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
# ==================== 7. 去重:根据文档内容去重 ==================== # 去重
|
|
211
|
+
def deduplicate(documents):
|
|
212
|
+
seen = set()
|
|
213
|
+
results = []
|
|
214
|
+
for document in documents:
|
|
215
|
+
if document.page_content not in seen:
|
|
216
|
+
seen.add(document.page_content)
|
|
217
|
+
results.append(document)
|
|
218
|
+
return results
|
|
219
|
+
|
|
220
|
+
from langchain_core.documents import Document
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
# ==================== 8. 重排:根据模型提示重排召回文档 ==================== # 重排
|
|
224
|
+
def rerank_documents(question: str, documents, top_k=5, model="qwen3-rerank"):
|
|
225
|
+
import os
|
|
226
|
+
from dotenv import load_dotenv
|
|
227
|
+
from langchain_community.document_compressors import DashScopeRerank
|
|
228
|
+
|
|
229
|
+
if not documents:
|
|
230
|
+
return []
|
|
231
|
+
load_dotenv()
|
|
232
|
+
reranker = DashScopeRerank(
|
|
233
|
+
model=model,
|
|
234
|
+
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY"),
|
|
235
|
+
top_n=top_k,
|
|
236
|
+
)
|
|
237
|
+
return list(reranker.compress_documents(documents, question))
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
# ==================== 9. 筛选:根据文档相似度筛选出相关文档 ==================== # 筛选
|
|
241
|
+
def similarity_filter(question: str, documents, embeddings, threshold=0.4):
|
|
242
|
+
import math
|
|
243
|
+
|
|
244
|
+
if not documents:
|
|
245
|
+
return []
|
|
246
|
+
query_vector = embeddings.embed_query(question)
|
|
247
|
+
vectors = embeddings.embed_documents([
|
|
248
|
+
document.page_content for document in documents
|
|
249
|
+
])
|
|
250
|
+
query_norm = math.sqrt(sum(value * value for value in query_vector))
|
|
251
|
+
|
|
252
|
+
results = []
|
|
253
|
+
for document, vector in zip(documents, vectors):
|
|
254
|
+
vector_norm = math.sqrt(sum(value * value for value in vector))
|
|
255
|
+
if query_norm == 0 or vector_norm == 0:
|
|
256
|
+
continue
|
|
257
|
+
similarity = sum(
|
|
258
|
+
query_value * value for query_value, value in zip(query_vector, vector)
|
|
259
|
+
) / (query_norm * vector_norm)
|
|
260
|
+
if similarity >= threshold:
|
|
261
|
+
result = document.model_copy(deep=True)
|
|
262
|
+
result.metadata["cosine_similarity"] = similarity
|
|
263
|
+
results.append(result)
|
|
264
|
+
return results
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
# ==================== 10. 压缩:根据模型提示压缩文档上下文 ==================== # 压缩
|
|
268
|
+
def compress_context(question: str, documents, model="qwen-plus"):
|
|
269
|
+
import os
|
|
270
|
+
from dotenv import load_dotenv
|
|
271
|
+
from langchain_openai import ChatOpenAI
|
|
272
|
+
from langchain_classic.retrievers.document_compressors import LLMChainExtractor
|
|
273
|
+
|
|
274
|
+
if not documents:
|
|
275
|
+
return []
|
|
276
|
+
load_dotenv()
|
|
277
|
+
llm = ChatOpenAI(
|
|
278
|
+
model=model,
|
|
279
|
+
api_key=os.getenv("DASHSCOPE_API_KEY"),
|
|
280
|
+
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
281
|
+
temperature=0,
|
|
282
|
+
)
|
|
283
|
+
compressor = LLMChainExtractor.from_llm(llm)
|
|
284
|
+
results = []
|
|
285
|
+
for document in documents:
|
|
286
|
+
is_table = (
|
|
287
|
+
document.metadata.get("content_type") == "table"
|
|
288
|
+
or "<table" in document.page_content.lower()
|
|
289
|
+
)
|
|
290
|
+
if is_table:
|
|
291
|
+
results.append(document)
|
|
292
|
+
else:
|
|
293
|
+
results.extend(compressor.compress_documents([document], question))
|
|
294
|
+
return results
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
# ==================== 11. 重排:根据模型提示重排文档顺序 ==================== # 重排
|
|
298
|
+
def reorder_documents(documents):
|
|
299
|
+
from langchain_community.document_transformers import LongContextReorder
|
|
300
|
+
|
|
301
|
+
return list(LongContextReorder().transform_documents(documents))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "demon-slaying-rag"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A modular RAG retrieval class based on LangChain and Chroma"
|
|
9
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"chromadb>=1.0",
|
|
19
|
+
"langchain-chroma>=1.0",
|
|
20
|
+
"langchain-core>=1.0",
|
|
21
|
+
"langchain-community>=0.4",
|
|
22
|
+
"langchain-openai>=1.0",
|
|
23
|
+
"langchain-classic>=1.0",
|
|
24
|
+
"dashscope>=2.0",
|
|
25
|
+
"python-dotenv>=1.0",
|
|
26
|
+
"jieba>=0.42",
|
|
27
|
+
"rank-bm25>=0.2",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
publish = ["build", "twine"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools]
|
|
34
|
+
py-modules = ["demon_slaying_rag"]
|