feat: add output length recommendation and description type to LLM summary
- Add SUMMARY_LENGTH_RECOMMENDED parameter (600 tokens) - Optimize prompt temple for LLM summary
This commit is contained in:
parent
025f70089a
commit
6bcfe696ee
6 changed files with 78 additions and 28 deletions
|
|
@ -128,10 +128,12 @@ ENABLE_LLM_CACHE_FOR_EXTRACT=true
|
|||
|
||||
### Number of summary semgments or tokens to trigger LLM summary on entity/relation merge (at least 3 is recommented)
|
||||
# FORCE_LLM_SUMMARY_ON_MERGE=8
|
||||
### Number of tokens to trigger LLM summary on entity/relation merge
|
||||
# SUMMARY_MAX_TOKENS = 500
|
||||
### Max description token size to trigger LLM summary
|
||||
# SUMMARY_MAX_TOKENS = 1200
|
||||
### Recommended LLM summary output length in tokens
|
||||
# SUMMARY_LENGTH_RECOMMENDED_=600
|
||||
### Maximum context size sent to LLM for description summary
|
||||
# SUMMARY_CONTEXT_SIZE=10000
|
||||
# SUMMARY_CONTEXT_SIZE=12000
|
||||
|
||||
###############################
|
||||
### Concurrency Configuration
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ from lightrag.constants import (
|
|||
DEFAULT_FORCE_LLM_SUMMARY_ON_MERGE,
|
||||
DEFAULT_MAX_ASYNC,
|
||||
DEFAULT_SUMMARY_MAX_TOKENS,
|
||||
DEFAULT_SUMMARY_LENGTH_RECOMMENDED,
|
||||
DEFAULT_SUMMARY_CONTEXT_SIZE,
|
||||
DEFAULT_SUMMARY_LANGUAGE,
|
||||
DEFAULT_EMBEDDING_FUNC_MAX_ASYNC,
|
||||
|
|
@ -133,6 +134,14 @@ def parse_args() -> argparse.Namespace:
|
|||
),
|
||||
help=f"LLM Summary Context size (default: from env or {DEFAULT_SUMMARY_CONTEXT_SIZE})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--summary-length-recommended",
|
||||
type=int,
|
||||
default=get_env_value(
|
||||
"SUMMARY_LENGTH_RECOMMENDED", DEFAULT_SUMMARY_LENGTH_RECOMMENDED, int
|
||||
),
|
||||
help=f"LLM Summary Context size (default: from env or {DEFAULT_SUMMARY_LENGTH_RECOMMENDED})",
|
||||
)
|
||||
|
||||
# Logging configuration
|
||||
parser.add_argument(
|
||||
|
|
|
|||
|
|
@ -14,9 +14,14 @@ DEFAULT_MAX_GRAPH_NODES = 1000
|
|||
DEFAULT_SUMMARY_LANGUAGE = "English" # Default language for summaries
|
||||
DEFAULT_MAX_GLEANING = 1
|
||||
|
||||
# Number of description fragments to trigger LLM summary
|
||||
DEFAULT_FORCE_LLM_SUMMARY_ON_MERGE = 8
|
||||
DEFAULT_SUMMARY_MAX_TOKENS = 500 # Max token size for entity/relation summary
|
||||
DEFAULT_SUMMARY_CONTEXT_SIZE = 10000 # Default maximum token size
|
||||
# Max description token size to trigger LLM summary
|
||||
DEFAULT_SUMMARY_MAX_TOKENS = 1200
|
||||
# Recommended LLM summary output length in tokens
|
||||
DEFAULT_SUMMARY_LENGTH_RECOMMENDED = 600
|
||||
# Maximum token size sent to LLM for summary
|
||||
DEFAULT_SUMMARY_CONTEXT_SIZE = 12000
|
||||
|
||||
# Separator for graph fields
|
||||
GRAPH_FIELD_SEP = "<SEP>"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ from lightrag.constants import (
|
|||
DEFAULT_MIN_RERANK_SCORE,
|
||||
DEFAULT_SUMMARY_MAX_TOKENS,
|
||||
DEFAULT_SUMMARY_CONTEXT_SIZE,
|
||||
DEFAULT_SUMMARY_LENGTH_RECOMMENDED,
|
||||
DEFAULT_MAX_ASYNC,
|
||||
DEFAULT_MAX_PARALLEL_INSERT,
|
||||
DEFAULT_MAX_GRAPH_NODES,
|
||||
|
|
@ -293,6 +294,13 @@ class LightRAG:
|
|||
)
|
||||
"""Maximum number of tokens allowed per LLM response."""
|
||||
|
||||
summary_length_recommended: int = field(
|
||||
default=int(
|
||||
os.getenv("SUMMARY_LENGTH_RECOMMENDED", DEFAULT_SUMMARY_LENGTH_RECOMMENDED)
|
||||
)
|
||||
)
|
||||
"""Recommended length of LLM summary output."""
|
||||
|
||||
llm_model_max_async: int = field(
|
||||
default=int(os.getenv("MAX_ASYNC", DEFAULT_MAX_ASYNC))
|
||||
)
|
||||
|
|
@ -435,9 +443,13 @@ class LightRAG:
|
|||
f"summary_context_size must be at least summary_max_tokens * force_llm_summary_on_merge, got {self.summary_context_size}"
|
||||
)
|
||||
if self.summary_context_size > self.max_total_tokens:
|
||||
logger.warning(
|
||||
logger.error(
|
||||
f"summary_context_size must be less than max_total_tokens, got {self.summary_context_size}"
|
||||
)
|
||||
if self.summary_length_recommended > self.summary_max_tokens:
|
||||
logger.warning(
|
||||
f"summary_length_recommended should less than max_total_tokens, got {self.summary_length_recommended}"
|
||||
)
|
||||
|
||||
# Fix global_config now
|
||||
global_config = asdict(self)
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ def chunking_by_token_size(
|
|||
|
||||
|
||||
async def _handle_entity_relation_summary(
|
||||
description_type: str,
|
||||
entity_or_relation_name: str,
|
||||
description_list: list[str],
|
||||
force_llm_summary_on_merge: int,
|
||||
|
|
@ -172,6 +173,7 @@ async def _handle_entity_relation_summary(
|
|||
else:
|
||||
# Final summarization of remaining descriptions - LLM will be used
|
||||
final_summary = await _summarize_descriptions(
|
||||
description_type,
|
||||
entity_or_relation_name,
|
||||
current_list,
|
||||
global_config,
|
||||
|
|
@ -213,7 +215,11 @@ async def _handle_entity_relation_summary(
|
|||
else:
|
||||
# Multiple descriptions need LLM summarization
|
||||
summary = await _summarize_descriptions(
|
||||
entity_or_relation_name, chunk, global_config, llm_response_cache
|
||||
description_type,
|
||||
entity_or_relation_name,
|
||||
chunk,
|
||||
global_config,
|
||||
llm_response_cache,
|
||||
)
|
||||
new_summaries.append(summary)
|
||||
llm_was_used = True # Mark that LLM was used in reduce phase
|
||||
|
|
@ -223,7 +229,8 @@ async def _handle_entity_relation_summary(
|
|||
|
||||
|
||||
async def _summarize_descriptions(
|
||||
entity_or_relation_name: str,
|
||||
description_type: str,
|
||||
description_name: str,
|
||||
description_list: list[str],
|
||||
global_config: dict,
|
||||
llm_response_cache: BaseKVStorage | None = None,
|
||||
|
|
@ -247,18 +254,22 @@ async def _summarize_descriptions(
|
|||
"language", PROMPTS["DEFAULT_LANGUAGE"]
|
||||
)
|
||||
|
||||
summary_length_recommended = global_config["summary_length_recommended"]
|
||||
|
||||
prompt_template = PROMPTS["summarize_entity_descriptions"]
|
||||
|
||||
# Prepare context for the prompt
|
||||
context_base = dict(
|
||||
entity_name=entity_or_relation_name,
|
||||
description_list="\n".join(description_list),
|
||||
description_type=description_type,
|
||||
description_name=description_name,
|
||||
description_list="\n\n".join(description_list),
|
||||
summary_length=summary_length_recommended,
|
||||
language=language,
|
||||
)
|
||||
use_prompt = prompt_template.format(**context_base)
|
||||
|
||||
logger.debug(
|
||||
f"Summarizing {len(description_list)} descriptions for: {entity_or_relation_name}"
|
||||
f"Summarizing {len(description_list)} descriptions for: {description_name}"
|
||||
)
|
||||
|
||||
# Use LLM function with cache (higher priority for summary generation)
|
||||
|
|
@ -563,7 +574,9 @@ async def _rebuild_knowledge_from_chunks(
|
|||
global_config=global_config,
|
||||
)
|
||||
rebuilt_relationships_count += 1
|
||||
status_message = f"Rebuilt `{src} - {tgt}` from {len(chunk_ids)} chunks"
|
||||
status_message = (
|
||||
f"Rebuilt `{src} - {tgt}` from {len(chunk_ids)} chunks"
|
||||
)
|
||||
logger.info(status_message)
|
||||
if pipeline_status is not None and pipeline_status_lock is not None:
|
||||
async with pipeline_status_lock:
|
||||
|
|
@ -571,9 +584,7 @@ async def _rebuild_knowledge_from_chunks(
|
|||
pipeline_status["history_messages"].append(status_message)
|
||||
except Exception as e:
|
||||
failed_relationships_count += 1
|
||||
status_message = (
|
||||
f"Failed to rebuild `{src} - {tgt}`: {e}"
|
||||
)
|
||||
status_message = f"Failed to rebuild `{src} - {tgt}`: {e}"
|
||||
logger.info(status_message) # Per requirement, change to info
|
||||
if pipeline_status is not None and pipeline_status_lock is not None:
|
||||
async with pipeline_status_lock:
|
||||
|
|
@ -873,6 +884,7 @@ async def _rebuild_single_entity(
|
|||
if description_list:
|
||||
force_llm_summary_on_merge = global_config["force_llm_summary_on_merge"]
|
||||
final_description, _ = await _handle_entity_relation_summary(
|
||||
"Entity",
|
||||
entity_name,
|
||||
description_list,
|
||||
force_llm_summary_on_merge,
|
||||
|
|
@ -915,6 +927,7 @@ async def _rebuild_single_entity(
|
|||
force_llm_summary_on_merge = global_config["force_llm_summary_on_merge"]
|
||||
if description_list:
|
||||
final_description, _ = await _handle_entity_relation_summary(
|
||||
"Entity",
|
||||
entity_name,
|
||||
description_list,
|
||||
force_llm_summary_on_merge,
|
||||
|
|
@ -996,6 +1009,7 @@ async def _rebuild_single_relationship(
|
|||
force_llm_summary_on_merge = global_config["force_llm_summary_on_merge"]
|
||||
if description_list:
|
||||
final_description, _ = await _handle_entity_relation_summary(
|
||||
"Relation",
|
||||
f"{src}-{tgt}",
|
||||
description_list,
|
||||
force_llm_summary_on_merge,
|
||||
|
|
@ -1100,6 +1114,7 @@ async def _merge_nodes_then_upsert(
|
|||
if num_fragment > 0:
|
||||
# Get summary and LLM usage status
|
||||
description, llm_was_used = await _handle_entity_relation_summary(
|
||||
"Entity",
|
||||
entity_name,
|
||||
description_list,
|
||||
force_llm_summary_on_merge,
|
||||
|
|
@ -1218,6 +1233,7 @@ async def _merge_edges_then_upsert(
|
|||
if num_fragment > 0:
|
||||
# Get summary and LLM usage status
|
||||
description, llm_was_used = await _handle_entity_relation_summary(
|
||||
"Relation",
|
||||
f"({src_id}, {tgt_id})",
|
||||
description_list,
|
||||
force_llm_summary_on_merge,
|
||||
|
|
@ -1595,7 +1611,7 @@ async def merge_nodes_and_edges(
|
|||
)
|
||||
# Don't raise exception to avoid affecting main flow
|
||||
|
||||
log_message = f"Completed merging: {len(processed_entities)} entities, {len(all_added_entities)} added entities, {len(processed_edges)} relations"
|
||||
log_message = f"Completed merging: {len(processed_entities)} entities, {len(all_added_entities)} extra entities, {len(processed_edges)} relations"
|
||||
logger.info(log_message)
|
||||
async with pipeline_status_lock:
|
||||
pipeline_status["latest_message"] = log_message
|
||||
|
|
|
|||
|
|
@ -133,20 +133,26 @@ Output:
|
|||
#############################""",
|
||||
]
|
||||
|
||||
PROMPTS[
|
||||
"summarize_entity_descriptions"
|
||||
] = """You are a helpful assistant responsible for generating a comprehensive summary of the data provided below.
|
||||
Given one or two entities, and a list of descriptions, all related to the same entity or group of entities.
|
||||
Please concatenate all of these into a single, comprehensive description. Make sure to include information collected from all the descriptions.
|
||||
If the provided descriptions are contradictory, please resolve the contradictions and provide a single, coherent summary.
|
||||
Make sure it is written in third person, and include the entity names so we the have full context.
|
||||
Use {language} as output language.
|
||||
PROMPTS["summarize_entity_descriptions"] = """---Role---
|
||||
You are a Knowledge Graph Specialist responsible for data curation and synthesis.
|
||||
|
||||
---Task---
|
||||
Your task is to synthesize a list of descriptions of a given entity or relation into a single, comprehensive, and cohesive summary.
|
||||
|
||||
---Instructions---
|
||||
1. **Comprehensiveness:** The summary must integrate key information from all provided descriptions. Do not omit important facts.
|
||||
2. **Consistency:** If the descriptions contain contradictions, you must resolve them to produce a logically consistent summary. If a contradiction cannot be resolved, phrase the information neutrally.
|
||||
3. **Context:** The summary must explicitly mention the name of the entity or relation for full context.
|
||||
4. **Style:** The output must be written from an objective, third-person perspective.
|
||||
5. **Conciseness:** Be concise and avoid redundancy. The summary's length must not exceed {summary_length} tokens.
|
||||
6. **Language:** The entire output must be written in {language}.
|
||||
|
||||
#######
|
||||
---Data---
|
||||
Entities: {entity_name}
|
||||
Description List: {description_list}
|
||||
#######
|
||||
{description_type} Name: {description_name}
|
||||
Description List:
|
||||
{description_list}
|
||||
|
||||
---Output---
|
||||
Output:
|
||||
"""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue