congiuluc commited on
Commit
d552244
·
1 Parent(s): d9fd40b

Added Azure OpenAI api sample with streaming

Browse files
.gitignore CHANGED
@@ -15,3 +15,7 @@ ignore_this.txt
15
  gui/
16
  *.log
17
  .vscode
 
 
 
 
 
15
  gui/
16
  *.log
17
  .vscode
18
+ .env
19
+ venv/
20
+ examples/input/
21
+ examples/output/
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
api/README_AZURE_OPENAI.md ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ## License
176
+
177
+ This project is licensed under the MIT License - see the LICENSE file for details.
178
+
179
+ ## Acknowledgments
180
+
181
+ - Built with [FastAPI](https://fastapi.tiangolo.com/)
182
+ - Uses [LightRAG](https://github.com/HKUDS/LightRAG) for document processing
183
+ - Powered by [OpenAI](https://openai.com/) for language model inference
api/azure_openai_lightrag_server.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 azure_openai_complete_if_cache, azure_openai_complete, azure_openai_embedding
8
+ from lightrag.utils import EmbeddingFunc
9
+ from typing import Optional, List
10
+ from enum import Enum
11
+ from pathlib import Path
12
+ import shutil
13
+ import aiofiles
14
+ from ascii_colors import trace_exception
15
+ import os
16
+ from dotenv import load_dotenv
17
+ import inspect
18
+ import json
19
+ from fastapi.responses import StreamingResponse
20
+
21
+ load_dotenv()
22
+
23
+ AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION")
24
+ AZURE_OPENAI_DEPLOYMENT = os.getenv("AZURE_OPENAI_DEPLOYMENT")
25
+ AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
26
+ AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
27
+
28
+ AZURE_EMBEDDING_DEPLOYMENT = os.getenv("AZURE_EMBEDDING_DEPLOYMENT")
29
+ AZURE_EMBEDDING_API_VERSION = os.getenv("AZURE_EMBEDDING_API_VERSION")
30
+
31
+ def parse_args():
32
+ parser = argparse.ArgumentParser(
33
+ description="LightRAG FastAPI Server with OpenAI integration"
34
+ )
35
+
36
+ # Server configuration
37
+ parser.add_argument(
38
+ "--host", default="0.0.0.0", help="Server host (default: 0.0.0.0)"
39
+ )
40
+ parser.add_argument(
41
+ "--port", type=int, default=9621, help="Server port (default: 9621)"
42
+ )
43
+
44
+ # Directory configuration
45
+ parser.add_argument(
46
+ "--working-dir",
47
+ default="./rag_storage",
48
+ help="Working directory for RAG storage (default: ./rag_storage)",
49
+ )
50
+ parser.add_argument(
51
+ "--input-dir",
52
+ default="./inputs",
53
+ help="Directory containing input documents (default: ./inputs)",
54
+ )
55
+
56
+ # Model configuration
57
+ parser.add_argument(
58
+ "--model", default="gpt-4o", help="OpenAI model name (default: gpt-4o)"
59
+ )
60
+ parser.add_argument(
61
+ "--embedding-model",
62
+ default="text-embedding-3-large",
63
+ help="OpenAI embedding model (default: text-embedding-3-large)",
64
+ )
65
+
66
+ # RAG configuration
67
+ parser.add_argument(
68
+ "--max-tokens",
69
+ type=int,
70
+ default=32768,
71
+ help="Maximum token size (default: 32768)",
72
+ )
73
+ parser.add_argument(
74
+ "--max-embed-tokens",
75
+ type=int,
76
+ default=8192,
77
+ help="Maximum embedding token size (default: 8192)",
78
+ )
79
+ parser.add_argument(
80
+ "--enable-cache",
81
+ default=True,
82
+ help="Enable response cache (default: True)",
83
+ )
84
+ # Logging configuration
85
+ parser.add_argument(
86
+ "--log-level",
87
+ default="INFO",
88
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
89
+ help="Logging level (default: INFO)",
90
+ )
91
+
92
+ return parser.parse_args()
93
+
94
+
95
+ class DocumentManager:
96
+ """Handles document operations and tracking"""
97
+
98
+ def __init__(self, input_dir: str, supported_extensions: tuple = (".txt", ".md")):
99
+ self.input_dir = Path(input_dir)
100
+ self.supported_extensions = supported_extensions
101
+ self.indexed_files = set()
102
+
103
+ # Create input directory if it doesn't exist
104
+ self.input_dir.mkdir(parents=True, exist_ok=True)
105
+
106
+ def scan_directory(self) -> List[Path]:
107
+ """Scan input directory for new files"""
108
+ new_files = []
109
+ for ext in self.supported_extensions:
110
+ for file_path in self.input_dir.rglob(f"*{ext}"):
111
+ if file_path not in self.indexed_files:
112
+ new_files.append(file_path)
113
+ return new_files
114
+
115
+ def mark_as_indexed(self, file_path: Path):
116
+ """Mark a file as indexed"""
117
+ self.indexed_files.add(file_path)
118
+
119
+ def is_supported_file(self, filename: str) -> bool:
120
+ """Check if file type is supported"""
121
+ return any(filename.lower().endswith(ext) for ext in self.supported_extensions)
122
+
123
+
124
+ # Pydantic models
125
+ class SearchMode(str, Enum):
126
+ naive = "naive"
127
+ local = "local"
128
+ global_ = "global"
129
+ hybrid = "hybrid"
130
+
131
+
132
+ class QueryRequest(BaseModel):
133
+ query: str
134
+ mode: SearchMode = SearchMode.hybrid
135
+ #stream: bool = False
136
+
137
+
138
+ class QueryResponse(BaseModel):
139
+ response: str
140
+
141
+
142
+ class InsertTextRequest(BaseModel):
143
+ text: str
144
+ description: Optional[str] = None
145
+
146
+
147
+ class InsertResponse(BaseModel):
148
+ status: str
149
+ message: str
150
+ document_count: int
151
+
152
+
153
+ async def get_embedding_dim(embedding_model: str) -> int:
154
+ """Get embedding dimensions for the specified model"""
155
+ test_text = ["This is a test sentence."]
156
+ embedding = await azure_openai_embedding(test_text, model=embedding_model)
157
+ return embedding.shape[1]
158
+
159
+
160
+ def create_app(args):
161
+ # Setup logging
162
+ logging.basicConfig(
163
+ format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
164
+ )
165
+
166
+ # Initialize FastAPI app
167
+ app = FastAPI(
168
+ title="LightRAG API",
169
+ description="API for querying text using LightRAG with OpenAI integration",
170
+ )
171
+
172
+ # Create working directory if it doesn't exist
173
+ Path(args.working_dir).mkdir(parents=True, exist_ok=True)
174
+
175
+ # Initialize document manager
176
+ doc_manager = DocumentManager(args.input_dir)
177
+
178
+ # Get embedding dimensions
179
+ embedding_dim = asyncio.run(get_embedding_dim(args.embedding_model))
180
+
181
+ async def async_openai_complete(
182
+ prompt, system_prompt=None, history_messages=[], **kwargs
183
+ ):
184
+ """Async wrapper for OpenAI completion"""
185
+ keyword_extraction = kwargs.pop("keyword_extraction", None)
186
+
187
+ return await azure_openai_complete_if_cache(
188
+ args.model,
189
+ prompt,
190
+ system_prompt=system_prompt,
191
+ history_messages=history_messages,
192
+ base_url=AZURE_OPENAI_ENDPOINT,
193
+ api_key=AZURE_OPENAI_API_KEY,
194
+ api_version=AZURE_OPENAI_API_VERSION,
195
+ **kwargs,
196
+ )
197
+
198
+ # Initialize RAG with OpenAI configuration
199
+ rag = LightRAG(
200
+ enable_llm_cache=args.enable_cache,
201
+ working_dir=args.working_dir,
202
+ llm_model_func=async_openai_complete,
203
+ llm_model_name=args.model,
204
+ llm_model_max_token_size=args.max_tokens,
205
+ embedding_func=EmbeddingFunc(
206
+ embedding_dim=embedding_dim,
207
+ max_token_size=args.max_embed_tokens,
208
+ func=lambda texts: azure_openai_embedding(texts, model=args.embedding_model),
209
+ ),
210
+ )
211
+
212
+
213
+ @app.on_event("startup")
214
+ async def startup_event():
215
+ """Index all files in input directory during startup"""
216
+ try:
217
+ new_files = doc_manager.scan_directory()
218
+ for file_path in new_files:
219
+ try:
220
+ # Use async file reading
221
+ async with aiofiles.open(file_path, "r", encoding="utf-8") as f:
222
+ content = await f.read()
223
+ # Use the async version of insert directly
224
+ await rag.ainsert(content)
225
+ doc_manager.mark_as_indexed(file_path)
226
+ logging.info(f"Indexed file: {file_path}")
227
+ except Exception as e:
228
+ trace_exception(e)
229
+ logging.error(f"Error indexing file {file_path}: {str(e)}")
230
+
231
+ logging.info(f"Indexed {len(new_files)} documents from {args.input_dir}")
232
+
233
+ except Exception as e:
234
+ logging.error(f"Error during startup indexing: {str(e)}")
235
+
236
+ @app.post("/documents/scan")
237
+ async def scan_for_new_documents():
238
+ """Manually trigger scanning for new documents"""
239
+ try:
240
+ new_files = doc_manager.scan_directory()
241
+ indexed_count = 0
242
+
243
+ for file_path in new_files:
244
+ try:
245
+ with open(file_path, "r", encoding="utf-8") as f:
246
+ content = f.read()
247
+ await rag.ainsert(content)
248
+ doc_manager.mark_as_indexed(file_path)
249
+ indexed_count += 1
250
+ except Exception as e:
251
+ logging.error(f"Error indexing file {file_path}: {str(e)}")
252
+
253
+ return {
254
+ "status": "success",
255
+ "indexed_count": indexed_count,
256
+ "total_documents": len(doc_manager.indexed_files),
257
+ }
258
+ except Exception as e:
259
+ raise HTTPException(status_code=500, detail=str(e))
260
+
261
+ @app.post("/resetcache")
262
+ async def reset_cache():
263
+ """Manually reset cache"""
264
+ try:
265
+ cachefile = args.working_dir + "/kv_store_llm_response_cache.json"
266
+ if os.path.exists(cachefile):
267
+ with open(cachefile, "w") as f:
268
+ f.write("{}")
269
+ return {
270
+ "status": "success"
271
+ }
272
+ except Exception as e:
273
+ raise HTTPException(status_code=500, detail=str(e))
274
+
275
+ @app.post("/documents/upload")
276
+ async def upload_to_input_dir(file: UploadFile = File(...)):
277
+ """Upload a file to the input directory"""
278
+ try:
279
+ if not doc_manager.is_supported_file(file.filename):
280
+ raise HTTPException(
281
+ status_code=400,
282
+ detail=f"Unsupported file type. Supported types: {doc_manager.supported_extensions}",
283
+ )
284
+
285
+ file_path = doc_manager.input_dir / file.filename
286
+ with open(file_path, "wb") as buffer:
287
+ shutil.copyfileobj(file.file, buffer)
288
+
289
+ # Immediately index the uploaded file
290
+ with open(file_path, "r", encoding="utf-8") as f:
291
+ content = f.read()
292
+ await rag.ainsert(content)
293
+ doc_manager.mark_as_indexed(file_path)
294
+
295
+ return {
296
+ "status": "success",
297
+ "message": f"File uploaded and indexed: {file.filename}",
298
+ "total_documents": len(doc_manager.indexed_files),
299
+ }
300
+ except Exception as e:
301
+ raise HTTPException(status_code=500, detail=str(e))
302
+
303
+ @app.post("/query", response_model=QueryResponse)
304
+ async def query_text(request: QueryRequest):
305
+ try:
306
+ response = await rag.aquery(
307
+ request.query,
308
+ param=QueryParam(mode=request.mode, stream=False),
309
+ )
310
+ return QueryResponse(response=response)
311
+ except Exception as e:
312
+ raise HTTPException(status_code=500, detail=str(e))
313
+
314
+ @app.post("/query/stream")
315
+ async def query_text_stream(request: QueryRequest):
316
+ try:
317
+ response = await rag.aquery(
318
+ request.query,
319
+ param=QueryParam(mode=request.mode, stream=True),
320
+ )
321
+ if inspect.isasyncgen(response):
322
+ async def stream_generator():
323
+ async for chunk in response:
324
+ yield json.dumps({"data": chunk}) + "\n"
325
+
326
+ return StreamingResponse(stream_generator(), media_type="application/json")
327
+ else:
328
+ return QueryResponse(response=response)
329
+
330
+
331
+ except Exception as e:
332
+ raise HTTPException(status_code=500, detail=str(e))
333
+
334
+ @app.post("/documents/text", response_model=InsertResponse)
335
+ async def insert_text(request: InsertTextRequest):
336
+ try:
337
+ rag.insert(request.text)
338
+ return InsertResponse(
339
+ status="success",
340
+ message="Text successfully inserted",
341
+ document_count=len(rag),
342
+ )
343
+ except Exception as e:
344
+ raise HTTPException(status_code=500, detail=str(e))
345
+
346
+ @app.post("/documents/file", response_model=InsertResponse)
347
+ async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
348
+ try:
349
+ content = await file.read()
350
+
351
+ if file.filename.endswith((".txt", ".md")):
352
+ text = content.decode("utf-8")
353
+ rag.insert(text)
354
+ else:
355
+ raise HTTPException(
356
+ status_code=400,
357
+ detail="Unsupported file type. Only .txt and .md files are supported",
358
+ )
359
+
360
+ return InsertResponse(
361
+ status="success",
362
+ message=f"File '{file.filename}' successfully inserted",
363
+ document_count=len(rag),
364
+ )
365
+ except UnicodeDecodeError:
366
+ raise HTTPException(status_code=400, detail="File encoding not supported")
367
+ except Exception as e:
368
+ raise HTTPException(status_code=500, detail=str(e))
369
+
370
+ @app.post("/documents/batch", response_model=InsertResponse)
371
+ async def insert_batch(files: List[UploadFile] = File(...)):
372
+ try:
373
+ inserted_count = 0
374
+ failed_files = []
375
+
376
+ for file in files:
377
+ try:
378
+ content = await file.read()
379
+ if file.filename.endswith((".txt", ".md")):
380
+ text = content.decode("utf-8")
381
+ rag.insert(text)
382
+ inserted_count += 1
383
+ else:
384
+ failed_files.append(f"{file.filename} (unsupported type)")
385
+ except Exception as e:
386
+ failed_files.append(f"{file.filename} ({str(e)})")
387
+
388
+ status_message = f"Successfully inserted {inserted_count} documents"
389
+ if failed_files:
390
+ status_message += f". Failed files: {', '.join(failed_files)}"
391
+
392
+ return InsertResponse(
393
+ status="success" if inserted_count > 0 else "partial_success",
394
+ message=status_message,
395
+ document_count=len(rag),
396
+ )
397
+ except Exception as e:
398
+ raise HTTPException(status_code=500, detail=str(e))
399
+
400
+ @app.delete("/documents", response_model=InsertResponse)
401
+ async def clear_documents():
402
+ try:
403
+ rag.text_chunks = []
404
+ rag.entities_vdb = None
405
+ rag.relationships_vdb = None
406
+ return InsertResponse(
407
+ status="success",
408
+ message="All documents cleared successfully",
409
+ document_count=0,
410
+ )
411
+ except Exception as e:
412
+ raise HTTPException(status_code=500, detail=str(e))
413
+
414
+ @app.get("/health")
415
+ async def get_status():
416
+ """Get current system status"""
417
+ return {
418
+ "status": "healthy",
419
+ "working_directory": str(args.working_dir),
420
+ "input_directory": str(args.input_dir),
421
+ "indexed_files": len(doc_manager.indexed_files),
422
+ "configuration": {
423
+ "model": args.model,
424
+ "embedding_model": args.embedding_model,
425
+ "max_tokens": args.max_tokens,
426
+ "embedding_dim": embedding_dim,
427
+ },
428
+ }
429
+
430
+ return app
431
+
432
+
433
+ if __name__ == "__main__":
434
+ args = parse_args()
435
+ import uvicorn
436
+ app = create_app(args)
437
+ uvicorn.run(app, host=args.host, port=args.port)
api/requirements.txt CHANGED
@@ -2,3 +2,16 @@ ascii_colors
2
  fastapi
3
  python-multipart
4
  uvicorn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  fastapi
3
  python-multipart
4
  uvicorn
5
+ nest_asyncio
6
+ lightrag-hku
7
+ tqdm
8
+ aioboto3
9
+ numpy
10
+ ollama
11
+ torch
12
+ openai
13
+ tenacity
14
+ transformers
15
+ tiktoken
16
+ nano_vectordb
17
+ python-dotenv
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/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):