Daniel.y commited on
Commit
a9f7068
·
unverified ·
2 Parent(s): 2a2462d 24a1471

Merge pull request #1171 from danielaskdd/add-temperature

Browse files
Files changed (35) hide show
  1. .github/workflows/linting.yaml +1 -1
  2. README-zh.md +1380 -0
  3. README.assets/b2aaf634151b4706892693ffb43d9093.png +3 -0
  4. README.assets/iShot_2025-03-23_12.40.08.png +3 -0
  5. README.md +55 -226
  6. env.example +11 -9
  7. lightrag/__init__.py +1 -1
  8. lightrag/api/README-zh.md +559 -0
  9. lightrag/api/README.md +3 -4
  10. lightrag/api/__init__.py +1 -1
  11. lightrag/api/lightrag_server.py +30 -22
  12. lightrag/api/routers/document_routes.py +38 -16
  13. lightrag/api/routers/graph_routes.py +5 -5
  14. lightrag/api/routers/ollama_api.py +11 -6
  15. lightrag/api/routers/query_routes.py +5 -5
  16. lightrag/api/utils_api.py +155 -42
  17. lightrag/api/webui/assets/index-CJhG62dt.css +0 -0
  18. lightrag/api/webui/assets/index-Cq65VeVX.css +0 -0
  19. lightrag/api/webui/assets/{index-DlScqWrq.js → index-DUmKHl1m.js} +0 -0
  20. lightrag/api/webui/index.html +0 -0
  21. lightrag/kg/faiss_impl.py +16 -24
  22. lightrag/kg/nano_vector_db_impl.py +18 -26
  23. lightrag/kg/networkx_impl.py +16 -24
  24. lightrag/kg/shared_storage.py +26 -34
  25. lightrag_webui/src/App.tsx +15 -15
  26. lightrag_webui/src/components/ApiKeyAlert.tsx +34 -25
  27. lightrag_webui/src/components/MessageAlert.tsx +0 -56
  28. lightrag_webui/src/components/{graph → status}/StatusCard.tsx +0 -0
  29. lightrag_webui/src/components/{graph → status}/StatusIndicator.tsx +1 -1
  30. lightrag_webui/src/components/ui/AsyncSearch.tsx +71 -73
  31. lightrag_webui/src/features/SiteHeader.tsx +14 -12
  32. lightrag_webui/src/locales/ar.json +6 -0
  33. lightrag_webui/src/locales/en.json +6 -0
  34. lightrag_webui/src/locales/fr.json +6 -0
  35. lightrag_webui/src/locales/zh.json +6 -0
.github/workflows/linting.yaml CHANGED
@@ -27,4 +27,4 @@ jobs:
27
  pip install pre-commit
28
 
29
  - name: Run pre-commit
30
- run: pre-commit run --all-files
 
27
  pip install pre-commit
28
 
29
  - name: Run pre-commit
