Remove JSON indentation from prompts to reduce token usage (#985)

Changes to `to_prompt_json()` helper to default to minified JSON (no indentation) instead of 2-space indentation. This reduces token consumption in LLM prompts while maintaining all necessary information.

- Changed default `indent` parameter from `2` to `None` in `prompt_helpers.py`
- Updated all prompt modules to remove explicit `indent=2` arguments
- Minor code formatting fixes in LLM clients

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Daniel Chalef 2025-10-06 16:08:43 -07:00 committed by GitHub
parent 24deb4d58d
commit 196eb2f077
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 63 additions and 53 deletions

View file

@ -349,7 +349,9 @@ class AnthropicClient(LLMClient):
# Common retry logic
retry_count += 1
messages.append(Message(role='user', content=error_context))
logger.warning(f'Retrying after error (attempt {retry_count}/{max_retries}): {e}')
logger.warning(
f'Retrying after error (attempt {retry_count}/{max_retries}): {e}'
)
# If we somehow get here, raise the last error
span.set_status('error', str(last_error))

View file

@ -209,7 +209,11 @@ class BaseOpenAIClient(LLMClient):
# These errors should not trigger retries
span.set_status('error', str(last_error))
raise
except (openai.APITimeoutError, openai.APIConnectionError, openai.InternalServerError):
except (
openai.APITimeoutError,
openai.APIConnectionError,
openai.InternalServerError,
):
# Let OpenAI's client handle these retries
span.set_status('error', str(last_error))
raise

View file

@ -161,7 +161,11 @@ class OpenAIGenericClient(LLMClient):
# These errors should not trigger retries
span.set_status('error', str(last_error))
raise
except (openai.APITimeoutError, openai.APIConnectionError, openai.InternalServerError):
except (
openai.APITimeoutError,
openai.APIConnectionError,
openai.InternalServerError,
):
# Let OpenAI's client handle these retries
span.set_status('error', str(last_error))
raise

View file

@ -67,13 +67,13 @@ def edge(context: dict[str, Any]) -> list[Message]:
Given the following context, determine whether the New Edge represents any of the edges in the list of Existing Edges.
<EXISTING EDGES>
{to_prompt_json(context['related_edges'], indent=2)}
{to_prompt_json(context['related_edges'])}
</EXISTING EDGES>
<NEW EDGE>
{to_prompt_json(context['extracted_edges'], indent=2)}
{to_prompt_json(context['extracted_edges'])}
</NEW EDGE>
Task:
If the New Edges represents the same factual information as any edge in Existing Edges, return the id of the duplicate fact
as part of the list of duplicate_facts.
@ -98,7 +98,7 @@ def edge_list(context: dict[str, Any]) -> list[Message]:
Given the following context, find all of the duplicates in a list of facts:
Facts:
{to_prompt_json(context['edges'], indent=2)}
{to_prompt_json(context['edges'])}
Task:
If any facts in Facts is a duplicate of another fact, return a new fact with one of their uuid's.

View file

@ -64,20 +64,20 @@ def node(context: dict[str, Any]) -> list[Message]:
role='user',
content=f"""
<PREVIOUS MESSAGES>
{to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
{to_prompt_json([ep for ep in context['previous_episodes']])}
</PREVIOUS MESSAGES>
<CURRENT MESSAGE>
{context['episode_content']}
</CURRENT MESSAGE>
<NEW ENTITY>
{to_prompt_json(context['extracted_node'], indent=2)}
{to_prompt_json(context['extracted_node'])}
</NEW ENTITY>
<ENTITY TYPE DESCRIPTION>
{to_prompt_json(context['entity_type_description'], indent=2)}
{to_prompt_json(context['entity_type_description'])}
</ENTITY TYPE DESCRIPTION>
<EXISTING ENTITIES>
{to_prompt_json(context['existing_nodes'], indent=2)}
{to_prompt_json(context['existing_nodes'])}
</EXISTING ENTITIES>
Given the above EXISTING ENTITIES and their attributes, MESSAGE, and PREVIOUS MESSAGES; Determine if the NEW ENTITY extracted from the conversation
@ -125,13 +125,13 @@ def nodes(context: dict[str, Any]) -> list[Message]:
role='user',
content=f"""
<PREVIOUS MESSAGES>
{to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
{to_prompt_json([ep for ep in context['previous_episodes']])}
</PREVIOUS MESSAGES>
<CURRENT MESSAGE>
{context['episode_content']}
</CURRENT MESSAGE>
Each of the following ENTITIES were extracted from the CURRENT MESSAGE.
Each entity in ENTITIES is represented as a JSON object with the following structure:
{{
@ -142,11 +142,11 @@ def nodes(context: dict[str, Any]) -> list[Message]:
}}
<ENTITIES>
{to_prompt_json(context['extracted_nodes'], indent=2)}
{to_prompt_json(context['extracted_nodes'])}
</ENTITIES>
<EXISTING ENTITIES>
{to_prompt_json(context['existing_nodes'], indent=2)}
{to_prompt_json(context['existing_nodes'])}
</EXISTING ENTITIES>
Each entry in EXISTING ENTITIES is an object with the following structure:
@ -197,7 +197,7 @@ def node_list(context: dict[str, Any]) -> list[Message]:
Given the following context, deduplicate a list of nodes:
Nodes:
{to_prompt_json(context['nodes'], indent=2)}
{to_prompt_json(context['nodes'])}
Task:
1. Group nodes together such that all duplicate nodes are in the same list of uuids

View file

@ -80,7 +80,7 @@ def edge(context: dict[str, Any]) -> list[Message]:
</FACT TYPES>
<PREVIOUS_MESSAGES>
{to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
{to_prompt_json([ep for ep in context['previous_episodes']])}
</PREVIOUS_MESSAGES>
<CURRENT_MESSAGE>
@ -88,7 +88,7 @@ def edge(context: dict[str, Any]) -> list[Message]:
</CURRENT_MESSAGE>
<ENTITIES>
{to_prompt_json(context['nodes'], indent=2)}
{to_prompt_json(context['nodes'])}
</ENTITIES>
<REFERENCE_TIME>
@ -141,7 +141,7 @@ def reflexion(context: dict[str, Any]) -> list[Message]:
user_prompt = f"""
<PREVIOUS MESSAGES>
{to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
{to_prompt_json([ep for ep in context['previous_episodes']])}
</PREVIOUS MESSAGES>
<CURRENT MESSAGE>
{context['episode_content']}
@ -175,7 +175,7 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
content=f"""
<MESSAGE>
{to_prompt_json(context['episode_content'], indent=2)}
{to_prompt_json(context['episode_content'])}
</MESSAGE>
<REFERENCE TIME>
{context['reference_time']}

View file

@ -93,7 +93,7 @@ def extract_message(context: dict[str, Any]) -> list[Message]:
</ENTITY TYPES>
<PREVIOUS MESSAGES>
{to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
{to_prompt_json([ep for ep in context['previous_episodes']])}
</PREVIOUS MESSAGES>
<CURRENT MESSAGE>
@ -201,7 +201,7 @@ def reflexion(context: dict[str, Any]) -> list[Message]:
user_prompt = f"""
<PREVIOUS MESSAGES>
{to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
{to_prompt_json([ep for ep in context['previous_episodes']])}
</PREVIOUS MESSAGES>
<CURRENT MESSAGE>
{context['episode_content']}
@ -225,22 +225,22 @@ def classify_nodes(context: dict[str, Any]) -> list[Message]:
user_prompt = f"""
<PREVIOUS MESSAGES>
{to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
{to_prompt_json([ep for ep in context['previous_episodes']])}
</PREVIOUS MESSAGES>
<CURRENT MESSAGE>
{context['episode_content']}
</CURRENT MESSAGE>
<EXTRACTED ENTITIES>
{context['extracted_entities']}
</EXTRACTED ENTITIES>
<ENTITY TYPES>
{context['entity_types']}
</ENTITY TYPES>
Given the above conversation, extracted entities, and provided entity types and their descriptions, classify the extracted entities.
Guidelines:
1. Each entity must have exactly one type
2. Only use the provided ENTITY TYPES as types, do not use additional types to classify entities.
@ -269,10 +269,10 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
2. Only use the provided MESSAGES and ENTITY to set attribute values.
<MESSAGES>
{to_prompt_json(context['previous_episodes'], indent=2)}
{to_prompt_json(context['episode_content'], indent=2)}
{to_prompt_json(context['previous_episodes'])}
{to_prompt_json(context['episode_content'])}
</MESSAGES>
<ENTITY>
{context['node']}
</ENTITY>
@ -292,12 +292,12 @@ def extract_summary(context: dict[str, Any]) -> list[Message]:
content=f"""
Given the MESSAGES and the ENTITY, update the summary that combines relevant information about the entity
from the messages and relevant information from the existing summary.
{summary_instructions}
<MESSAGES>
{to_prompt_json(context['previous_episodes'], indent=2)}
{to_prompt_json(context['episode_content'], indent=2)}
{to_prompt_json(context['previous_episodes'])}
{to_prompt_json(context['episode_content'])}
</MESSAGES>
<ENTITY>

View file

@ -20,14 +20,14 @@ from typing import Any
DO_NOT_ESCAPE_UNICODE = '\nDo not escape unicode characters.\n'
def to_prompt_json(data: Any, ensure_ascii: bool = False, indent: int = 2) -> str:
def to_prompt_json(data: Any, ensure_ascii: bool = False, indent: int | None = None) -> str:
"""
Serialize data to JSON for use in prompts.
Args:
data: The data to serialize
ensure_ascii: If True, escape non-ASCII characters. If False (default), preserve them.
indent: Number of spaces for indentation
indent: Number of spaces for indentation. Defaults to None (minified).
Returns:
JSON string representation of the data

View file

@ -56,11 +56,11 @@ def summarize_pair(context: dict[str, Any]) -> list[Message]:
role='user',
content=f"""
Synthesize the information from the following two summaries into a single succinct summary.
IMPORTANT: Keep the summary concise and to the point. SUMMARIES MUST BE LESS THAN 250 CHARACTERS.
Summaries:
{to_prompt_json(context['node_summaries'], indent=2)}
{to_prompt_json(context['node_summaries'])}
""",
),
]
@ -77,28 +77,28 @@ def summarize_context(context: dict[str, Any]) -> list[Message]:
content=f"""
Given the MESSAGES and the ENTITY name, create a summary for the ENTITY. Your summary must only use
information from the provided MESSAGES. Your summary should also only contain information relevant to the
provided ENTITY.
provided ENTITY.
In addition, extract any values for the provided entity properties based on their descriptions.
If the value of the entity property cannot be found in the current context, set the value of the property to the Python value None.
{summary_instructions}
<MESSAGES>
{to_prompt_json(context['previous_episodes'], indent=2)}
{to_prompt_json(context['episode_content'], indent=2)}
{to_prompt_json(context['previous_episodes'])}
{to_prompt_json(context['episode_content'])}
</MESSAGES>
<ENTITY>
{context['node_name']}
</ENTITY>
<ENTITY CONTEXT>
{context['node_summary']}
</ENTITY CONTEXT>
<ATTRIBUTES>
{to_prompt_json(context['attributes'], indent=2)}
{to_prompt_json(context['attributes'])}
</ATTRIBUTES>
""",
),
@ -118,7 +118,7 @@ def summary_description(context: dict[str, Any]) -> list[Message]:
Summaries must be under 250 characters.
Summary:
{to_prompt_json(context['summary'], indent=2)}
{to_prompt_json(context['summary'])}
""",
),
]

View file

@ -56,16 +56,16 @@ def search_results_to_context_string(search_results: SearchResults) -> str:
These are the most relevant facts and their valid and invalid dates. Facts are considered valid
between their valid_at and invalid_at dates. Facts with an invalid_at date of "Present" are considered valid.
<FACTS>
{to_prompt_json(fact_json, indent=12)}
{to_prompt_json(fact_json)}
</FACTS>
<ENTITIES>
{to_prompt_json(entity_json, indent=12)}
{to_prompt_json(entity_json)}
</ENTITIES>
<EPISODES>
{to_prompt_json(episode_json, indent=12)}
{to_prompt_json(episode_json)}
</EPISODES>
<COMMUNITIES>
{to_prompt_json(community_json, indent=12)}
{to_prompt_json(community_json)}
</COMMUNITIES>
"""

2
uv.lock generated
View file

@ -783,7 +783,7 @@ wheels = [
[[package]]
name = "graphiti-core"
version = "0.22.0rc3"
version = "0.22.0rc4"
source = { editable = "." }
dependencies = [
{ name = "diskcache" },