diff --git a/.gitignore b/.gitignore index 0bb7ea15..2d9a41f3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ examples/input/ examples/output/ .DS_Store #Remove config.ini from repo -*.ini \ No newline at end of file +*.ini diff --git a/lightrag/api/lightrag_server.py b/lightrag/api/lightrag_server.py index abd26222..87a72a98 100644 --- a/lightrag/api/lightrag_server.py +++ b/lightrag/api/lightrag_server.py @@ -684,7 +684,9 @@ def create_app(args): trace_exception(e) logging.error(f"Error indexing file {file_path}: {str(e)}") - ASCIIColors.info(f"Indexed {len(new_files)} documents from {args.input_dir}") + ASCIIColors.info( + f"Indexed {len(new_files)} documents from {args.input_dir}" + ) except Exception as e: logging.error(f"Error during startup indexing: {str(e)}") yield @@ -917,7 +919,6 @@ def create_app(args): else: logging.warning(f"No content extracted from file: {file_path}") - @app.post("/documents/scan", dependencies=[Depends(optional_api_key)]) async def scan_for_new_documents(): """ diff --git a/lightrag/api/static/index.html b/lightrag/api/static/index.html index b32be046..36690ec9 100644 --- a/lightrag/api/static/index.html +++ b/lightrag/api/static/index.html @@ -622,7 +622,7 @@ const data = await response.json(); // Convert indexed_files to array if it's not already const files = Array.isArray(data.indexed_files) ? data.indexed_files : data.indexed_files.split(','); - + healthInfo.innerHTML = `
diff --git a/lightrag/exceptions.py b/lightrag/exceptions.py index 249ba7e2..5de6b334 100644 --- a/lightrag/exceptions.py +++ b/lightrag/exceptions.py @@ -1,6 +1,7 @@ import httpx from typing import Literal + class APIStatusError(Exception): """Raised when an API response has a status code of 4xx or 5xx.""" @@ -8,14 +9,19 @@ class APIStatusError(Exception): status_code: int request_id: str | None - def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None: + def __init__( + self, message: str, *, response: httpx.Response, body: object | None + ) -> None: super().__init__(message, response.request, body=body) self.response = response self.status_code = response.status_code self.request_id = response.headers.get("x-request-id") + class APIConnectionError(Exception): - def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None: + def __init__( + self, *, message: str = "Connection error.", request: httpx.Request + ) -> None: super().__init__(message, request, body=None) @@ -46,10 +52,7 @@ class UnprocessableEntityError(APIStatusError): class RateLimitError(APIStatusError): status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride] + class APITimeoutError(APIConnectionError): def __init__(self, request: httpx.Request) -> None: super().__init__(message="Request timed out.", request=request) - - -class BadRequestError(APIStatusError): - status_code: Literal[400] = 400 # pyright: ignore[reportIncompatibleVariableOverride] diff --git a/lightrag/kg/redis_impl.py b/lightrag/kg/redis_impl.py index 111fc6c8..a126074d 100644 --- a/lightrag/kg/redis_impl.py +++ b/lightrag/kg/redis_impl.py @@ -1,6 +1,7 @@ import os from tqdm.asyncio import tqdm as tqdm_async from dataclasses import dataclass + # aioredis is a depricated library, replaced with redis from redis.asyncio import Redis from lightrag.utils import logger diff --git a/lightrag/llm/azure_openai.py b/lightrag/llm/azure_openai.py index 83f89bcb..1070af7f 100644 --- a/lightrag/llm/azure_openai.py +++ b/lightrag/llm/azure_openai.py @@ -42,7 +42,7 @@ __status__ = "Production" import os -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("openai"): @@ -71,6 +71,7 @@ from lightrag.utils import ( import numpy as np + @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10), @@ -153,6 +154,7 @@ async def azure_openai_complete( return locate_json_string_body_from_string(result) return result + @wrap_embedding_func_with_attrs(embedding_dim=1536, max_token_size=8191) @retry( stop=stop_after_attempt(3), @@ -185,4 +187,3 @@ async def azure_openai_embed( model=model, input=texts, encoding_format="float" ) return np.array([dp.embedding for dp in response.data]) - diff --git a/lightrag/llm/bedrock.py b/lightrag/llm/bedrock.py index c03ec42d..f3b2143c 100644 --- a/lightrag/llm/bedrock.py +++ b/lightrag/llm/bedrock.py @@ -41,12 +41,12 @@ __author__ = "lightrag Team" __status__ = "Production" -import sys import copy import os import json -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install + if not pm.is_installed("aioboto3"): pm.install("aioboto3") if not pm.is_installed("tenacity"): @@ -60,15 +60,11 @@ from tenacity import ( retry_if_exception_type, ) -from lightrag.exceptions import ( - APIConnectionError, - RateLimitError, - APITimeoutError, -) from lightrag.utils import ( locate_json_string_body_from_string, ) + class BedrockError(Exception): """Generic error for issues related to Amazon Bedrock""" diff --git a/lightrag/llm/hf.py b/lightrag/llm/hf.py index dad8f3a8..075fc357 100644 --- a/lightrag/llm/hf.py +++ b/lightrag/llm/hf.py @@ -42,7 +42,7 @@ __status__ = "Production" import copy import os -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("transformers"): @@ -69,9 +69,11 @@ from lightrag.utils import ( locate_json_string_body_from_string, ) import torch +import numpy as np os.environ["TOKENIZERS_PARALLELISM"] = "false" + @lru_cache(maxsize=1) def initialize_hf_model(model_name): hf_tokenizer = AutoTokenizer.from_pretrained( @@ -155,7 +157,6 @@ async def hf_model_if_cache( return response_text - async def hf_model_complete( prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs ) -> str: diff --git a/lightrag/llm/jina.py b/lightrag/llm/jina.py index 07e680e1..62404dde 100644 --- a/lightrag/llm/jina.py +++ b/lightrag/llm/jina.py @@ -39,7 +39,7 @@ __author__ = "lightrag Team" __status__ = "Production" import os -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("lmdeploy"): @@ -47,25 +47,8 @@ if not pm.is_installed("lmdeploy"): if not pm.is_installed("tenacity"): pm.install("tenacity") -from tenacity import ( - retry, - stop_after_attempt, - wait_exponential, - retry_if_exception_type, -) - -from lightrag.utils import ( - wrap_embedding_func_with_attrs, - locate_json_string_body_from_string, - safe_unicode_decode, - logger, -) - -from lightrag.types import GPTKeywordExtractionFormat -from functools import lru_cache import numpy as np -from typing import Union import aiohttp @@ -101,4 +84,3 @@ async def jina_embed( } data_list = await fetch_data(url, headers, data) return np.array([dp["embedding"] for dp in data_list]) - diff --git a/lightrag/llm/lmdeploy.py b/lightrag/llm/lmdeploy.py index 7accbfab..99d1c807 100644 --- a/lightrag/llm/lmdeploy.py +++ b/lightrag/llm/lmdeploy.py @@ -40,7 +40,7 @@ __version__ = "1.0.0" __author__ = "lightrag Team" __status__ = "Production" -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("lmdeploy"): @@ -63,6 +63,7 @@ from tenacity import ( from functools import lru_cache + @lru_cache(maxsize=1) def initialize_lmdeploy_pipeline( model, @@ -187,4 +188,4 @@ async def lmdeploy_model_if_cache( session_id=1, ): response += res.response - return response \ No newline at end of file + return response diff --git a/lightrag/llm/lollms.py b/lightrag/llm/lollms.py index 98c59734..00dd1da9 100644 --- a/lightrag/llm/lollms.py +++ b/lightrag/llm/lollms.py @@ -62,11 +62,13 @@ __status__ = "Production" __project_url__ = "https://github.com/ParisNeo/lollms" __doc_url__ = "https://github.com/ParisNeo/lollms/docs" import sys + if sys.version_info < (3, 9): from typing import AsyncIterator else: from collections.abc import AsyncIterator -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install + if not pm.is_installed("aiohttp"): pm.install("aiohttp") if not pm.is_installed("tenacity"): @@ -89,6 +91,7 @@ from lightrag.exceptions import ( from typing import Union, List import numpy as np + @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10), @@ -185,7 +188,6 @@ async def lollms_model_complete( ) - async def lollms_embed( texts: List[str], embed_model=None, base_url="http://localhost:9600", **kwargs ) -> np.ndarray: @@ -219,4 +221,4 @@ async def lollms_embed( result = await response.json() embeddings.append(result["vector"]) - return np.array(embeddings) \ No newline at end of file + return np.array(embeddings) diff --git a/lightrag/llm/nvidia_openai.py b/lightrag/llm/nvidia_openai.py index 3023af3f..f298039d 100644 --- a/lightrag/llm/nvidia_openai.py +++ b/lightrag/llm/nvidia_openai.py @@ -41,15 +41,14 @@ __author__ = "lightrag Team" __status__ = "Production" - import sys import os if sys.version_info < (3, 9): - from typing import AsyncIterator + pass else: - from collections.abc import AsyncIterator -import pipmaster as pm # Pipmaster for dynamic library install + pass +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("openai"): @@ -70,15 +69,12 @@ from tenacity import ( from lightrag.utils import ( wrap_embedding_func_with_attrs, - locate_json_string_body_from_string, - safe_unicode_decode, - logger, ) -from lightrag.types import GPTKeywordExtractionFormat import numpy as np + @wrap_embedding_func_with_attrs(embedding_dim=2048, max_token_size=512) @retry( stop=stop_after_attempt(3), diff --git a/lightrag/llm/ollama.py b/lightrag/llm/ollama.py index 353ddacc..19f560e7 100644 --- a/lightrag/llm/ollama.py +++ b/lightrag/llm/ollama.py @@ -41,11 +41,12 @@ __author__ = "lightrag Team" __status__ = "Production" import sys + if sys.version_info < (3, 9): from typing import AsyncIterator else: from collections.abc import AsyncIterator -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("ollama"): @@ -114,6 +115,7 @@ async def ollama_model_if_cache( else: return response["message"]["content"] + async def ollama_model_complete( prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs ) -> Union[str, AsyncIterator[str]]: @@ -129,6 +131,7 @@ async def ollama_model_complete( **kwargs, ) + async def ollama_embedding(texts: list[str], embed_model, **kwargs) -> np.ndarray: """ Deprecated in favor of `embed`. @@ -152,4 +155,4 @@ async def ollama_embed(texts: list[str], embed_model, **kwargs) -> np.ndarray: kwargs["headers"] = headers ollama_client = ollama.Client(**kwargs) data = ollama_client.embed(model=embed_model, input=texts) - return data["embeddings"] \ No newline at end of file + return data["embeddings"] diff --git a/lightrag/llm/openai.py b/lightrag/llm/openai.py index d19fc7e1..11ba69c0 100644 --- a/lightrag/llm/openai.py +++ b/lightrag/llm/openai.py @@ -41,7 +41,6 @@ __author__ = "lightrag Team" __status__ = "Production" - import sys import os @@ -49,7 +48,7 @@ if sys.version_info < (3, 9): from typing import AsyncIterator else: from collections.abc import AsyncIterator -import pipmaster as pm # Pipmaster for dynamic library install +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("openai"): @@ -78,6 +77,7 @@ from lightrag.types import GPTKeywordExtractionFormat import numpy as np from typing import Union + @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10), @@ -141,7 +141,6 @@ async def openai_complete_if_cache( return content - async def openai_complete( prompt, system_prompt=None, history_messages=[], keyword_extraction=False, **kwargs ) -> Union[str, AsyncIterator[str]]: @@ -205,7 +204,6 @@ async def nvidia_openai_complete( return result - @wrap_embedding_func_with_attrs(embedding_dim=1536, max_token_size=8192) @retry( stop=stop_after_attempt(3), diff --git a/lightrag/llm/siliconcloud.py b/lightrag/llm/siliconcloud.py index 201fc93a..4aaaf7e7 100644 --- a/lightrag/llm/siliconcloud.py +++ b/lightrag/llm/siliconcloud.py @@ -39,23 +39,18 @@ __author__ = "lightrag Team" __status__ = "Production" import sys -import copy -import os -import json if sys.version_info < (3, 9): - from typing import AsyncIterator + pass else: - from collections.abc import AsyncIterator -import pipmaster as pm # Pipmaster for dynamic library install + pass +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("lmdeploy"): pm.install("lmdeploy") from openai import ( - AsyncOpenAI, - AsyncAzureOpenAI, APIConnectionError, RateLimitError, APITimeoutError, @@ -67,19 +62,12 @@ from tenacity import ( retry_if_exception_type, ) -from lightrag.utils import ( - wrap_embedding_func_with_attrs, - locate_json_string_body_from_string, - safe_unicode_decode, - logger, -) - -from lightrag.types import GPTKeywordExtractionFormat -from functools import lru_cache import numpy as np -from typing import Union import aiohttp +import base64 +import struct + @retry( stop=stop_after_attempt(3), diff --git a/lightrag/llm/zhipu.py b/lightrag/llm/zhipu.py index 08d98108..9f5d9ca5 100644 --- a/lightrag/llm/zhipu.py +++ b/lightrag/llm/zhipu.py @@ -45,18 +45,16 @@ import re import json if sys.version_info < (3, 9): - from typing import AsyncIterator + pass else: - from collections.abc import AsyncIterator -import pipmaster as pm # Pipmaster for dynamic library install + pass +import pipmaster as pm # Pipmaster for dynamic library install # install specific modules if not pm.is_installed("zhipuai"): pm.install("zhipuai") from openai import ( - AsyncOpenAI, - AsyncAzureOpenAI, APIConnectionError, RateLimitError, APITimeoutError, @@ -70,17 +68,15 @@ from tenacity import ( from lightrag.utils import ( wrap_embedding_func_with_attrs, - locate_json_string_body_from_string, - safe_unicode_decode, logger, ) from lightrag.types import GPTKeywordExtractionFormat -from functools import lru_cache import numpy as np from typing import Union, List, Optional, Dict + @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10), @@ -247,4 +243,4 @@ async def zhipu_embedding( except Exception as e: raise Exception(f"Error calling ChatGLM Embedding API: {str(e)}") - return np.array(embeddings) \ No newline at end of file + return np.array(embeddings) diff --git a/lightrag/storage.py b/lightrag/storage.py index f28d52d0..3bee911b 100644 --- a/lightrag/storage.py +++ b/lightrag/storage.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from typing import Any, Union, cast, Dict import networkx as nx import numpy as np -import pipmaster as pm from nano_vectordb import NanoVectorDB import time diff --git a/lightrag/types.py b/lightrag/types.py index bc0c1186..8190b2ea 100644 --- a/lightrag/types.py +++ b/lightrag/types.py @@ -1,6 +1,7 @@ from pydantic import BaseModel from typing import List + class GPTKeywordExtractionFormat(BaseModel): high_level_keywords: List[str] low_level_keywords: List[str] diff --git a/lightrag/utils.py b/lightrag/utils.py index 898e66b4..3454ea7c 100644 --- a/lightrag/utils.py +++ b/lightrag/utils.py @@ -535,7 +535,8 @@ class CacheData: min_val: Optional[float] = None max_val: Optional[float] = None mode: str = "default" - cache_type: str ="query" + cache_type: str = "query" + async def save_to_cache(hashing_kv, cache_data: CacheData): if hashing_kv is None or hasattr(cache_data.content, "__aiter__"): diff --git a/requirements.txt b/requirements.txt index 84f0f63c..c372cf9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ accelerate aiofiles aiohttp -redis asyncpg configparser @@ -30,6 +29,7 @@ python-docx python-dotenv python-pptx pyvis +redis setuptools sqlalchemy tenacity @@ -39,4 +39,3 @@ tenacity tiktoken tqdm xxhash -