30
+ run: pre-commit run --all-files --show-diff-on-failure
README-zh.md CHANGED
@@ -0,0 +1,1380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LightRAG: Simple and Fast Retrieval-Augmented Generation
2
+
3
+ <img src="./README.assets/b2aaf634151b4706892693ffb43d9093.png" width="800" alt="LightRAG Diagram">
4
+
5
+ ## 🎉 新闻
6
+
7
+ - [X] [2025.03.18]🎯📢LightRAG现已支持引文功能。
8
+ - [X] [2025.02.05]🎯📢我们团队发布了[VideoRAG](https://github.com/HKUDS/VideoRAG),用于理解超长上下文视频。
9
+ - [X] [2025.01.13]🎯📢我们团队发布了[MiniRAG](https://github.com/HKUDS/MiniRAG),使用小型模型简化RAG。
10
+ - [X] [2025.01.06]🎯📢现在您可以[使用PostgreSQL进行存储](#using-postgresql-for-storage)。
11
+ - [X] [2024.12.31]🎯📢LightRAG现在支持[通过文档ID删除](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#delete)。
12
+ - [X] [2024.11.25]🎯📢LightRAG现在支持无缝集成[自定义知识图谱](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#insert-custom-kg),使用户能够用自己的领域专业知识增强系统。
13
+ - [X] [2024.11.19]🎯📢LightRAG的综合指南现已在[LearnOpenCV](https://learnopencv.com/lightrag)上发布。非常感谢博客作者。
14
+ - [X] [2024.11.12]🎯📢LightRAG现在支持[Oracle Database 23ai的所有存储类型(KV、向量和图)](https://github.com/HKUDS/LightRAG/blob/main/examples/lightrag_oracle_demo.py)。
15
+ - [X] [2024.11.11]🎯📢LightRAG现在支持[通过实体名称删除实体](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#delete)。
16
+ - [X] [2024.11.09]🎯📢推出[LightRAG Gui](https://lightrag-gui.streamlit.app),允许您插入、查询、可视化和下载LightRAG知识。
17
+ - [X] [2024.11.04]🎯📢现在您可以[使用Neo4J进行存储](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#using-neo4j-for-storage)。
18
+ - [X] [2024.10.29]🎯📢LightRAG现在通过`textract`支持多种文件类型,包括PDF、DOC、PPT和CSV。
19
+ - [X] [2024.10.20]🎯📢我们为LightRAG添加了一个新功能:图形可视化。
20
+ - [X] [2024.10.18]🎯📢我们添加了[LightRAG介绍视频](https://youtu.be/oageL-1I0GE)的链接。感谢作者!
21
+ - [X] [2024.10.17]🎯📢我们创建了一个[Discord频道](https://discord.gg/yF2MmDJyGJ)!欢迎加入分享和讨论!🎉🎉
22
+ - [X] [2024.10.16]🎯📢LightRAG现在支持[Ollama模型](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#quick-start)!
23
+ - [X] [2024.10.15]🎯📢LightRAG现在支持[Hugging Face模型](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#quick-start)!
24
+
25
+ <details>
26
+ <summary style="font-size: 1.4em; font-weight: bold; cursor: pointer; display: list-item;">
27
+ 算法流程图
28
+ </summary>
29
+
30
+ ![LightRAG索引流程图](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-VectorDB-Json-KV-Store-Indexing-Flowchart-scaled.jpg)
31
+ *图1:LightRAG索引流程图 - 图片来源:[Source](https://learnopencv.com/lightrag/)*
32
+ ![LightRAG检索和查询流程图](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-Querying-Flowchart-Dual-Level-Retrieval-Generation-Knowledge-Graphs-scaled.jpg)
33
+ *图2:LightRAG检索和查询流程图 - 图片来源:[Source](https://learnopencv.com/lightrag/)*
34
+
35
+ </details>
36
+
37
+ ## 安装
38
+
39
+ ### 安装LightRAG核心
40
+
41
+ * 从源代码安装(推荐)
42
+
43
+ ```bash
44
+ cd LightRAG
45
+ pip install -e .
46
+ ```
47
+
48
+ * 从PyPI安装
49
+
50
+ ```bash
51
+ pip install lightrag-hku
52
+ ```
53
+
54
+ ### 安装LightRAG服务器
55
+
56
+ LightRAG服务器旨在提供Web UI和API支持。Web UI便于文档索引、知识图谱探索和简单的RAG查询界面。LightRAG服务器还提供兼容Ollama的接口,旨在将LightRAG模拟为Ollama聊天模型。这使得AI聊天机器人(如Open WebUI)可以轻松访问LightRAG。
57
+
58
+ * 从PyPI安装
59
+
60
+ ```bash
61
+ pip install "lightrag-hku[api]"
62
+ ```
63
+
64
+ * 从源代码安装
65
+
66
+ ```bash
67
+ # 如有必要,创建Python虚拟环境
68
+ # 以可编辑模式安装并支持API
69
+ pip install -e ".[api]"
70
+ ```
71
+
72
+ **有关LightRAG服务器的更多信息,请参阅[LightRAG服务器](./lightrag/api/README.md)。**
73
+
74
+ ## 快速开始
75
+
76
+ * [视频演示](https://www.youtube.com/watch?v=g21royNJ4fw)展示如何在本地运行LightRAG。
77
+ * 所有代码都可以在`examples`中找到。
78
+ * 如果使用OpenAI模型,请在环境中设置OpenAI API密钥:`export OPENAI_API_KEY="sk-..."`。
79
+ * 下载演示文本"狄更斯的圣诞颂歌":
80
+
81
+ ```bash
82
+ curl https://raw.githubusercontent.com/gusye1234/nano-graphrag/main/tests/mock_data.txt > ./book.txt
83
+ ```
84
+
85
+ ## 查询
86
+
87
+ 使用以下Python代码片段(在脚本中)初始化LightRAG并执行查询:
88
+
89
+ ```python
90
+ import os
91
+ import asyncio
92
+ from lightrag import LightRAG, QueryParam
93
+ from lightrag.llm.openai import gpt_4o_mini_complete, gpt_4o_complete, openai_embed
94
+ from lightrag.kg.shared_storage import initialize_pipeline_status
95
+ from lightrag.utils import setup_logger
96
+
97
+ setup_logger("lightrag", level="INFO")
98
+
99
+ async def initialize_rag():
100
+ rag = LightRAG(
101
+ working_dir="your/path",
102
+ embedding_func=openai_embed,
103
+ llm_model_func=gpt_4o_mini_complete
104
+ )
105
+
106
+ await rag.initialize_storages()
107
+ await initialize_pipeline_status()
108
+
109
+ return rag
110
+
111
+ def main():
112
+ # 初始化RAG实例
113
+ rag = asyncio.run(initialize_rag())
114
+ # 插入文本
115
+ rag.insert("Your text")
116
+
117
+ # 执行朴素搜索
118
+ mode="naive"
119
+ # 执行本地搜索
120
+ mode="local"
121
+ # 执行全局搜索
122
+ mode="global"
123
+ # 执行混合搜索
124
+ mode="hybrid"
125
+ # 混合模式集成知识图谱和向量检索
126
+ mode="mix"
127
+
128
+ rag.query(
129
+ "这个故事的主要主题是什么?",
130
+ param=QueryParam(mode=mode)
131
+ )
132
+
133
+ if __name__ == "__main__":
134
+ main()
135
+ ```
136
+
137
+ ### 查询参数
138
+
139
+ ```python
140
+ class QueryParam:
141
+ mode: Literal["local", "global", "hybrid", "naive", "mix"] = "global"
142
+ """指定检索模式:
143
+ - "local":专注于上下文相关信息。
144
+ - "global":利用全局知识。
145
+ - "hybrid":结合本地和全局检索方法。
146
+ - "naive":执行基本搜索,不使用高级技术。
147
+ - "mix":集成知识图谱和向量检索。混合模式结合知识图谱和向量搜索:
148
+ - 同时使用结构化(KG)和非结构化(向量)信息
149
+ - 通过分析关系和上下文提供全面的答案
150
+ - 通过HTML img标签支持图像内容
151
+ - 允许通过top_k参数控制检索深度
152
+ """
153
+ only_need_context: bool = False
154
+ """如果为True,仅返回检索到的上下文而不生成响应。"""
155
+ response_type: str = "Multiple Paragraphs"
156
+ """定义响应格式。示例:'Multiple Paragraphs'(多段落), 'Single Paragraph'(单段落), 'Bullet Points'(要点列表)。"""
157
+ top_k: int = 60
158
+ """要检索的顶部项目数量。在'local'模式下代表实体,在'global'模式下代表关系。"""
159
+ max_token_for_text_unit: int = 4000
160
+ """每个检索文本块允许的最大令牌数。"""
161
+ max_token_for_global_context: int = 4000
162
+ """全局检索中关系描述的最大令牌分配。"""
163
+ max_token_for_local_context: int = 4000
164
+ """本地检索中实体描述的最大令牌分配。"""
165
+ ids: list[str] | None = None # 仅支持PG向量数据库
166
+ """用于过滤RAG的ID列表。"""
167
+ ...
168
+ ```
169
+
170
+ > top_k的默认值可以通过环境变量TOP_K更改。
171
+
172
+ <details>
173
+ <summary> <b>使用类OpenAI的API</b> </summary>
174
+
175
+ * LightRAG还支持类OpenAI的聊天/嵌入API:
176
+
177
+ ```python
178
+ async def llm_model_func(
179
+ prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs
180
+ ) -> str:
181
+ return await openai_complete_if_cache(
182
+ "solar-mini",
183
+ prompt,
184
+ system_prompt=system_prompt,
185
+ history_messages=history_messages,
186
+ api_key=os.getenv("UPSTAGE_API_KEY"),
187
+ base_url="https://api.upstage.ai/v1/solar",
188
+ **kwargs
189
+ )
190
+
191
+ async def embedding_func(texts: list[str]) -> np.ndarray:
192
+ return await openai_embed(
193
+ texts,
194
+ model="solar-embedding-1-large-query",
195
+ api_key=os.getenv("UPSTAGE_API_KEY"),
196
+ base_url="https://api.upstage.ai/v1/solar"
197
+ )
198
+
199
+ async def initialize_rag():
200
+ rag = LightRAG(
201
+ working_dir=WORKING_DIR,
202
+ llm_model_func=llm_model_func,
203
+ embedding_func=EmbeddingFunc(
204
+ embedding_dim=4096,
205
+ max_token_size=8192,
206
+ func=embedding_func
207
+ )
208
+ )
209
+
210
+ await rag.initialize_storages()
211
+ await initialize_pipeline_status()
212
+
213
+ return rag
214
+ ```
215
+
216
+ </details>
217
+
218
+ <details>
219
+ <summary> <b>使用Hugging Face模型</b> </summary>
220
+
221
+ * 如果您想使用Hugging Face模型,只需要按如下方式设置LightRAG:
222
+
223
+ 参见`lightrag_hf_demo.py`
224
+
225
+ ```python
226
+ # 使用Hugging Face模型初始化LightRAG
227
+ rag = LightRAG(
228
+ working_dir=WORKING_DIR,
229
+ llm_model_func=hf_model_complete, # 使用Hugging Face模型进行文本生成
230
+ llm_model_name='meta-llama/Llama-3.1-8B-Instruct', # Hugging Face的模型名称
231
+ # 使用Hugging Face嵌入函数
232
+ embedding_func=EmbeddingFunc(
233
+ embedding_dim=384,
234
+ max_token_size=5000,
235
+ func=lambda texts: hf_embed(
236
+ texts,
237
+ tokenizer=AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2"),
238
+ embed_model=AutoModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
239
+ )
240
+ ),
241
+ )
242
+ ```
243
+
244
+ </details>
245
+
246
+ <details>
247
+ <summary> <b>使用Ollama模型</b> </summary>
248
+
249
+ ### 概述
250
+
251
+ 如果您想使用Ollama模型,您需要拉取计划使用的模型和嵌入模型,例如`nomic-embed-text`。
252
+
253
+ 然后您只需要按如下方式设置LightRAG:
254
+
255
+ ```python
256
+ # 使用Ollama模型初始化LightRAG
257
+ rag = LightRAG(
258
+ working_dir=WORKING_DIR,
259
+ llm_model_func=ollama_model_complete, # 使用Ollama模型进行文本生成
260
+ llm_model_name='your_model_name', # 您的模型名称
261
+ # 使用Ollama嵌入函数
262
+ embedding_func=EmbeddingFunc(
263
+ embedding_dim=768,
264
+ max_token_size=8192,
265
+ func=lambda texts: ollama_embed(
266
+ texts,
267
+ embed_model="nomic-embed-text"
268
+ )
269
+ ),
270
+ )
271
+ ```
272
+
273
+ ### 增加上下文大小
274
+
275
+ 为了使LightRAG��常工作,上下文应至少为32k令牌。默认情况下,Ollama模型的上下文大小为8k。您可以通过以下两种方式之一实现这一点:
276
+
277
+ #### 在Modelfile中增加`num_ctx`参数。
278
+
279
+ 1. 拉取模型:
280
+
281
+ ```bash
282
+ ollama pull qwen2
283
+ ```
284
+
285
+ 2. 显示模型文件:
286
+
287
+ ```bash
288
+ ollama show --modelfile qwen2 > Modelfile
289
+ ```
290
+
291
+ 3. 编辑Modelfile,添加以下行:
292
+
293
+ ```bash
294
+ PARAMETER num_ctx 32768
295
+ ```
296
+
297
+ 4. 创建修改后的模型:
298
+
299
+ ```bash
300
+ ollama create -f Modelfile qwen2m
301
+ ```
302
+
303
+ #### 通过Ollama API设置`num_ctx`。
304
+
305
+ 您可以使用`llm_model_kwargs`参数配置ollama:
306
+
307
+ ```python
308
+ rag = LightRAG(
309
+ working_dir=WORKING_DIR,
310
+ llm_model_func=ollama_model_complete, # 使用Ollama模型进行文本生成
311
+ llm_model_name='your_model_name', # 您的模型名称
312
+ llm_model_kwargs={"options": {"num_ctx": 32768}},
313
+ # 使用Ollama嵌入函数
314
+ embedding_func=EmbeddingFunc(
315
+ embedding_dim=768,
316
+ max_token_size=8192,
317
+ func=lambda texts: ollama_embedding(
318
+ texts,
319
+ embed_model="nomic-embed-text"
320
+ )
321
+ ),
322
+ )
323
+ ```
324
+
325
+ #### 低RAM GPU
326
+
327
+ 为了在低RAM GPU上运行此实验,您应该选择小型模型并调整上下文窗口(增加上下文会增加内存消耗)。例如,在6Gb RAM的改装挖矿GPU上运行这个ollama示例需要将上下文大小设置为26k,同时使用`gemma2:2b`。它能够在`book.txt`中找到197个实体和19个关系。
328
+
329
+ </details>
330
+ <details>
331
+ <summary> <b>LlamaIndex</b> </summary>
332
+
333
+ LightRAG支持与LlamaIndex集成。
334
+
335
+ 1. **LlamaIndex** (`llm/llama_index_impl.py`):
336
+ - 通过LlamaIndex与OpenAI和其他提供商集成
337
+ - 详细设置和示例请参见[LlamaIndex文档](lightrag/llm/Readme.md)
338
+
339
+ ### 使用示例
340
+
341
+ ```python
342
+ # 使用LlamaIndex直接访问OpenAI
343
+ import asyncio
344
+ from lightrag import LightRAG
345
+ from lightrag.llm.llama_index_impl import llama_index_complete_if_cache, llama_index_embed
346
+ from llama_index.embeddings.openai import OpenAIEmbedding
347
+ from llama_index.llms.openai import OpenAI
348
+ from lightrag.kg.shared_storage import initialize_pipeline_status
349
+ from lightrag.utils import setup_logger
350
+
351
+ # 为LightRAG设置日志处理程序
352
+ setup_logger("lightrag", level="INFO")
353
+
354
+ async def initialize_rag():
355
+ rag = LightRAG(
356
+ working_dir="your/path",
357
+ llm_model_func=llama_index_complete_if_cache, # LlamaIndex兼容的完成函数
358
+ embedding_func=EmbeddingFunc( # LlamaIndex兼容的嵌入函数
359
+ embedding_dim=1536,
360
+ max_token_size=8192,
361
+ func=lambda texts: llama_index_embed(texts, embed_model=embed_model)
362
+ ),
363
+ )
364
+
365
+ await rag.initialize_storages()
366
+ await initialize_pipeline_status()
367
+
368
+ return rag
369
+
370
+ def main():
371
+ # 初始化RAG实例
372
+ rag = asyncio.run(initialize_rag())
373
+
374
+ with open("./book.txt", "r", encoding="utf-8") as f:
375
+ rag.insert(f.read())
376
+
377
+ # 执行朴素搜索
378
+ print(
379
+ rag.query("这个故事的主要主题是什么?", param=QueryParam(mode="naive"))
380
+ )
381
+
382
+ # 执行本地搜索
383
+ print(
384
+ rag.query("这个故事的主要主题是什么?", param=QueryParam(mode="local"))
385
+ )
386
+
387
+ # 执行全局搜索
388
+ print(
389
+ rag.query("这个故事的主要主题是什么?", param=QueryParam(mode="global"))
390
+ )
391
+
392
+ # 执行混合搜索
393
+ print(
394
+ rag.query("这个故事的主要主题是什么?", param=QueryParam(mode="hybrid"))
395
+ )
396
+
397
+ if __name__ == "__main__":
398
+ main()
399
+ ```
400
+
401
+ #### 详细文档和示例,请参见:
402
+
403
+ - [LlamaIndex文档](lightrag/llm/Readme.md)
404
+ - [直接OpenAI示例](examples/lightrag_llamaindex_direct_demo.py)
405
+ - [LiteLLM代理示例](examples/lightrag_llamaindex_litellm_demo.py)
406
+
407
+ </details>
408
+ <details>
409
+ <summary> <b>对话历史支持</b> </summary>
410
+
411
+ LightRAG现在通过对话历史功能支持多轮对话。以下是使用方法:
412
+
413
+ ```python
414
+ # 创建对话历史
415
+ conversation_history = [
416
+ {"role": "user", "content": "主角对圣诞节的态度是什么?"},
417
+ {"role": "assistant", "content": "在故事开始时,埃比尼泽·斯克鲁奇对圣诞节持非常消极的态度..."},
418
+ {"role": "user", "content": "他的态度是如何改变的?"}
419
+ ]
420
+
421
+ # 创建带有对话历史的查询参数
422
+ query_param = QueryParam(
423
+ mode="mix", # 或其他模式:"local"、"global"、"hybrid"
424
+ conversation_history=conversation_history, # 添加对话历史
425
+ history_turns=3 # 考虑最近的对话轮数
426
+ )
427
+
428
+ # 进行考虑对话历史的查询
429
+ response = rag.query(
430
+ "是什么导致了他性格的这种变化?",
431
+ param=query_param
432
+ )
433
+ ```
434
+
435
+ </details>
436
+
437
+ <details>
438
+ <summary> <b>自定义提示支持</b> </summary>
439
+
440
+ LightRAG现在支持自定义提示,以便对系统行为进行精细控制。以下是使用方法:
441
+
442
+ ```python
443
+ # 创建查询参数
444
+ query_param = QueryParam(
445
+ mode="hybrid", # 或其他模式:"local"、"global"、"hybrid"、"mix"和"naive"
446
+ )
447
+
448
+ # 示例1:使用默认系统提示
449
+ response_default = rag.query(
450
+ "可再生能源的主要好处是什么?",
451
+ param=query_param
452
+ )
453
+ print(response_default)
454
+
455
+ # 示例2:使用自定义提示
456
+ custom_prompt = """
457
+ 您是环境科学领域的专家助手。请提供详细且结构化的答案,并附带示例。
458
+ ---对话历史---
459
+ {history}
460
+
461
+ ---知识库---
462
+ {context_data}
463
+
464
+ ---响应规则---
465
+
466
+ - 目标格式和长度:{response_type}
467
+ """
468
+ response_custom = rag.query(
469
+ "可再生能源的主要好处是什么?",
470
+ param=query_param,
471
+ system_prompt=custom_prompt # 传递自定义提示
472
+ )
473
+ print(response_custom)
474
+ ```
475
+
476
+ </details>
477
+
478
+ <details>
479
+ <summary> <b>独立关键词提取</b> </summary>
480
+
481
+ 我们引入了新函数`query_with_separate_keyword_extraction`来增强关键词提取功能。该函数将关键词提取过程与用户提示分开,专注于查询以提高提取关键词的相关性。
482
+
483
+ ##### 工作原理
484
+
485
+ 该函数将输入分为两部分:
486
+
487
+ - `用户查询`
488
+ - `提示`
489
+
490
+ 然后仅对`用户查询`执行关键词提取。这种分离确保提取过程是集中和相关的,不受`提示`中任何额外语言的影响。它还允许`提示`纯粹用于响应格式化,保持用户原始问题的意图和清晰度。
491
+
492
+ ##### 使用示例
493
+
494
+ 这个`示例`展示了如何为教育内容定制函数,专注于为高年级学生提供详细解释。
495
+
496
+ ```python
497
+ rag.query_with_separate_keyword_extraction(
498
+ query="解释重力定律",
499
+ prompt="提供适合学习物理的高中生的详细解释。",
500
+ param=QueryParam(mode="hybrid")
501
+ )
502
+ ```
503
+
504
+ </details>
505
+
506
+ <details>
507
+ <summary> <b>插入自定义KG</b> </summary>
508
+
509
+ ```python
510
+ custom_kg = {
511
+ "chunks": [
512
+ {
513
+ "content": "Alice和Bob正在合作进行量子计算研究。",
514
+ "source_id": "doc-1"
515
+ }
516
+ ],
517
+ "entities": [
518
+ {
519
+ "entity_name": "Alice",
520
+ "entity_type": "person",
521
+ "description": "Alice是一位专门研究量子物理的研究员。",
522
+ "source_id": "doc-1"
523
+ },
524
+ {
525
+ "entity_name": "Bob",
526
+ "entity_type": "person",
527
+ "description": "Bob是一位数学家。",
528
+ "source_id": "doc-1"
529
+ },
530
+ {
531
+ "entity_name": "量子计算",
532
+ "entity_type": "technology",
533
+ "description": "量子计算利用量子力学现象进行计算。",
534
+ "source_id": "doc-1"
535
+ }
536
+ ],
537
+ "relationships": [
538
+ {
539
+ "src_id": "Alice",
540
+ "tgt_id": "Bob",
541
+ "description": "Alice和Bob是研究伙伴。",
542
+ "keywords": "合作 研究",
543
+ "weight": 1.0,
544
+ "source_id": "doc-1"
545
+ },
546
+ {
547
+ "src_id": "Alice",
548
+ "tgt_id": "量子计算",
549
+ "description": "Alice进行量子计算研究。",
550
+ "keywords": "研究 专业",
551
+ "weight": 1.0,
552
+ "source_id": "doc-1"
553
+ },
554
+ {
555
+ "src_id": "Bob",
556
+ "tgt_id": "量子计算",
557
+ "description": "Bob研究量子计算。",
558
+ "keywords": "研究 应用",
559
+ "weight": 1.0,
560
+ "source_id": "doc-1"
561
+ }
562
+ ]
563
+ }
564
+
565
+ rag.insert_custom_kg(custom_kg)
566
+ ```
567
+
568
+ </details>
569
+
570
+ ## 插入
571
+
572
+ #### 基本插入
573
+
574
+ ```python
575
+ # 基本插入
576
+ rag.insert("文本")
577
+ ```
578
+
579
+ <details>
580
+ <summary> <b> 批量插入 </b></summary>
581
+
582
+ ```python
583
+ # 基本批量插入:一次插入多个文本
584
+ rag.insert(["文本1", "文本2",...])
585
+
586
+ # 带有自定义批量大小配置的批量插入
587
+ rag = LightRAG(
588
+ working_dir=WORKING_DIR,
589
+ addon_params={
590
+ "insert_batch_size": 4 # 每批处理4个文档
591
+ }
592
+ )
593
+
594
+ rag.insert(["文本1", "文本2", "文本3", ...]) # 文档将以4个为一批进行处理
595
+ ```
596
+
597
+ `addon_params`中的`insert_batch_size`参数控制插入过程中每批处理的文档数量。这对于以下情况很有用:
598
+
599
+ - 管理大型文档集合的内存使用
600
+ - 优化处理速度
601
+ - 提供更好的进度跟踪
602
+ - 如果未指定,默认值为10
603
+
604
+ </details>
605
+
606
+ <details>
607
+ <summary> <b> 带ID插入 </b></summary>
608
+
609
+ 如果您想为文档提供自己的ID,文档数量和ID数量必须相同。
610
+
611
+ ```python
612
+ # 插入单个文本,并为其提供ID
613
+ rag.insert("文本1", ids=["文本1的ID"])
614
+
615
+ # 插入多个文本,并为它们提供ID
616
+ rag.insert(["文本1", "文本2",...], ids=["文本1的ID", "文本2的ID"])
617
+ ```
618
+
619
+ </details>
620
+
621
+ <details>
622
+ <summary><b>使用管道插入</b></summary>
623
+
624
+ `apipeline_enqueue_documents`和`apipeline_process_enqueue_documents`函数允许您对文档进行增量插入到图中。
625
+
626
+ 这对于需要在后台处理文档的场景很有用,同时仍允许主线程继续执行。
627
+
628
+ 并使用例程处理新文档。
629
+
630
+ ```python
631
+ rag = LightRAG(..)
632
+
633
+ await rag.apipeline_enqueue_documents(input)
634
+ # 您的循环例程
635
+ await rag.apipeline_process_enqueue_documents(input)
636
+ ```
637
+
638
+ </details>
639
+
640
+ <details>
641
+ <summary><b>插入多文件类型支持</b></summary>
642
+
643
+ `textract`支持读取TXT、DOCX、PPTX、CSV和PDF等文件类型。
644
+
645
+ ```python
646
+ import textract
647
+
648
+ file_path = 'TEXT.pdf'
649
+ text_content = textract.process(file_path)
650
+
651
+ rag.insert(text_content.decode('utf-8'))
652
+ ```
653
+
654
+ </details>
655
+
656
+ <details>
657
+ <summary><b>引文功能</b></summary>
658
+
659
+ 通过提供文件路径,系统确保可以将来源追溯到其原始文档。
660
+
661
+ ```python
662
+ # 定义文档及其文件路径
663
+ documents = ["文档内容1", "文档内容2"]
664
+ file_paths = ["path/to/doc1.txt", "path/to/doc2.txt"]
665
+
666
+ # 插入带有文件路径的文档
667
+ rag.insert(documents, file_paths=file_paths)
668
+ ```
669
+
670
+ </details>
671
+
672
+ ## 存储
673
+
674
+ <details>
675
+ <summary> <b>使用Neo4J进行存储</b> </summary>
676
+
677
+ * 对于生产级场景,您很可能想要利用企业级解决方案
678
+ * 进行KG存储。推荐在Docker中运行Neo4J以进行无缝本地测试。
679
+ * 参见:https://hub.docker.com/_/neo4j
680
+
681
+ ```python
682
+ export NEO4J_URI="neo4j://localhost:7687"
683
+ export NEO4J_USERNAME="neo4j"
684
+ export NEO4J_PASSWORD="password"
685
+
686
+ # 为LightRAG设置日志记录器
687
+ setup_logger("lightrag", level="INFO")
688
+
689
+ # 当您启动项目时,请确保通过指定kg="Neo4JStorage"来覆盖默认的KG:NetworkX。
690
+
691
+ # 注意:默认设置使用NetworkX
692
+ # 使用Neo4J实现初始化LightRAG。
693
+ async def initialize_rag():
694
+ rag = LightRAG(
695
+ working_dir=WORKING_DIR,
696
+ llm_model_func=gpt_4o_mini_complete, # 使用gpt_4o_mini_complete LLM模型
697
+ graph_storage="Neo4JStorage", #<-----------覆盖KG默认值
698
+ )
699
+
700
+ # 初始化数据库连接
701
+ await rag.initialize_storages()
702
+ # 初始化文档处理的管道状态
703
+ await initialize_pipeline_status()
704
+
705
+ return rag
706
+ ```
707
+
708
+ 参见test_neo4j.py获取工作示例。
709
+
710
+ </details>
711
+
712
+ <details>
713
+ <summary> <b>使用Faiss进行存储</b> </summary>
714
+
715
+ - 安装所需依赖:
716
+
717
+ ```
718
+ pip install faiss-cpu
719
+ ```
720
+
721
+ 如果您有GPU支持,也可以安装`faiss-gpu`。
722
+
723
+ - 这里我们使用`sentence-transformers`,但您也可以使用维度为`3072`的`OpenAIEmbedding`模型。
724
+
725
+ ```python
726
+ async def embedding_func(texts: list[str]) -> np.ndarray:
727
+ model = SentenceTransformer('all-MiniLM-L6-v2')
728
+ embeddings = model.encode(texts, convert_to_numpy=True)
729
+ return embeddings
730
+
731
+ # 使用LLM模型函数和嵌入函数初始化LightRAG
732
+ rag = LightRAG(
733
+ working_dir=WORKING_DIR,
734
+ llm_model_func=llm_model_func,
735
+ embedding_func=EmbeddingFunc(
736
+ embedding_dim=384,
737
+ max_token_size=8192,
738
+ func=embedding_func,
739
+ ),
740
+ vector_storage="FaissVectorDBStorage",
741
+ vector_db_storage_cls_kwargs={
742
+ "cosine_better_than_threshold": 0.3 # 您期望的阈值
743
+ }
744
+ )
745
+ ```
746
+
747
+ </details>
748
+
749
+ <details>
750
+ <summary> <b>使用PostgreSQL进行存储</b> </summary>
751
+
752
+ 对于生产级场景,您很可能想要利用企业级解决方案。PostgreSQL可以为您提供一站式解决方案,作为KV存储、向量数据库(pgvector)和图数据库(apache AGE)。
753
+
754
+ * PostgreSQL很轻量,整个二进制发行版包括所有必要的插件可以压缩到40MB:参考[Windows发布版](https://github.com/ShanGor/apache-age-windows/releases/tag/PG17%2Fv1.5.0-rc0),它在Linux/Mac上也很容易安装。
755
+ * 如果您是初学者并想避免麻烦,推荐使用docker,请从这个镜像开始(请务必阅读概述):https://hub.docker.com/r/shangor/postgres-for-rag
756
+ * 如何开始?参考:[examples/lightrag_zhipu_postgres_demo.py](https://github.com/HKUDS/LightRAG/blob/main/examples/lightrag_zhipu_postgres_demo.py)
757
+ * 为AGE创建索引示例:(如有必要,将下面的`dickens`改为您的图名)
758
+ ```sql
759
+ load 'age';
760
+ SET search_path = ag_catalog, "$user", public;
761
+ CREATE INDEX CONCURRENTLY entity_p_idx ON dickens."Entity" (id);
762
+ CREATE INDEX CONCURRENTLY vertex_p_idx ON dickens."_ag_label_vertex" (id);
763
+ CREATE INDEX CONCURRENTLY directed_p_idx ON dickens."DIRECTED" (id);
764
+ CREATE INDEX CONCURRENTLY directed_eid_idx ON dickens."DIRECTED" (end_id);
765
+ CREATE INDEX CONCURRENTLY directed_sid_idx ON dickens."DIRECTED" (start_id);
766
+ CREATE INDEX CONCURRENTLY directed_seid_idx ON dickens."DIRECTED" (start_id,end_id);
767
+ CREATE INDEX CONCURRENTLY edge_p_idx ON dickens."_ag_label_edge" (id);
768
+ CREATE INDEX CONCURRENTLY edge_sid_idx ON dickens."_ag_label_edge" (start_id);
769
+ CREATE INDEX CONCURRENTLY edge_eid_idx ON dickens."_ag_label_edge" (end_id);
770
+ CREATE INDEX CONCURRENTLY edge_seid_idx ON dickens."_ag_label_edge" (start_id,end_id);
771
+ create INDEX CONCURRENTLY vertex_idx_node_id ON dickens."_ag_label_vertex" (ag_catalog.agtype_access_operator(properties, '"node_id"'::agtype));
772
+ create INDEX CONCURRENTLY entity_idx_node_id ON dickens."Entity" (ag_catalog.agtype_access_operator(properties, '"node_id"'::agtype));
773
+ CREATE INDEX CONCURRENTLY entity_node_id_gin_idx ON dickens."Entity" using gin(properties);
774
+ ALTER TABLE dickens."DIRECTED" CLUSTER ON directed_sid_idx;
775
+
776
+ -- 如有必要可以删除
777
+ drop INDEX entity_p_idx;
778
+ drop INDEX vertex_p_idx;
779
+ drop INDEX directed_p_idx;
780
+ drop INDEX directed_eid_idx;
781
+ drop INDEX directed_sid_idx;
782
+ drop INDEX directed_seid_idx;
783
+ drop INDEX edge_p_idx;
784
+ drop INDEX edge_sid_idx;
785
+ drop INDEX edge_eid_idx;
786
+ drop INDEX edge_seid_idx;
787
+ drop INDEX vertex_idx_node_id;
788
+ drop INDEX entity_idx_node_id;
789
+ drop INDEX entity_node_id_gin_idx;
790
+ ```
791
+ * Apache AGE的已知问题:发布版本存在以下问题:
792
+ > 您可能会发现节点/边的属性是空的。
793
+ > 这是发布版本的已知问题:https://github.com/apache/age/pull/1721
794
+ >
795
+ > 您可以从源代码编译AGE来修复它。
796
+ >
797
+
798
+ ## 删除
799
+
800
+ ```python
801
+ # 删除实体:通过实体名称删除实体
802
+ rag.delete_by_entity("Project Gutenberg")
803
+
804
+ # 删除文档:通过文档ID删除与文档相关的实体和关系
805
+ rag.delete_by_doc_id("doc_id")
806
+ ```
807
+
808
+ ## 编辑实体和关系
809
+
810
+ LightRAG现在支持全面的知识图谱管理功能,允许您在知识图谱中创建、编辑和删除实体和关系。
811
+
812
+ <details>
813
+ <summary> <b>创建实体和关系</b> </summary>
814
+
815
+ ```python
816
+ # 创建新实体
817
+ entity = rag.create_entity("Google", {
818
+ "description": "Google是一家专注于互联网相关服务和产品的跨国科技公司。",
819
+ "entity_type": "company"
820
+ })
821
+
822
+ # 创建另一个实体
823
+ product = rag.create_entity("Gmail", {
824
+ "description": "Gmail是由Google开发的电子邮件服务。",
825
+ "entity_type": "product"
826
+ })
827
+
828
+ # 创建实体之间的关系
829
+ relation = rag.create_relation("Google", "Gmail", {
830
+ "description": "Google开发和运营Gmail。",
831
+ "keywords": "开发 运营 服务",
832
+ "weight": 2.0
833
+ })
834
+ ```
835
+
836
+ </details>
837
+
838
+ <details>
839
+ <summary> <b>编辑实体和关系</b> </summary>
840
+
841
+ ```python
842
+ # 编辑现有实体
843
+ updated_entity = rag.edit_entity("Google", {
844
+ "description": "Google是Alphabet Inc.的子公司,成立于1998年。",
845
+ "entity_type": "tech_company"
846
+ })
847
+
848
+ # 重命名实体(所有关系都会正确迁移)
849
+ renamed_entity = rag.edit_entity("Gmail", {
850
+ "entity_name": "Google Mail",
851
+ "description": "Google Mail(前身为Gmail)是一项电子邮件服务。"
852
+ })
853
+
854
+ # 编辑实体之间的关系
855
+ updated_relation = rag.edit_relation("Google", "Google Mail", {
856
+ "description": "Google创建并维护Google Mail服务。",
857
+ "keywords": "创建 维护 电子邮件服务",
858
+ "weight": 3.0
859
+ })
860
+ ```
861
+
862
+ </details>
863
+
864
+ 所有操作都有同步和异步版本。异步版本带有前缀"a"(例如,`acreate_entity`,`aedit_relation`)。
865
+
866
+ #### 实体操作
867
+
868
+ - **create_entity**:创建具有指定属性的新实体
869
+ - **edit_entity**:更新现有实体的属性或重命名它
870
+
871
+ #### 关系操作
872
+
873
+ - **create_relation**:在现有实体之间创建新关系
874
+ - **edit_relation**:更新现有关系的属性
875
+
876
+ 这些操作在图数据库和向量数据库组件之间保持数据一致性,确保您的知识图谱保持连贯。
877
+
878
+ ## 数据导出功能
879
+
880
+ ## 概述
881
+
882
+ LightRAG允许您以各种格式导出知识图谱数据,用于分析、共享和备份目的。系统支持导出实体、关系和关系数据。
883
+
884
+ ## 导出功能
885
+
886
+ ### 基本用法
887
+
888
+ ```python
889
+ # 基本CSV导出(默认格式)
890
+ rag.export_data("knowledge_graph.csv")
891
+
892
+ # 指定任意格式
893
+ rag.export_data("output.xlsx", file_format="excel")
894
+ ```
895
+
896
+ ### 支持的不同文件格式
897
+
898
+ ```python
899
+ # 以CSV格式导出数据
900
+ rag.export_data("graph_data.csv", file_format="csv")
901
+
902
+ # 导出数据到Excel表格
903
+ rag.export_data("graph_data.xlsx", file_format="excel")
904
+
905
+ # 以markdown格式导出数据
906
+ rag.export_data("graph_data.md", file_format="md")
907
+
908
+ # 导出数据为文本
909
+ rag.export_data("graph_data.txt", file_format="txt")
910
+ ```
911
+
912
+ ## 附加选项
913
+
914
+ 在导出中包含向量嵌入(可选):
915
+
916
+ ```python
917
+ rag.export_data("complete_data.csv", include_vector_data=True)
918
+ ```
919
+
920
+ ## 导出数据包括
921
+
922
+ 所有导出包括:
923
+
924
+ * 实体信息(名称、ID、元数据)
925
+ * 关系数据(实体之间的连接)
926
+ * 来自向量数据库的关系信息
927
+
928
+ ## 实体合并
929
+
930
+ <details>
931
+ <summary> <b>合并实体及其关系</b> </summary>
932
+
933
+ LightRAG现在支持将多个实体合并为单个实体,自动处理所有关系:
934
+
935
+ ```python
936
+ # 基本实体合并
937
+ rag.merge_entities(
938
+ source_entities=["人工智能", "AI", "机器智能"],
939
+ target_entity="AI技术"
940
+ )
941
+ ```
942
+
943
+ 使用自定义合并策略:
944
+
945
+ ```python
946
+ # 为不同字段定义自定义合并策略
947
+ rag.merge_entities(
948
+ source_entities=["约翰·史密斯", "史密斯博士", "J·史密斯"],
949
+ target_entity="约翰·史密斯",
950
+ merge_strategy={
951
+ "description": "concatenate", # 组合所有描述
952
+ "entity_type": "keep_first", # 保留第一个实体的类型
953
+ "source_id": "join_unique" # 组合所有唯一的源ID
954
+ }
955
+ )
956
+ ```
957
+
958
+ 使用自定义目标实体数据:
959
+
960
+ ```python
961
+ # 为合并后的实体指定确切值
962
+ rag.merge_entities(
963
+ source_entities=["纽约", "NYC", "大苹果"],
964
+ target_entity="纽约市",
965
+ target_entity_data={
966
+ "entity_type": "LOCATION",
967
+ "description": "纽约市是美国人口最多的城市。",
968
+ }
969
+ )
970
+ ```
971
+
972
+ 结合两种方法的高级用法:
973
+
974
+ ```python
975
+ # 使用策略和自定义数据合并公司实体
976
+ rag.merge_entities(
977
+ source_entities=["微软公司", "Microsoft Corporation", "MSFT"],
978
+ target_entity="微软",
979
+ merge_strategy={
980
+ "description": "concatenate", # 组合所有描述
981
+ "source_id": "join_unique" # 组合源ID
982
+ },
983
+ target_entity_data={
984
+ "entity_type": "ORGANIZATION",
985
+ }
986
+ )
987
+ ```
988
+
989
+ 合并实体时:
990
+
991
+ * 所有来自源实体的关系都会重定向到目标实体
992
+ * 重复的关系会被智能合并
993
+ * 防止自我关系(循环)
994
+ * 合并后删除源实体
995
+ * 保留关系权重和属性
996
+
997
+ </details>
998
+
999
+ ## 缓存
1000
+
1001
+ <details>
1002
+ <summary> <b>清除缓存</b> </summary>
1003
+
1004
+ 您可以使用不同模式清除LLM响应缓存:
1005
+
1006
+ ```python
1007
+ # 清除所有缓存
1008
+ await rag.aclear_cache()
1009
+
1010
+ # 清除本地模式缓存
1011
+ await rag.aclear_cache(modes=["local"])
1012
+
1013
+ # 清除提取缓存
1014
+ await rag.aclear_cache(modes=["default"])
1015
+
1016
+ # 清除多个模式
1017
+ await rag.aclear_cache(modes=["local", "global", "hybrid"])
1018
+
1019
+ # 同步版本
1020
+ rag.clear_cache(modes=["local"])
1021
+ ```
1022
+
1023
+ 有效的模式包括:
1024
+
1025
+ - `"default"`:提取缓存
1026
+ - `"naive"`:朴素搜索缓存
1027
+ - `"local"`:本地搜索缓存
1028
+ - `"global"`:全局搜索缓存
1029
+ - `"hybrid"`:混合搜索缓存
1030
+ - `"mix"`:混合搜索缓存
1031
+
1032
+ </details>
1033
+
1034
+ ## LightRAG初始化参数
1035
+
1036
+ <details>
1037
+ <summary> 参数 </summary>
1038
+
1039
+ | **参数** | **类型** | **说明** | **默认值** |
1040
+ |--------------|----------|-----------------|-------------|
1041
+ | **working_dir** | `str` | 存储缓存的目录 | `lightrag_cache+timestamp` |
1042
+ | **kv_storage** | `str` | 文档和文本块的存储类型。支持的类型:`JsonKVStorage`、`OracleKVStorage` | `JsonKVStorage` |
1043
+ | **vector_storage** | `str` | 嵌入向量的存储类型。支持的类型:`NanoVectorDBStorage`、`OracleVectorDBStorage` | `NanoVectorDBStorage` |
1044
+ | **graph_storage** | `str` | 图边和节点的存储类型。支持的类型:`NetworkXStorage`、`Neo4JStorage`、`OracleGraphStorage` | `NetworkXStorage` |
1045
+ | **chunk_token_size** | `int` | 拆分文档时每个块的最大令牌大小 | `1200` |
1046
+ | **chunk_overlap_token_size** | `int` | 拆分文档时两个块之间的重叠令牌大小 | `100` |
1047
+ | **tiktoken_model_name** | `str` | 用于计算令牌数的Tiktoken编码器的模型名称 | `gpt-4o-mini` |
1048
+ | **entity_extract_max_gleaning** | `int` | 实体提取过程中的循环次数,附加历史消息 | `1` |
1049
+ | **entity_summary_to_max_tokens** | `int` | 每个实体摘要的最大令牌大小 | `500` |
1050
+ | **node_embedding_algorithm** | `str` | 节点嵌入算法(当前未使用) | `node2vec` |
1051
+ | **node2vec_params** | `dict` | 节点嵌入的参数 | `{"dimensions": 1536,"num_walks": 10,"walk_length": 40,"window_size": 2,"iterations": 3,"random_seed": 3,}` |
1052
+ | **embedding_func** | `EmbeddingFunc` | 从文本生成嵌入向量的函数 | `openai_embed` |
1053
+ | **embedding_batch_num** | `int` | 嵌入过程的最大批量大小(每批发送多个文本) | `32` |
1054
+ | **embedding_func_max_async** | `int` | 最大并发异步嵌入进程数 | `16` |
1055
+ | **llm_model_func** | `callable` | LLM生成的函数 | `gpt_4o_mini_complete` |
1056
+ | **llm_model_name** | `str` | 用于生成的LLM模型名称 | `meta-llama/Llama-3.2-1B-Instruct` |
1057
+ | **llm_model_max_token_size** | `int` | LLM生成的最大令牌大小(影响实体关系摘要) | `32768`(默认值由环境变量MAX_TOKENS更改) |
1058
+ | **llm_model_max_async** | `int` | 最大并发异步LLM进程数 | `4`(默认值由环境变量MAX_ASYNC更改) |
1059
+ | **llm_model_kwargs** | `dict` | LLM生成的附加参数 | |
1060
+ | **vector_db_storage_cls_kwargs** | `dict` | 向量数据库的附加参数,如设置节点和关系检索的阈值 | cosine_better_than_threshold: 0.2(默认值由环境变量COSINE_THRESHOLD更改) |
1061
+ | **enable_llm_cache** | `bool` | 如果为`TRUE`,将LLM结果存储在缓存中;重复的提示返回缓存的响应 | `TRUE` |
1062
+ | **enable_llm_cache_for_entity_extract** | `bool` | 如果为`TRUE`,将实体提取的LLM结果存储在缓存中;适合初学者调试应用程序 | `TRUE` |
1063
+ | **addon_params** | `dict` | 附加参数,例如`{"example_number": 1, "language": "Simplified Chinese", "entity_types": ["organization", "person", "geo", "event"], "insert_batch_size": 10}`:设置示例限制、输出语言和文档处理的批量大小 | `example_number: 所有示例, language: English, insert_batch_size: 10` |
1064
+ | **convert_response_to_json_func** | `callable` | 未使用 | `convert_response_to_json` |
1065
+ | **embedding_cache_config** | `dict` | 问答缓存的配置。包含三个参数:`enabled`:布尔值,启用/禁用缓存查找功能。启用时,系统将在生成新答案之前检查缓存的响应。`similarity_threshold`:浮点值(0-1),相似度阈值。当新问题与缓存问题的相似度超过此阈值时,将直接返回缓存的答案而不调用LLM。`use_llm_check`:布尔值,启用/禁用LLM相似度验证。启用时,在返回缓存答案之前,将使用LLM作为二次检查来验证问题之间的相似度。 | 默认:`{"enabled": False, "similarity_threshold": 0.95, "use_llm_check": False}` |
1066
+
1067
+ </details>
1068
+
1069
+ ## 错误处理
1070
+
1071
+ <details>
1072
+ <summary>点击查看错误处理详情</summary>
1073
+
1074
+ API包括全面的错误处理:
1075
+
1076
+ - 文件未找到错误(404)
1077
+ - 处理错误(500)
1078
+ - 支持多种文件编码(UTF-8和GBK)
1079
+
1080
+ </details>
1081
+
1082
+ ## API
1083
+
1084
+ LightRag可以安装API支持,以提供Fast api接口来执行数据上传和索引/Rag操作/重新扫描输入文件夹等。
1085
+
1086
+ [LightRag API](lightrag/api/README.md)
1087
+
1088
+ ## 图形可视化
1089
+
1090
+ LightRAG服务器提供全面的知识图谱可视化功能。它支持各种重力布局、节点查询、子图过滤等。**有关LightRAG服务器的更多信息,请参阅[LightRAG服务器](./lightrag/api/README.md)。**
1091
+
1092
+ ![iShot_2025-03-23_12.40.08](./README.assets/iShot_2025-03-23_12.40.08.png)
1093
+
1094
+ ## 评估
1095
+
1096
+ ### 数据集
1097
+
1098
+ LightRAG使用的数据集可以从[TommyChien/UltraDomain](https://huggingface.co/datasets/TommyChien/UltraDomain)下载。
1099
+
1100
+ ### 生成查询
1101
+
1102
+ LightRAG使用以下提示生成高级查询,相应的代码在`example/generate_query.py`中。
1103
+
1104
+ <details>
1105
+ <summary> 提示 </summary>
1106
+
1107
+ ```python
1108
+ 给定以下数据集描述:
1109
+
1110
+ {description}
1111
+
1112
+ 请识别5个可能会使用此数据集的潜在用户。对于每个用户,列出他们会使用此数据集执行的5个任务。然后,对于每个(用户,任务)组合,生成5个需要对整个数据集有高级理解的问题。
1113
+
1114
+ 按以下结构输出结果:
1115
+ - 用户1:[用户描述]
1116
+ - 任务1:[任务描述]
1117
+ - 问题1:
1118
+ - 问题2:
1119
+ - 问题3:
1120
+ - 问题4:
1121
+ - 问题5:
1122
+ - 任务2:[任务描述]
1123
+ ...
1124
+ - 任务5:[任务描述]
1125
+ - 用户2:[用户描述]
1126
+ ...
1127
+ - 用户5:[用户描述]
1128
+ ...
1129
+ ```
1130
+
1131
+ </details>
1132
+
1133
+ ### 批量评估
1134
+
1135
+ 为了评估两个RAG系统在高级查询上的性能,LightRAG使用以下提示,具体代码可在`example/batch_eval.py`中找到。
1136
+
1137
+ <details>
1138
+ <summary> 提示 </summary>
1139
+
1140
+ ```python
1141
+ ---角色---
1142
+ 您是一位专家,负责根据三个标准评估同一问题的两个答案:**全面性**、**多样性**和**赋能性**。
1143
+ ---目标---
1144
+ 您将根据三个标准评估同一问题的两个答案:**全面性**、**多样性**和**赋能性**。
1145
+
1146
+ - **全面性**:答案提供了多少细节来涵盖问题的所有方面和细节?
1147
+ - **多样性**:答案在提供关于问题的不同视角和见解方面有多丰富多样?
1148
+ - **赋能性**:答案在多大程度上帮助读者理解并对主题做出明智判断?
1149
+
1150
+ 对于每个标准,选择更好的答案(答案1或答案2)并解释原因。然后,根据这三个类别选择总体赢家。
1151
+
1152
+ 这是问题:
1153
+ {query}
1154
+
1155
+ 这是两个答案:
1156
+
1157
+ **答案1:**
1158
+ {answer1}
1159
+
1160
+ **答案2:**
1161
+ {answer2}
1162
+
1163
+ 使用上述三个标准评估两个答案,并为每个标准提供详细解释。
1164
+
1165
+ 以下列JSON格式输出您的评估:
1166
+
1167
+ {{
1168
+ "全面性": {{
1169
+ "获胜者": "[答案1或答案2]",
1170
+ "解释": "[在此提供解释]"
1171
+ }},
1172
+ "赋能性": {{
1173
+ "获胜者": "[答案1或答案2]",
1174
+ "解释": "[在此提供解释]"
1175
+ }},
1176
+ "总体获胜者": {{
1177
+ "获胜者": "[答案1或答案2]",
1178
+ "解释": "[根据三个标准总结为什么这个答案是总体获胜者]"
1179
+ }}
1180
+ }}
1181
+ ```
1182
+
1183
+ </details>
1184
+
1185
+ ### 总体性能表
1186
+
1187
+ | |**农业**| |**计算机科学**| |**法律**| |**混合**| |
1188
+ |----------------------|---------------|------------|------|------------|---------|------------|-------|------------|
1189
+ | |NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|
1190
+ |**全面性**|32.4%|**67.6%**|38.4%|**61.6%**|16.4%|**83.6%**|38.8%|**61.2%**|
1191
+ |**多样性**|23.6%|**76.4%**|38.0%|**62.0%**|13.6%|**86.4%**|32.4%|**67.6%**|
1192
+ |**赋能性**|32.4%|**67.6%**|38.8%|**61.2%**|16.4%|**83.6%**|42.8%|**57.2%**|
1193
+ |**总体**|32.4%|**67.6%**|38.8%|**61.2%**|15.2%|**84.8%**|40.0%|**60.0%**|
1194
+ | |RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|
1195
+ |**全面性**|31.6%|**68.4%**|38.8%|**61.2%**|15.2%|**84.8%**|39.2%|**60.8%**|
1196
+ |**多样性**|29.2%|**70.8%**|39.2%|**60.8%**|11.6%|**88.4%**|30.8%|**69.2%**|
1197
+ |**赋能性**|31.6%|**68.4%**|36.4%|**63.6%**|15.2%|**84.8%**|42.4%|**57.6%**|
1198
+ |**总体**|32.4%|**67.6%**|38.0%|**62.0%**|14.4%|**85.6%**|40.0%|**60.0%**|
1199
+ | |HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|
1200
+ |**全面性**|26.0%|**74.0%**|41.6%|**58.4%**|26.8%|**73.2%**|40.4%|**59.6%**|
1201
+ |**多样性**|24.0%|**76.0%**|38.8%|**61.2%**|20.0%|**80.0%**|32.4%|**67.6%**|
1202
+ |**赋能性**|25.2%|**74.8%**|40.8%|**59.2%**|26.0%|**74.0%**|46.0%|**54.0%**|
1203
+ |**总体**|24.8%|**75.2%**|41.6%|**58.4%**|26.4%|**73.6%**|42.4%|**57.6%**|
1204
+ | |GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|
1205
+ |**全面性**|45.6%|**54.4%**|48.4%|**51.6%**|48.4%|**51.6%**|**50.4%**|49.6%|
1206
+ |**多样性**|22.8%|**77.2%**|40.8%|**59.2%**|26.4%|**73.6%**|36.0%|**64.0%**|
1207
+ |**赋能性**|41.2%|**58.8%**|45.2%|**54.8%**|43.6%|**56.4%**|**50.8%**|49.2%|
1208
+ |**总体**|45.2%|**54.8%**|48.0%|**52.0%**|47.2%|**52.8%**|**50.4%**|49.6%|
1209
+
1210
+ ## 复现
1211
+
1212
+ 所有代码都可以在`./reproduce`目录中找到。
1213
+
1214
+ ### 步骤0 提取唯一上下文
1215
+
1216
+ 首先,我们需要提取数据集中的唯一上下文。
1217
+
1218
+ <details>
1219
+ <summary> 代码 </summary>
1220
+
1221
+ ```python
1222
+ def extract_unique_contexts(input_directory, output_directory):
1223
+
1224
+ os.makedirs(output_directory, exist_ok=True)
1225
+
1226
+ jsonl_files = glob.glob(os.path.join(input_directory, '*.jsonl'))
1227
+ print(f"找到{len(jsonl_files)}个JSONL文件。")
1228
+
1229
+ for file_path in jsonl_files:
1230
+ filename = os.path.basename(file_path)
1231
+ name, ext = os.path.splitext(filename)
1232
+ output_filename = f"{name}_unique_contexts.json"
1233
+ output_path = os.path.join(output_directory, output_filename)
1234
+
1235
+ unique_contexts_dict = {}
1236
+
1237
+ print(f"处理文件:{filename}")
1238
+
1239
+ try:
1240
+ with open(file_path, 'r', encoding='utf-8') as infile:
1241
+ for line_number, line in enumerate(infile, start=1):
1242
+ line = line.strip()
1243
+ if not line:
1244
+ continue
1245
+ try:
1246
+ json_obj = json.loads(line)
1247
+ context = json_obj.get('context')
1248
+ if context and context not in unique_contexts_dict:
1249
+ unique_contexts_dict[context] = None
1250
+ except json.JSONDecodeError as e:
1251
+ print(f"文件{filename}第{line_number}行JSON解码错误:{e}")
1252
+ except FileNotFoundError:
1253
+ print(f"未找到文件:{filename}")
1254
+ continue
1255
+ except Exception as e:
1256
+ print(f"处理文件{filename}时发生错误:{e}")
1257
+ continue
1258
+
1259
+ unique_contexts_list = list(unique_contexts_dict.keys())
1260
+ print(f"文件{filename}中有{len(unique_contexts_list)}个唯一的`context`条目。")
1261
+
1262
+ try:
1263
+ with open(output_path, 'w', encoding='utf-8') as outfile:
1264
+ json.dump(unique_contexts_list, outfile, ensure_ascii=False, indent=4)
1265
+ print(f"唯一的`context`条目已保存到:{output_filename}")
1266
+ except Exception as e:
1267
+ print(f"保存到文件{output_filename}时发生错误:{e}")
1268
+
1269
+ print("所有文件已处理完成。")
1270
+
1271
+ ```
1272
+
1273
+ </details>
1274
+
1275
+ ### 步骤1 插入上下文
1276
+
1277
+ 对于提取的上下文,我们将它们插入到LightRAG系统中。
1278
+
1279
+ <details>
1280
+ <summary> 代码 </summary>
1281
+
1282
+ ```python
1283
+ def insert_text(rag, file_path):
1284
+ with open(file_path, mode='r') as f:
1285
+ unique_contexts = json.load(f)
1286
+
1287
+ retries = 0
1288
+ max_retries = 3
1289
+ while retries < max_retries:
1290
+ try:
1291
+ rag.insert(unique_contexts)
1292
+ break
1293
+ except Exception as e:
1294
+ retries += 1
1295
+ print(f"插入失败,重试({retries}/{max_retries}),错误:{e}")
1296
+ time.sleep(10)
1297
+ if retries == max_retries:
1298
+ print("超过最大重试次数后插入失败")
1299
+ ```
1300
+
1301
+ </details>
1302
+
1303
+ ### 步骤2 生成查询
1304
+
1305
+ 我们从数据集中每个上下文的前半部分和后半部分提取令牌,然后将它们组合为数据集描述以生成查询。
1306
+
1307
+ <details>
1308
+ <summary> 代码 </summary>
1309
+
1310
+ ```python
1311
+ tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
1312
+
1313
+ def get_summary(context, tot_tokens=2000):
1314
+ tokens = tokenizer.tokenize(context)
1315
+ half_tokens = tot_tokens // 2
1316
+
1317
+ start_tokens = tokens[1000:1000 + half_tokens]
1318
+ end_tokens = tokens[-(1000 + half_tokens):1000]
1319
+
1320
+ summary_tokens = start_tokens + end_tokens
1321
+ summary = tokenizer.convert_tokens_to_string(summary_tokens)
1322
+
1323
+ return summary
1324
+ ```
1325
+
1326
+ </details>
1327
+
1328
+ ### 步骤3 查询
1329
+
1330
+ 对于步骤2中生成的查询,我们将提取它们并查询LightRAG。
1331
+
1332
+ <details>
1333
+ <summary> 代码 </summary>
1334
+
1335
+ ```python
1336
+ def extract_queries(file_path):
1337
+ with open(file_path, 'r') as f:
1338
+ data = f.read()
1339
+
1340
+ data = data.replace('**', '')
1341
+
1342
+ queries = re.findall(r'- Question \d+: (.+)', data)
1343
+
1344
+ return queries
1345
+ ```
1346
+
1347
+ </details>
1348
+
1349
+ ## Star历史
1350
+
1351
+ <a href="https://star-history.com/#HKUDS/LightRAG&Date">
1352
+ <picture>
1353
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=HKUDS/LightRAG&type=Date&theme=dark" />
1354
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=HKUDS/LightRAG&type=Date" />
1355
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=HKUDS/LightRAG&type=Date" />
1356
+ </picture>
1357
+ </a>
1358
+
1359
+ ## 贡献
1360
+
1361
+ 感谢所有贡献者!
1362
+
1363
+ <a href="https://github.com/HKUDS/LightRAG/graphs/contributors">
1364
+ <img src="https://contrib.rocks/image?repo=HKUDS/LightRAG" />
1365
+ </a>
1366
+
1367
+ ## 🌟引用
1368
+
1369
+ ```python
1370
+ @article{guo2024lightrag,
1371
+ title={LightRAG: Simple and Fast Retrieval-Augmented Generation},
1372
+ author={Zirui Guo and Lianghao Xia and Yanhua Yu and Tu Ao and Chao Huang},
1373
+ year={2024},
1374
+ eprint={2410.05779},
1375
+ archivePrefix={arXiv},
1376
+ primaryClass={cs.IR}
1377
+ }
1378
+ ```
1379
+
1380
+ **感谢您对我们工作的关注!**
README.assets/b2aaf634151b4706892693ffb43d9093.png ADDED

Git LFS Details

  • SHA256: 5f2a92c86504f916b635874cfbaaf36fb7b90f997f3744aadf2844f2803667c1
  • Pointer size: 131 Bytes
  • Size of remote file: 482 kB
README.assets/iShot_2025-03-23_12.40.08.png ADDED

Git LFS Details

  • SHA256: f33ee342d6899f9ee0d7583be90fd6c5fc2b1aba433bc347ccc535810f9c678e
  • Pointer size: 131 Bytes
  • Size of remote file: 410 kB
README.md CHANGED
@@ -28,22 +28,10 @@
28
  </tr>
29
  </table>
30
 
31
- <div align="center">
32
- This repository hosts the code of LightRAG. The structure of this code is based on <a href="https://github.com/gusye1234/nano-graphrag">nano-graphrag</a>.
33
-
34
- <img src="https://i-blog.csdnimg.cn/direct/b2aaf634151b4706892693ffb43d9093.png" width="800" alt="LightRAG Diagram">
35
- </div>
36
- </div>
37
- </br>
38
-
39
-
40
-
41
 
 
42
 
43
- <details>
44
- <summary style="font-size: 1.4em; font-weight: bold; cursor: pointer; display: list-item;">
45
- 🎉 News
46
- </summary>
47
 
48
  - [X] [2025.03.18]🎯📢LightRAG now supports citation functionality.
49
  - [X] [2025.02.05]🎯📢Our team has released [VideoRAG](https://github.com/HKUDS/VideoRAG) understanding extremely long-context videos.
@@ -63,8 +51,6 @@ This repository hosts the code of LightRAG. The structure of this code is based
63
  - [X] [2024.10.16]🎯📢LightRAG now supports [Ollama models](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#quick-start)!
64
  - [X] [2024.10.15]🎯📢LightRAG now supports [Hugging Face models](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#quick-start)!
65
 
66
- </details>
67
-
68
  <details>
69
  <summary style="font-size: 1.4em; font-weight: bold; cursor: pointer; display: list-item;">
70
  Algorithm Flowchart
@@ -630,11 +616,11 @@ rag.insert(["TEXT1", "TEXT2",...])
630
  rag = LightRAG(
631
  working_dir=WORKING_DIR,
632
  addon_params={
633
- "insert_batch_size": 20 # Process 20 documents per batch
634
  }
635
  )
636
 
637
- rag.insert(["TEXT1", "TEXT2", "TEXT3", ...]) # Documents will be processed in batches of 20
638
  ```
639
 
640
  The `insert_batch_size` parameter in `addon_params` controls how many documents are processed in each batch during insertion. This is useful for:
@@ -1081,33 +1067,33 @@ Valid modes are:
1081
  <details>
1082
  <summary> Parameters </summary>
1083
 
1084
- | **Parameter** | **Type** | **Explanation** | **Default** |
1085
- | -------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
1086
- | **working\_dir** | `str` | Directory where the cache will be stored | `lightrag_cache+timestamp` |
1087
- | **kv\_storage** | `str` | Storage type for documents and text chunks. Supported types:`JsonKVStorage`, `OracleKVStorage` | `JsonKVStorage` |
1088
- | **vector\_storage** | `str` | Storage type for embedding vectors. Supported types:`NanoVectorDBStorage`, `OracleVectorDBStorage` | `NanoVectorDBStorage` |
1089
- | **graph\_storage** | `str` | Storage type for graph edges and nodes. Supported types:`NetworkXStorage`, `Neo4JStorage`, `OracleGraphStorage` | `NetworkXStorage` |
1090
- | **chunk\_token\_size** | `int` | Maximum token size per chunk when splitting documents | `1200` |
1091
- | **chunk\_overlap\_token\_size** | `int` | Overlap token size between two chunks when splitting documents | `100` |
1092
- | **tiktoken\_model\_name** | `str` | Model name for the Tiktoken encoder used to calculate token numbers | `gpt-4o-mini` |
1093
- | **entity\_extract\_max\_gleaning** | `int` | Number of loops in the entity extraction process, appending history messages | `1` |
1094
- | **entity\_summary\_to\_max\_tokens** | `int` | Maximum token size for each entity summary | `500` |
1095
- | **node\_embedding\_algorithm** | `str` | Algorithm for node embedding (currently not used) | `node2vec` |
1096
- | **node2vec\_params** | `dict` | Parameters for node embedding | `{"dimensions": 1536,"num_walks": 10,"walk_length": 40,"window_size": 2,"iterations": 3,"random_seed": 3,}` |
1097
- | **embedding\_func** | `EmbeddingFunc` | Function to generate embedding vectors from text | `openai_embed` |
1098
- | **embedding\_batch\_num** | `int` | Maximum batch size for embedding processes (multiple texts sent per batch) | `32` |
1099
- | **embedding\_func\_max\_async** | `int` | Maximum number of concurrent asynchronous embedding processes | `16` |
1100
- | **llm\_model\_func** | `callable` | Function for LLM generation | `gpt_4o_mini_complete` |
1101
- | **llm\_model\_name** | `str` | LLM model name for generation | `meta-llama/Llama-3.2-1B-Instruct` |
1102
- | **llm\_model\_max\_token\_size** | `int` | Maximum token size for LLM generation (affects entity relation summaries) | `32768`(default value changed by env var MAX_TOKENS) |
1103
- | **llm\_model\_max\_async** | `int` | Maximum number of concurrent asynchronous LLM processes | `4`(default value changed by env var MAX_ASYNC) |
1104
- | **llm\_model\_kwargs** | `dict` | Additional parameters for LLM generation | |
1105
- | **vector\_db\_storage\_cls\_kwargs** | `dict` | Additional parameters for vector database, like setting the threshold for nodes and relations retrieval. | cosine_better_than_threshold: 0.2(default value changed by env var COSINE_THRESHOLD) |
1106
- | **enable\_llm\_cache** | `bool` | If `TRUE`, stores LLM results in cache; repeated prompts return cached responses | `TRUE` |
1107
- | **enable\_llm\_cache\_for\_entity\_extract** | `bool` | If `TRUE`, stores LLM results in cache for entity extraction; Good for beginners to debug your application | `TRUE` |
1108
- | **addon\_params** | `dict` | Additional parameters, e.g.,`{"example_number": 1, "language": "Simplified Chinese", "entity_types": ["organization", "person", "geo", "event"], "insert_batch_size": 10}`: sets example limit, output language, and batch size for document processing | `example_number: all examples, language: English, insert_batch_size: 10` |
1109
- | **convert\_response\_to\_json\_func** | `callable` | Not used | `convert_response_to_json` |
1110
- | **embedding\_cache\_config** | `dict` | Configuration for question-answer caching. Contains three parameters:`<br>`- `enabled`: Boolean value to enable/disable cache lookup functionality. When enabled, the system will check cached responses before generating new answers.`<br>`- `similarity_threshold`: Float value (0-1), similarity threshold. When a new question's similarity with a cached question exceeds this threshold, the cached answer will be returned directly without calling the LLM.`<br>`- `use_llm_check`: Boolean value to enable/disable LLM similarity verification. When enabled, LLM will be used as a secondary check to verify the similarity between questions before returning cached answers. | Default:`{"enabled": False, "similarity_threshold": 0.95, "use_llm_check": False}` |
1111
 
1112
  </details>
1113
 
@@ -1132,166 +1118,9 @@ LightRag can be installed with API support to serve a Fast api interface to perf
1132
 
1133
  ## Graph Visualization
1134
 
1135
- <details>
1136
- <summary> <b>Graph visualization with html</b> </summary>
1137
-
1138
- * The following code can be found in `examples/graph_visual_with_html.py`
1139
 
1140
- ```python
1141
- import networkx as nx
1142
- from pyvis.network import Network
1143
-
1144
- # Load the GraphML file
1145
- G = nx.read_graphml('./dickens/graph_chunk_entity_relation.graphml')
1146
-
1147
- # Create a Pyvis network
1148
- net = Network(notebook=True)
1149
-
1150
- # Convert NetworkX graph to Pyvis network
1151
- net.from_nx(G)
1152
-
1153
- # Save and display the network
1154
- net.show('knowledge_graph.html')
1155
- ```
1156
-
1157
- </details>
1158
-
1159
- <details>
1160
- <summary> <b>Graph visualization with Neo4</b> </summary>
1161
-
1162
- * The following code can be found in `examples/graph_visual_with_neo4j.py`
1163
-
1164
- ```python
1165
- import os
1166
- import json
1167
- from lightrag.utils import xml_to_json
1168
- from neo4j import GraphDatabase
1169
-
1170
- # Constants
1171
- WORKING_DIR = "./dickens"
1172
- BATCH_SIZE_NODES = 500
1173
- BATCH_SIZE_EDGES = 100
1174
-
1175
- # Neo4j connection credentials
1176
- NEO4J_URI = "bolt://localhost:7687"
1177
- NEO4J_USERNAME = "neo4j"
1178
- NEO4J_PASSWORD = "your_password"
1179
-
1180
- def convert_xml_to_json(xml_path, output_path):
1181
- """Converts XML file to JSON and saves the output."""
1182
- if not os.path.exists(xml_path):
1183
- print(f"Error: File not found - {xml_path}")
1184
- return None
1185
-
1186
- json_data = xml_to_json(xml_path)
1187
- if json_data:
1188
- with open(output_path, 'w', encoding='utf-8') as f:
1189
- json.dump(json_data, f, ensure_ascii=False, indent=2)
1190
- print(f"JSON file created: {output_path}")
1191
- return json_data
1192
- else:
1193
- print("Failed to create JSON data")
1194
- return None
1195
-
1196
- def process_in_batches(tx, query, data, batch_size):
1197
- """Process data in batches and execute the given query."""
1198
- for i in range(0, len(data), batch_size):
1199
- batch = data[i:i + batch_size]
1200
- tx.run(query, {"nodes": batch} if "nodes" in query else {"edges": batch})
1201
-
1202
- def main():
1203
- # Paths
1204
- xml_file = os.path.join(WORKING_DIR, 'graph_chunk_entity_relation.graphml')
1205
- json_file = os.path.join(WORKING_DIR, 'graph_data.json')
1206
-
1207
- # Convert XML to JSON
1208
- json_data = convert_xml_to_json(xml_file, json_file)
1209
- if json_data is None:
1210
- return
1211
-
1212
- # Load nodes and edges
1213
- nodes = json_data.get('nodes', [])
1214
- edges = json_data.get('edges', [])
1215
-
1216
- # Neo4j queries
1217
- create_nodes_query = """
1218
- UNWIND $nodes AS node
1219
- MERGE (e:Entity {id: node.id})
1220
- SET e.entity_type = node.entity_type,
1221
- e.description = node.description,
1222
- e.source_id = node.source_id,
1223
- e.displayName = node.id
1224
- REMOVE e:Entity
1225
- WITH e, node
1226
- CALL apoc.create.addLabels(e, [node.entity_type]) YIELD node AS labeledNode
1227
- RETURN count(*)
1228
- """
1229
-
1230
- create_edges_query = """
1231
- UNWIND $edges AS edge
1232
- MATCH (source {id: edge.source})
1233
- MATCH (target {id: edge.target})
1234
- WITH source, target, edge,
1235
- CASE
1236
- WHEN edge.keywords CONTAINS 'lead' THEN 'lead'
1237
- WHEN edge.keywords CONTAINS 'participate' THEN 'participate'
1238
- WHEN edge.keywords CONTAINS 'uses' THEN 'uses'
1239
- WHEN edge.keywords CONTAINS 'located' THEN 'located'
1240
- WHEN edge.keywords CONTAINS 'occurs' THEN 'occurs'
1241
- ELSE REPLACE(SPLIT(edge.keywords, ',')[0], '\"', '')
1242
- END AS relType
1243
- CALL apoc.create.relationship(source, relType, {
1244
- weight: edge.weight,
1245
- description: edge.description,
1246
- keywords: edge.keywords,
1247
- source_id: edge.source_id
1248
- }, target) YIELD rel
1249
- RETURN count(*)
1250
- """
1251
-
1252
- set_displayname_and_labels_query = """
1253
- MATCH (n)
1254
- SET n.displayName = n.id
1255
- WITH n
1256
- CALL apoc.create.setLabels(n, [n.entity_type]) YIELD node
1257
- RETURN count(*)
1258
- """
1259
-
1260
- # Create a Neo4j driver
1261
- driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USERNAME, NEO4J_PASSWORD))
1262
-
1263
- try:
1264
- # Execute queries in batches
1265
- with driver.session() as session:
1266
- # Insert nodes in batches
1267
- session.execute_write(process_in_batches, create_nodes_query, nodes, BATCH_SIZE_NODES)
1268
-
1269
- # Insert edges in batches
1270
- session.execute_write(process_in_batches, create_edges_query, edges, BATCH_SIZE_EDGES)
1271
-
1272
- # Set displayName and labels
1273
- session.run(set_displayname_and_labels_query)
1274
-
1275
- except Exception as e:
1276
- print(f"Error occurred: {e}")
1277
-
1278
- finally:
1279
- driver.close()
1280
-
1281
- if __name__ == "__main__":
1282
- main()
1283
- ```
1284
-
1285
- </details>
1286
-
1287
- <details>
1288
- <summary> <b>Graphml 3d visualizer</b> </summary>
1289
-
1290
- LightRag can be installed with Tools support to add extra tools like the graphml 3d visualizer.
1291
-
1292
- [LightRag Visualizer](lightrag/tools/lightrag_visualizer/README.md)
1293
-
1294
- </details>
1295
 
1296
  ## Evaluation
1297
 
@@ -1386,28 +1215,28 @@ Output your evaluation in the following JSON format:
1386
 
1387
  ### Overall Performance Table
1388
 
1389
- | | **Agriculture** | | **CS** | | **Legal** | | **Mix** | |
1390
- | --------------------------- | --------------------- | ------------------ | ------------ | ------------------ | --------------- | ------------------ | --------------- | ------------------ |
1391
- | | NaiveRAG | **LightRAG** | NaiveRAG | **LightRAG** | NaiveRAG | **LightRAG** | NaiveRAG | **LightRAG** |
1392
- | **Comprehensiveness** | 32.4% | **67.6%** | 38.4% | **61.6%** | 16.4% | **83.6%** | 38.8% | **61.2%** |
1393
- | **Diversity** | 23.6% | **76.4%** | 38.0% | **62.0%** | 13.6% | **86.4%** | 32.4% | **67.6%** |
1394
- | **Empowerment** | 32.4% | **67.6%** | 38.8% | **61.2%** | 16.4% | **83.6%** | 42.8% | **57.2%** |
1395
- | **Overall** | 32.4% | **67.6%** | 38.8% | **61.2%** | 15.2% | **84.8%** | 40.0% | **60.0%** |
1396
- | | RQ-RAG | **LightRAG** | RQ-RAG | **LightRAG** | RQ-RAG | **LightRAG** | RQ-RAG | **LightRAG** |
1397
- | **Comprehensiveness** | 31.6% | **68.4%** | 38.8% | **61.2%** | 15.2% | **84.8%** | 39.2% | **60.8%** |
1398
- | **Diversity** | 29.2% | **70.8%** | 39.2% | **60.8%** | 11.6% | **88.4%** | 30.8% | **69.2%** |
1399
- | **Empowerment** | 31.6% | **68.4%** | 36.4% | **63.6%** | 15.2% | **84.8%** | 42.4% | **57.6%** |
1400
- | **Overall** | 32.4% | **67.6%** | 38.0% | **62.0%** | 14.4% | **85.6%** | 40.0% | **60.0%** |
1401
- | | HyDE | **LightRAG** | HyDE | **LightRAG** | HyDE | **LightRAG** | HyDE | **LightRAG** |
1402
- | **Comprehensiveness** | 26.0% | **74.0%** | 41.6% | **58.4%** | 26.8% | **73.2%** | 40.4% | **59.6%** |
1403
- | **Diversity** | 24.0% | **76.0%** | 38.8% | **61.2%** | 20.0% | **80.0%** | 32.4% | **67.6%** |
1404
- | **Empowerment** | 25.2% | **74.8%** | 40.8% | **59.2%** | 26.0% | **74.0%** | 46.0% | **54.0%** |
1405
- | **Overall** | 24.8% | **75.2%** | 41.6% | **58.4%** | 26.4% | **73.6%** | 42.4% | **57.6%** |
1406
- | | GraphRAG | **LightRAG** | GraphRAG | **LightRAG** | GraphRAG | **LightRAG** | GraphRAG | **LightRAG** |
1407
- | **Comprehensiveness** | 45.6% | **54.4%** | 48.4% | **51.6%** | 48.4% | **51.6%** | **50.4%** | 49.6% |
1408
- | **Diversity** | 22.8% | **77.2%** | 40.8% | **59.2%** | 26.4% | **73.6%** | 36.0% | **64.0%** |
1409
- | **Empowerment** | 41.2% | **58.8%** | 45.2% | **54.8%** | 43.6% | **56.4%** | **50.8%** | 49.2% |
1410
- | **Overall** | 45.2% | **54.8%** | 48.0% | **52.0%** | 47.2% | **52.8%** | **50.4%** | 49.6% |
1411
 
1412
  ## Reproduce
1413
 
 
28
  </tr>
29
  </table>
30
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ <img src="./README.assets/b2aaf634151b4706892693ffb43d9093.png" width="800" alt="LightRAG Diagram">
33
 
34
+ ## 🎉 News
 
 
 
35
 
36
  - [X] [2025.03.18]🎯📢LightRAG now supports citation functionality.
37
  - [X] [2025.02.05]🎯📢Our team has released [VideoRAG](https://github.com/HKUDS/VideoRAG) understanding extremely long-context videos.
 
51
  - [X] [2024.10.16]🎯📢LightRAG now supports [Ollama models](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#quick-start)!
52
  - [X] [2024.10.15]🎯📢LightRAG now supports [Hugging Face models](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#quick-start)!
53
 
 
 
54
  <details>
55
  <summary style="font-size: 1.4em; font-weight: bold; cursor: pointer; display: list-item;">
56
  Algorithm Flowchart
 
616
  rag = LightRAG(
617
  working_dir=WORKING_DIR,
618
  addon_params={
619
+ "insert_batch_size": 4 # Process 4 documents per batch
620
  }
621
  )
622
 
623
+ rag.insert(["TEXT1", "TEXT2", "TEXT3", ...]) # Documents will be processed in batches of 4
624
  ```
625
 
626
  The `insert_batch_size` parameter in `addon_params` controls how many documents are processed in each batch during insertion. This is useful for:
 
1067
  <details>
1068
  <summary> Parameters </summary>
1069
 
1070
+ | **Parameter** | **Type** | **Explanation** | **Default** |
1071
+ |--------------|----------|-----------------|-------------|
1072
+ | **working_dir** | `str` | Directory where the cache will be stored | `lightrag_cache+timestamp` |
1073
+ | **kv_storage** | `str` | Storage type for documents and text chunks. Supported types: `JsonKVStorage`, `OracleKVStorage` | `JsonKVStorage` |
1074
+ | **vector_storage** | `str` | Storage type for embedding vectors. Supported types: `NanoVectorDBStorage`, `OracleVectorDBStorage` | `NanoVectorDBStorage` |
1075
+ | **graph_storage** | `str` | Storage type for graph edges and nodes. Supported types: `NetworkXStorage`, `Neo4JStorage`, `OracleGraphStorage` | `NetworkXStorage` |
1076
+ | **chunk_token_size** | `int` | Maximum token size per chunk when splitting documents | `1200` |
1077
+ | **chunk_overlap_token_size** | `int` | Overlap token size between two chunks when splitting documents | `100` |
1078
+ | **tiktoken_model_name** | `str` | Model name for the Tiktoken encoder used to calculate token numbers | `gpt-4o-mini` |
1079
+ | **entity_extract_max_gleaning** | `int` | Number of loops in the entity extraction process, appending history messages | `1` |
1080
+ | **entity_summary_to_max_tokens** | `int` | Maximum token size for each entity summary | `500` |
1081
+ | **node_embedding_algorithm** | `str` | Algorithm for node embedding (currently not used) | `node2vec` |
1082
+ | **node2vec_params** | `dict` | Parameters for node embedding | `{"dimensions": 1536,"num_walks": 10,"walk_length": 40,"window_size": 2,"iterations": 3,"random_seed": 3,}` |
1083
+ | **embedding_func** | `EmbeddingFunc` | Function to generate embedding vectors from text | `openai_embed` |
1084
+ | **embedding_batch_num** | `int` | Maximum batch size for embedding processes (multiple texts sent per batch) | `32` |
1085
+ | **embedding_func_max_async** | `int` | Maximum number of concurrent asynchronous embedding processes | `16` |
1086
+ | **llm_model_func** | `callable` | Function for LLM generation | `gpt_4o_mini_complete` |
1087
+ | **llm_model_name** | `str` | LLM model name for generation | `meta-llama/Llama-3.2-1B-Instruct` |
1088
+ | **llm_model_max_token_size** | `int` | Maximum token size for LLM generation (affects entity relation summaries) | `32768`(default value changed by env var MAX_TOKENS) |
1089
+ | **llm_model_max_async** | `int` | Maximum number of concurrent asynchronous LLM processes | `4`(default value changed by env var MAX_ASYNC) |
1090
+ | **llm_model_kwargs** | `dict` | Additional parameters for LLM generation | |
1091
+ | **vector_db_storage_cls_kwargs** | `dict` | Additional parameters for vector database, like setting the threshold for nodes and relations retrieval | cosine_better_than_threshold: 0.2(default value changed by env var COSINE_THRESHOLD) |
1092
+ | **enable_llm_cache** | `bool` | If `TRUE`, stores LLM results in cache; repeated prompts return cached responses | `TRUE` |
1093
+ | **enable_llm_cache_for_entity_extract** | `bool` | If `TRUE`, stores LLM results in cache for entity extraction; Good for beginners to debug your application | `TRUE` |
1094
+ | **addon_params** | `dict` | Additional parameters, e.g., `{"example_number": 1, "language": "Simplified Chinese", "entity_types": ["organization", "person", "geo", "event"], "insert_batch_size": 10}`: sets example limit, output language, and batch size for document processing | `example_number: all examples, language: English, insert_batch_size: 10` |
1095
+ | **convert_response_to_json_func** | `callable` | Not used | `convert_response_to_json` |
1096
+ | **embedding_cache_config** | `dict` | Configuration for question-answer caching. Contains three parameters: `enabled`: Boolean value to enable/disable cache lookup functionality. When enabled, the system will check cached responses before generating new answers. `similarity_threshold`: Float value (0-1), similarity threshold. When a new question's similarity with a cached question exceeds this threshold, the cached answer will be returned directly without calling the LLM. `use_llm_check`: Boolean value to enable/disable LLM similarity verification. When enabled, LLM will be used as a secondary check to verify the similarity between questions before returning cached answers. | Default: `{"enabled": False, "similarity_threshold": 0.95, "use_llm_check": False}` |
1097
 
1098
  </details>
1099
 
 
1118
 
1119
  ## Graph Visualization
1120
 
1121
+ The LightRAG Server offers a comprehensive knowledge graph visualization feature. It supports various gravity layouts, node queries, subgraph filtering, and more. **For more information about LightRAG Server, please refer to [LightRAG Server](./lightrag/api/README.md).**
 
 
 
1122
 
1123
+ ![iShot_2025-03-23_12.40.08](./README.assets/iShot_2025-03-23_12.40.08.png)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1124
 
1125
  ## Evaluation
1126
 
 
1215
 
1216
  ### Overall Performance Table
1217
 
1218
+ | |**Agriculture**| |**CS**| |**Legal**| |**Mix**| |
1219
+ |----------------------|---------------|------------|------|------------|---------|------------|-------|------------|
1220
+ | |NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|
1221
+ |**Comprehensiveness**|32.4%|**67.6%**|38.4%|**61.6%**|16.4%|**83.6%**|38.8%|**61.2%**|
1222
+ |**Diversity**|23.6%|**76.4%**|38.0%|**62.0%**|13.6%|**86.4%**|32.4%|**67.6%**|
1223
+ |**Empowerment**|32.4%|**67.6%**|38.8%|**61.2%**|16.4%|**83.6%**|42.8%|**57.2%**|
1224
+ |**Overall**|32.4%|**67.6%**|38.8%|**61.2%**|15.2%|**84.8%**|40.0%|**60.0%**|
1225
+ | |RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|
1226
+ |**Comprehensiveness**|31.6%|**68.4%**|38.8%|**61.2%**|15.2%|**84.8%**|39.2%|**60.8%**|
1227
+ |**Diversity**|29.2%|**70.8%**|39.2%|**60.8%**|11.6%|**88.4%**|30.8%|**69.2%**|
1228
+ |**Empowerment**|31.6%|**68.4%**|36.4%|**63.6%**|15.2%|**84.8%**|42.4%|**57.6%**|
1229
+ |**Overall**|32.4%|**67.6%**|38.0%|**62.0%**|14.4%|**85.6%**|40.0%|**60.0%**|
1230
+ | |HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|
1231
+ |**Comprehensiveness**|26.0%|**74.0%**|41.6%|**58.4%**|26.8%|**73.2%**|40.4%|**59.6%**|
1232
+ |**Diversity**|24.0%|**76.0%**|38.8%|**61.2%**|20.0%|**80.0%**|32.4%|**67.6%**|
1233
+ |**Empowerment**|25.2%|**74.8%**|40.8%|**59.2%**|26.0%|**74.0%**|46.0%|**54.0%**|
1234
+ |**Overall**|24.8%|**75.2%**|41.6%|**58.4%**|26.4%|**73.6%**|42.4%|**57.6%**|
1235
+ | |GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|
1236
+ |**Comprehensiveness**|45.6%|**54.4%**|48.4%|**51.6%**|48.4%|**51.6%**|**50.4%**|49.6%|
1237
+ |**Diversity**|22.8%|**77.2%**|40.8%|**59.2%**|26.4%|**73.6%**|36.0%|**64.0%**|
1238
+ |**Empowerment**|41.2%|**58.8%**|45.2%|**54.8%**|43.6%|**56.4%**|**50.8%**|49.2%|
1239
+ |**Overall**|45.2%|**54.8%**|48.0%|**52.0%**|47.2%|**52.8%**|**50.4%**|49.6%|
1240
 
1241
  ## Reproduce
1242
 
env.example CHANGED
@@ -13,9 +13,6 @@
13
  # SSL_CERTFILE=/path/to/cert.pem
14
  # SSL_KEYFILE=/path/to/key.pem
15
 
16
- ### Security (empty for no api-key is needed)
17
- # LIGHTRAG_API_KEY=your-secure-api-key-here
18
-
19
  ### Directory Configuration
20
  # WORKING_DIR=<absolute_path_for_working_dir>
21
  # INPUT_DIR=<absolute_path_for_doc_input_dir>
@@ -39,21 +36,23 @@
39
  # MAX_TOKEN_ENTITY_DESC=4000
40
 
41
  ### Settings for document indexing
42
- # SUMMARY_LANGUAGE=English
 
43
  # CHUNK_SIZE=1200
44
  # CHUNK_OVERLAP_SIZE=100
45
  # MAX_TOKEN_SUMMARY=500 # Max tokens for entity or relations summary
46
  # MAX_PARALLEL_INSERT=2 # Number of parallel processing documents in one patch
47
- # MAX_ASYNC=4 # Max concurrency requests of LLM
48
- # ENABLE_LLM_CACHE_FOR_EXTRACT=true # Enable LLM cache for entity extraction
49
 
50
  # EMBEDDING_BATCH_NUM=32 # num of chunks send to Embedding in one request
51
  # EMBEDDING_FUNC_MAX_ASYNC=16 # Max concurrency requests for Embedding
52
  # MAX_EMBED_TOKENS=8192
53
 
54
  ### LLM Configuration (Use valid host. For local services installed with docker, you can use host.docker.internal)
55
- # MAX_TOKENS=32768 # Max tokens send to LLM (less than context size of the model)
56
- # TIMEOUT=150 # Time out in seconds for LLM, None for infinite timeout
 
 
 
57
  LLM_BINDING=ollama
58
  LLM_MODEL=mistral-nemo:latest
59
  LLM_BINDING_API_KEY=your_api_key
@@ -163,4 +162,7 @@ AUTH_USERNAME=admin # login name
163
  AUTH_PASSWORD=admin123 # password
164
  TOKEN_SECRET=your-key-for-LightRAG-API-Server # JWT key
165
  TOKEN_EXPIRE_HOURS=4 # expire duration
166
- WHITELIST_PATHS=/login,/health # white list
 
 
 
 
13
  # SSL_CERTFILE=/path/to/cert.pem
14
  # SSL_KEYFILE=/path/to/key.pem
15
 
 
 
 
16
  ### Directory Configuration
17
  # WORKING_DIR=<absolute_path_for_working_dir>
18
  # INPUT_DIR=<absolute_path_for_doc_input_dir>
 
36
  # MAX_TOKEN_ENTITY_DESC=4000
37
 
38
  ### Settings for document indexing
39
+ ENABLE_LLM_CACHE_FOR_EXTRACT=true # Enable LLM cache for entity extraction
40
+ SUMMARY_LANGUAGE=English
41
  # CHUNK_SIZE=1200
42
  # CHUNK_OVERLAP_SIZE=100
43
  # MAX_TOKEN_SUMMARY=500 # Max tokens for entity or relations summary
44
  # MAX_PARALLEL_INSERT=2 # Number of parallel processing documents in one patch
 
 
45
 
46
  # EMBEDDING_BATCH_NUM=32 # num of chunks send to Embedding in one request
47
  # EMBEDDING_FUNC_MAX_ASYNC=16 # Max concurrency requests for Embedding
48
  # MAX_EMBED_TOKENS=8192
49
 
50
  ### LLM Configuration (Use valid host. For local services installed with docker, you can use host.docker.internal)
51
+ TIMEOUT=150 # Time out in seconds for LLM, None for infinite timeout
52
+ TEMPERATURE=0.5
53
+ MAX_ASYNC=4 # Max concurrency requests of LLM
54
+ MAX_TOKENS=32768 # Max tokens send to LLM (less than context size of the model)
55
+
56
  LLM_BINDING=ollama
57
  LLM_MODEL=mistral-nemo:latest
58
  LLM_BINDING_API_KEY=your_api_key
 
162
  AUTH_PASSWORD=admin123 # password
163
  TOKEN_SECRET=your-key-for-LightRAG-API-Server # JWT key
164
  TOKEN_EXPIRE_HOURS=4 # expire duration
165
+
166
+ ### API-Key to access LightRAG Server API
167
+ # LIGHTRAG_API_KEY=your-secure-api-key-here
168
+ # WHITELIST_PATHS=/health,/api/*
lightrag/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
  from .lightrag import LightRAG as LightRAG, QueryParam as QueryParam
2
 
3
- __version__ = "1.2.8"
4
  __author__ = "Zirui Guo"
5
  __url__ = "https://github.com/HKUDS/LightRAG"
 
1
  from .lightrag import LightRAG as LightRAG, QueryParam as QueryParam
2
 
3
+ __version__ = "1.2.9"
4
  __author__ = "Zirui Guo"
5
  __url__ = "https://github.com/HKUDS/LightRAG"
lightrag/api/README-zh.md ADDED
@@ -0,0 +1,559 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LightRAG 服务器和 Web 界面
2
+
3
+ LightRAG 服务器旨在提供 Web 界面和 API 支持。Web 界面便于文档索引、知识图谱探索和简单的 RAG 查询界面。LightRAG 服务器还提供了与 Ollama 兼容的接口,旨在将 LightRAG 模拟为 Ollama 聊天模型。这使得 AI 聊天机器人(如 Open WebUI)可以轻松访问 LightRAG。
4
+
5
+ ![image-20250323122538997](./README.assets/image-20250323122538997.png)
6
+
7
+ ![image-20250323122754387](./README.assets/image-20250323122754387.png)
8
+
9
+ ![image-20250323123011220](./README.assets/image-20250323123011220.png)
10
+
11
+ ## 入门指南
12
+
13
+ ### 安装
14
+
15
+ * 从 PyPI 安装
16
+
17
+ ```bash
18
+ pip install "lightrag-hku[api]"
19
+ ```
20
+
21
+ * 从源代码安装
22
+
23
+ ```bash
24
+ # 克隆仓库
25
+ git clone https://github.com/HKUDS/lightrag.git
26
+
27
+ # 切换到仓库目录
28
+ cd lightrag
29
+
30
+ # 如有必要,创建 Python 虚拟环境
31
+ # 以可编辑模式安装并支持 API
32
+ pip install -e ".[api]"
33
+ ```
34
+
35
+ ### 启动 LightRAG 服务器前的准备
36
+
37
+ LightRAG 需要同时集成 LLM(大型语言模型)和嵌入模型以有效执行文档索引和查询操作。在首次部署 LightRAG 服务器之前,必须配置 LLM 和嵌入模型的设置。LightRAG 支持绑定到各种 LLM/嵌入后端:
38
+
39
+ * ollama
40
+ * lollms
41
+ * openai 或 openai 兼容
42
+ * azure_openai
43
+
44
+ 建议使用环境变量来配置 LightRAG 服务器。项目根目录中有一个名为 `env.example` 的示例环境变量文件。请将此文件复制到启动目录并重命名为 `.env`。之后,您可以在 `.env` 文件中修改与 LLM 和嵌入模型相关的参数。需要注意的是,LightRAG 服务器每次启动时都会将 `.env` 中的环境变量加载到系统环境变量中。由于 LightRAG 服务器会优先使用系统环境变量中的设置,如果您在通过命令行启动 LightRAG 服务器后修改了 `.env` 文件,则需要执行 `source .env` 使新设置生效。
45
+
46
+ 以下是 LLM 和嵌入模型的一些常见设置示例:
47
+
48
+ * OpenAI LLM + Ollama 嵌入
49
+
50
+ ```
51
+ LLM_BINDING=openai
52
+ LLM_MODEL=gpt-4o
53
+ LLM_BINDING_HOST=https://api.openai.com/v1
54
+ LLM_BINDING_API_KEY=your_api_key
55
+ MAX_TOKENS=32768 # 发送给 LLM 的最大 token 数(小于模型上下文大小)
56
+
57
+ EMBEDDING_BINDING=ollama
58
+ EMBEDDING_BINDING_HOST=http://localhost:11434
59
+ EMBEDDING_MODEL=bge-m3:latest
60
+ EMBEDDING_DIM=1024
61
+ # EMBEDDING_BINDING_API_KEY=your_api_key
62
+ ```
63
+
64
+ * Ollama LLM + Ollama 嵌入
65
+
66
+ ```
67
+ LLM_BINDING=ollama
68
+ LLM_MODEL=mistral-nemo:latest
69
+ LLM_BINDING_HOST=http://localhost:11434
70
+ # LLM_BINDING_API_KEY=your_api_key
71
+ MAX_TOKENS=8192 # 发送给 LLM 的最大 token 数(基于您的 Ollama 服务器容量)
72
+
73
+ EMBEDDING_BINDING=ollama
74
+ EMBEDDING_BINDING_HOST=http://localhost:11434
75
+ EMBEDDING_MODEL=bge-m3:latest
76
+ EMBEDDING_DIM=1024
77
+ # EMBEDDING_BINDING_API_KEY=your_api_key
78
+ ```
79
+
80
+ ### 启动 LightRAG 服务器
81
+
82
+ LightRAG 服务器支持两种运行模式:
83
+ * 简单高效的 Uvicorn 模式
84
+
85
+ ```
86
+ lightrag-server
87
+ ```
88
+ * 多进程 Gunicorn + Uvicorn 模式(生产模式,不支持 Windows 环境)
89
+
90
+ ```
91
+ lightrag-gunicorn --workers 4
92
+ ```
93
+ `.env` 文件必须放在启动目录中。启动时,LightRAG 服务器将创建一个文档目录(默认为 `./inputs`)和一个数据目录(默认为 `./rag_storage`)。这允许您从不同目录启动多个 LightRAG 服务器实例,每个实例配置为监听不同的网络端口。
94
+
95
+ 以下是一些常用的启动参数:
96
+
97
+ - `--host`:服务器监听地址(默认:0.0.0.0)
98
+ - `--port`:服务器监听端口(默认:9621)
99
+ - `--timeout`:LLM 请求超时时间(默认:150 秒)
100
+ - `--log-level`:日志级别(默认:INFO)
101
+ - --input-dir:指定要扫描文档的目录(默认:./input)
102
+
103
+ ### 启动时自动扫描
104
+
105
+ 当使用 `--auto-scan-at-startup` 参数启动任何服务器时,系统将自动:
106
+
107
+ 1. 扫描输入目录中的新文件
108
+ 2. 为尚未在数据库中的新文档建立索引
109
+ 3. 使所有内容立即可用于 RAG 查询
110
+
111
+ > `--input-dir` 参数指定要扫描的输入目录。您可以从 webui 触发输入目录扫描。
112
+
113
+ ### Gunicorn + Uvicorn 的多工作进程
114
+
115
+ LightRAG 服务器可以在 `Gunicorn + Uvicorn` 预加载模式下运行。Gunicorn 的多工作进程(多进程)功能可以防止文档索引任务阻塞 RAG 查询。使用 CPU 密集型文档提取工具(如 docling)在纯 Uvicorn 模式下可能会导致整个系统被阻塞。
116
+
117
+ 虽然 LightRAG 服务器使用一个工作进程来处理文档索引流程,但通过 Uvicorn 的异步任务支持,可以并行处理多个文件。文档索引速度的瓶颈主要在于 LLM。如果您的 LLM 支持高并发,您可以通过增加 LLM 的并发级别来加速文档索引。以下是几个与并发处理相关的环境变量及其默认值:
118
+
119
+ ```
120
+ WORKERS=2 # 工作进程数,不大于 (2 x 核心数) + 1
121
+ MAX_PARALLEL_INSERT=2 # 一批中并行处理的文件数
122
+ MAX_ASYNC=4 # LLM 的最大并发请求数
123
+ ```
124
+
125
+ ### 将 Lightrag 安装为 Linux 服务
126
+
127
+ 从示例文件 `lightrag.sevice.example` 创建您的服务文件 `lightrag.sevice`。修改服务文件中的 WorkingDirectory 和 ExecStart:
128
+
129
+ ```text
130
+ Description=LightRAG Ollama Service
131
+ WorkingDirectory=<lightrag 安装目录>
132
+ ExecStart=<lightrag 安装目录>/lightrag/api/lightrag-api
133
+ ```
134
+
135
+ 修改您的服务启动脚本:`lightrag-api`。根据需要更改 python 虚拟环境激活命令:
136
+
137
+ ```shell
138
+ #!/bin/bash
139
+
140
+ # 您的 python 虚拟环境激活命令
141
+ source /home/netman/lightrag-xyj/venv/bin/activate
142
+ # 启动 lightrag api 服务器
143
+ lightrag-server
144
+ ```
145
+
146
+ 安装 LightRAG 服务。如果您的系统是 Ubuntu,以下命令将生效:
147
+
148
+ ```shell
149
+ sudo cp lightrag.service /etc/systemd/system/
150
+ sudo systemctl daemon-reload
151
+ sudo systemctl start lightrag.service
152
+ sudo systemctl status lightrag.service
153
+ sudo systemctl enable lightrag.service
154
+ ```
155
+
156
+ ## Ollama 模拟
157
+
158
+ 我们为 LightRAG 提供了 Ollama 兼容接口,旨在将 LightRAG 模拟为 Ollama 聊天模型。这使得支持 Ollama 的 AI 聊天前端(如 Open WebUI)可以轻松访问 LightRAG。
159
+
160
+ ### 将 Open WebUI 连接到 LightRAG
161
+
162
+ 启动 lightrag-server 后,您可以在 Open WebUI 管理面板中添加 Ollama 类型的连接。然后,一个名为 lightrag:latest 的模型将出现在 Open WebUI 的模型管理界面中。用户随后可以通过聊天界面向 LightRAG 发送查询。对于这种用例,最好将 LightRAG 安装为服务。
163
+
164
+ Open WebUI 使用 LLM 来执行会话标题和会话关键词生成任务。因此,Ollama 聊天补全 API 会检测并将 OpenWebUI 会话相关请求直接转发给底层 LLM。Open WebUI 的截图:
165
+
166
+ ![image-20250323194750379](./README.assets/image-20250323194750379.png)
167
+
168
+ ### 在聊天中选择查询模式
169
+
170
+ 查询字符串中的查询前缀可以决定使用哪种 LightRAG 查询模式来生成响应。支持的前缀包括:
171
+
172
+ ```
173
+ /local
174
+ /global
175
+ /hybrid
176
+ /naive
177
+ /mix
178
+ /bypass
179
+ ```
180
+
181
+ 例如,聊天消息 "/mix 唐僧有几个徒弟" 将触发 LightRAG 的混合模式查询。没有查询前缀的聊天消息默认会触发混合模式查询。
182
+
183
+ "/bypass" 不是 LightRAG 查询模式,它会告诉 API 服务器将查询连同聊天历史直接传递给底层 LLM。因此用户可以使用 LLM 基于聊天历史回答问题。如果您使用 Open WebUI 作为前端,您可以直接切换到普通 LLM 模型,而不是使用 /bypass 前缀。
184
+
185
+ ## API 密钥和认证
186
+
187
+ 默认情况下,LightRAG 服务器可以在没有任何认证的情况下访问。我们可以使用 API 密钥或账户凭证配置服务器以确保其安全。
188
+
189
+ * API 密钥
190
+
191
+ ```
192
+ LIGHTRAG_API_KEY=your-secure-api-key-here
193
+ WHITELIST_PATHS=/health,/api/*
194
+ ```
195
+
196
+ > 健康检查和 Ollama 模拟端点默认不进行 API 密钥检查。
197
+
198
+ * 账户凭证(Web 界面需要登录后才能访问)
199
+
200
+ LightRAG API 服务器使用基于 HS256 算法的 JWT 认证。要启用安全访问控制,需要以下环境变量:
201
+
202
+ ```bash
203
+ # JWT 认证
204
+ AUTH_USERNAME=admin # 登录名
205
+ AUTH_PASSWORD=admin123 # 密码
206
+ TOKEN_SECRET=your-key # JWT 密钥
207
+ TOKEN_EXPIRE_HOURS=4 # 过期时间
208
+ ```
209
+
210
+ > 目前仅支持配置一个管理员账户和密码。尚未开发和实现完整的账户系统。
211
+
212
+ 如果未配置账户凭证,Web 界面将以访客身份访问系统。因此,即使仅配置了 API 密钥,所有 API 仍然可以通过访客账户访问,这仍然不安全。因此,要保护 API,需要同时配置这两种认证方法。
213
+
214
+ ## Azure OpenAI 后端配置
215
+
216
+ 可以使用以下 Azure CLI 命令创建 Azure OpenAI API(您需要先从 [https://docs.microsoft.com/en-us/cli/azure/install-azure-cli](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) 安装 Azure CLI):
217
+
218
+ ```bash
219
+ # 根据需要更改资源组名称、位置和 OpenAI 资源名称
220
+ RESOURCE_GROUP_NAME=LightRAG
221
+ LOCATION=swedencentral
222
+ RESOURCE_NAME=LightRAG-OpenAI
223
+
224
+ az login
225
+ az group create --name $RESOURCE_GROUP_NAME --location $LOCATION
226
+ az cognitiveservices account create --name $RESOURCE_NAME --resource-group $RESOURCE_GROUP_NAME --kind OpenAI --sku S0 --location swedencentral
227
+ az cognitiveservices account deployment create --resource-group $RESOURCE_GROUP_NAME --model-format OpenAI --name $RESOURCE_NAME --deployment-name gpt-4o --model-name gpt-4o --model-version "2024-08-06" --sku-capacity 100 --sku-name "Standard"
228
+ az cognitiveservices account deployment create --resource-group $RESOURCE_GROUP_NAME --model-format OpenAI --name $RESOURCE_NAME --deployment-name text-embedding-3-large --model-name text-embedding-3-large --model-version "1" --sku-capacity 80 --sku-name "Standard"
229
+ az cognitiveservices account show --name $RESOURCE_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.endpoint"
230
+ az cognitiveservices account keys list --name $RESOURCE_NAME -g $RESOURCE_GROUP_NAME
231
+ ```
232
+
233
+ 最后一个命令的输出将提供 OpenAI API 的端点和密钥。您可以使用这些值在 `.env` 文件中设置环境变量。
234
+
235
+ ```
236
+ # .env 中的 Azure OpenAI 配置
237
+ LLM_BINDING=azure_openai
238
+ LLM_BINDING_HOST=your-azure-endpoint
239
+ LLM_MODEL=your-model-deployment-name
240
+ LLM_BINDING_API_KEY=your-azure-api-key
241
+ AZURE_OPENAI_API_VERSION=2024-08-01-preview # 可选,默认为最新版本
242
+ EMBEDDING_BINDING=azure_openai # 如果使用 Azure OpenAI 进行嵌入
243
+ EMBEDDING_MODEL=your-embedding-deployment-name
244
+ ```
245
+
246
+ ## LightRAG 服务器详细配置
247
+
248
+ API 服务器可以通过三种方式配置(优先级从高到低):
249
+
250
+ * 命令行参数
251
+ * 环境变量或 .env 文件
252
+ * Config.ini(仅用于存储配置)
253
+
254
+ 大多数配置都有默认设置,详细信息请查看示例文件:`.env.example`。数据存储配置也可以通过 config.ini 设置。为方便起见,提供了示例文件 `config.ini.example`。
255
+
256
+ ### 支持的 LLM 和嵌入后端
257
+
258
+ LightRAG 支持绑定到各种 LLM/嵌入后端:
259
+
260
+ * ollama
261
+ * lollms
262
+ * openai 和 openai 兼容
263
+ * azure_openai
264
+
265
+ 使用环境变量 `LLM_BINDING` 或 CLI 参数 `--llm-binding` 选择 LLM 后端类型。使用环境变量 `EMBEDDING_BINDING` 或 CLI 参数 `--embedding-binding` 选择嵌入后端类型。
266
+
267
+ ### 实体提取配置
268
+ * ENABLE_LLM_CACHE_FOR_EXTRACT:为实体提取启用 LLM 缓存(默认:true)
269
+
270
+ 在测试环境中将 `ENABLE_LLM_CACHE_FOR_EXTRACT` 设置为 true 以减少 LLM 调用成本是很常见的做法。
271
+
272
+ ### 支持的存储类型
273
+
274
+ LightRAG 使用 4 种类型的存储用于不同目的:
275
+
276
+ * KV_STORAGE:llm 响应缓存、文本块、文档信息
277
+ * VECTOR_STORAGE:实体向量、关系向量、块向量
278
+ * GRAPH_STORAGE:实体关系图
279
+ * DOC_STATUS_STORAGE:文档索引状态
280
+
281
+ 每种存储类型都有几种实现:
282
+
283
+ * KV_STORAGE 支持的实现名称
284
+
285
+ ```
286
+ JsonKVStorage JsonFile(默认)
287
+ MongoKVStorage MogonDB
288
+ RedisKVStorage Redis
289
+ TiDBKVStorage TiDB
290
+ PGKVStorage Postgres
291
+ OracleKVStorage Oracle
292
+ ```
293
+
294
+ * GRAPH_STORAGE 支持的实现名称
295
+
296
+ ```
297
+ NetworkXStorage NetworkX(默认)
298
+ Neo4JStorage Neo4J
299
+ MongoGraphStorage MongoDB
300
+ TiDBGraphStorage TiDB
301
+ AGEStorage AGE
302
+ GremlinStorage Gremlin
303
+ PGGraphStorage Postgres
304
+ OracleGraphStorage Postgres
305
+ ```
306
+
307
+ * VECTOR_STORAGE 支持的实现名称
308
+
309
+ ```
310
+ NanoVectorDBStorage NanoVector(默认)
311
+ MilvusVectorDBStorge Milvus
312
+ ChromaVectorDBStorage Chroma
313
+ TiDBVectorDBStorage TiDB
314
+ PGVectorStorage Postgres
315
+ FaissVectorDBStorage Faiss
316
+ QdrantVectorDBStorage Qdrant
317
+ OracleVectorDBStorage Oracle
318
+ MongoVectorDBStorage MongoDB
319
+ ```
320
+
321
+ * DOC_STATUS_STORAGE 支持的实现名称
322
+
323
+ ```
324
+ JsonDocStatusStorage JsonFile(默认)
325
+ PGDocStatusStorage Postgres
326
+ MongoDocStatusStorage MongoDB
327
+ ```
328
+
329
+ ### 如何选择存储实现
330
+
331
+ 您可以通过环境变量选择存储实现。在首次启动 API 服务器之前,您可以将以下环境变量设置为特定的存储实现名称:
332
+
333
+ ```
334
+ LIGHTRAG_KV_STORAGE=PGKVStorage
335
+ LIGHTRAG_VECTOR_STORAGE=PGVectorStorage
336
+ LIGHTRAG_GRAPH_STORAGE=PGGraphStorage
337
+ LIGHTRAG_DOC_STATUS_STORAGE=PGDocStatusStorage
338
+ ```
339
+
340
+ 在向 LightRAG 添加文档后,您不能更改存储实现选择。目前尚不支持从一个存储实现迁移到另一个存储实现。更多信息请阅读示例 env 文件或 config.ini 文件。
341
+
342
+ ### LightRag API 服务器命令行选项
343
+
344
+ | 参数 | 默认值 | 描述 |
345
+ |-----------|---------|-------------|
346
+ | --host | 0.0.0.0 | 服务器主机 |
347
+ | --port | 9621 | 服务器端口 |
348
+ | --working-dir | ./rag_storage | RAG 存储的工作目录 |
349
+ | --input-dir | ./inputs | 包含输入文档的目录 |
350
+ | --max-async | 4 | 最大异步操作数 |
351
+ | --max-tokens | 32768 | 最大 token 大小 |
352
+ | --timeout | 150 | 超时时间(秒)。None 表示无限超时(不推荐) |
353
+ | --log-level | INFO | 日志级别(DEBUG、INFO、WARNING、ERROR、CRITICAL) |
354
+ | --verbose | - | 详细调试输出(True、False) |
355
+ | --key | None | 用于认证的 API 密钥。保护 lightrag 服务器免受未授权访问 |
356
+ | --ssl | False | 启用 HTTPS |
357
+ | --ssl-certfile | None | SSL 证书文件路径(如果启用 --ssl 则必需) |
358
+ | --ssl-keyfile | None | SSL 私钥文件路径(如果启用 --ssl 则必需) |
359
+ | --top-k | 50 | 要检索的 top-k 项目数;在"local"模式下对应实体,在"global"模式下对应关系。 |
360
+ | --cosine-threshold | 0.4 | 节点和关系检索的余弦阈值,与 top-k 一起控制节点和关系的检索。 |
361
+ | --llm-binding | ollama | LLM 绑定类型(lollms、ollama、openai、openai-ollama、azure_openai) |
362
+ | --embedding-binding | ollama | 嵌入绑定类型(lollms、ollama、openai、azure_openai) |
363
+ | auto-scan-at-startup | - | 扫描输入目录中的新文件并开始索引 |
364
+
365
+ ### 使用示例
366
+
367
+ #### 使用 ollama 默认本地服务器作为 llm 和嵌入后端运行 Lightrag 服务器
368
+
369
+ Ollama 是 llm 和嵌入的默认后端,因此默认情况下您可以不带参数运行 lightrag-server,将使用默认值。确保已安装 ollama 并且正在运行,且默认模型已安装在 ollama 上。
370
+
371
+ ```bash
372
+ # 使用 ollama 运行 lightrag,llm 使用 mistral-nemo:latest,嵌入使用 bge-m3:latest
373
+ lightrag-server
374
+
375
+ # 使用认证密钥
376
+ lightrag-server --key my-key
377
+ ```
378
+
379
+ #### 使用 lollms 默认本地服务器作为 llm 和嵌入后端运行 Lightrag 服务器
380
+
381
+ ```bash
382
+ # 使用 lollms 运行 lightrag,llm 使用 mistral-nemo:latest,嵌入使用 bge-m3:latest
383
+ # 在 .env 或 config.ini 中配置 LLM_BINDING=lollms 和 EMBEDDING_BINDING=lollms
384
+ lightrag-server
385
+
386
+ # 使用认证密钥
387
+ lightrag-server --key my-key
388
+ ```
389
+
390
+ #### 使用 openai 服务器作为 llm 和嵌入后端运行 Lightrag 服务器
391
+
392
+ ```bash
393
+ # 使用 openai 运行 lightrag,llm 使用 GPT-4o-mini,嵌入使用 text-embedding-3-small
394
+ # 在 .env 或 config.ini 中配置:
395
+ # LLM_BINDING=openai
396
+ # LLM_MODEL=GPT-4o-mini
397
+ # EMBEDDING_BINDING=openai
398
+ # EMBEDDING_MODEL=text-embedding-3-small
399
+ lightrag-server
400
+
401
+ # 使用认证密钥
402
+ lightrag-server --key my-key
403
+ ```
404
+
405
+ #### 使用 azure openai 服务器作为 llm 和嵌入后端运行 Lightrag 服务器
406
+
407
+ ```bash
408
+ # 使用 azure_openai 运行 lightrag
409
+ # 在 .env 或 config.ini 中配置:
410
+ # LLM_BINDING=azure_openai
411
+ # LLM_MODEL=your-model
412
+ # EMBEDDING_BINDING=azure_openai
413
+ # EMBEDDING_MODEL=your-embedding-model
414
+ lightrag-server
415
+
416
+ # 使用认证密钥
417
+ lightrag-server --key my-key
418
+ ```
419
+
420
+ **重要说明:**
421
+ - 对于 LoLLMs:确保指定的模型已安装在您的 LoLLMs 实例中
422
+ - 对于 Ollama:确保指定的模型已安装在您的 Ollama 实例中
423
+ - 对于 OpenAI:确保您已设置 OPENAI_API_KEY 环境变量
424
+ - 对于 Azure OpenAI:按照先决条件部分所述构建和配置您的服务器
425
+
426
+ 要获取任何服务器的帮助,使用 --help 标志:
427
+ ```bash
428
+ lightrag-server --help
429
+ ```
430
+
431
+ 注意:如果您不需要 API 功能,可以使用以下命令安装不带 API 支持的基本包:
432
+ ```bash
433
+ pip install lightrag-hku
434
+ ```
435
+
436
+ ## API 端点
437
+
438
+ 所有服务器(LoLLMs、Ollama、OpenAI 和 Azure OpenAI)都为 RAG 功能提供相同的 REST API 端点。当 API 服务器运行时,访问:
439
+
440
+ - Swagger UI:http://localhost:9621/docs
441
+ - ReDoc:http://localhost:9621/redoc
442
+
443
+ 您可以使用提供的 curl 命令或通过 Swagger UI 界面测试 API 端点。确保:
444
+
445
+ 1. 启动适当的后端服务(LoLLMs、Ollama 或 OpenAI)
446
+ 2. 启动 RAG 服务器
447
+ 3. 使用文档管理端点上传一些文档
448
+ 4. 使用查询端点查询系统
449
+ 5. 如果在输入目录中放入新文件,触发文档扫描
450
+
451
+ ### 查询端点
452
+
453
+ #### POST /query
454
+ 使用不同搜索模式查询 RAG 系统。
455
+
456
+ ```bash
457
+ curl -X POST "http://localhost:9621/query" \
458
+ -H "Content-Type: application/json" \
459
+ -d '{"query": "您的问题", "mode": "hybrid", ""}'
460
+ ```
461
+
462
+ #### POST /query/stream
463
+ 从 RAG 系统流式获取响应。
464
+
465
+ ```bash
466
+ curl -X POST "http://localhost:9621/query/stream" \
467
+ -H "Content-Type: application/json" \
468
+ -d '{"query": "您的问题", "mode": "hybrid"}'
469
+ ```
470
+
471
+ ### 文档管理端点
472
+
473
+ #### POST /documents/text
474
+ 直接将文本插入 RAG 系统。
475
+
476
+ ```bash
477
+ curl -X POST "http://localhost:9621/documents/text" \
478
+ -H "Content-Type: application/json" \
479
+ -d '{"text": "您的文本内容", "description": "可选描述"}'
480
+ ```
481
+
482
+ #### POST /documents/file
483
+ 向 RAG 系统上传单个文件。
484
+
485
+ ```bash
486
+ curl -X POST "http://localhost:9621/documents/file" \
487
+ -F "file=@/path/to/your/document.txt" \
488
+ -F "description=可选描述"
489
+ ```
490
+
491
+ #### POST /documents/batch
492
+ 一次上传多个文件。
493
+
494
+ ```bash
495
+ curl -X POST "http://localhost:9621/documents/batch" \
496
+ -F "files=@/path/to/doc1.txt" \
497
+ -F "files=@/path/to/doc2.txt"
498
+ ```
499
+
500
+ #### POST /documents/scan
501
+
502
+ 触发输入目录中新文件的文档扫描。
503
+
504
+ ```bash
505
+ curl -X POST "http://localhost:9621/documents/scan" --max-time 1800
506
+ ```
507
+
508
+ > 根据所有新文件的预计索引时间调整 max-time。
509
+
510
+ #### DELETE /documents
511
+
512
+ 从 RAG 系统中清除所有文档。
513
+
514
+ ```bash
515
+ curl -X DELETE "http://localhost:9621/documents"
516
+ ```
517
+
518
+ ### Ollama 模拟端点
519
+
520
+ #### GET /api/version
521
+
522
+ 获取 Ollama 版本信息。
523
+
524
+ ```bash
525
+ curl http://localhost:9621/api/version
526
+ ```
527
+
528
+ #### GET /api/tags
529
+
530
+ 获取 Ollama 可用模型。
531
+
532
+ ```bash
533
+ curl http://localhost:9621/api/tags
534
+ ```
535
+
536
+ #### POST /api/chat
537
+
538
+ 处理聊天补全请求。通过根据查询前缀选择查询模式将用户查询路由到 LightRAG。检测并将 OpenWebUI 会话相关请求(用于元数据生成任务)直接转发给底层 LLM。
539
+
540
+ ```shell
541
+ curl -N -X POST http://localhost:9621/api/chat -H "Content-Type: application/json" -d \
542
+ '{"model":"lightrag:latest","messages":[{"role":"user","content":"猪八戒是谁"}],"stream":true}'
543
+ ```
544
+
545
+ > 有关 Ollama API 的更多信息,请访问:[Ollama API 文档](https://github.com/ollama/ollama/blob/main/docs/api.md)
546
+
547
+ #### POST /api/generate
548
+
549
+ 处理生成补全请求。为了兼容性目的,该请求不由 LightRAG 处理,而是由底层 LLM 模型处理。
550
+
551
+ ### 实用工具端点
552
+
553
+ #### GET /health
554
+ 检查��务器健康状况和配置。
555
+
556
+ ```bash
557
+ curl "http://localhost:9621/health"
558
+
559
+ ```
lightrag/api/README.md CHANGED
@@ -153,10 +153,6 @@ sudo systemctl status lightrag.service
153
  sudo systemctl enable lightrag.service
154
  ```
155
 
156
-
157
-
158
-
159
-
160
  ## Ollama Emulation
161
 
162
  We provide an Ollama-compatible interfaces for LightRAG, aiming to emulate LightRAG as an Ollama chat model. This allows AI chat frontends supporting Ollama, such as Open WebUI, to access LightRAG easily.
@@ -196,8 +192,11 @@ By default, the LightRAG Server can be accessed without any authentication. We c
196
 
197
  ```
198
  LIGHTRAG_API_KEY=your-secure-api-key-here
 
199
  ```
200
 
 
 
201
  * Account credentials (the web UI requires login before access)
202
 
203
  LightRAG API Server implements JWT-based authentication using HS256 algorithm. To enable secure access control, the following environment variables are required:
 
153
  sudo systemctl enable lightrag.service
154
  ```
155
 
 
 
 
 
156
  ## Ollama Emulation
157
 
158
  We provide an Ollama-compatible interfaces for LightRAG, aiming to emulate LightRAG as an Ollama chat model. This allows AI chat frontends supporting Ollama, such as Open WebUI, to access LightRAG easily.
 
192
 
193
  ```
194
  LIGHTRAG_API_KEY=your-secure-api-key-here
195
+ WHITELIST_PATHS=/health,/api/*
196
  ```
197
 
198
+ > Health check and Ollama emuluation endpoins is exclude from API-KEY check by default.
199
+
200
  * Account credentials (the web UI requires login before access)
201
 
202
  LightRAG API Server implements JWT-based authentication using HS256 algorithm. To enable secure access control, the following environment variables are required:
lightrag/api/__init__.py CHANGED
@@ -1 +1 @@
1
- __api_version__ = "1.2.2"
 
1
+ __api_version__ = "1.2.5"
lightrag/api/lightrag_server.py CHANGED
@@ -18,7 +18,7 @@ from fastapi.middleware.cors import CORSMiddleware
18
  from contextlib import asynccontextmanager
19
  from dotenv import load_dotenv
20
  from lightrag.api.utils_api import (
21
- get_api_key_dependency,
22
  parse_args,
23
  get_default_host,
24
  display_splash_screen,
@@ -41,7 +41,6 @@ from lightrag.kg.shared_storage import (
41
  get_namespace_data,
42
  get_pipeline_status_lock,
43
  initialize_pipeline_status,
44
- get_all_update_flags_status,
45
  )
46
  from fastapi.security import OAuth2PasswordRequestForm
47
  from .auth import auth_handler
@@ -136,19 +135,28 @@ def create_app(args):
136
  await rag.finalize_storages()
137
 
138
  # Initialize FastAPI
139
- app = FastAPI(
140
- title="LightRAG API",
141
- description="API for querying text using LightRAG with separate storage and input directories"
142
  + "(With authentication)"
143
  if api_key
144
  else "",
145
- version=__api_version__,
146
- openapi_url="/openapi.json", # Explicitly set OpenAPI schema URL
147
- docs_url="/docs", # Explicitly set docs URL
148
- redoc_url="/redoc", # Explicitly set redoc URL
149
- openapi_tags=[{"name": "api"}],
150
- lifespan=lifespan,
151
- )
 
 
 
 
 
 
 
 
 
152
 
153
  def get_cors_origins():
154
  """Get allowed origins from environment variable
@@ -168,8 +176,8 @@ def create_app(args):
168
  allow_headers=["*"],
169
  )
170
 
171
- # Create the optional API key dependency
172
- optional_api_key = get_api_key_dependency(api_key)
173
 
174
  # Create working directory if it doesn't exist
175
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
@@ -200,6 +208,7 @@ def create_app(args):
200
  kwargs["response_format"] = GPTKeywordExtractionFormat
201
  if history_messages is None:
202
  history_messages = []
 
203
  return await openai_complete_if_cache(
204
  args.llm_model,
205
  prompt,
@@ -222,6 +231,7 @@ def create_app(args):
222
  kwargs["response_format"] = GPTKeywordExtractionFormat
223
  if history_messages is None:
224
  history_messages = []
 
225
  return await azure_openai_complete_if_cache(
226
  args.llm_model,
227
  prompt,
@@ -302,6 +312,7 @@ def create_app(args):
302
  },
303
  namespace_prefix=args.namespace_prefix,
304
  auto_manage_storages_states=False,
 
305
  )
306
  else: # azure_openai
307
  rag = LightRAG(
@@ -331,6 +342,7 @@ def create_app(args):
331
  },
332
  namespace_prefix=args.namespace_prefix,
333
  auto_manage_storages_states=False,
 
334
  )
335
 
336
  # Add routes
@@ -339,7 +351,7 @@ def create_app(args):
339
  app.include_router(create_graph_routes(rag, api_key))
340
 
341
  # Add Ollama API routes
342
- ollama_api = OllamaAPI(rag, top_k=args.top_k)
343
  app.include_router(ollama_api.router, prefix="/api")
344
 
345
  @app.get("/")
@@ -347,7 +359,7 @@ def create_app(args):
347
  """Redirect root path to /webui"""
348
  return RedirectResponse(url="/webui")
349
 
350
- @app.get("/auth-status", dependencies=[Depends(optional_api_key)])
351
  async def get_auth_status():
352
  """Get authentication status and guest token if auth is not configured"""
353
  username = os.getenv("AUTH_USERNAME")
@@ -375,7 +387,7 @@ def create_app(args):
375
  "api_version": __api_version__,
376
  }
377
 
378
- @app.post("/login", dependencies=[Depends(optional_api_key)])
379
  async def login(form_data: OAuth2PasswordRequestForm = Depends()):
380
  username = os.getenv("AUTH_USERNAME")
381
  password = os.getenv("AUTH_PASSWORD")
@@ -411,12 +423,9 @@ def create_app(args):
411
  "api_version": __api_version__,
412
  }
413
 
414
- @app.get("/health", dependencies=[Depends(optional_api_key)])
415
  async def get_status():
416
  """Get current system status"""
417
- # Get update flags status for all namespaces
418
- update_status = await get_all_update_flags_status()
419
-
420
  username = os.getenv("AUTH_USERNAME")
421
  password = os.getenv("AUTH_PASSWORD")
422
  if not (username and password):
@@ -444,7 +453,6 @@ def create_app(args):
444
  "vector_storage": args.vector_storage,
445
  "enable_llm_cache_for_extract": args.enable_llm_cache_for_extract,
446
  },
447
- "update_status": update_status,
448
  "core_version": core_version,
449
  "api_version": __api_version__,
450
  "auth_mode": auth_mode,
 
18
  from contextlib import asynccontextmanager
19
  from dotenv import load_dotenv
20
  from lightrag.api.utils_api import (
21
+ get_combined_auth_dependency,
22
  parse_args,
23
  get_default_host,
24
  display_splash_screen,
 
41
  get_namespace_data,
42
  get_pipeline_status_lock,
43
  initialize_pipeline_status,
 
44
  )
45
  from fastapi.security import OAuth2PasswordRequestForm
46
  from .auth import auth_handler
 
135
  await rag.finalize_storages()
136
 
137
  # Initialize FastAPI
138
+ app_kwargs = {
139
+ "title": "LightRAG Server API",
140
+ "description": "Providing API for LightRAG core, Web UI and Ollama Model Emulation"
141
  + "(With authentication)"
142
  if api_key
143
  else "",
144
+ "version": __api_version__,
145
+ "openapi_url": "/openapi.json", # Explicitly set OpenAPI schema URL
146
+ "docs_url": "/docs", # Explicitly set docs URL
147
+ "redoc_url": "/redoc", # Explicitly set redoc URL
148
+ "openapi_tags": [{"name": "api"}],
149
+ "lifespan": lifespan,
150
+ }
151
+
152
+ # Configure Swagger UI parameters
153
+ # Enable persistAuthorization and tryItOutEnabled for better user experience
154
+ app_kwargs["swagger_ui_parameters"] = {
155
+ "persistAuthorization": True,
156
+ "tryItOutEnabled": True,
157
+ }
158
+
159
+ app = FastAPI(**app_kwargs)
160
 
161
  def get_cors_origins():
162
  """Get allowed origins from environment variable
 
176
  allow_headers=["*"],
177
  )
178
 
179
+ # Create combined auth dependency for all endpoints
180
+ combined_auth = get_combined_auth_dependency(api_key)
181
 
182
  # Create working directory if it doesn't exist
183
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
 
208
  kwargs["response_format"] = GPTKeywordExtractionFormat
209
  if history_messages is None:
210
  history_messages = []
211
+ kwargs["temperature"] = args.temperature
212
  return await openai_complete_if_cache(
213
  args.llm_model,
214
  prompt,
 
231
  kwargs["response_format"] = GPTKeywordExtractionFormat
232
  if history_messages is None:
233
  history_messages = []
234
+ kwargs["temperature"] = args.temperature
235
  return await azure_openai_complete_if_cache(
236
  args.llm_model,
237
  prompt,
 
312
  },
313
  namespace_prefix=args.namespace_prefix,
314
  auto_manage_storages_states=False,
315
+ max_parallel_insert=args.max_parallel_insert,
316
  )
317
  else: # azure_openai
318
  rag = LightRAG(
 
342
  },
343
  namespace_prefix=args.namespace_prefix,
344
  auto_manage_storages_states=False,
345
+ max_parallel_insert=args.max_parallel_insert,
346
  )
347
 
348
  # Add routes
 
351
  app.include_router(create_graph_routes(rag, api_key))
352
 
353
  # Add Ollama API routes
354
+ ollama_api = OllamaAPI(rag, top_k=args.top_k, api_key=api_key)
355
  app.include_router(ollama_api.router, prefix="/api")
356
 
357
  @app.get("/")
 
359
  """Redirect root path to /webui"""
360
  return RedirectResponse(url="/webui")
361
 
362
+ @app.get("/auth-status")
363
  async def get_auth_status():
364
  """Get authentication status and guest token if auth is not configured"""
365
  username = os.getenv("AUTH_USERNAME")
 
387
  "api_version": __api_version__,
388
  }
389
 
390
+ @app.post("/login")
391
  async def login(form_data: OAuth2PasswordRequestForm = Depends()):
392
  username = os.getenv("AUTH_USERNAME")
393
  password = os.getenv("AUTH_PASSWORD")
 
423
  "api_version": __api_version__,
424
  }
425
 
426
+ @app.get("/health", dependencies=[Depends(combined_auth)])
427
  async def get_status():
428
  """Get current system status"""
 
 
 
429
  username = os.getenv("AUTH_USERNAME")
430
  password = os.getenv("AUTH_PASSWORD")
431
  if not (username and password):
 
453
  "vector_storage": args.vector_storage,
454
  "enable_llm_cache_for_extract": args.enable_llm_cache_for_extract,
455
  },
 
456
  "core_version": core_version,
457
  "api_version": __api_version__,
458
  "auth_mode": auth_mode,
lightrag/api/routers/document_routes.py CHANGED
@@ -17,15 +17,13 @@ from pydantic import BaseModel, Field, field_validator
17
  from lightrag import LightRAG
18
  from lightrag.base import DocProcessingStatus, DocStatus
19
  from lightrag.api.utils_api import (
20
- get_api_key_dependency,
21
  global_args,
22
- get_auth_dependency,
23
  )
24
 
25
  router = APIRouter(
26
  prefix="/documents",
27
  tags=["documents"],
28
- dependencies=[Depends(get_auth_dependency())],
29
  )
30
 
31
  # Temporary file prefix
@@ -113,6 +111,7 @@ class PipelineStatusResponse(BaseModel):
113
  request_pending: Flag for pending request for processing
114
  latest_message: Latest message from pipeline processing
115
  history_messages: List of history messages
 
116
  """
117
 
118
  autoscanned: bool = False
@@ -125,6 +124,7 @@ class PipelineStatusResponse(BaseModel):
125
  request_pending: bool = False
126
  latest_message: str = ""
127
  history_messages: Optional[List[str]] = None
 
128
 
129
  class Config:
130
  extra = "allow" # Allow additional fields from the pipeline status
@@ -475,8 +475,8 @@ async def run_scanning_process(rag: LightRAG, doc_manager: DocumentManager):
475
  if not new_files:
476
  return
477
 
478
- # Get MAX_PARALLEL_INSERT from global_args
479
- max_parallel = global_args["max_parallel_insert"]
480
  # Calculate batch size as 2 * MAX_PARALLEL_INSERT
481
  batch_size = 2 * max_parallel
482
 
@@ -505,9 +505,10 @@ async def run_scanning_process(rag: LightRAG, doc_manager: DocumentManager):
505
  def create_document_routes(
506
  rag: LightRAG, doc_manager: DocumentManager, api_key: Optional[str] = None
507
  ):
508
- optional_api_key = get_api_key_dependency(api_key)
 
509
 
510
- @router.post("/scan", dependencies=[Depends(optional_api_key)])
511
  async def scan_for_new_documents(background_tasks: BackgroundTasks):
512
  """
513
  Trigger the scanning process for new documents.
@@ -523,7 +524,7 @@ def create_document_routes(
523
  background_tasks.add_task(run_scanning_process, rag, doc_manager)
524
  return {"status": "scanning_started"}
525
 
526
- @router.post("/upload", dependencies=[Depends(optional_api_key)])
527
  async def upload_to_input_dir(
528
  background_tasks: BackgroundTasks, file: UploadFile = File(...)
529
  ):
@@ -568,7 +569,7 @@ def create_document_routes(
568
  raise HTTPException(status_code=500, detail=str(e))
569
 
570
  @router.post(
571
- "/text", response_model=InsertResponse, dependencies=[Depends(optional_api_key)]
572
  )
573
  async def insert_text(
574
  request: InsertTextRequest, background_tasks: BackgroundTasks
@@ -603,7 +604,7 @@ def create_document_routes(
603
  @router.post(
604
  "/texts",
605
  response_model=InsertResponse,
606
- dependencies=[Depends(optional_api_key)],
607
  )
608
  async def insert_texts(
609
  request: InsertTextsRequest, background_tasks: BackgroundTasks
@@ -636,7 +637,7 @@ def create_document_routes(
636
  raise HTTPException(status_code=500, detail=str(e))
637
 
638
  @router.post(
639
- "/file", response_model=InsertResponse, dependencies=[Depends(optional_api_key)]
640
  )
641
  async def insert_file(
642
  background_tasks: BackgroundTasks, file: UploadFile = File(...)
@@ -681,7 +682,7 @@ def create_document_routes(
681
  @router.post(
682
  "/file_batch",
683
  response_model=InsertResponse,
684
- dependencies=[Depends(optional_api_key)],
685
  )
686
  async def insert_batch(
687
  background_tasks: BackgroundTasks, files: List[UploadFile] = File(...)
@@ -742,7 +743,7 @@ def create_document_routes(
742
  raise HTTPException(status_code=500, detail=str(e))
743
 
744
  @router.delete(
745
- "", response_model=InsertResponse, dependencies=[Depends(optional_api_key)]
746
  )
747
  async def clear_documents():
748
  """
@@ -771,7 +772,7 @@ def create_document_routes(
771
 
772
  @router.get(
773
  "/pipeline_status",
774
- dependencies=[Depends(optional_api_key)],
775
  response_model=PipelineStatusResponse,
776
  )
777
  async def get_pipeline_status() -> PipelineStatusResponse:
@@ -798,13 +799,34 @@ def create_document_routes(
798
  HTTPException: If an error occurs while retrieving pipeline status (500)
799
  """
800
  try:
801
- from lightrag.kg.shared_storage import get_namespace_data
 
 
 
802
 
803
  pipeline_status = await get_namespace_data("pipeline_status")
804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  # Convert to regular dict if it's a Manager.dict
806
  status_dict = dict(pipeline_status)
807
 
 
 
 
808
  # Convert history_messages to a regular list if it's a Manager.list
809
  if "history_messages" in status_dict:
810
  status_dict["history_messages"] = list(status_dict["history_messages"])
@@ -819,7 +841,7 @@ def create_document_routes(
819
  logger.error(traceback.format_exc())
820
  raise HTTPException(status_code=500, detail=str(e))
821
 
822
- @router.get("", dependencies=[Depends(optional_api_key)])
823
  async def documents() -> DocsStatusesResponse:
824
  """
825
  Get the status of all documents in the system.
 
17
  from lightrag import LightRAG
18
  from lightrag.base import DocProcessingStatus, DocStatus
19
  from lightrag.api.utils_api import (
20
+ get_combined_auth_dependency,
21
  global_args,
 
22
  )
23
 
24
  router = APIRouter(
25
  prefix="/documents",
26
  tags=["documents"],
 
27
  )
28
 
29
  # Temporary file prefix
 
111
  request_pending: Flag for pending request for processing
112
  latest_message: Latest message from pipeline processing
113
  history_messages: List of history messages
114
+ update_status: Status of update flags for all namespaces
115
  """
116
 
117
  autoscanned: bool = False
 
124
  request_pending: bool = False
125
  latest_message: str = ""
126
  history_messages: Optional[List[str]] = None
127
+ update_status: Optional[dict] = None
128
 
129
  class Config:
130
  extra = "allow" # Allow additional fields from the pipeline status
 
475
  if not new_files:
476
  return
477
 
478
+ # Get MAX_PARALLEL_INSERT from global_args["main_args"]
479
+ max_parallel = global_args["main_args"].max_parallel_insert
480
  # Calculate batch size as 2 * MAX_PARALLEL_INSERT
481
  batch_size = 2 * max_parallel
482
 
 
505
  def create_document_routes(
506
  rag: LightRAG, doc_manager: DocumentManager, api_key: Optional[str] = None
507
  ):
508
+ # Create combined auth dependency for document routes
509
+ combined_auth = get_combined_auth_dependency(api_key)
510
 
511
+ @router.post("/scan", dependencies=[Depends(combined_auth)])
512
  async def scan_for_new_documents(background_tasks: BackgroundTasks):
513
  """
514
  Trigger the scanning process for new documents.
 
524
  background_tasks.add_task(run_scanning_process, rag, doc_manager)
525
  return {"status": "scanning_started"}
526
 
527
+ @router.post("/upload", dependencies=[Depends(combined_auth)])
528
  async def upload_to_input_dir(
529
  background_tasks: BackgroundTasks, file: UploadFile = File(...)
530
  ):
 
569
  raise HTTPException(status_code=500, detail=str(e))
570
 
571
  @router.post(
572
+ "/text", response_model=InsertResponse, dependencies=[Depends(combined_auth)]
573
  )
574
  async def insert_text(
575
  request: InsertTextRequest, background_tasks: BackgroundTasks
 
604
  @router.post(
605
  "/texts",
606
  response_model=InsertResponse,
607
+ dependencies=[Depends(combined_auth)],
608
  )
609
  async def insert_texts(
610
  request: InsertTextsRequest, background_tasks: BackgroundTasks
 
637
  raise HTTPException(status_code=500, detail=str(e))
638
 
639
  @router.post(
640
+ "/file", response_model=InsertResponse, dependencies=[Depends(combined_auth)]
641
  )
642
  async def insert_file(
643
  background_tasks: BackgroundTasks, file: UploadFile = File(...)
 
682
  @router.post(
683
  "/file_batch",
684
  response_model=InsertResponse,
685
+ dependencies=[Depends(combined_auth)],
686
  )
687
  async def insert_batch(
688
  background_tasks: BackgroundTasks, files: List[UploadFile] = File(...)
 
743
  raise HTTPException(status_code=500, detail=str(e))
744
 
745
  @router.delete(
746
+ "", response_model=InsertResponse, dependencies=[Depends(combined_auth)]
747
  )
748
  async def clear_documents():
749
  """
 
772
 
773
  @router.get(
774
  "/pipeline_status",
775
+ dependencies=[Depends(combined_auth)],
776
  response_model=PipelineStatusResponse,
777
  )
778
  async def get_pipeline_status() -> PipelineStatusResponse:
 
799
  HTTPException: If an error occurs while retrieving pipeline status (500)
800
  """
801
  try:
802
+ from lightrag.kg.shared_storage import (
803
+ get_namespace_data,
804
+ get_all_update_flags_status,
805
+ )
806
 
807
  pipeline_status = await get_namespace_data("pipeline_status")
808
 
809
+ # Get update flags status for all namespaces
810
+ update_status = await get_all_update_flags_status()
811
+
812
+ # Convert MutableBoolean objects to regular boolean values
813
+ processed_update_status = {}
814
+ for namespace, flags in update_status.items():
815
+ processed_flags = []
816
+ for flag in flags:
817
+ # Handle both multiprocess and single process cases
818
+ if hasattr(flag, "value"):
819
+ processed_flags.append(bool(flag.value))
820
+ else:
821
+ processed_flags.append(bool(flag))
822
+ processed_update_status[namespace] = processed_flags
823
+
824
  # Convert to regular dict if it's a Manager.dict
825
  status_dict = dict(pipeline_status)
826
 
827
+ # Add processed update_status to the status dictionary
828
+ status_dict["update_status"] = processed_update_status
829
+
830
  # Convert history_messages to a regular list if it's a Manager.list
831
  if "history_messages" in status_dict:
832
  status_dict["history_messages"] = list(status_dict["history_messages"])
 
841
  logger.error(traceback.format_exc())
842
  raise HTTPException(status_code=500, detail=str(e))
843
 
844
+ @router.get("", dependencies=[Depends(combined_auth)])
845
  async def documents() -> DocsStatusesResponse:
846
  """
847
  Get the status of all documents in the system.
lightrag/api/routers/graph_routes.py CHANGED
@@ -5,15 +5,15 @@ This module contains all graph-related routes for the LightRAG API.
5
  from typing import Optional
6
  from fastapi import APIRouter, Depends
7
 
8
- from ..utils_api import get_api_key_dependency, get_auth_dependency
9
 
10
- router = APIRouter(tags=["graph"], dependencies=[Depends(get_auth_dependency())])
11
 
12
 
13
  def create_graph_routes(rag, api_key: Optional[str] = None):
14
- optional_api_key = get_api_key_dependency(api_key)
15
 
16
- @router.get("/graph/label/list", dependencies=[Depends(optional_api_key)])
17
  async def get_graph_labels():
18
  """
19
  Get all graph labels
@@ -23,7 +23,7 @@ def create_graph_routes(rag, api_key: Optional[str] = None):
23
  """
24
  return await rag.get_graph_labels()
25
 
26
- @router.get("/graphs", dependencies=[Depends(optional_api_key)])
27
  async def get_knowledge_graph(
28
  label: str, max_depth: int = 3, min_degree: int = 0, inclusive: bool = False
29
  ):
 
5
  from typing import Optional
6
  from fastapi import APIRouter, Depends
7
 
8
+ from ..utils_api import get_combined_auth_dependency
9
 
10
+ router = APIRouter(tags=["graph"])
11
 
12
 
13
  def create_graph_routes(rag, api_key: Optional[str] = None):
14
+ combined_auth = get_combined_auth_dependency(api_key)
15
 
16
+ @router.get("/graph/label/list", dependencies=[Depends(combined_auth)])
17
  async def get_graph_labels():
18
  """
19
  Get all graph labels
 
23
  """
24
  return await rag.get_graph_labels()
25
 
26
+ @router.get("/graphs", dependencies=[Depends(combined_auth)])
27
  async def get_knowledge_graph(
28
  label: str, max_depth: int = 3, min_degree: int = 0, inclusive: bool = False
29
  ):
lightrag/api/routers/ollama_api.py CHANGED
@@ -11,7 +11,8 @@ import asyncio
11
  from ascii_colors import trace_exception
12
  from lightrag import LightRAG, QueryParam
13
  from lightrag.utils import encode_string_by_tiktoken
14
- from lightrag.api.utils_api import ollama_server_infos
 
15
 
16
 
17
  # query mode according to query prefix (bypass is not LightRAG quer mode)
@@ -122,20 +123,24 @@ def parse_query_mode(query: str) -> tuple[str, SearchMode]:
122
 
123
 
124
  class OllamaAPI:
125
- def __init__(self, rag: LightRAG, top_k: int = 60):
126
  self.rag = rag
127
  self.ollama_server_infos = ollama_server_infos
128
  self.top_k = top_k
 
129
  self.router = APIRouter(tags=["ollama"])
130
  self.setup_routes()
131
 
132
  def setup_routes(self):
133
- @self.router.get("/version")
 
 
 
134
  async def get_version():
135
  """Get Ollama version information"""
136
  return OllamaVersionResponse(version="0.5.4")
137
 
138
- @self.router.get("/tags")
139
  async def get_tags():
140
  """Return available models acting as an Ollama server"""
141
  return OllamaTagResponse(
@@ -158,7 +163,7 @@ class OllamaAPI:
158
  ]
159
  )
160
 
161
- @self.router.post("/generate")
162
  async def generate(raw_request: Request, request: OllamaGenerateRequest):
163
  """Handle generate completion requests acting as an Ollama model
164
  For compatibility purpose, the request is not processed by LightRAG,
@@ -324,7 +329,7 @@ class OllamaAPI:
324
  trace_exception(e)
325
  raise HTTPException(status_code=500, detail=str(e))
326
 
327
- @self.router.post("/chat")
328
  async def chat(raw_request: Request, request: OllamaChatRequest):
329
  """Process chat completion requests acting as an Ollama model
330
  Routes user queries through LightRAG by selecting query mode based on prefix indicators.
 
11
  from ascii_colors import trace_exception
12
  from lightrag import LightRAG, QueryParam
13
  from lightrag.utils import encode_string_by_tiktoken
14
+ from lightrag.api.utils_api import ollama_server_infos, get_combined_auth_dependency
15
+ from fastapi import Depends
16
 
17
 
18
  # query mode according to query prefix (bypass is not LightRAG quer mode)
 
123
 
124
 
125
  class OllamaAPI:
126
+ def __init__(self, rag: LightRAG, top_k: int = 60, api_key: Optional[str] = None):
127
  self.rag = rag
128
  self.ollama_server_infos = ollama_server_infos
129
  self.top_k = top_k
130
+ self.api_key = api_key
131
  self.router = APIRouter(tags=["ollama"])
132
  self.setup_routes()
133
 
134
  def setup_routes(self):
135
+ # Create combined auth dependency for Ollama API routes
136
+ combined_auth = get_combined_auth_dependency(self.api_key)
137
+
138
+ @self.router.get("/version", dependencies=[Depends(combined_auth)])
139
  async def get_version():
140
  """Get Ollama version information"""
141
  return OllamaVersionResponse(version="0.5.4")
142
 
143
+ @self.router.get("/tags", dependencies=[Depends(combined_auth)])
144
  async def get_tags():
145
  """Return available models acting as an Ollama server"""
146
  return OllamaTagResponse(
 
163
  ]
164
  )
165
 
166
+ @self.router.post("/generate", dependencies=[Depends(combined_auth)])
167
  async def generate(raw_request: Request, request: OllamaGenerateRequest):
168
  """Handle generate completion requests acting as an Ollama model
169
  For compatibility purpose, the request is not processed by LightRAG,
 
329
  trace_exception(e)
330
  raise HTTPException(status_code=500, detail=str(e))
331
 
332
+ @self.router.post("/chat", dependencies=[Depends(combined_auth)])
333
  async def chat(raw_request: Request, request: OllamaChatRequest):
334
  """Process chat completion requests acting as an Ollama model
335
  Routes user queries through LightRAG by selecting query mode based on prefix indicators.
lightrag/api/routers/query_routes.py CHANGED
@@ -8,12 +8,12 @@ from typing import Any, Dict, List, Literal, Optional
8
 
9
  from fastapi import APIRouter, Depends, HTTPException
10
  from lightrag.base import QueryParam
11
- from ..utils_api import get_api_key_dependency, get_auth_dependency
12
  from pydantic import BaseModel, Field, field_validator
13
 
14
  from ascii_colors import trace_exception
15
 
16
- router = APIRouter(tags=["query"], dependencies=[Depends(get_auth_dependency())])
17
 
18
 
19
  class QueryRequest(BaseModel):
@@ -139,10 +139,10 @@ class QueryResponse(BaseModel):
139
 
140
 
141
  def create_query_routes(rag, api_key: Optional[str] = None, top_k: int = 60):
142
- optional_api_key = get_api_key_dependency(api_key)
143
 
144
  @router.post(
145
- "/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)]
146
  )
147
  async def query_text(request: QueryRequest):
148
  """
@@ -176,7 +176,7 @@ def create_query_routes(rag, api_key: Optional[str] = None, top_k: int = 60):
176
  trace_exception(e)
177
  raise HTTPException(status_code=500, detail=str(e))
178
 
179
- @router.post("/query/stream", dependencies=[Depends(optional_api_key)])
180
  async def query_text_stream(request: QueryRequest):
181
  """
182
  This endpoint performs a retrieval-augmented generation (RAG) query and streams the response.
 
8
 
9
  from fastapi import APIRouter, Depends, HTTPException
10
  from lightrag.base import QueryParam
11
+ from ..utils_api import get_combined_auth_dependency
12
  from pydantic import BaseModel, Field, field_validator
13
 
14
  from ascii_colors import trace_exception
15
 
16
+ router = APIRouter(tags=["query"])
17
 
18
 
19
  class QueryRequest(BaseModel):
 
139
 
140
 
141
  def create_query_routes(rag, api_key: Optional[str] = None, top_k: int = 60):
142
+ combined_auth = get_combined_auth_dependency(api_key)
143
 
144
  @router.post(
145
+ "/query", response_model=QueryResponse, dependencies=[Depends(combined_auth)]
146
  )
147
  async def query_text(request: QueryRequest):
148
  """
 
176
  trace_exception(e)
177
  raise HTTPException(status_code=500, detail=str(e))
178
 
179
+ @router.post("/query/stream", dependencies=[Depends(combined_auth)])
180
  async def query_text_stream(request: QueryRequest):
181
  """
182
  This endpoint performs a retrieval-augmented generation (RAG) query and streams the response.
lightrag/api/utils_api.py CHANGED
@@ -4,22 +4,44 @@ Utility functions for the LightRAG API.
4
 
5
  import os
6
  import argparse
7
- from typing import Optional
8
  import sys
9
  import logging
10
  from ascii_colors import ASCIIColors
11
  from lightrag.api import __api_version__
12
- from fastapi import HTTPException, Security, Depends, Request, status
13
  from dotenv import load_dotenv
14
  from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
15
  from starlette.status import HTTP_403_FORBIDDEN
16
  from .auth import auth_handler
 
17
 
18
  # Load environment variables
19
  load_dotenv()
20
 
21
  global_args = {"main_args": None}
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  class OllamaServerInfos:
25
  # Constants for emulated Ollama model information
@@ -34,49 +56,114 @@ class OllamaServerInfos:
34
  ollama_server_infos = OllamaServerInfos()
35
 
36
 
37
- def get_auth_dependency():
38
- # Set default whitelist paths
39
- whitelist = os.getenv("WHITELIST_PATHS", "/login,/health").split(",")
 
40
 
41
- async def dependency(
42
- request: Request,
43
- token: str = Depends(OAuth2PasswordBearer(tokenUrl="login", auto_error=False)),
44
- ):
45
- # Check if authentication is configured
46
- auth_configured = bool(
47
- os.getenv("AUTH_USERNAME") and os.getenv("AUTH_PASSWORD")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  )
49
 
50
- # If authentication is not configured, skip all validation
51
- if not auth_configured:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  return
53
 
54
- # For configured auth, allow whitelist paths without token
55
- if request.url.path in whitelist:
56
- return
 
 
 
 
 
 
57
 
58
- # Require token for all other paths when auth is configured
59
- if not token:
60
  raise HTTPException(
61
- status_code=status.HTTP_401_UNAUTHORIZED, detail="Token required"
 
62
  )
63
 
64
- try:
65
- token_info = auth_handler.validate_token(token)
66
- # Reject guest tokens when authentication is configured
67
- if token_info.get("role") == "guest":
68
- raise HTTPException(
69
- status_code=status.HTTP_401_UNAUTHORIZED,
70
- detail="Authentication required. Guest access not allowed when authentication is configured.",
71
- )
72
- except Exception:
73
  raise HTTPException(
74
- status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token"
 
75
  )
76
 
77
- return
 
 
 
 
78
 
79
- return dependency
80
 
81
 
82
  def get_api_key_dependency(api_key: Optional[str]):
@@ -90,19 +177,37 @@ def get_api_key_dependency(api_key: Optional[str]):
90
  Returns:
91
  Callable: A dependency function that validates the API key.
92
  """
93
- if not api_key:
 
 
 
 
 
 
94
  # If no API key is configured, return a dummy dependency that always succeeds
95
- async def no_auth():
96
  return None
97
 
98
  return no_auth
99
 
100
- # If API key is configured, use proper authentication
101
  api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
102
 
103
  async def api_key_auth(
104
- api_key_header_value: Optional[str] = Security(api_key_header),
 
 
 
105
  ):
 
 
 
 
 
 
 
 
 
106
  if not api_key_header_value:
107
  raise HTTPException(
108
  status_code=HTTP_403_FORBIDDEN, detail="API Key required"
@@ -366,7 +471,7 @@ def parse_args(is_uvicorn_mode: bool = False) -> argparse.Namespace:
366
  )
367
 
368
  # Get MAX_PARALLEL_INSERT from environment
369
- global_args["max_parallel_insert"] = get_env_value("MAX_PARALLEL_INSERT", 2, int)
370
 
371
  # Handle openai-ollama special case
372
  if args.llm_binding == "openai-ollama":
@@ -397,6 +502,9 @@ def parse_args(is_uvicorn_mode: bool = False) -> argparse.Namespace:
397
  "ENABLE_LLM_CACHE_FOR_EXTRACT", True, bool
398
  )
399
 
 
 
 
400
  # Select Document loading tool (DOCLING, DEFAULT)
401
  args.document_loading_engine = get_env_value("DOCUMENT_LOADING_ENGINE", "DEFAULT")
402
 
@@ -464,6 +572,12 @@ def display_splash_screen(args: argparse.Namespace) -> None:
464
  ASCIIColors.yellow(f"{args.llm_binding_host}")
465
  ASCIIColors.white(" ├─ Model: ", end="")
466
  ASCIIColors.yellow(f"{args.llm_model}")
 
 
 
 
 
 
467
  ASCIIColors.white(" └─ Timeout: ", end="")
468
  ASCIIColors.yellow(f"{args.timeout if args.timeout else 'None (infinite)'}")
469
 
@@ -479,13 +593,12 @@ def display_splash_screen(args: argparse.Namespace) -> None:
479
  ASCIIColors.yellow(f"{args.embedding_dim}")
480
 
481
  # RAG Configuration
 
482
  ASCIIColors.magenta("\n⚙️ RAG Configuration:")
483
- ASCIIColors.white(" ├─ Max Async for LLM: ", end="")
484
- ASCIIColors.yellow(f"{args.max_async}")
485
  ASCIIColors.white(" ├─ Max Parallel Insert: ", end="")
486
- ASCIIColors.yellow(f"{global_args['max_parallel_insert']}")
487
- ASCIIColors.white(" ├─ Max Tokens: ", end="")
488
- ASCIIColors.yellow(f"{args.max_tokens}")
489
  ASCIIColors.white(" ├─ Max Embed Tokens: ", end="")
490
  ASCIIColors.yellow(f"{args.max_embed_tokens}")
491
  ASCIIColors.white(" ├─ Chunk Size: ", end="")
 
4
 
5
  import os
6
  import argparse
7
+ from typing import Optional, List, Tuple
8
  import sys
9
  import logging
10
  from ascii_colors import ASCIIColors
11
  from lightrag.api import __api_version__
12
+ from fastapi import HTTPException, Security, Request, status
13
  from dotenv import load_dotenv
14
  from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
15
  from starlette.status import HTTP_403_FORBIDDEN
16
  from .auth import auth_handler
17
+ from ..prompt import PROMPTS
18
 
19
  # Load environment variables
20
  load_dotenv()
21
 
22
  global_args = {"main_args": None}
23
 
24
+ # Get whitelist paths from environment variable, only once during initialization
25
+ default_whitelist = "/health,/api/*"
26
+ whitelist_paths = os.getenv("WHITELIST_PATHS", default_whitelist).split(",")
27
+
28
+ # Pre-compile path matching patterns
29
+ whitelist_patterns: List[Tuple[str, bool]] = []
30
+ for path in whitelist_paths:
31
+ path = path.strip()
32
+ if path:
33
+ # If path ends with /*, match all paths with that prefix
34
+ if path.endswith("/*"):
35
+ prefix = path[:-2]
36
+ whitelist_patterns.append((prefix, True)) # (prefix, is_prefix_match)
37
+ else:
38
+ whitelist_patterns.append((path, False)) # (exact_path, is_prefix_match)
39
+
40
+ # Global authentication configuration
41
+ auth_username = os.getenv("AUTH_USERNAME")
42
+ auth_password = os.getenv("AUTH_PASSWORD")
43
+ auth_configured = bool(auth_username and auth_password)
44
+
45
 
46
  class OllamaServerInfos:
47
  # Constants for emulated Ollama model information
 
56
  ollama_server_infos = OllamaServerInfos()
57
 
58
 
59
+ def get_combined_auth_dependency(api_key: Optional[str] = None):
60
+ """
61
+ Create a combined authentication dependency that implements authentication logic
62
+ based on API key, OAuth2 token, and whitelist paths.
63
 
64
+ Args:
65
+ api_key (Optional[str]): API key for validation
66
+
67
+ Returns:
68
+ Callable: A dependency function that implements the authentication logic
69
+ """
70
+ # Use global whitelist_patterns and auth_configured variables
71
+ # whitelist_patterns and auth_configured are already initialized at module level
72
+
73
+ # Only calculate api_key_configured as it depends on the function parameter
74
+ api_key_configured = bool(api_key)
75
+
76
+ # Create security dependencies with proper descriptions for Swagger UI
77
+ oauth2_scheme = OAuth2PasswordBearer(
78
+ tokenUrl="login", auto_error=False, description="OAuth2 Password Authentication"
79
+ )
80
+
81
+ # If API key is configured, create an API key header security
82
+ api_key_header = None
83
+ if api_key_configured:
84
+ api_key_header = APIKeyHeader(
85
+ name="X-API-Key", auto_error=False, description="API Key Authentication"
86
  )
87
 
88
+ async def combined_dependency(
89
+ request: Request,
90
+ token: str = Security(oauth2_scheme),
91
+ api_key_header_value: Optional[str] = None
92
+ if api_key_header is None
93
+ else Security(api_key_header),
94
+ ):
95
+ # 1. Check if path is in whitelist
96
+ path = request.url.path
97
+ for pattern, is_prefix in whitelist_patterns:
98
+ if (is_prefix and path.startswith(pattern)) or (
99
+ not is_prefix and path == pattern
100
+ ):
101
+ return # Whitelist path, allow access
102
+
103
+ # 2. Validate token first if provided in the request (Ensure 401 error if token is invalid)
104
+ if token:
105
+ try:
106
+ token_info = auth_handler.validate_token(token)
107
+ # Accept guest token if no auth is configured
108
+ if not auth_configured and token_info.get("role") == "guest":
109
+ return
110
+ # Accept non-guest token if auth is configured
111
+ if auth_configured and token_info.get("role") != "guest":
112
+ return
113
+
114
+ # Token validation failed, immediately return 401 error
115
+ raise HTTPException(
116
+ status_code=status.HTTP_401_UNAUTHORIZED,
117
+ detail="Invalid token. Please login again.",
118
+ )
119
+ except HTTPException as e:
120
+ # If already a 401 error, re-raise it
121
+ if e.status_code == status.HTTP_401_UNAUTHORIZED:
122
+ raise
123
+ # For other exceptions, continue processing
124
+
125
+ # 3. Acept all request if no API protection needed
126
+ if not auth_configured and not api_key_configured:
127
  return
128
 
129
+ # 4. Validate API key if provided and API-Key authentication is configured
130
+ if (
131
+ api_key_configured
132
+ and api_key_header_value
133
+ and api_key_header_value == api_key
134
+ ):
135
+ return # API key validation successful
136
+
137
+ ### Authentication failed ####
138
 
139
+ # if password authentication is configured but not provided, ensure 401 error if auth_configured
140
+ if auth_configured and not token:
141
  raise HTTPException(
142
+ status_code=status.HTTP_401_UNAUTHORIZED,
143
+ detail="No credentials provided. Please login.",
144
  )
145
 
146
+ # if api key is provided but validation failed
147
+ if api_key_header_value:
148
+ raise HTTPException(
149
+ status_code=HTTP_403_FORBIDDEN,
150
+ detail="Invalid API Key",
151
+ )
152
+
153
+ # if api_key_configured but not provided
154
+ if api_key_configured and not api_key_header_value:
155
  raise HTTPException(
156
+ status_code=HTTP_403_FORBIDDEN,
157
+ detail="API Key required",
158
  )
159
 
160
+ # Otherwise: refuse access and return 403 error
161
+ raise HTTPException(
162
+ status_code=HTTP_403_FORBIDDEN,
163
+ detail="API Key required or login authentication required.",
164
+ )
165
 
166
+ return combined_dependency
167
 
168
 
169
  def get_api_key_dependency(api_key: Optional[str]):
 
177
  Returns:
178
  Callable: A dependency function that validates the API key.
179
  """
180
+ # Use global whitelist_patterns and auth_configured variables
181
+ # whitelist_patterns and auth_configured are already initialized at module level
182
+
183
+ # Only calculate api_key_configured as it depends on the function parameter
184
+ api_key_configured = bool(api_key)
185
+
186
+ if not api_key_configured:
187
  # If no API key is configured, return a dummy dependency that always succeeds
188
+ async def no_auth(request: Request = None, **kwargs):
189
  return None
190
 
191
  return no_auth
192
 
193
+ # If API key is configured, use proper authentication with Security for Swagger UI
194
  api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
195
 
196
  async def api_key_auth(
197
+ request: Request,
198
+ api_key_header_value: Optional[str] = Security(
199
+ api_key_header, description="API Key for authentication"
200
+ ),
201
  ):
202
+ # Check if request path is in whitelist
203
+ path = request.url.path
204
+ for pattern, is_prefix in whitelist_patterns:
205
+ if (is_prefix and path.startswith(pattern)) or (
206
+ not is_prefix and path == pattern
207
+ ):
208
+ return # Whitelist path, allow access
209
+
210
+ # Non-whitelist path, validate API key
211
  if not api_key_header_value:
212
  raise HTTPException(
213
  status_code=HTTP_403_FORBIDDEN, detail="API Key required"
 
471
  )
472
 
473
  # Get MAX_PARALLEL_INSERT from environment
474
+ args.max_parallel_insert = get_env_value("MAX_PARALLEL_INSERT", 2, int)
475
 
476
  # Handle openai-ollama special case
477
  if args.llm_binding == "openai-ollama":
 
502
  "ENABLE_LLM_CACHE_FOR_EXTRACT", True, bool
503
  )
504
 
505
+ # Inject LLM temperature configuration
506
+ args.temperature = get_env_value("TEMPERATURE", 0.5, float)
507
+
508
  # Select Document loading tool (DOCLING, DEFAULT)
509
  args.document_loading_engine = get_env_value("DOCUMENT_LOADING_ENGINE", "DEFAULT")
510
 
 
572
  ASCIIColors.yellow(f"{args.llm_binding_host}")
573
  ASCIIColors.white(" ├─ Model: ", end="")
574
  ASCIIColors.yellow(f"{args.llm_model}")
575
+ ASCIIColors.white(" ├─ Temperature: ", end="")
576
+ ASCIIColors.yellow(f"{args.temperature}")
577
+ ASCIIColors.white(" ├─ Max Async for LLM: ", end="")
578
+ ASCIIColors.yellow(f"{args.max_async}")
579
+ ASCIIColors.white(" ├─ Max Tokens: ", end="")
580
+ ASCIIColors.yellow(f"{args.max_tokens}")
581
  ASCIIColors.white(" └─ Timeout: ", end="")
582
  ASCIIColors.yellow(f"{args.timeout if args.timeout else 'None (infinite)'}")
583
 
 
593
  ASCIIColors.yellow(f"{args.embedding_dim}")
594
 
595
  # RAG Configuration
596
+ summary_language = os.getenv("SUMMARY_LANGUAGE", PROMPTS["DEFAULT_LANGUAGE"])
597
  ASCIIColors.magenta("\n⚙️ RAG Configuration:")
598
+ ASCIIColors.white(" ├─ Summary Language: ", end="")
599
+ ASCIIColors.yellow(f"{summary_language}")
600
  ASCIIColors.white(" ├─ Max Parallel Insert: ", end="")
601
+ ASCIIColors.yellow(f"{args.max_parallel_insert}")
 
 
602
  ASCIIColors.white(" ├─ Max Embed Tokens: ", end="")
603
  ASCIIColors.yellow(f"{args.max_embed_tokens}")
604
  ASCIIColors.white(" ├─ Chunk Size: ", end="")
lightrag/api/webui/assets/index-CJhG62dt.css ADDED
Binary file (52 kB). View file
 
lightrag/api/webui/assets/index-Cq65VeVX.css DELETED
Binary file (53.1 kB)
 
lightrag/api/webui/assets/{index-DlScqWrq.js → index-DUmKHl1m.js} RENAMED
Binary files a/lightrag/api/webui/assets/index-DlScqWrq.js and b/lightrag/api/webui/assets/index-DUmKHl1m.js differ
 
lightrag/api/webui/index.html CHANGED
Binary files a/lightrag/api/webui/index.html and b/lightrag/api/webui/index.html differ
 
lightrag/kg/faiss_impl.py CHANGED
@@ -19,7 +19,6 @@ from .shared_storage import (
19
  get_storage_lock,
20
  get_update_flag,
21
  set_all_update_flags,
22
- is_multiprocess,
23
  )
24
 
25
 
@@ -73,9 +72,7 @@ class FaissVectorDBStorage(BaseVectorStorage):
73
  # Acquire lock to prevent concurrent read and write
74
  async with self._storage_lock:
75
  # Check if storage was updated by another process
76
- if (is_multiprocess and self.storage_updated.value) or (
77
- not is_multiprocess and self.storage_updated
78
- ):
79
  logger.info(
80
  f"Process {os.getpid()} FAISS reloading {self.namespace} due to update by another process"
81
  )
@@ -83,10 +80,7 @@ class FaissVectorDBStorage(BaseVectorStorage):
83
  self._index = faiss.IndexFlatIP(self._dim)
84
  self._id_to_meta = {}
85
  self._load_faiss_index()
86
- if is_multiprocess:
87
- self.storage_updated.value = False
88
- else:
89
- self.storage_updated = False
90
  return self._index
91
 
92
  async def upsert(self, data: dict[str, dict[str, Any]]) -> None:
@@ -343,18 +337,19 @@ class FaissVectorDBStorage(BaseVectorStorage):
343
  self._id_to_meta = {}
344
 
345
  async def index_done_callback(self) -> None:
346
- # Check if storage was updated by another process
347
- if is_multiprocess and self.storage_updated.value:
348
- # Storage was updated by another process, reload data instead of saving
349
- logger.warning(
350
- f"Storage for FAISS {self.namespace} was updated by another process, reloading..."
351
- )
352
- async with self._storage_lock:
353
- self._index = faiss.IndexFlatIP(self._dim)
354
- self._id_to_meta = {}
355
- self._load_faiss_index()
356
- self.storage_updated.value = False
357
- return False # Return error
 
358
 
359
  # Acquire lock and perform persistence
360
  async with self._storage_lock:
@@ -364,10 +359,7 @@ class FaissVectorDBStorage(BaseVectorStorage):
364
  # Notify other processes that data has been updated
365
  await set_all_update_flags(self.namespace)
366
  # Reset own update flag to avoid self-reloading
367
- if is_multiprocess:
368
- self.storage_updated.value = False
369
- else:
370
- self.storage_updated = False
371
  except Exception as e:
372
  logger.error(f"Error saving FAISS index for {self.namespace}: {e}")
373
  return False # Return error
 
19
  get_storage_lock,
20
  get_update_flag,
21
  set_all_update_flags,
 
22
  )
23
 
24
 
 
72
  # Acquire lock to prevent concurrent read and write
73
  async with self._storage_lock:
74
  # Check if storage was updated by another process
75
+ if self.storage_updated.value:
 
 
76
  logger.info(
77
  f"Process {os.getpid()} FAISS reloading {self.namespace} due to update by another process"
78
  )
 
80
  self._index = faiss.IndexFlatIP(self._dim)
81
  self._id_to_meta = {}
82
  self._load_faiss_index()
83
+ self.storage_updated.value = False
 
 
 
84
  return self._index
85
 
86
  async def upsert(self, data: dict[str, dict[str, Any]]) -> None:
 
337
  self._id_to_meta = {}
338
 
339
  async def index_done_callback(self) -> None:
340
+ async with self._storage_lock:
341
+ # Check if storage was updated by another process
342
+ if self.storage_updated.value:
343
+ # Storage was updated by another process, reload data instead of saving
344
+ logger.warning(
345
+ f"Storage for FAISS {self.namespace} was updated by another process, reloading..."
346
+ )
347
+ async with self._storage_lock:
348
+ self._index = faiss.IndexFlatIP(self._dim)
349
+ self._id_to_meta = {}
350
+ self._load_faiss_index()
351
+ self.storage_updated.value = False
352
+ return False # Return error
353
 
354
  # Acquire lock and perform persistence
355
  async with self._storage_lock:
 
359
  # Notify other processes that data has been updated
360
  await set_all_update_flags(self.namespace)
361
  # Reset own update flag to avoid self-reloading
362
+ self.storage_updated.value = False
 
 
 
363
  except Exception as e:
364
  logger.error(f"Error saving FAISS index for {self.namespace}: {e}")
365
  return False # Return error
lightrag/kg/nano_vector_db_impl.py CHANGED
@@ -20,7 +20,6 @@ from .shared_storage import (
20
  get_storage_lock,
21
  get_update_flag,
22
  set_all_update_flags,
23
- is_multiprocess,
24
  )
25
 
26
 
@@ -57,16 +56,14 @@ class NanoVectorDBStorage(BaseVectorStorage):
57
  # Get the update flag for cross-process update notification
58
  self.storage_updated = await get_update_flag(self.namespace)
59
  # Get the storage lock for use in other methods
60
- self._storage_lock = get_storage_lock()
61
 
62
  async def _get_client(self):
63
  """Check if the storage should be reloaded"""
64
  # Acquire lock to prevent concurrent read and write
65
  async with self._storage_lock:
66
  # Check if data needs to be reloaded
67
- if (is_multiprocess and self.storage_updated.value) or (
68
- not is_multiprocess and self.storage_updated
69
- ):
70
  logger.info(
71
  f"Process {os.getpid()} reloading {self.namespace} due to update by another process"
72
  )
@@ -76,10 +73,7 @@ class NanoVectorDBStorage(BaseVectorStorage):
76
  storage_file=self._client_file_name,
77
  )
78
  # Reset update flag
79
- if is_multiprocess:
80
- self.storage_updated.value = False
81
- else:
82
- self.storage_updated = False
83
 
84
  return self._client
85
 
@@ -206,19 +200,20 @@ class NanoVectorDBStorage(BaseVectorStorage):
206
 
207
  async def index_done_callback(self) -> bool:
208
  """Save data to disk"""
209
- # Check if storage was updated by another process
210
- if is_multiprocess and self.storage_updated.value:
211
- # Storage was updated by another process, reload data instead of saving
212
- logger.warning(
213
- f"Storage for {self.namespace} was updated by another process, reloading..."
214
- )
215
- self._client = NanoVectorDB(
216
- self.embedding_func.embedding_dim,
217
- storage_file=self._client_file_name,
218
- )
219
- # Reset update flag
220
- self.storage_updated.value = False
221
- return False # Return error
 
222
 
223
  # Acquire lock and perform persistence
224
  async with self._storage_lock:
@@ -228,10 +223,7 @@ class NanoVectorDBStorage(BaseVectorStorage):
228
  # Notify other processes that data has been updated
229
  await set_all_update_flags(self.namespace)
230
  # Reset own update flag to avoid self-reloading
231
- if is_multiprocess:
232
- self.storage_updated.value = False
233
- else:
234
- self.storage_updated = False
235
  return True # Return success
236
  except Exception as e:
237
  logger.error(f"Error saving data for {self.namespace}: {e}")
 
20
  get_storage_lock,
21
  get_update_flag,
22
  set_all_update_flags,
 
23
  )
24
 
25
 
 
56
  # Get the update flag for cross-process update notification
57
  self.storage_updated = await get_update_flag(self.namespace)
58
  # Get the storage lock for use in other methods
59
+ self._storage_lock = get_storage_lock(enable_logging=False)
60
 
61
  async def _get_client(self):
62
  """Check if the storage should be reloaded"""
63
  # Acquire lock to prevent concurrent read and write
64
  async with self._storage_lock:
65
  # Check if data needs to be reloaded
66
+ if self.storage_updated.value:
 
 
67
  logger.info(
68
  f"Process {os.getpid()} reloading {self.namespace} due to update by another process"
69
  )
 
73
  storage_file=self._client_file_name,
74
  )
75
  # Reset update flag
76
+ self.storage_updated.value = False
 
 
 
77
 
78
  return self._client
79
 
 
200
 
201
  async def index_done_callback(self) -> bool:
202
  """Save data to disk"""
203
+ async with self._storage_lock:
204
+ # Check if storage was updated by another process
205
+ if self.storage_updated.value:
206
+ # Storage was updated by another process, reload data instead of saving
207
+ logger.warning(
208
+ f"Storage for {self.namespace} was updated by another process, reloading..."
209
+ )
210
+ self._client = NanoVectorDB(
211
+ self.embedding_func.embedding_dim,
212
+ storage_file=self._client_file_name,
213
+ )
214
+ # Reset update flag
215
+ self.storage_updated.value = False
216
+ return False # Return error
217
 
218
  # Acquire lock and perform persistence
219
  async with self._storage_lock:
 
223
  # Notify other processes that data has been updated
224
  await set_all_update_flags(self.namespace)
225
  # Reset own update flag to avoid self-reloading
226
+ self.storage_updated.value = False
 
 
 
227
  return True # Return success
228
  except Exception as e:
229
  logger.error(f"Error saving data for {self.namespace}: {e}")
lightrag/kg/networkx_impl.py CHANGED
@@ -21,7 +21,6 @@ from .shared_storage import (
21
  get_storage_lock,
22
  get_update_flag,
23
  set_all_update_flags,
24
- is_multiprocess,
25
  )
26
 
27
  MAX_GRAPH_NODES = int(os.getenv("MAX_GRAPH_NODES", 1000))
@@ -110,9 +109,7 @@ class NetworkXStorage(BaseGraphStorage):
110
  # Acquire lock to prevent concurrent read and write
111
  async with self._storage_lock:
112
  # Check if data needs to be reloaded
113
- if (is_multiprocess and self.storage_updated.value) or (
114
- not is_multiprocess and self.storage_updated
115
- ):
116
  logger.info(
117
  f"Process {os.getpid()} reloading graph {self.namespace} due to update by another process"
118
  )
@@ -121,10 +118,7 @@ class NetworkXStorage(BaseGraphStorage):
121
  NetworkXStorage.load_nx_graph(self._graphml_xml_file) or nx.Graph()
122
  )
123
  # Reset update flag
124
- if is_multiprocess:
125
- self.storage_updated.value = False
126
- else:
127
- self.storage_updated = False
128
 
129
  return self._graph
130
 
@@ -401,18 +395,19 @@ class NetworkXStorage(BaseGraphStorage):
401
 
402
  async def index_done_callback(self) -> bool:
403
  """Save data to disk"""
404
- # Check if storage was updated by another process
405
- if is_multiprocess and self.storage_updated.value:
406
- # Storage was updated by another process, reload data instead of saving
407
- logger.warning(
408
- f"Graph for {self.namespace} was updated by another process, reloading..."
409
- )
410
- self._graph = (
411
- NetworkXStorage.load_nx_graph(self._graphml_xml_file) or nx.Graph()
412
- )
413
- # Reset update flag
414
- self.storage_updated.value = False
415
- return False # Return error
 
416
 
417
  # Acquire lock and perform persistence
418
  async with self._storage_lock:
@@ -422,10 +417,7 @@ class NetworkXStorage(BaseGraphStorage):
422
  # Notify other processes that data has been updated
423
  await set_all_update_flags(self.namespace)
424
  # Reset own update flag to avoid self-reloading
425
- if is_multiprocess:
426
- self.storage_updated.value = False
427
- else:
428
- self.storage_updated = False
429
  return True # Return success
430
  except Exception as e:
431
  logger.error(f"Error saving graph for {self.namespace}: {e}")
 
21
  get_storage_lock,
22
  get_update_flag,
23
  set_all_update_flags,
 
24
  )
25
 
26
  MAX_GRAPH_NODES = int(os.getenv("MAX_GRAPH_NODES", 1000))
 
109
  # Acquire lock to prevent concurrent read and write
110
  async with self._storage_lock:
111
  # Check if data needs to be reloaded
112
+ if self.storage_updated.value:
 
 
113
  logger.info(
114
  f"Process {os.getpid()} reloading graph {self.namespace} due to update by another process"
115
  )
 
118
  NetworkXStorage.load_nx_graph(self._graphml_xml_file) or nx.Graph()
119
  )
120
  # Reset update flag
121
+ self.storage_updated.value = False
 
 
 
122
 
123
  return self._graph
124
 
 
395
 
396
  async def index_done_callback(self) -> bool:
397
  """Save data to disk"""
398
+ async with self._storage_lock:
399
+ # Check if storage was updated by another process
400
+ if self.storage_updated.value:
401
+ # Storage was updated by another process, reload data instead of saving
402
+ logger.warning(
403
+ f"Graph for {self.namespace} was updated by another process, reloading..."
404
+ )
405
+ self._graph = (
406
+ NetworkXStorage.load_nx_graph(self._graphml_xml_file) or nx.Graph()
407
+ )
408
+ # Reset update flag
409
+ self.storage_updated.value = False
410
+ return False # Return error
411
 
412
  # Acquire lock and perform persistence
413
  async with self._storage_lock:
 
417
  # Notify other processes that data has been updated
418
  await set_all_update_flags(self.namespace)
419
  # Reset own update flag to avoid self-reloading
420
+ self.storage_updated.value = False
 
 
 
421
  return True # Return success
422
  except Exception as e:
423
  logger.error(f"Error saving graph for {self.namespace}: {e}")
lightrag/kg/shared_storage.py CHANGED
@@ -24,7 +24,7 @@ def direct_log(message, level="INFO", enable_output: bool = True):
24
  T = TypeVar("T")
25
  LockType = Union[ProcessLock, asyncio.Lock]
26
 
27
- is_multiprocess = None
28
  _workers = None
29
  _manager = None
30
  _initialized = None
@@ -218,10 +218,10 @@ class UnifiedLock(Generic[T]):
218
 
219
  def get_internal_lock(enable_logging: bool = False) -> UnifiedLock:
220
  """return unified storage lock for data consistency"""
221
- async_lock = _async_locks.get("internal_lock") if is_multiprocess else None
222
  return UnifiedLock(
223
  lock=_internal_lock,
224
- is_async=not is_multiprocess,
225
  name="internal_lock",
226
  enable_logging=enable_logging,
227
  async_lock=async_lock,
@@ -230,10 +230,10 @@ def get_internal_lock(enable_logging: bool = False) -> UnifiedLock:
230
 
231
  def get_storage_lock(enable_logging: bool = False) -> UnifiedLock:
232
  """return unified storage lock for data consistency"""
233
- async_lock = _async_locks.get("storage_lock") if is_multiprocess else None
234
  return UnifiedLock(
235
  lock=_storage_lock,
236
- is_async=not is_multiprocess,
237
  name="storage_lock",
238
  enable_logging=enable_logging,
239
  async_lock=async_lock,
@@ -242,10 +242,10 @@ def get_storage_lock(enable_logging: bool = False) -> UnifiedLock:
242
 
243
  def get_pipeline_status_lock(enable_logging: bool = False) -> UnifiedLock:
244
  """return unified storage lock for data consistency"""
245
- async_lock = _async_locks.get("pipeline_status_lock") if is_multiprocess else None
246
  return UnifiedLock(
247
  lock=_pipeline_status_lock,
248
- is_async=not is_multiprocess,
249
  name="pipeline_status_lock",
250
  enable_logging=enable_logging,
251
  async_lock=async_lock,
@@ -254,10 +254,10 @@ def get_pipeline_status_lock(enable_logging: bool = False) -> UnifiedLock:
254
 
255
  def get_graph_db_lock(enable_logging: bool = False) -> UnifiedLock:
256
  """return unified graph database lock for ensuring atomic operations"""
257
- async_lock = _async_locks.get("graph_db_lock") if is_multiprocess else None
258
  return UnifiedLock(
259
  lock=_graph_db_lock,
260
- is_async=not is_multiprocess,
261
  name="graph_db_lock",
262
  enable_logging=enable_logging,
263
  async_lock=async_lock,
@@ -266,10 +266,10 @@ def get_graph_db_lock(enable_logging: bool = False) -> UnifiedLock:
266
 
267
  def get_data_init_lock(enable_logging: bool = False) -> UnifiedLock:
268
  """return unified data initialization lock for ensuring atomic data initialization"""
269
- async_lock = _async_locks.get("data_init_lock") if is_multiprocess else None
270
  return UnifiedLock(
271
  lock=_data_init_lock,
272
- is_async=not is_multiprocess,
273
  name="data_init_lock",
274
  enable_logging=enable_logging,
275
  async_lock=async_lock,
@@ -297,7 +297,7 @@ def initialize_share_data(workers: int = 1):
297
  global \
298
  _manager, \
299
  _workers, \
300
- is_multiprocess, \
301
  _storage_lock, \
302
  _internal_lock, \
303
  _pipeline_status_lock, \
@@ -312,14 +312,14 @@ def initialize_share_data(workers: int = 1):
312
  # Check if already initialized
313
  if _initialized:
314
  direct_log(
315
- f"Process {os.getpid()} Shared-Data already initialized (multiprocess={is_multiprocess})"
316
  )
317
  return
318
 
319
  _workers = workers
320
 
321
  if workers > 1:
322
- is_multiprocess = True
323
  _manager = Manager()
324
  _internal_lock = _manager.Lock()
325
  _storage_lock = _manager.Lock()
@@ -343,7 +343,7 @@ def initialize_share_data(workers: int = 1):
343
  f"Process {os.getpid()} Shared-Data created for Multiple Process (workers={workers})"
344
  )
345
  else:
346
- is_multiprocess = False
347
  _internal_lock = asyncio.Lock()
348
  _storage_lock = asyncio.Lock()
349
  _pipeline_status_lock = asyncio.Lock()
@@ -372,7 +372,7 @@ async def initialize_pipeline_status():
372
  return
373
 
374
  # Create a shared list object for history_messages
375
- history_messages = _manager.list() if is_multiprocess else []
376
  pipeline_namespace.update(
377
  {
378
  "autoscanned": False, # Auto-scan started
@@ -401,7 +401,7 @@ async def get_update_flag(namespace: str):
401
 
402
  async with get_internal_lock():
403
  if namespace not in _update_flags:
404
- if is_multiprocess and _manager is not None:
405
  _update_flags[namespace] = _manager.list()
406
  else:
407
  _update_flags[namespace] = []
@@ -409,7 +409,7 @@ async def get_update_flag(namespace: str):
409
  f"Process {os.getpid()} initialized updated flags for namespace: [{namespace}]"
410
  )
411
 
412
- if is_multiprocess and _manager is not None:
413
  new_update_flag = _manager.Value("b", False)
414
  else:
415
  # Create a simple mutable object to store boolean value for compatibility with mutiprocess
@@ -434,11 +434,7 @@ async def set_all_update_flags(namespace: str):
434
  raise ValueError(f"Namespace {namespace} not found in update flags")
435
  # Update flags for both modes
436
  for i in range(len(_update_flags[namespace])):
437
- if is_multiprocess:
438
- _update_flags[namespace][i].value = True
439
- else:
440
- # Use .value attribute instead of direct assignment
441
- _update_flags[namespace][i].value = True
442
 
443
 
444
  async def clear_all_update_flags(namespace: str):
@@ -452,11 +448,7 @@ async def clear_all_update_flags(namespace: str):
452
  raise ValueError(f"Namespace {namespace} not found in update flags")
453
  # Update flags for both modes
454
  for i in range(len(_update_flags[namespace])):
455
- if is_multiprocess:
456
- _update_flags[namespace][i].value = False
457
- else:
458
- # Use .value attribute instead of direct assignment
459
- _update_flags[namespace][i].value = False
460
 
461
 
462
  async def get_all_update_flags_status() -> Dict[str, list]:
@@ -474,7 +466,7 @@ async def get_all_update_flags_status() -> Dict[str, list]:
474
  for namespace, flags in _update_flags.items():
475
  worker_statuses = []
476
  for flag in flags:
477
- if is_multiprocess:
478
  worker_statuses.append(flag.value)
479
  else:
480
  worker_statuses.append(flag)
@@ -518,7 +510,7 @@ async def get_namespace_data(namespace: str) -> Dict[str, Any]:
518
 
519
  async with get_internal_lock():
520
  if namespace not in _shared_dicts:
521
- if is_multiprocess and _manager is not None:
522
  _shared_dicts[namespace] = _manager.dict()
523
  else:
524
  _shared_dicts[namespace] = {}
@@ -538,7 +530,7 @@ def finalize_share_data():
538
  """
539
  global \
540
  _manager, \
541
- is_multiprocess, \
542
  _storage_lock, \
543
  _internal_lock, \
544
  _pipeline_status_lock, \
@@ -558,11 +550,11 @@ def finalize_share_data():
558
  return
559
 
560
  direct_log(
561
- f"Process {os.getpid()} finalizing storage data (multiprocess={is_multiprocess})"
562
  )
563
 
564
  # In multi-process mode, shut down the Manager
565
- if is_multiprocess and _manager is not None:
566
  try:
567
  # Clear shared resources before shutting down Manager
568
  if _shared_dicts is not None:
@@ -604,7 +596,7 @@ def finalize_share_data():
604
  # Reset global variables
605
  _manager = None
606
  _initialized = None
607
- is_multiprocess = None
608
  _shared_dicts = None
609
  _init_flags = None
610
  _storage_lock = None
 
24
  T = TypeVar("T")
25
  LockType = Union[ProcessLock, asyncio.Lock]
26
 
27
+ _is_multiprocess = None
28
  _workers = None
29
  _manager = None
30
  _initialized = None
 
218
 
219
  def get_internal_lock(enable_logging: bool = False) -> UnifiedLock:
220
  """return unified storage lock for data consistency"""
221
+ async_lock = _async_locks.get("internal_lock") if _is_multiprocess else None
222
  return UnifiedLock(
223
  lock=_internal_lock,
224
+ is_async=not _is_multiprocess,
225
  name="internal_lock",
226
  enable_logging=enable_logging,
227
  async_lock=async_lock,
 
230
 
231
  def get_storage_lock(enable_logging: bool = False) -> UnifiedLock:
232
  """return unified storage lock for data consistency"""
233
+ async_lock = _async_locks.get("storage_lock") if _is_multiprocess else None
234
  return UnifiedLock(
235
  lock=_storage_lock,
236
+ is_async=not _is_multiprocess,
237
  name="storage_lock",
238
  enable_logging=enable_logging,
239
  async_lock=async_lock,
 
242
 
243
  def get_pipeline_status_lock(enable_logging: bool = False) -> UnifiedLock:
244
  """return unified storage lock for data consistency"""
245
+ async_lock = _async_locks.get("pipeline_status_lock") if _is_multiprocess else None
246
  return UnifiedLock(
247
  lock=_pipeline_status_lock,
248
+ is_async=not _is_multiprocess,
249
  name="pipeline_status_lock",
250
  enable_logging=enable_logging,
251
  async_lock=async_lock,
 
254
 
255
  def get_graph_db_lock(enable_logging: bool = False) -> UnifiedLock:
256
  """return unified graph database lock for ensuring atomic operations"""
257
+ async_lock = _async_locks.get("graph_db_lock") if _is_multiprocess else None
258
  return UnifiedLock(
259
  lock=_graph_db_lock,
260
+ is_async=not _is_multiprocess,
261
  name="graph_db_lock",
262
  enable_logging=enable_logging,
263
  async_lock=async_lock,
 
266
 
267
  def get_data_init_lock(enable_logging: bool = False) -> UnifiedLock:
268
  """return unified data initialization lock for ensuring atomic data initialization"""
269
+ async_lock = _async_locks.get("data_init_lock") if _is_multiprocess else None
270
  return UnifiedLock(
271
  lock=_data_init_lock,
272
+ is_async=not _is_multiprocess,
273
  name="data_init_lock",
274
  enable_logging=enable_logging,
275
  async_lock=async_lock,
 
297
  global \
298
  _manager, \
299
  _workers, \
300
+ _is_multiprocess, \
301
  _storage_lock, \
302
  _internal_lock, \
303
  _pipeline_status_lock, \
 
312
  # Check if already initialized
313
  if _initialized:
314
  direct_log(
315
+ f"Process {os.getpid()} Shared-Data already initialized (multiprocess={_is_multiprocess})"
316
  )
317
  return
318
 
319
  _workers = workers
320
 
321
  if workers > 1:
322
+ _is_multiprocess = True
323
  _manager = Manager()
324
  _internal_lock = _manager.Lock()
325
  _storage_lock = _manager.Lock()
 
343
  f"Process {os.getpid()} Shared-Data created for Multiple Process (workers={workers})"
344
  )
345
  else:
346
+ _is_multiprocess = False
347
  _internal_lock = asyncio.Lock()
348
  _storage_lock = asyncio.Lock()
349
  _pipeline_status_lock = asyncio.Lock()
 
372
  return
373
 
374
  # Create a shared list object for history_messages
375
+ history_messages = _manager.list() if _is_multiprocess else []
376
  pipeline_namespace.update(
377
  {
378
  "autoscanned": False, # Auto-scan started
 
401
 
402
  async with get_internal_lock():
403
  if namespace not in _update_flags:
404
+ if _is_multiprocess and _manager is not None:
405
  _update_flags[namespace] = _manager.list()
406
  else:
407
  _update_flags[namespace] = []
 
409
  f"Process {os.getpid()} initialized updated flags for namespace: [{namespace}]"
410
  )
411
 
412
+ if _is_multiprocess and _manager is not None:
413
  new_update_flag = _manager.Value("b", False)
414
  else:
415
  # Create a simple mutable object to store boolean value for compatibility with mutiprocess
 
434
  raise ValueError(f"Namespace {namespace} not found in update flags")
435
  # Update flags for both modes
436
  for i in range(len(_update_flags[namespace])):
437
+ _update_flags[namespace][i].value = True
 
 
 
 
438
 
439
 
440
  async def clear_all_update_flags(namespace: str):
 
448
  raise ValueError(f"Namespace {namespace} not found in update flags")
449
  # Update flags for both modes
450
  for i in range(len(_update_flags[namespace])):
451
+ _update_flags[namespace][i].value = False
 
 
 
 
452
 
453
 
454
  async def get_all_update_flags_status() -> Dict[str, list]:
 
466
  for namespace, flags in _update_flags.items():
467
  worker_statuses = []
468
  for flag in flags:
469
+ if _is_multiprocess:
470
  worker_statuses.append(flag.value)
471
  else:
472
  worker_statuses.append(flag)
 
510
 
511
  async with get_internal_lock():
512
  if namespace not in _shared_dicts:
513
+ if _is_multiprocess and _manager is not None:
514
  _shared_dicts[namespace] = _manager.dict()
515
  else:
516
  _shared_dicts[namespace] = {}
 
530
  """
531
  global \
532
  _manager, \
533
+ _is_multiprocess, \
534
  _storage_lock, \
535
  _internal_lock, \
536
  _pipeline_status_lock, \
 
550
  return
551
 
552
  direct_log(
553
+ f"Process {os.getpid()} finalizing storage data (multiprocess={_is_multiprocess})"
554
  )
555
 
556
  # In multi-process mode, shut down the Manager
557
+ if _is_multiprocess and _manager is not None:
558
  try:
559
  # Clear shared resources before shutting down Manager
560
  if _shared_dicts is not None:
 
596
  # Reset global variables
597
  _manager = None
598
  _initialized = None
599
+ _is_multiprocess = None
600
  _shared_dicts = None
601
  _init_flags = None
602
  _storage_lock = None
lightrag_webui/src/App.tsx CHANGED
@@ -1,9 +1,8 @@
1
  import { useState, useCallback, useEffect, useRef } from 'react'
2
  import ThemeProvider from '@/components/ThemeProvider'
3
  import TabVisibilityProvider from '@/contexts/TabVisibilityProvider'
4
- import MessageAlert from '@/components/MessageAlert'
5
  import ApiKeyAlert from '@/components/ApiKeyAlert'
6
- import StatusIndicator from '@/components/graph/StatusIndicator'
7
  import { healthCheckInterval } from '@/lib/constants'
8
  import { useBackendState, useAuthStore } from '@/stores/state'
9
  import { useSettingsStore } from '@/stores/settings'
@@ -22,26 +21,30 @@ function App() {
22
  const message = useBackendState.use.message()
23
  const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
24
  const currentTab = useSettingsStore.use.currentTab()
25
- const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
26
  const versionCheckRef = useRef(false); // Prevent duplicate calls in Vite dev mode
27
 
 
 
 
 
 
 
 
28
  // Health check - can be disabled
29
  useEffect(() => {
30
- // Only execute if health check is enabled
31
- if (!enableHealthCheck) return;
32
 
33
  // Health check function
34
  const performHealthCheck = async () => {
35
  await useBackendState.getState().check();
36
  };
37
 
38
- // Execute immediately
39
- performHealthCheck();
40
-
41
  // Set interval for periodic execution
42
  const interval = setInterval(performHealthCheck, healthCheckInterval * 1000);
43
  return () => clearInterval(interval);
44
- }, [enableHealthCheck]);
45
 
46
  // Version check - independent and executed only once
47
  useEffect(() => {
@@ -90,12 +93,10 @@ function App() {
90
  useEffect(() => {
91
  if (message) {
92
  if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
93
- setApiKeyInvalid(true)
94
- return
95
  }
96
  }
97
- setApiKeyInvalid(false)
98
- }, [message, setApiKeyInvalid])
99
 
100
  return (
101
  <ThemeProvider>
@@ -123,8 +124,7 @@ function App() {
123
  </div>
124
  </Tabs>
125
  {enableHealthCheck && <StatusIndicator />}
126
- {message !== null && !apiKeyInvalid && <MessageAlert />}
127
- {apiKeyInvalid && <ApiKeyAlert />}
128
  </main>
129
  </TabVisibilityProvider>
130
  </ThemeProvider>
 
1
  import { useState, useCallback, useEffect, useRef } from 'react'
2
  import ThemeProvider from '@/components/ThemeProvider'
3
  import TabVisibilityProvider from '@/contexts/TabVisibilityProvider'
 
4
  import ApiKeyAlert from '@/components/ApiKeyAlert'
5
+ import StatusIndicator from '@/components/status/StatusIndicator'
6
  import { healthCheckInterval } from '@/lib/constants'
7
  import { useBackendState, useAuthStore } from '@/stores/state'
8
  import { useSettingsStore } from '@/stores/settings'
 
21
  const message = useBackendState.use.message()
22
  const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
23
  const currentTab = useSettingsStore.use.currentTab()
24
+ const [apiKeyAlertOpen, setApiKeyAlertOpen] = useState(false)
25
  const versionCheckRef = useRef(false); // Prevent duplicate calls in Vite dev mode
26
 
27
+ const handleApiKeyAlertOpenChange = useCallback((open: boolean) => {
28
+ setApiKeyAlertOpen(open)
29
+ if (!open) {
30
+ useBackendState.getState().clear()
31
+ }
32
+ }, [])
33
+
34
  // Health check - can be disabled
35
  useEffect(() => {
36
+ // Only execute if health check is enabled and ApiKeyAlert is closed
37
+ if (!enableHealthCheck || apiKeyAlertOpen) return;
38
 
39
  // Health check function
40
  const performHealthCheck = async () => {
41
  await useBackendState.getState().check();
42
  };
43
 
 
 
 
44
  // Set interval for periodic execution
45
  const interval = setInterval(performHealthCheck, healthCheckInterval * 1000);
46
  return () => clearInterval(interval);
47
+ }, [enableHealthCheck, apiKeyAlertOpen]);
48
 
49
  // Version check - independent and executed only once
50
  useEffect(() => {
 
93
  useEffect(() => {
94
  if (message) {
95
  if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
96
+ setApiKeyAlertOpen(true)
 
97
  }
98
  }
99
+ }, [message])
 
100
 
101
  return (
102
  <ThemeProvider>
 
124
  </div>
125
  </Tabs>
126
  {enableHealthCheck && <StatusIndicator />}
127
+ <ApiKeyAlert open={apiKeyAlertOpen} onOpenChange={handleApiKeyAlertOpenChange} />
 
128
  </main>
129
  </TabVisibilityProvider>
130
  </ThemeProvider>
lightrag_webui/src/components/ApiKeyAlert.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { useState, useCallback, useEffect } from 'react'
 
2
  import {
3
  AlertDialog,
4
  AlertDialogContent,
@@ -12,10 +13,13 @@ import { useSettingsStore } from '@/stores/settings'
12
  import { useBackendState } from '@/stores/state'
13
  import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
14
 
15
- import { toast } from 'sonner'
 
 
 
16
 
17
- const ApiKeyAlert = () => {
18
- const [opened, setOpened] = useState<boolean>(true)
19
  const apiKey = useSettingsStore.use.apiKey()
20
  const [tempApiKey, setTempApiKey] = useState<string>('')
21
  const message = useBackendState.use.message()
@@ -32,14 +36,10 @@ const ApiKeyAlert = () => {
32
  }
33
  }, [message, setOpened])
34
 
35
- const setApiKey = useCallback(async () => {
36
  useSettingsStore.setState({ apiKey: tempApiKey || null })
37
- if (await useBackendState.getState().check()) {
38
- setOpened(false)
39
- return
40
- }
41
- toast.error('API Key is invalid')
42
- }, [tempApiKey])
43
 
44
  const handleTempApiKeyChange = useCallback(
45
  (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -52,23 +52,32 @@ const ApiKeyAlert = () => {
52
  <AlertDialog open={opened} onOpenChange={setOpened}>
53
  <AlertDialogContent>
54
  <AlertDialogHeader>
55
- <AlertDialogTitle>API Key is required</AlertDialogTitle>
56
- <AlertDialogDescription>Please enter your API key</AlertDialogDescription>
 
 
57
  </AlertDialogHeader>
58
- <form className="flex gap-2" onSubmit={(e) => e.preventDefault()}>
59
- <Input
60
- type="password"
61
- value={tempApiKey}
62
- onChange={handleTempApiKeyChange}
63
- placeholder="Enter your API key"
64
- className="max-h-full w-full min-w-0"
65
- autoComplete="off"
66
- />
 
67
 
68
- <Button onClick={setApiKey} variant="outline" size="sm">
69
- Save
70
- </Button>
71
- </form>
 
 
 
 
 
 
72
  </AlertDialogContent>
73
  </AlertDialog>
74
  )
 
1
  import { useState, useCallback, useEffect } from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
  import {
4
  AlertDialog,
5
  AlertDialogContent,
 
13
  import { useBackendState } from '@/stores/state'
14
  import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
15
 
16
+ interface ApiKeyAlertProps {
17
+ open: boolean;
18
+ onOpenChange: (open: boolean) => void;
19
+ }
20
 
21
+ const ApiKeyAlert = ({ open: opened, onOpenChange: setOpened }: ApiKeyAlertProps) => {
22
+ const { t } = useTranslation()
23
  const apiKey = useSettingsStore.use.apiKey()
24
  const [tempApiKey, setTempApiKey] = useState<string>('')
25
  const message = useBackendState.use.message()
 
36
  }
37
  }, [message, setOpened])
38
 
39
+ const setApiKey = useCallback(() => {
40
  useSettingsStore.setState({ apiKey: tempApiKey || null })
41
+ setOpened(false)
42
+ }, [tempApiKey, setOpened])
 
 
 
 
43
 
44
  const handleTempApiKeyChange = useCallback(
45
  (e: React.ChangeEvent<HTMLInputElement>) => {
 
52
  <AlertDialog open={opened} onOpenChange={setOpened}>
53
  <AlertDialogContent>
54
  <AlertDialogHeader>
55
+ <AlertDialogTitle>{t('apiKeyAlert.title')}</AlertDialogTitle>
56
+ <AlertDialogDescription>
57
+ {t('apiKeyAlert.description')}
58
+ </AlertDialogDescription>
59
  </AlertDialogHeader>
60
+ <div className="flex flex-col gap-4">
61
+ <form className="flex gap-2" onSubmit={(e) => e.preventDefault()}>
62
+ <Input
63
+ type="password"
64
+ value={tempApiKey}
65
+ onChange={handleTempApiKeyChange}
66
+ placeholder={t('apiKeyAlert.placeholder')}
67
+ className="max-h-full w-full min-w-0"
68
+ autoComplete="off"
69
+ />
70
 
71
+ <Button onClick={setApiKey} variant="outline" size="sm">
72
+ {t('apiKeyAlert.save')}
73
+ </Button>
74
+ </form>
75
+ {message && (
76
+ <div className="text-sm text-red-500">
77
+ {message}
78
+ </div>
79
+ )}
80
+ </div>
81
  </AlertDialogContent>
82
  </AlertDialog>
83
  )
lightrag_webui/src/components/MessageAlert.tsx DELETED
@@ -1,56 +0,0 @@
1
- import { Alert, AlertDescription, AlertTitle } from '@/components/ui/Alert'
2
- import { useBackendState } from '@/stores/state'
3
- import { useEffect, useState } from 'react'
4
- import { cn } from '@/lib/utils'
5
-
6
- // import Button from '@/components/ui/Button'
7
- // import { controlButtonVariant } from '@/lib/constants'
8
-
9
- import { AlertCircle } from 'lucide-react'
10
-
11
- const MessageAlert = () => {
12
- const health = useBackendState.use.health()
13
- const message = useBackendState.use.message()
14
- const messageTitle = useBackendState.use.messageTitle()
15
- const [isMounted, setIsMounted] = useState(false)
16
-
17
- useEffect(() => {
18
- setTimeout(() => {
19
- setIsMounted(true)
20
- }, 50)
21
- }, [])
22
-
23
- return (
24
- <Alert
25
- // variant={health ? 'default' : 'destructive'}
26
- className={cn(
27
- 'bg-background/90 absolute top-12 left-1/2 flex w-auto max-w-lg -translate-x-1/2 transform items-center gap-4 shadow-md backdrop-blur-lg transition-all duration-500 ease-in-out',
28
- isMounted ? 'translate-y-0 opacity-100' : '-translate-y-20 opacity-0',
29
- !health && 'bg-red-700 text-white'
30
- )}
31
- >
32
- {!health && (
33
- <div>
34
- <AlertCircle className="size-4" />
35
- </div>
36
- )}
37
- <div>
38
- <AlertTitle className="font-bold">{messageTitle}</AlertTitle>
39
- <AlertDescription>{message}</AlertDescription>
40
- </div>
41
- {/* <div className="flex">
42
- <div className="flex-auto" />
43
- <Button
44
- size="sm"
45
- variant={controlButtonVariant}
46
- className="border-primary max-h-8 border !p-2 text-xs"
47
- onClick={() => useBackendState.getState().clear()}
48
- >
49
- Close
50
- </Button>
51
- </div> */}
52
- </Alert>
53
- )
54
- }
55
-
56
- export default MessageAlert
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lightrag_webui/src/components/{graph → status}/StatusCard.tsx RENAMED
File without changes
lightrag_webui/src/components/{graph → status}/StatusIndicator.tsx RENAMED
@@ -2,7 +2,7 @@ import { cn } from '@/lib/utils'
2
  import { useBackendState } from '@/stores/state'
3
  import { useEffect, useState } from 'react'
4
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
5
- import StatusCard from '@/components/graph/StatusCard'
6
  import { useTranslation } from 'react-i18next'
7
 
8
  const StatusIndicator = () => {
 
2
  import { useBackendState } from '@/stores/state'
3
  import { useEffect, useState } from 'react'
4
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
5
+ import StatusCard from '@/components/status/StatusCard'
6
  import { useTranslation } from 'react-i18next'
7
 
8
  const StatusIndicator = () => {
lightrag_webui/src/components/ui/AsyncSearch.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback } from 'react'
2
  import { Loader2 } from 'lucide-react'
3
  import { useDebounce } from '@/hooks/useDebounce'
4
 
@@ -81,100 +81,97 @@ export function AsyncSearch<T>({
81
  const [options, setOptions] = useState<T[]>([])
82
  const [loading, setLoading] = useState(false)
83
  const [error, setError] = useState<string | null>(null)
84
- const [selectedValue, setSelectedValue] = useState(value)
85
- const [focusedValue, setFocusedValue] = useState<string | null>(null)
86
  const [searchTerm, setSearchTerm] = useState('')
87
  const debouncedSearchTerm = useDebounce(searchTerm, preload ? 0 : 150)
88
- const [originalOptions, setOriginalOptions] = useState<T[]>([])
89
 
90
  useEffect(() => {
91
  setMounted(true)
92
- setSelectedValue(value)
93
- }, [value])
94
 
95
- // Effect for initial fetch
96
  useEffect(() => {
97
- const initializeOptions = async () => {
98
- try {
99
- setLoading(true)
100
- setError(null)
101
- // If we have a value, use it for the initial search
102
- const data = value !== null ? await fetcher(value) : []
103
- setOriginalOptions(data)
104
- setOptions(data)
105
- } catch (err) {
106
- setError(err instanceof Error ? err.message : 'Failed to fetch options')
107
- } finally {
108
- setLoading(false)
109
  }
110
  }
111
 
112
- if (!mounted) {
113
- initializeOptions()
 
114
  }
115
- }, [mounted, fetcher, value])
116
 
117
- useEffect(() => {
118
- const fetchOptions = async () => {
119
- try {
120
- setLoading(true)
121
- setError(null)
122
- const data = await fetcher(debouncedSearchTerm)
123
- setOriginalOptions(data)
124
- setOptions(data)
125
- } catch (err) {
126
- setError(err instanceof Error ? err.message : 'Failed to fetch options')
127
- } finally {
128
- setLoading(false)
129
- }
130
  }
 
 
 
 
 
131
 
132
- if (!mounted) {
133
- fetchOptions()
134
- } else if (!preload) {
135
- fetchOptions()
136
- } else if (preload) {
137
  if (debouncedSearchTerm) {
138
- setOptions(
139
- originalOptions.filter((option) =>
140
  filterFn ? filterFn(option, debouncedSearchTerm) : true
141
  )
142
  )
143
- } else {
144
- setOptions(originalOptions)
145
  }
 
 
146
  }
147
- // eslint-disable-next-line react-hooks/exhaustive-deps
148
- }, [fetcher, debouncedSearchTerm, mounted, preload, filterFn])
149
-
150
- const handleSelect = useCallback(
151
- (currentValue: string) => {
152
- if (currentValue !== selectedValue) {
153
- setSelectedValue(currentValue)
154
- onChange(currentValue)
155
- }
 
 
 
 
 
 
156
  setOpen(false)
157
- },
158
- [selectedValue, setSelectedValue, setOpen, onChange]
159
- )
160
 
161
- const handleFocus = useCallback(
162
- (currentValue: string) => {
163
- if (currentValue !== focusedValue) {
164
- setFocusedValue(currentValue)
165
- onFocus(currentValue)
166
- }
167
- },
168
- [focusedValue, setFocusedValue, onFocus]
169
- )
 
 
 
170
 
171
  return (
172
  <div
 
173
  className={cn(disabled && 'cursor-not-allowed opacity-50', className)}
174
- onFocus={() => {
175
- setOpen(true)
176
- }}
177
- onBlur={() => setOpen(false)}
178
  >
179
  <Command shouldFilter={false} className="bg-transparent">
180
  <div>
@@ -182,12 +179,13 @@ export function AsyncSearch<T>({
182
  placeholder={placeholder}
183
  value={searchTerm}
184
  className="max-h-8"
 
185
  onValueChange={(value) => {
186
  setSearchTerm(value)
187
- if (value && !open) setOpen(true)
188
  }}
189
  />
190
- {loading && options.length > 0 && (
191
  <div className="absolute top-1/2 right-2 flex -translate-y-1/2 transform items-center">
192
  <Loader2 className="h-4 w-4 animate-spin" />
193
  </div>
@@ -209,8 +207,8 @@ export function AsyncSearch<T>({
209
  key={getOptionValue(option) + `${idx}`}
210
  value={getOptionValue(option)}
211
  onSelect={handleSelect}
212
- onMouseEnter={() => handleFocus(getOptionValue(option))}
213
- className="truncate"
214
  >
215
  {renderOption(option)}
216
  </CommandItem>
 
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react'
2
  import { Loader2 } from 'lucide-react'
3
  import { useDebounce } from '@/hooks/useDebounce'
4
 
 
81
  const [options, setOptions] = useState<T[]>([])
82
  const [loading, setLoading] = useState(false)
83
  const [error, setError] = useState<string | null>(null)
 
 
84
  const [searchTerm, setSearchTerm] = useState('')
85
  const debouncedSearchTerm = useDebounce(searchTerm, preload ? 0 : 150)
86
+ const containerRef = useRef<HTMLDivElement>(null)
87
 
88
  useEffect(() => {
89
  setMounted(true)
90
+ }, [])
 
91
 
92
+ // Handle clicks outside of the component
93
  useEffect(() => {
94
+ const handleClickOutside = (event: MouseEvent) => {
95
+ if (
96
+ containerRef.current &&
97
+ !containerRef.current.contains(event.target as Node) &&
98
+ open
99
+ ) {
100
+ setOpen(false)
 
 
 
 
 
101
  }
102
  }
103
 
104
+ document.addEventListener('mousedown', handleClickOutside)
105
+ return () => {
106
+ document.removeEventListener('mousedown', handleClickOutside)
107
  }
108
+ }, [open])
109
 
110
+ const fetchOptions = useCallback(async (query: string) => {
111
+ try {
112
+ setLoading(true)
113
+ setError(null)
114
+ const data = await fetcher(query)
115
+ setOptions(data)
116
+ } catch (err) {
117
+ setError(err instanceof Error ? err.message : 'Failed to fetch options')
118
+ } finally {
119
+ setLoading(false)
 
 
 
120
  }
121
+ }, [fetcher])
122
+
123
+ // Load options when search term changes
124
+ useEffect(() => {
125
+ if (!mounted) return
126
 
127
+ if (preload) {
 
 
 
 
128
  if (debouncedSearchTerm) {
129
+ setOptions((prev) =>
130
+ prev.filter((option) =>
131
  filterFn ? filterFn(option, debouncedSearchTerm) : true
132
  )
133
  )
 
 
134
  }
135
+ } else {
136
+ fetchOptions(debouncedSearchTerm)
137
  }
138
+ }, [mounted, debouncedSearchTerm, preload, filterFn, fetchOptions])
139
+
140
+ // Load initial value
141
+ useEffect(() => {
142
+ if (!mounted || !value) return
143
+ fetchOptions(value)
144
+ }, [mounted, value, fetchOptions])
145
+
146
+ const handleSelect = useCallback((currentValue: string) => {
147
+ onChange(currentValue)
148
+ requestAnimationFrame(() => {
149
+ // Blur the input to ensure focus event triggers on next click
150
+ const input = document.activeElement as HTMLElement
151
+ input?.blur()
152
+ // Close the dropdown
153
  setOpen(false)
154
+ })
155
+ }, [onChange])
 
156
 
157
+ const handleFocus = useCallback(() => {
158
+ setOpen(true)
159
+ // Use current search term to fetch options
160
+ fetchOptions(searchTerm)
161
+ }, [searchTerm, fetchOptions])
162
+
163
+ const handleMouseDown = useCallback((e: React.MouseEvent) => {
164
+ const target = e.target as HTMLElement
165
+ if (target.closest('.cmd-item')) {
166
+ e.preventDefault()
167
+ }
168
+ }, [])
169
 
170
  return (
171
  <div
172
+ ref={containerRef}
173
  className={cn(disabled && 'cursor-not-allowed opacity-50', className)}
174
+ onMouseDown={handleMouseDown}
 
 
 
175
  >
176
  <Command shouldFilter={false} className="bg-transparent">
177
  <div>
 
179
  placeholder={placeholder}
180
  value={searchTerm}
181
  className="max-h-8"
182
+ onFocus={handleFocus}
183
  onValueChange={(value) => {
184
  setSearchTerm(value)
185
+ if (!open) setOpen(true)
186
  }}
187
  />
188
+ {loading && (
189
  <div className="absolute top-1/2 right-2 flex -translate-y-1/2 transform items-center">
190
  <Loader2 className="h-4 w-4 animate-spin" />
191
  </div>
 
207
  key={getOptionValue(option) + `${idx}`}
208
  value={getOptionValue(option)}
209
  onSelect={handleSelect}
210
+ onMouseMove={() => onFocus(getOptionValue(option))}
211
+ className="truncate cmd-item"
212
  >
213
  {renderOption(option)}
214
  </CommandItem>
lightrag_webui/src/features/SiteHeader.tsx CHANGED
@@ -67,18 +67,20 @@ export default function SiteHeader() {
67
 
68
  return (
69
  <header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex h-10 w-full border-b px-4 backdrop-blur">
70
- <a href={webuiPrefix} className="mr-6 flex items-center gap-2">
71
- <ZapIcon className="size-4 text-emerald-400" aria-hidden="true" />
72
- {/* <img src='/logo.png' className="size-4" /> */}
73
- <span className="font-bold md:inline-block">{SiteInfo.name}</span>
74
- {versionDisplay && (
75
- <span className="ml-2 text-xs text-gray-500 dark:text-gray-400">
76
- v{versionDisplay}
77
- </span>
78
- )}
79
- </a>
 
 
80
 
81
- <div className="flex h-10 flex-1 justify-center">
82
  <TabsNavigation />
83
  {isGuestMode && (
84
  <div className="ml-2 self-center px-2 py-1 text-xs bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200 rounded-md">
@@ -87,7 +89,7 @@ export default function SiteHeader() {
87
  )}
88
  </div>
89
 
90
- <nav className="flex items-center">
91
  <div className="flex items-center gap-2">
92
  <Button variant="ghost" size="icon" side="bottom" tooltip={t('header.projectRepository')}>
93
  <a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
 
67
 
68
  return (
69
  <header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex h-10 w-full border-b px-4 backdrop-blur">
70
+ <div className="w-[200px] flex items-center">
71
+ <a href={webuiPrefix} className="flex items-center gap-2">
72
+ <ZapIcon className="size-4 text-emerald-400" aria-hidden="true" />
73
+ {/* <img src='/logo.png' className="size-4" /> */}
74
+ <span className="font-bold md:inline-block">{SiteInfo.name}</span>
75
+ {versionDisplay && (
76
+ <span className="ml-2 text-xs text-gray-500 dark:text-gray-400">
77
+ v{versionDisplay}
78
+ </span>
79
+ )}
80
+ </a>
81
+ </div>
82
 
83
+ <div className="flex h-10 flex-1 items-center justify-center">
84
  <TabsNavigation />
85
  {isGuestMode && (
86
  <div className="ml-2 self-center px-2 py-1 text-xs bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200 rounded-md">
 
89
  )}
90
  </div>
91
 
92
+ <nav className="w-[200px] flex items-center justify-end">
93
  <div className="flex items-center gap-2">
94
  <Button variant="ghost" size="icon" side="bottom" tooltip={t('header.projectRepository')}>
95
  <a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
lightrag_webui/src/locales/ar.json CHANGED
@@ -259,5 +259,11 @@
259
  },
260
  "apiSite": {
261
  "loading": "جارٍ تحميل وثائق واجهة برمجة التطبيقات..."
 
 
 
 
 
 
262
  }
263
  }
 
259
  },
260
  "apiSite": {
261
  "loading": "جارٍ تحميل وثائق واجهة برمجة التطبيقات..."
262
+ },
263
+ "apiKeyAlert": {
264
+ "title": "مفتاح واجهة برمجة التطبيقات مطلوب",
265
+ "description": "الرجاء إدخال مفتاح واجهة برمجة التطبيقات للوصول إلى الخدمة",
266
+ "placeholder": "أدخل مفتاح واجهة برمجة التطبيقات",
267
+ "save": "حفظ"
268
  }
269
  }
lightrag_webui/src/locales/en.json CHANGED
@@ -274,5 +274,11 @@
274
  },
275
  "apiSite": {
276
  "loading": "Loading API Documentation..."
 
 
 
 
 
 
277
  }
278
  }
 
274
  },
275
  "apiSite": {
276
  "loading": "Loading API Documentation..."
277
+ },
278
+ "apiKeyAlert": {
279
+ "title": "API Key is required",
280
+ "description": "Please enter your API key to access the service",
281
+ "placeholder": "Enter your API key",
282
+ "save": "Save"
283
  }
284
  }
lightrag_webui/src/locales/fr.json CHANGED
@@ -259,5 +259,11 @@
259
  },
260
  "apiSite": {
261
  "loading": "Chargement de la documentation de l'API..."
 
 
 
 
 
 
262
  }
263
  }
 
259
  },
260
  "apiSite": {
261
  "loading": "Chargement de la documentation de l'API..."
262
+ },
263
+ "apiKeyAlert": {
264
+ "title": "Clé API requise",
265
+ "description": "Veuillez entrer votre clé API pour accéder au service",
266
+ "placeholder": "Entrez votre clé API",
267
+ "save": "Sauvegarder"
268
  }
269
  }
lightrag_webui/src/locales/zh.json CHANGED
@@ -259,5 +259,11 @@
259
  },
260
  "apiSite": {
261
  "loading": "正在加载 API 文档..."
 
 
 
 
 
 
262
  }
263
  }
 
259
  },
260
  "apiSite": {
261
  "loading": "正在加载 API 文档..."
262
+ },
263
+ "apiKeyAlert": {
264
+ "title": "需要 API Key",
265
+ "description": "请输入您的 API Key 以访问服务",
266
+ "placeholder": "请输入 API Key",
267
+ "save": "保存"
268
  }
269
  }