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:
yangdx 2025-08-26 14:41:12 +08:00
parent 025f70089a
commit 6bcfe696ee
6 changed files with 78 additions and 28 deletions

View file

@ -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

View file

@ -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(

View file

@ -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>"

View file

@ -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)

View file

@ -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

View file

@ -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:
"""