ParisNeo commited on
Commit
7937719
·
2 Parent(s): 0e07b80 87ca7c3

Merge remote-tracking branch 'upstream/main'

Browse files
.gitignore CHANGED
@@ -17,3 +17,7 @@ gui/
17
  .vscode
18
  inputs
19
  rag_storage
 
 
 
 
 
17
  .vscode
18
  inputs
19
  rag_storage
20
+ .env
21
+ venv/
22
+ examples/input/
23
+ examples/output/
examples/.env.oai.example ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ AZURE_OPENAI_API_VERSION=2024-08-01-preview
2
+ AZURE_OPENAI_DEPLOYMENT=gpt-4o
3
+ AZURE_OPENAI_API_KEY=myapikey
4
+ AZURE_OPENAI_ENDPOINT=https://myendpoint.openai.azure.com
5
+
6
+ AZURE_EMBEDDING_DEPLOYMENT=text-embedding-3-large
7
+ AZURE_EMBEDDING_API_VERSION=2023-05-15
lightrag/api/.env.aoi.example ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ AZURE_OPENAI_API_VERSION=2024-08-01-preview
2
+ AZURE_OPENAI_DEPLOYMENT=gpt-4o
3
+ AZURE_OPENAI_API_KEY=myapikey
4
+ AZURE_OPENAI_ENDPOINT=https://myendpoint.openai.azure.com
5
+
6
+ AZURE_EMBEDDING_DEPLOYMENT=text-embedding-3-large
7
+ AZURE_EMBEDDING_API_VERSION=2023-05-15
lightrag/api/README_AZURE_OPENAI.md ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # LightRAG API Server
3
+
4
+ A powerful FastAPI-based server for managing and querying documents using LightRAG (Light Retrieval-Augmented Generation). This server provides a REST API interface for document management and intelligent querying using OpenAI's language models.
5
+
6
+ ## Features
7
+
8
+ - 🔍 Multiple search modes (naive, local, global, hybrid)
9
+ - 📡 Streaming and non-streaming responses
10
+ - 📝 Document management (insert, batch upload, clear)
11
+ - ⚙️ Highly configurable model parameters
12
+ - 📚 Support for text and file uploads
13
+ - 🔧 RESTful API with automatic documentation
14
+ - 🚀 Built with FastAPI for high performance
15
+
16
+ ## Prerequisites
17
+
18
+ - Python 3.8+
19
+ - Azure OpenAI API key
20
+ - Azure OpenAI Deployments (gpt-4o, text-embedding-3-large)
21
+ - Required Python packages:
22
+ - fastapi
23
+ - uvicorn
24
+ - lightrag
25
+ - pydantic
26
+ - openai
27
+ - nest-asyncio
28
+
29
+ ## Installation
30
+ If you are using Windows, you will need to download and install visual c++ build tools from [https://visualstudio.microsoft.com/visual-cpp-build-tools/](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
31
+ Make sure you install the VS 2022 C++ x64/x86 Build tools from individual components tab.
32
+
33
+ 1. Clone the repository:
34
+ ```bash
35
+ git clone https://github.com/ParisNeo/LightRAG.git
36
+ cd api
37
+ ```
38
+
39
+ 2. Install dependencies:
40
+ ```bash
41
+ python -m venv venv
42
+ source venv/bin/activate
43
+ #venv\Scripts\activate for Windows
44
+ pip install -r requirements.txt
45
+ ```
46
+
47
+ 3. Set up environment variables:
48
+ use the `.env` file to set the environment variables (you can copy the `.env.aoi.example` file and rename it to `.env`),
49
+ or set them manually:
50
+ ```bash
51
+ export AZURE_OPENAI_API_VERSION='2024-08-01-preview'
52
+ export AZURE_OPENAI_DEPLOYMENT='gpt-4o'
53
+ export AZURE_OPENAI_API_KEY='myapikey'
54
+ export AZURE_OPENAI_ENDPOINT='https://myendpoint.openai.azure.com'
55
+ export AZURE_EMBEDDING_DEPLOYMENT='text-embedding-3-large'
56
+ export AZURE_EMBEDDING_API_VERSION='2023-05-15'
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ The server can be configured using command-line arguments:
62
+
63
+ ```bash
64
+ python azure_openai_lightrag_server.py --help
65
+ ```
66
+
67
+ Available options:
68
+
69
+ | Parameter | Default | Description |
70
+ |-----------|---------|-------------|
71
+ | --host | 0.0.0.0 | Server host |
72
+ | --port | 9621 | Server port |
73
+ | --model | gpt-4 | OpenAI model name |
74
+ | --embedding-model | text-embedding-3-large | OpenAI embedding model |
75
+ | --working-dir | ./rag_storage | Working directory for RAG |
76
+ | --max-tokens | 32768 | Maximum token size |
77
+ | --max-embed-tokens | 8192 | Maximum embedding token size |
78
+ | --input-dir | ./inputs | Input directory for documents |
79
+ | --enable-cache | True | Enable response cache |
80
+ | --log-level | INFO | Logging level |
81
+
82
+ ## Quick Start
83
+
84
+ 1. Basic usage with default settings:
85
+ ```bash
86
+ python azure_openai_lightrag_server.py
87
+ ```
88
+
89
+ 2. Custom configuration:
90
+ ```bash
91
+ python azure_openai_lightrag_server.py --model gpt-4o --port 8080 --working-dir ./custom_rag
92
+ ```
93
+
94
+ ## API Endpoints
95
+
96
+ ### Query Endpoints
97
+
98
+ #### POST /query
99
+ Query the RAG system with options for different search modes.
100
+
101
+ ```bash
102
+ curl -X POST "http://localhost:9621/query" \
103
+ -H "Content-Type: application/json" \
104
+ -d '{"query": "Your question here", "mode": "hybrid"}'
105
+ ```
106
+
107
+ #### POST /query/stream
108
+ Stream responses from the RAG system.
109
+
110
+ ```bash
111
+ curl -X POST "http://localhost:9621/query/stream" \
112
+ -H "Content-Type: application/json" \
113
+ -d '{"query": "Your question here", "mode": "hybrid"}'
114
+ ```
115
+
116
+ ### Document Management Endpoints
117
+
118
+ #### POST /documents/text
119
+ Insert text directly into the RAG system.
120
+
121
+ ```bash
122
+ curl -X POST "http://localhost:9621/documents/text" \
123
+ -H "Content-Type: application/json" \
124
+ -d '{"text": "Your text content here", "description": "Optional description"}'
125
+ ```
126
+
127
+ #### POST /documents/file
128
+ Upload a single file to the RAG system.
129
+
130
+ ```bash
131
+ curl -X POST "http://localhost:9621/documents/file" \
132
+ -F "file=@/path/to/your/document.txt" \
133
+ -F "description=Optional description"
134
+ ```
135
+
136
+ #### POST /documents/batch
137
+ Upload multiple files at once.
138
+
139
+ ```bash
140
+ curl -X POST "http://localhost:9621/documents/batch" \
141
+ -F "files=@/path/to/doc1.txt" \
142
+ -F "files=@/path/to/doc2.txt"
143
+ ```
144
+
145
+ #### DELETE /documents
146
+ Clear all documents from the RAG system.
147
+
148
+ ```bash
149
+ curl -X DELETE "http://localhost:9621/documents"
150
+ ```
151
+
152
+ ### Utility Endpoints
153
+
154
+ #### GET /health
155
+ Check server health and configuration.
156
+
157
+ ```bash
158
+ curl "http://localhost:9621/health"
159
+ ```
160
+
161
+ ## Development
162
+
163
+ ### Running in Development Mode
164
+
165
+ ```bash
166
+ uvicorn azure_openai_lightrag_server:app --reload --port 9621
167
+ ```
168
+
169
+ ### API Documentation
170
+
171
+ When the server is running, visit:
172
+ - Swagger UI: http://localhost:9621/docs
173
+ - ReDoc: http://localhost:9621/redoc
174
+
175
+ ## Deployment
176
+ Azure OpenAI API can be created using the following commands in Azure CLI (you need to install Azure CLI first from [https://docs.microsoft.com/en-us/cli/azure/install-azure-cli](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)):
177
+ ```bash
178
+ # Change the resource group name, location and OpenAI resource name as needed
179
+ RESOURCE_GROUP_NAME=LightRAG
180
+ LOCATION=swedencentral
181
+ RESOURCE_NAME=LightRAG-OpenAI
182
+
183
+ az login
184
+ az group create --name $RESOURCE_GROUP_NAME --location $LOCATION
185
+ az cognitiveservices account create --name $RESOURCE_NAME --resource-group $RESOURCE_GROUP_NAME --kind OpenAI --sku S0 --location swedencentral
186
+ 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"
187
+ 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"
188
+ az cognitiveservices account show --name $RESOURCE_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.endpoint"
189
+ az cognitiveservices account keys list --name $RESOURCE_NAME -g $RESOURCE_GROUP_NAME
190
+
191
+ ```
192
+ The output of the last command will give you the endpoint and the key for the OpenAI API. You can use these values to set the environment variables in the `.env` file.
193
+
194
+ ## License
195
+
196
+ This project is licensed under the MIT License - see the LICENSE file for details.
197
+
198
+ ## Acknowledgments
199
+
200
+ - Built with [FastAPI](https://fastapi.tiangolo.com/)
201
+ - Uses [LightRAG](https://github.com/HKUDS/LightRAG) for document processing
202
+ - Powered by [OpenAI](https://openai.com/) for language model inference
lightrag/api/azure_openai_lightrag_server.py ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, File, UploadFile, Form
2
+ from pydantic import BaseModel
3
+ import asyncio
4
+ import logging
5
+ import argparse
6
+ from lightrag import LightRAG, QueryParam
7
+ from lightrag.llm import (
8
+ azure_openai_complete_if_cache,
9
+ azure_openai_embedding,
10
+ )
11
+ from lightrag.utils import EmbeddingFunc
12
+ from typing import Optional, List
13
+ from enum import Enum
14
+ from pathlib import Path
15
+ import shutil
16
+ import aiofiles
17
+ from ascii_colors import trace_exception
18
+ import os
19
+ from dotenv import load_dotenv
20
+ import inspect
21
+ import json
22
+ from fastapi.responses import StreamingResponse
23
+
24
+ load_dotenv()
25
+
26
+ AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION")
27
+ AZURE_OPENAI_DEPLOYMENT = os.getenv("AZURE_OPENAI_DEPLOYMENT")
28
+ AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
29
+ AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
30
+
31
+ AZURE_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_EMBEDDING_DEPLOYMENT")
32
+ AZURE_EMBEDDING_API_VERSION = os.getenv("AZURE_EMBEDDING_API_VERSION")
33
+
34
+
35
+ def parse_args():
36
+ parser = argparse.ArgumentParser(
37
+ description="LightRAG FastAPI Server with OpenAI integration"
38
+ )
39
+
40
+ # Server configuration
41
+ parser.add_argument(
42
+ "--host", default="0.0.0.0", help="Server host (default: 0.0.0.0)"
43
+ )
44
+ parser.add_argument(
45
+ "--port", type=int, default=9621, help="Server port (default: 9621)"
46
+ )
47
+
48
+ # Directory configuration
49
+ parser.add_argument(
50
+ "--working-dir",
51
+ default="./rag_storage",
52
+ help="Working directory for RAG storage (default: ./rag_storage)",
53
+ )
54
+ parser.add_argument(
55
+ "--input-dir",
56
+ default="./inputs",
57
+ help="Directory containing input documents (default: ./inputs)",
58
+ )
59
+
60
+ # Model configuration
61
+ parser.add_argument(
62
+ "--model", default="gpt-4o", help="OpenAI model name (default: gpt-4o)"
63
+ )
64
+ parser.add_argument(
65
+ "--embedding-model",
66
+ default="text-embedding-3-large",
67
+ help="OpenAI embedding model (default: text-embedding-3-large)",
68
+ )
69
+
70
+ # RAG configuration
71
+ parser.add_argument(
72
+ "--max-tokens",
73
+ type=int,
74
+ default=32768,
75
+ help="Maximum token size (default: 32768)",
76
+ )
77
+ parser.add_argument(
78
+ "--max-embed-tokens",
79
+ type=int,
80
+ default=8192,
81
+ help="Maximum embedding token size (default: 8192)",
82
+ )
83
+ parser.add_argument(
84
+ "--enable-cache",
85
+ default=True,
86
+ help="Enable response cache (default: True)",
87
+ )
88
+ # Logging configuration
89
+ parser.add_argument(
90
+ "--log-level",
91
+ default="INFO",
92
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
93
+ help="Logging level (default: INFO)",
94
+ )
95
+
96
+ return parser.parse_args()
97
+
98
+
99
+ class DocumentManager:
100
+ """Handles document operations and tracking"""
101
+
102
+ def __init__(self, input_dir: str, supported_extensions: tuple = (".txt", ".md")):
103
+ self.input_dir = Path(input_dir)
104
+ self.supported_extensions = supported_extensions
105
+ self.indexed_files = set()
106
+
107
+ # Create input directory if it doesn't exist
108
+ self.input_dir.mkdir(parents=True, exist_ok=True)
109
+
110
+ def scan_directory(self) -> List[Path]:
111
+ """Scan input directory for new files"""
112
+ new_files = []
113
+ for ext in self.supported_extensions:
114
+ for file_path in self.input_dir.rglob(f"*{ext}"):
115
+ if file_path not in self.indexed_files:
116
+ new_files.append(file_path)
117
+ return new_files
118
+
119
+ def mark_as_indexed(self, file_path: Path):
120
+ """Mark a file as indexed"""
121
+ self.indexed_files.add(file_path)
122
+
123
+ def is_supported_file(self, filename: str) -> bool:
124
+ """Check if file type is supported"""
125
+ return any(filename.lower().endswith(ext) for ext in self.supported_extensions)
126
+
127
+
128
+ # Pydantic models
129
+ class SearchMode(str, Enum):
130
+ naive = "naive"
131
+ local = "local"
132
+ global_ = "global"
133
+ hybrid = "hybrid"
134
+
135
+
136
+ class QueryRequest(BaseModel):
137
+ query: str
138
+ mode: SearchMode = SearchMode.hybrid
139
+ # stream: bool = False
140
+
141
+
142
+ class QueryResponse(BaseModel):
143
+ response: str
144
+
145
+
146
+ class InsertTextRequest(BaseModel):
147
+ text: str
148
+ description: Optional[str] = None
149
+
150
+
151
+ class InsertResponse(BaseModel):
152
+ status: str
153
+ message: str
154
+ document_count: int
155
+
156
+
157
+ async def get_embedding_dim(embedding_model: str) -> int:
158
+ """Get embedding dimensions for the specified model"""
159
+ test_text = ["This is a test sentence."]
160
+ embedding = await azure_openai_embedding(test_text, model=embedding_model)
161
+ return embedding.shape[1]
162
+
163
+
164
+ def create_app(args):
165
+ # Setup logging
166
+ logging.basicConfig(
167
+ format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
168
+ )
169
+
170
+ # Initialize FastAPI app
171
+ app = FastAPI(
172
+ title="LightRAG API",
173
+ description="API for querying text using LightRAG with OpenAI integration",
174
+ )
175
+
176
+ # Create working directory if it doesn't exist
177
+ Path(args.working_dir).mkdir(parents=True, exist_ok=True)
178
+
179
+ # Initialize document manager
180
+ doc_manager = DocumentManager(args.input_dir)
181
+
182
+ # Get embedding dimensions
183
+ embedding_dim = asyncio.run(get_embedding_dim(args.embedding_model))
184
+
185
+ async def async_openai_complete(
186
+ prompt, system_prompt=None, history_messages=[], **kwargs
187
+ ):
188
+ """Async wrapper for OpenAI completion"""
189
+ kwargs.pop("keyword_extraction", None)
190
+
191
+ return await azure_openai_complete_if_cache(
192
+ args.model,
193
+ prompt,
194
+ system_prompt=system_prompt,
195
+ history_messages=history_messages,
196
+ base_url=AZURE_OPENAI_ENDPOINT,
197
+ api_key=AZURE_OPENAI_API_KEY,
198
+ api_version=AZURE_OPENAI_API_VERSION,
199
+ **kwargs,
200
+ )
201
+
202
+ # Initialize RAG with OpenAI configuration
203
+ rag = LightRAG(
204
+ enable_llm_cache=args.enable_cache,
205
+ working_dir=args.working_dir,
206
+ llm_model_func=async_openai_complete,
207
+ llm_model_name=args.model,
208
+ llm_model_max_token_size=args.max_tokens,
209
+ embedding_func=EmbeddingFunc(
210
+ embedding_dim=embedding_dim,
211
+ max_token_size=args.max_embed_tokens,
212
+ func=lambda texts: azure_openai_embedding(
213
+ texts, model=args.embedding_model
214
+ ),
215
+ ),
216
+ )
217
+
218
+ @app.on_event("startup")
219
+ async def startup_event():
220
+ """Index all files in input directory during startup"""
221
+ try:
222
+ new_files = doc_manager.scan_directory()
223
+ for file_path in new_files:
224
+ try:
225
+ # Use async file reading
226
+ async with aiofiles.open(file_path, "r", encoding="utf-8") as f:
227
+ content = await f.read()
228
+ # Use the async version of insert directly
229
+ await rag.ainsert(content)
230
+ doc_manager.mark_as_indexed(file_path)
231
+ logging.info(f"Indexed file: {file_path}")
232
+ except Exception as e:
233
+ trace_exception(e)
234
+ logging.error(f"Error indexing file {file_path}: {str(e)}")
235
+
236
+ logging.info(f"Indexed {len(new_files)} documents from {args.input_dir}")
237
+
238
+ except Exception as e:
239
+ logging.error(f"Error during startup indexing: {str(e)}")
240
+
241
+ @app.post("/documents/scan")
242
+ async def scan_for_new_documents():
243
+ """Manually trigger scanning for new documents"""
244
+ try:
245
+ new_files = doc_manager.scan_directory()
246
+ indexed_count = 0
247
+
248
+ for file_path in new_files:
249
+ try:
250
+ with open(file_path, "r", encoding="utf-8") as f:
251
+ content = f.read()
252
+ await rag.ainsert(content)
253
+ doc_manager.mark_as_indexed(file_path)
254
+ indexed_count += 1
255
+ except Exception as e:
256
+ logging.error(f"Error indexing file {file_path}: {str(e)}")
257
+
258
+ return {
259
+ "status": "success",
260
+ "indexed_count": indexed_count,
261
+ "total_documents": len(doc_manager.indexed_files),
262
+ }
263
+ except Exception as e:
264
+ raise HTTPException(status_code=500, detail=str(e))
265
+
266
+ @app.post("/resetcache")
267
+ async def reset_cache():
268
+ """Manually reset cache"""
269
+ try:
270
+ cachefile = args.working_dir + "/kv_store_llm_response_cache.json"
271
+ if os.path.exists(cachefile):
272
+ with open(cachefile, "w") as f:
273
+ f.write("{}")
274
+ return {"status": "success"}
275
+ except Exception as e:
276
+ raise HTTPException(status_code=500, detail=str(e))
277
+
278
+ @app.post("/documents/upload")
279
+ async def upload_to_input_dir(file: UploadFile = File(...)):
280
+ """Upload a file to the input directory"""
281
+ try:
282
+ if not doc_manager.is_supported_file(file.filename):
283
+ raise HTTPException(
284
+ status_code=400,
285
+ detail=f"Unsupported file type. Supported types: {doc_manager.supported_extensions}",
286
+ )
287
+
288
+ file_path = doc_manager.input_dir / file.filename
289
+ with open(file_path, "wb") as buffer:
290
+ shutil.copyfileobj(file.file, buffer)
291
+
292
+ # Immediately index the uploaded file
293
+ with open(file_path, "r", encoding="utf-8") as f:
294
+ content = f.read()
295
+ await rag.ainsert(content)
296
+ doc_manager.mark_as_indexed(file_path)
297
+
298
+ return {
299
+ "status": "success",
300
+ "message": f"File uploaded and indexed: {file.filename}",
301
+ "total_documents": len(doc_manager.indexed_files),
302
+ }
303
+ except Exception as e:
304
+ raise HTTPException(status_code=500, detail=str(e))
305
+
306
+ @app.post("/query", response_model=QueryResponse)
307
+ async def query_text(request: QueryRequest):
308
+ try:
309
+ response = await rag.aquery(
310
+ request.query,
311
+ param=QueryParam(mode=request.mode, stream=False),
312
+ )
313
+ return QueryResponse(response=response)
314
+ except Exception as e:
315
+ raise HTTPException(status_code=500, detail=str(e))
316
+
317
+ @app.post("/query/stream")
318
+ async def query_text_stream(request: QueryRequest):
319
+ try:
320
+ response = await rag.aquery(
321
+ request.query,
322
+ param=QueryParam(mode=request.mode, stream=True),
323
+ )
324
+ if inspect.isasyncgen(response):
325
+
326
+ async def stream_generator():
327
+ async for chunk in response:
328
+ yield json.dumps({"data": chunk}) + "\n"
329
+
330
+ return StreamingResponse(
331
+ stream_generator(), media_type="application/json"
332
+ )
333
+ else:
334
+ return QueryResponse(response=response)
335
+
336
+ except Exception as e:
337
+ raise HTTPException(status_code=500, detail=str(e))
338
+
339
+ @app.post("/documents/text", response_model=InsertResponse)
340
+ async def insert_text(request: InsertTextRequest):
341
+ try:
342
+ rag.insert(request.text)
343
+ return InsertResponse(
344
+ status="success",
345
+ message="Text successfully inserted",
346
+ document_count=len(rag),
347
+ )
348
+ except Exception as e:
349
+ raise HTTPException(status_code=500, detail=str(e))
350
+
351
+ @app.post("/documents/file", response_model=InsertResponse)
352
+ async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
353
+ try:
354
+ content = await file.read()
355
+
356
+ if file.filename.endswith((".txt", ".md")):
357
+ text = content.decode("utf-8")
358
+ rag.insert(text)
359
+ else:
360
+ raise HTTPException(
361
+ status_code=400,
362
+ detail="Unsupported file type. Only .txt and .md files are supported",
363
+ )
364
+
365
+ return InsertResponse(
366
+ status="success",
367
+ message=f"File '{file.filename}' successfully inserted",
368
+ document_count=len(rag),
369
+ )
370
+ except UnicodeDecodeError:
371
+ raise HTTPException(status_code=400, detail="File encoding not supported")
372
+ except Exception as e:
373
+ raise HTTPException(status_code=500, detail=str(e))
374
+
375
+ @app.post("/documents/batch", response_model=InsertResponse)
376
+ async def insert_batch(files: List[UploadFile] = File(...)):
377
+ try:
378
+ inserted_count = 0
379
+ failed_files = []
380
+
381
+ for file in files:
382
+ try:
383
+ content = await file.read()
384
+ if file.filename.endswith((".txt", ".md")):
385
+ text = content.decode("utf-8")
386
+ rag.insert(text)
387
+ inserted_count += 1
388
+ else:
389
+ failed_files.append(f"{file.filename} (unsupported type)")
390
+ except Exception as e:
391
+ failed_files.append(f"{file.filename} ({str(e)})")
392
+
393
+ status_message = f"Successfully inserted {inserted_count} documents"
394
+ if failed_files:
395
+ status_message += f". Failed files: {', '.join(failed_files)}"
396
+
397
+ return InsertResponse(
398
+ status="success" if inserted_count > 0 else "partial_success",
399
+ message=status_message,
400
+ document_count=len(rag),
401
+ )
402
+ except Exception as e:
403
+ raise HTTPException(status_code=500, detail=str(e))
404
+
405
+ @app.delete("/documents", response_model=InsertResponse)
406
+ async def clear_documents():
407
+ try:
408
+ rag.text_chunks = []
409
+ rag.entities_vdb = None
410
+ rag.relationships_vdb = None
411
+ return InsertResponse(
412
+ status="success",
413
+ message="All documents cleared successfully",
414
+ document_count=0,
415
+ )
416
+ except Exception as e:
417
+ raise HTTPException(status_code=500, detail=str(e))
418
+
419
+ @app.get("/health")
420
+ async def get_status():
421
+ """Get current system status"""
422
+ return {
423
+ "status": "healthy",
424
+ "working_directory": str(args.working_dir),
425
+ "input_directory": str(args.input_dir),
426
+ "indexed_files": len(doc_manager.indexed_files),
427
+ "configuration": {
428
+ "model": args.model,
429
+ "embedding_model": args.embedding_model,
430
+ "max_tokens": args.max_tokens,
431
+ "embedding_dim": embedding_dim,
432
+ },
433
+ }
434
+
435
+ return app
436
+
437
+
438
+ if __name__ == "__main__":
439
+ args = parse_args()
440
+ import uvicorn
441
+
442
+ app = create_app(args)
443
+ uvicorn.run(app, host=args.host, port=args.port)
lightrag/api/requirements.txt CHANGED
@@ -1,4 +1,17 @@
 
1
  ascii_colors
2
  fastapi
 
 
 
 
 
 
 
3
  python-multipart
 
 
 
 
 
4
  uvicorn
 
1
+ aioboto3
2
  ascii_colors
3
  fastapi
4
+ lightrag-hku
5
+ nano_vectordb
6
+ nest_asyncio
7
+ numpy
8
+ ollama
9
+ openai
10
+ python-dotenv
11
  python-multipart
12
+ tenacity
13
+ tiktoken
14
+ torch
15
+ tqdm
16
+ transformers
17
  uvicorn
lightrag/llm.py CHANGED
@@ -140,12 +140,34 @@ async def azure_openai_complete_if_cache(
140
  if prompt is not None:
141
  messages.append({"role": "user", "content": prompt})
142
 
143
- response = await openai_async_client.chat.completions.create(
144
- model=model, messages=messages, **kwargs
145
- )
146
- content = response.choices[0].message.content
 
 
 
 
 
 
147
 
148
- return content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
 
151
  class BedrockError(Exception):
 
140
  if prompt is not None:
141
  messages.append({"role": "user", "content": prompt})
142
 
143
+ if "response_format" in kwargs:
144
+ response = await openai_async_client.beta.chat.completions.parse(
145
+ model=model, messages=messages, **kwargs
146
+ )
147
+ else:
148
+ response = await openai_async_client.chat.completions.create(
149
+ model=model, messages=messages, **kwargs
150
+ )
151
+
152
+ if hasattr(response, "__aiter__"):
153
 
154
+ async def inner():
155
+ async for chunk in response:
156
+ if len(chunk.choices) == 0:
157
+ continue
158
+ content = chunk.choices[0].delta.content
159
+ if content is None:
160
+ continue
161
+ if r"\u" in content:
162
+ content = safe_unicode_decode(content.encode("utf-8"))
163
+ yield content
164
+
165
+ return inner()
166
+ else:
167
+ content = response.choices[0].message.content
168
+ if r"\u" in content:
169
+ content = safe_unicode_decode(content.encode("utf-8"))
170
+ return content
171
 
172
 
173
  class BedrockError(Exception):