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 # Common retry logic
retry_count += 1 retry_count += 1
messages.append(Message(role='user', content=error_context)) 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 # If we somehow get here, raise the last error
span.set_status('error', str(last_error)) span.set_status('error', str(last_error))

View file

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

View file

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

View file

@ -67,11 +67,11 @@ 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. Given the following context, determine whether the New Edge represents any of the edges in the list of Existing Edges.
<EXISTING EDGES> <EXISTING EDGES>
{to_prompt_json(context['related_edges'], indent=2)} {to_prompt_json(context['related_edges'])}
</EXISTING EDGES> </EXISTING EDGES>
<NEW EDGE> <NEW EDGE>
{to_prompt_json(context['extracted_edges'], indent=2)} {to_prompt_json(context['extracted_edges'])}
</NEW EDGE> </NEW EDGE>
Task: Task:
@ -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: Given the following context, find all of the duplicates in a list of facts:
Facts: Facts:
{to_prompt_json(context['edges'], indent=2)} {to_prompt_json(context['edges'])}
Task: Task:
If any facts in Facts is a duplicate of another fact, return a new fact with one of their uuid's. 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', role='user',
content=f""" content=f"""
<PREVIOUS MESSAGES> <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> </PREVIOUS MESSAGES>
<CURRENT MESSAGE> <CURRENT MESSAGE>
{context['episode_content']} {context['episode_content']}
</CURRENT MESSAGE> </CURRENT MESSAGE>
<NEW ENTITY> <NEW ENTITY>
{to_prompt_json(context['extracted_node'], indent=2)} {to_prompt_json(context['extracted_node'])}
</NEW ENTITY> </NEW ENTITY>
<ENTITY TYPE DESCRIPTION> <ENTITY TYPE DESCRIPTION>
{to_prompt_json(context['entity_type_description'], indent=2)} {to_prompt_json(context['entity_type_description'])}
</ENTITY TYPE DESCRIPTION> </ENTITY TYPE DESCRIPTION>
<EXISTING ENTITIES> <EXISTING ENTITIES>
{to_prompt_json(context['existing_nodes'], indent=2)} {to_prompt_json(context['existing_nodes'])}
</EXISTING ENTITIES> </EXISTING ENTITIES>
Given the above EXISTING ENTITIES and their attributes, MESSAGE, and PREVIOUS MESSAGES; Determine if the NEW ENTITY extracted from the conversation Given the above EXISTING ENTITIES and their attributes, MESSAGE, and PREVIOUS MESSAGES; Determine if the NEW ENTITY extracted from the conversation
@ -125,7 +125,7 @@ def nodes(context: dict[str, Any]) -> list[Message]:
role='user', role='user',
content=f""" content=f"""
<PREVIOUS MESSAGES> <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> </PREVIOUS MESSAGES>
<CURRENT MESSAGE> <CURRENT MESSAGE>
{context['episode_content']} {context['episode_content']}
@ -142,11 +142,11 @@ def nodes(context: dict[str, Any]) -> list[Message]:
}} }}
<ENTITIES> <ENTITIES>
{to_prompt_json(context['extracted_nodes'], indent=2)} {to_prompt_json(context['extracted_nodes'])}
</ENTITIES> </ENTITIES>
<EXISTING ENTITIES> <EXISTING ENTITIES>
{to_prompt_json(context['existing_nodes'], indent=2)} {to_prompt_json(context['existing_nodes'])}
</EXISTING ENTITIES> </EXISTING ENTITIES>
Each entry in EXISTING ENTITIES is an object with the following structure: 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: Given the following context, deduplicate a list of nodes:
Nodes: Nodes:
{to_prompt_json(context['nodes'], indent=2)} {to_prompt_json(context['nodes'])}
Task: Task:
1. Group nodes together such that all duplicate nodes are in the same list of uuids 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> </FACT TYPES>
<PREVIOUS_MESSAGES> <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> </PREVIOUS_MESSAGES>
<CURRENT_MESSAGE> <CURRENT_MESSAGE>
@ -88,7 +88,7 @@ def edge(context: dict[str, Any]) -> list[Message]:
</CURRENT_MESSAGE> </CURRENT_MESSAGE>
<ENTITIES> <ENTITIES>
{to_prompt_json(context['nodes'], indent=2)} {to_prompt_json(context['nodes'])}
</ENTITIES> </ENTITIES>
<REFERENCE_TIME> <REFERENCE_TIME>
@ -141,7 +141,7 @@ def reflexion(context: dict[str, Any]) -> list[Message]:
user_prompt = f""" user_prompt = f"""
<PREVIOUS MESSAGES> <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> </PREVIOUS MESSAGES>
<CURRENT MESSAGE> <CURRENT MESSAGE>
{context['episode_content']} {context['episode_content']}
@ -175,7 +175,7 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
content=f""" content=f"""
<MESSAGE> <MESSAGE>
{to_prompt_json(context['episode_content'], indent=2)} {to_prompt_json(context['episode_content'])}
</MESSAGE> </MESSAGE>
<REFERENCE TIME> <REFERENCE TIME>
{context['reference_time']} {context['reference_time']}

View file

@ -93,7 +93,7 @@ def extract_message(context: dict[str, Any]) -> list[Message]:
</ENTITY TYPES> </ENTITY TYPES>
<PREVIOUS MESSAGES> <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> </PREVIOUS MESSAGES>
<CURRENT MESSAGE> <CURRENT MESSAGE>
@ -201,7 +201,7 @@ def reflexion(context: dict[str, Any]) -> list[Message]:
user_prompt = f""" user_prompt = f"""
<PREVIOUS MESSAGES> <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> </PREVIOUS MESSAGES>
<CURRENT MESSAGE> <CURRENT MESSAGE>
{context['episode_content']} {context['episode_content']}
@ -225,7 +225,7 @@ def classify_nodes(context: dict[str, Any]) -> list[Message]:
user_prompt = f""" user_prompt = f"""
<PREVIOUS MESSAGES> <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> </PREVIOUS MESSAGES>
<CURRENT MESSAGE> <CURRENT MESSAGE>
{context['episode_content']} {context['episode_content']}
@ -269,8 +269,8 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
2. Only use the provided MESSAGES and ENTITY to set attribute values. 2. Only use the provided MESSAGES and ENTITY to set attribute values.
<MESSAGES> <MESSAGES>
{to_prompt_json(context['previous_episodes'], indent=2)} {to_prompt_json(context['previous_episodes'])}
{to_prompt_json(context['episode_content'], indent=2)} {to_prompt_json(context['episode_content'])}
</MESSAGES> </MESSAGES>
<ENTITY> <ENTITY>
@ -296,8 +296,8 @@ def extract_summary(context: dict[str, Any]) -> list[Message]:
{summary_instructions} {summary_instructions}
<MESSAGES> <MESSAGES>
{to_prompt_json(context['previous_episodes'], indent=2)} {to_prompt_json(context['previous_episodes'])}
{to_prompt_json(context['episode_content'], indent=2)} {to_prompt_json(context['episode_content'])}
</MESSAGES> </MESSAGES>
<ENTITY> <ENTITY>

View file

@ -20,14 +20,14 @@ from typing import Any
DO_NOT_ESCAPE_UNICODE = '\nDo not escape unicode characters.\n' 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. Serialize data to JSON for use in prompts.
Args: Args:
data: The data to serialize data: The data to serialize
ensure_ascii: If True, escape non-ASCII characters. If False (default), preserve them. 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: Returns:
JSON string representation of the data JSON string representation of the data

View file

@ -60,7 +60,7 @@ def summarize_pair(context: dict[str, Any]) -> list[Message]:
IMPORTANT: Keep the summary concise and to the point. SUMMARIES MUST BE LESS THAN 250 CHARACTERS. IMPORTANT: Keep the summary concise and to the point. SUMMARIES MUST BE LESS THAN 250 CHARACTERS.
Summaries: Summaries:
{to_prompt_json(context['node_summaries'], indent=2)} {to_prompt_json(context['node_summaries'])}
""", """,
), ),
] ]
@ -85,8 +85,8 @@ def summarize_context(context: dict[str, Any]) -> list[Message]:
{summary_instructions} {summary_instructions}
<MESSAGES> <MESSAGES>
{to_prompt_json(context['previous_episodes'], indent=2)} {to_prompt_json(context['previous_episodes'])}
{to_prompt_json(context['episode_content'], indent=2)} {to_prompt_json(context['episode_content'])}
</MESSAGES> </MESSAGES>
<ENTITY> <ENTITY>
@ -98,7 +98,7 @@ def summarize_context(context: dict[str, Any]) -> list[Message]:
</ENTITY CONTEXT> </ENTITY CONTEXT>
<ATTRIBUTES> <ATTRIBUTES>
{to_prompt_json(context['attributes'], indent=2)} {to_prompt_json(context['attributes'])}
</ATTRIBUTES> </ATTRIBUTES>
""", """,
), ),
@ -118,7 +118,7 @@ def summary_description(context: dict[str, Any]) -> list[Message]:
Summaries must be under 250 characters. Summaries must be under 250 characters.
Summary: 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 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. between their valid_at and invalid_at dates. Facts with an invalid_at date of "Present" are considered valid.
<FACTS> <FACTS>
{to_prompt_json(fact_json, indent=12)} {to_prompt_json(fact_json)}
</FACTS> </FACTS>
<ENTITIES> <ENTITIES>
{to_prompt_json(entity_json, indent=12)} {to_prompt_json(entity_json)}
</ENTITIES> </ENTITIES>
<EPISODES> <EPISODES>
{to_prompt_json(episode_json, indent=12)} {to_prompt_json(episode_json)}
</EPISODES> </EPISODES>
<COMMUNITIES> <COMMUNITIES>
{to_prompt_json(community_json, indent=12)} {to_prompt_json(community_json)}
</COMMUNITIES> </COMMUNITIES>
""" """

2
uv.lock generated
View file

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