zrguo commited on
Commit
764ded8
·
unverified ·
2 Parent(s): 0fc6305 492af66

Merge branch 'main' into pkaushal/vectordb-chroma 

Browse files
README.md CHANGED
@@ -594,7 +594,7 @@ if __name__ == "__main__":
594
  | **llm\_model\_kwargs** | `dict` | Additional parameters for LLM generation | |
595
  | **vector\_db\_storage\_cls\_kwargs** | `dict` | Additional parameters for vector database (currently not used) | |
596
  | **enable\_llm\_cache** | `bool` | If `TRUE`, stores LLM results in cache; repeated prompts return cached responses | `TRUE` |
597
- | **addon\_params** | `dict` | Additional parameters, e.g., `{"example_number": 1, "language": "Simplified Chinese"}`: sets example limit and output language | `example_number: all examples, language: English` |
598
  | **convert\_response\_to\_json\_func** | `callable` | Not used | `convert_response_to_json` |
599
  | **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}` |
600
 
 
594
  | **llm\_model\_kwargs** | `dict` | Additional parameters for LLM generation | |
595
  | **vector\_db\_storage\_cls\_kwargs** | `dict` | Additional parameters for vector database (currently not used) | |
596
  | **enable\_llm\_cache** | `bool` | If `TRUE`, stores LLM results in cache; repeated prompts return cached responses | `TRUE` |
597
+ | **addon\_params** | `dict` | Additional parameters, e.g., `{"example_number": 1, "language": "Simplified Chinese", "entity_types": ["organization", "person", "geo", "event"]}`: sets example limit and output language | `example_number: all examples, language: English` |
598
  | **convert\_response\_to\_json\_func** | `callable` | Not used | `convert_response_to_json` |
599
  | **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}` |
600
 
lightrag/lightrag.py CHANGED
@@ -50,16 +50,17 @@ from .storage import (
50
  def lazy_external_import(module_name: str, class_name: str):
51
  """Lazily import a class from an external module based on the package of the caller."""
52
 
 
 
 
 
 
 
 
53
  def import_class(*args, **kwargs):
54
- import inspect
55
  import importlib
56
 
57
- # Get the caller's module and package
58
- caller_frame = inspect.currentframe().f_back
59
- module = inspect.getmodule(caller_frame)
60
- package = module.__package__ if module else None
61
-
62
- # Import the module using importlib with package context
63
  module = importlib.import_module(module_name, package=package)
64
 
65
  # Get the class from the module and instantiate it
 
50
  def lazy_external_import(module_name: str, class_name: str):
51
  """Lazily import a class from an external module based on the package of the caller."""
52
 
53
+ # Get the caller's module and package
54
+ import inspect
55
+
56
+ caller_frame = inspect.currentframe().f_back
57
+ module = inspect.getmodule(caller_frame)
58
+ package = module.__package__ if module else None
59
+
60
  def import_class(*args, **kwargs):
 
61
  import importlib
62
 
63
+ # Import the module using importlib
 
 
 
 
 
64
  module = importlib.import_module(module_name, package=package)
65
 
66
  # Get the class from the module and instantiate it
lightrag/llm.py CHANGED
@@ -30,6 +30,7 @@ from .utils import (
30
  wrap_embedding_func_with_attrs,
31
  locate_json_string_body_from_string,
32
  safe_unicode_decode,
 
33
  )
34
 
35
  import sys
@@ -63,12 +64,18 @@ async def openai_complete_if_cache(
63
  AsyncOpenAI() if base_url is None else AsyncOpenAI(base_url=base_url)
64
  )
65
  kwargs.pop("hashing_kv", None)
 
66
  messages = []
67
  if system_prompt:
68
  messages.append({"role": "system", "content": system_prompt})
69
  messages.extend(history_messages)
70
  messages.append({"role": "user", "content": prompt})
71
 
 
 
 
 
 
72
  if "response_format" in kwargs:
73
  response = await openai_async_client.beta.chat.completions.parse(
74
  model=model, messages=messages, **kwargs
 
30
  wrap_embedding_func_with_attrs,
31
  locate_json_string_body_from_string,
32
  safe_unicode_decode,
33
+ logger,
34
  )
35
 
36
  import sys
 
64
  AsyncOpenAI() if base_url is None else AsyncOpenAI(base_url=base_url)
65
  )
66
  kwargs.pop("hashing_kv", None)
67
+ kwargs.pop("keyword_extraction", None)
68
  messages = []
69
  if system_prompt:
70
  messages.append({"role": "system", "content": system_prompt})
71
  messages.extend(history_messages)
72
  messages.append({"role": "user", "content": prompt})
73
 
74
+ # 添加日志输出
75
+ logger.debug("===== Query Input to LLM =====")
76
+ logger.debug(f"Query: {prompt}")
77
+ logger.debug(f"System prompt: {system_prompt}")
78
+ logger.debug("Full context:")
79
  if "response_format" in kwargs:
80
  response = await openai_async_client.beta.chat.completions.parse(
81
  model=model, messages=messages, **kwargs
lightrag/operate.py CHANGED
@@ -260,6 +260,9 @@ async def extract_entities(
260
  language = global_config["addon_params"].get(
261
  "language", PROMPTS["DEFAULT_LANGUAGE"]
262
  )
 
 
 
263
  example_number = global_config["addon_params"].get("example_number", None)
264
  if example_number and example_number < len(PROMPTS["entity_extraction_examples"]):
265
  examples = "\n".join(
@@ -272,7 +275,7 @@ async def extract_entities(
272
  tuple_delimiter=PROMPTS["DEFAULT_TUPLE_DELIMITER"],
273
  record_delimiter=PROMPTS["DEFAULT_RECORD_DELIMITER"],
274
  completion_delimiter=PROMPTS["DEFAULT_COMPLETION_DELIMITER"],
275
- entity_types=",".join(PROMPTS["DEFAULT_ENTITY_TYPES"]),
276
  language=language,
277
  )
278
  # add example's format
@@ -283,7 +286,7 @@ async def extract_entities(
283
  tuple_delimiter=PROMPTS["DEFAULT_TUPLE_DELIMITER"],
284
  record_delimiter=PROMPTS["DEFAULT_RECORD_DELIMITER"],
285
  completion_delimiter=PROMPTS["DEFAULT_COMPLETION_DELIMITER"],
286
- entity_types=",".join(PROMPTS["DEFAULT_ENTITY_TYPES"]),
287
  examples=examples,
288
  language=language,
289
  )
@@ -412,15 +415,17 @@ async def extract_entities(
412
  ):
413
  all_relationships_data.append(await result)
414
 
415
- if not len(all_entities_data):
416
- logger.warning("Didn't extract any entities, maybe your LLM is not working")
417
- return None
418
- if not len(all_relationships_data):
419
  logger.warning(
420
- "Didn't extract any relationships, maybe your LLM is not working"
421
  )
422
  return None
423
 
 
 
 
 
 
424
  if entity_vdb is not None:
425
  data_for_vdb = {
426
  compute_mdhash_id(dp["entity_name"], prefix="ent-"): {
@@ -630,6 +635,13 @@ async def _build_query_context(
630
  text_chunks_db,
631
  query_param,
632
  )
 
 
 
 
 
 
 
633
  if query_param.mode == "hybrid":
634
  entities_context, relations_context, text_units_context = combine_contexts(
635
  [hl_entities_context, ll_entities_context],
@@ -865,7 +877,7 @@ async def _get_edge_data(
865
  results = await relationships_vdb.query(keywords, top_k=query_param.top_k)
866
 
867
  if not len(results):
868
- return None
869
 
870
  edge_datas = await asyncio.gather(
871
  *[knowledge_graph_inst.get_edge(r["src_id"], r["tgt_id"]) for r in results]
 
260
  language = global_config["addon_params"].get(
261
  "language", PROMPTS["DEFAULT_LANGUAGE"]
262
  )
263
+ entity_types = global_config["addon_params"].get(
264
+ "entity_types", PROMPTS["DEFAULT_ENTITY_TYPES"]
265
+ )
266
  example_number = global_config["addon_params"].get("example_number", None)
267
  if example_number and example_number < len(PROMPTS["entity_extraction_examples"]):
268
  examples = "\n".join(
 
275
  tuple_delimiter=PROMPTS["DEFAULT_TUPLE_DELIMITER"],
276
  record_delimiter=PROMPTS["DEFAULT_RECORD_DELIMITER"],
277
  completion_delimiter=PROMPTS["DEFAULT_COMPLETION_DELIMITER"],
278
+ entity_types=",".join(entity_types),
279
  language=language,
280
  )
281
  # add example's format
 
286
  tuple_delimiter=PROMPTS["DEFAULT_TUPLE_DELIMITER"],
287
  record_delimiter=PROMPTS["DEFAULT_RECORD_DELIMITER"],
288
  completion_delimiter=PROMPTS["DEFAULT_COMPLETION_DELIMITER"],
289
+ entity_types=",".join(entity_types),
290
  examples=examples,
291
  language=language,
292
  )
 
415
  ):
416
  all_relationships_data.append(await result)
417
 
418
+ if not len(all_entities_data) and not len(all_relationships_data):
 
 
 
419
  logger.warning(
420
+ "Didn't extract any entities and relationships, maybe your LLM is not working"
421
  )
422
  return None
423
 
424
+ if not len(all_entities_data):
425
+ logger.warning("Didn't extract any entities")
426
+ if not len(all_relationships_data):
427
+ logger.warning("Didn't extract any relationships")
428
+
429
  if entity_vdb is not None:
430
  data_for_vdb = {
431
  compute_mdhash_id(dp["entity_name"], prefix="ent-"): {
 
635
  text_chunks_db,
636
  query_param,
637
  )
638
+ if (
639
+ hl_entities_context == ""
640
+ and hl_relations_context == ""
641
+ and hl_text_units_context == ""
642
+ ):
643
+ logger.warn("No high level context found. Switching to local mode.")
644
+ query_param.mode = "local"
645
  if query_param.mode == "hybrid":
646
  entities_context, relations_context, text_units_context = combine_contexts(
647
  [hl_entities_context, ll_entities_context],
 
877
  results = await relationships_vdb.query(keywords, top_k=query_param.top_k)
878
 
879
  if not len(results):
880
+ return "", "", ""
881
 
882
  edge_datas = await asyncio.gather(
883
  *[knowledge_graph_inst.get_edge(r["src_id"], r["tgt_id"]) for r in results]
lightrag/prompt.py CHANGED
@@ -8,7 +8,7 @@ PROMPTS["DEFAULT_RECORD_DELIMITER"] = "##"
8
  PROMPTS["DEFAULT_COMPLETION_DELIMITER"] = "<|COMPLETE|>"
9
  PROMPTS["process_tickers"] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
10
 
11
- PROMPTS["DEFAULT_ENTITY_TYPES"] = ["organization", "person", "geo", "event"]
12
 
13
  PROMPTS["entity_extraction"] = """-Goal-
14
  Given a text document that is potentially relevant to this activity and a list of entity types, identify all entities of those types from the text and all relationships among the identified entities.
@@ -268,14 +268,19 @@ PROMPTS[
268
  Question 1: {original_prompt}
269
  Question 2: {cached_prompt}
270
 
271
- Please evaluate:
272
  1. Whether these two questions are semantically similar
273
  2. Whether the answer to Question 2 can be used to answer Question 1
274
-
275
- Please provide a similarity score between 0 and 1, where:
276
- 0: Completely unrelated or answer cannot be reused
 
 
 
 
 
 
277
  1: Identical and answer can be directly reused
278
  0.5: Partially related and answer needs modification to be used
279
-
280
  Return only a number between 0-1, without any additional content.
281
  """
 
8
  PROMPTS["DEFAULT_COMPLETION_DELIMITER"] = "<|COMPLETE|>"
9
  PROMPTS["process_tickers"] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
10
 
11
+ PROMPTS["DEFAULT_ENTITY_TYPES"] = ["organization", "person", "geo", "event", "category"]
12
 
13
  PROMPTS["entity_extraction"] = """-Goal-
14
  Given a text document that is potentially relevant to this activity and a list of entity types, identify all entities of those types from the text and all relationships among the identified entities.
 
268
  Question 1: {original_prompt}
269
  Question 2: {cached_prompt}
270
 
271
+ Please evaluate the following two points and provide a similarity score between 0 and 1 directly:
272
  1. Whether these two questions are semantically similar
273
  2. Whether the answer to Question 2 can be used to answer Question 1
274
+ Similarity score criteria:
275
+ 0: Completely unrelated or answer cannot be reused, including but not limited to:
276
+ - The questions have different topics
277
+ - The locations mentioned in the questions are different
278
+ - The times mentioned in the questions are different
279
+ - The specific individuals mentioned in the questions are different
280
+ - The specific events mentioned in the questions are different
281
+ - The background information in the questions is different
282
+ - The key conditions in the questions are different
283
  1: Identical and answer can be directly reused
284
  0.5: Partially related and answer needs modification to be used
 
285
  Return only a number between 0-1, without any additional content.
286
  """
lightrag/storage.py CHANGED
@@ -107,10 +107,16 @@ class NanoVectorDBStorage(BaseVectorStorage):
107
  embeddings = await f
108
  embeddings_list.append(embeddings)
109
  embeddings = np.concatenate(embeddings_list)
110
- for i, d in enumerate(list_data):
111
- d["__vector__"] = embeddings[i]
112
- results = self._client.upsert(datas=list_data)
113
- return results
 
 
 
 
 
 
114
 
115
  async def query(self, query: str, top_k=5):
116
  embedding = await self.embedding_func([query])
 
107
  embeddings = await f
108
  embeddings_list.append(embeddings)
109
  embeddings = np.concatenate(embeddings_list)
110
+ if len(embeddings) == len(list_data):
111
+ for i, d in enumerate(list_data):
112
+ d["__vector__"] = embeddings[i]
113
+ results = self._client.upsert(datas=list_data)
114
+ return results
115
+ else:
116
+ # sometimes the embedding is not returned correctly. just log it.
117
+ logger.error(
118
+ f"embedding is not 1-1 with data, {len(embeddings)} != {len(list_data)}"
119
+ )
120
 
121
  async def query(self, query: str, top_k=5):
122
  embedding = await self.embedding_func([query])
lightrag/utils.py CHANGED
@@ -17,6 +17,17 @@ import tiktoken
17
 
18
  from lightrag.prompt import PROMPTS
19
 
 
 
 
 
 
 
 
 
 
 
 
20
  ENCODER = None
21
 
22
  logger = logging.getLogger("lightrag")
@@ -42,9 +53,17 @@ class EmbeddingFunc:
42
  embedding_dim: int
43
  max_token_size: int
44
  func: callable
 
 
 
 
 
 
 
45
 
46
  async def __call__(self, *args, **kwargs) -> np.ndarray:
47
- return await self.func(*args, **kwargs)
 
48
 
49
 
50
  def locate_json_string_body_from_string(content: str) -> Union[str, None]:
 
17
 
18
  from lightrag.prompt import PROMPTS
19
 
20
+
21
+ class UnlimitedSemaphore:
22
+ """A context manager that allows unlimited access."""
23
+
24
+ async def __aenter__(self):
25
+ pass
26
+
27
+ async def __aexit__(self, exc_type, exc, tb):
28
+ pass
29
+
30
+
31
  ENCODER = None
32
 
33
  logger = logging.getLogger("lightrag")
 
53
  embedding_dim: int
54
  max_token_size: int
55
  func: callable
56
+ concurrent_limit: int = 16
57
+
58
+ def __post_init__(self):
59
+ if self.concurrent_limit != 0:
60
+ self._semaphore = asyncio.Semaphore(self.concurrent_limit)
61
+ else:
62
+ self._semaphore = UnlimitedSemaphore()
63
 
64
  async def __call__(self, *args, **kwargs) -> np.ndarray:
65
+ async with self._semaphore:
66
+ return await self.func(*args, **kwargs)
67
 
68
 
69
  def locate_json_string_body_from_string(content: str) -> Union[str, None]: