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:
parent
24deb4d58d
commit
196eb2f077
12 changed files with 63 additions and 53 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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']}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'])}
|
||||
""",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
2
uv.lock
generated
|
|
@ -783,7 +783,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "graphiti-core"
|
||||
version = "0.22.0rc3"
|
||||
version = "0.22.0rc4"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "diskcache" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue