feat: add jinja templating for prompts

This commit is contained in:
Rajat Ahuja 2025-11-03 13:51:43 -05:00
parent d7bdcc3bc1
commit 205a9d4070
14 changed files with 469 additions and 322 deletions

View File

@ -92,6 +92,10 @@ LLM_ANTHROPIC_API_KEY=your-anthropic-api-key-here
# DERIVER_REPRESENTATION_BATCH_MAX_TOKENS=4096
# DERIVER_MAX_INPUT_TOKENS=23000
# Template paths for deriver prompts
# DERIVER_CRITICAL_ANALYSIS_TEMPLATE=deriver/critical_analysis.jinja
# DERIVER_PEER_CARD_TEMPLATE=deriver/peer_card.jinja
# =============================================================================
# Peer Card Configuration
# =============================================================================
@ -114,6 +118,10 @@ LLM_ANTHROPIC_API_KEY=your-anthropic-api-key-here
# DIALECTIC_THINKING_BUDGET_TOKENS=1024
# DIALECTIC_CONTEXT_WINDOW_SIZE=100000
# Template paths for dialectic prompts
# DIALECTIC_DIALECTIC_TEMPLATE=dialectic/dialectic.jinja
# DIALECTIC_QUERY_GENERATION_TEMPLATE=dialectic/query_generation.jinja
# =============================================================================
# Summary Settings
# =============================================================================
@ -157,3 +165,18 @@ LLM_ANTHROPIC_API_KEY=your-anthropic-api-key-here
# CACHE_NAMESPACE="honcho"
# CACHE_DEFAULT_TTL_SECONDS=300
# CACHE_DEFAULT_LOCK_TTL_SECONDS=5
# =============================================================================
# Dream Settings (Consolidation)
# =============================================================================
# DREAM_ENABLED=true
# DREAM_DOCUMENT_THRESHOLD=50
# DREAM_IDLE_TIMEOUT_MINUTES=60
# DREAM_MIN_HOURS_BETWEEN_DREAMS=8
# DREAM_ENABLED_TYPES=["consolidate"]
# DREAM_PROVIDER=openai
# DREAM_MODEL=gpt-4o-mini-2024-07-18
# DREAM_MAX_OUTPUT_TOKENS=2000
# Template path for dream consolidation prompt
# DREAM_CONSOLIDATION_TEMPLATE=dreamer/consolidation.jinja

View File

@ -71,6 +71,10 @@ WORKING_REPRESENTATION_MAX_OBSERVATIONS = 100
REPRESENTATION_BATCH_MAX_TOKENS = 4096
MAX_INPUT_TOKENS = 23000
# Template paths for deriver prompts
CRITICAL_ANALYSIS_TEMPLATE = "deriver/critical_analysis.jinja"
PEER_CARD_TEMPLATE = "deriver/peer_card.jinja"
# Peer card settings
[peer_card]
ENABLED = true
@ -91,6 +95,10 @@ SEMANTIC_SEARCH_MAX_DISTANCE = 0.85
THINKING_BUDGET_TOKENS = 1024
CONTEXT_WINDOW_SIZE = 100000
# Template paths for dialectic prompts
DIALECTIC_TEMPLATE = "dialectic/dialectic.jinja"
QUERY_GENERATION_TEMPLATE = "dialectic/query_generation.jinja"
# Summary settings
[summary]
ENABLED = true
@ -119,3 +127,17 @@ URL = "redis://localhost:6379/0"
NAMESPACE="honcho"
DEFAULT_TTL_SECONDS = 300
DEFAULT_LOCK_TTL_SECONDS = 5
# Dream settings (consolidation)
[dream]
ENABLED = true
DOCUMENT_THRESHOLD = 50
IDLE_TIMEOUT_MINUTES = 60
MIN_HOURS_BETWEEN_DREAMS = 8
ENABLED_TYPES = ["consolidate"]
PROVIDER = "openai"
MODEL = "gpt-4o-mini-2024-07-18"
MAX_OUTPUT_TOKENS = 2000
# Template path for dream consolidation prompt
CONSOLIDATION_TEMPLATE = "dreamer/consolidation.jinja"

View File

@ -35,9 +35,11 @@ dependencies = [
"json-repair>=0.49.0",
"redis>=6.0.0",
"cashews[redis]>=7.4.3",
"jinja2>=3.1.6",
]
[tool.uv]
dev-dependencies = [
[dependency-groups]
dev = [
"pytest>=8.2.2",
"sqlalchemy-utils>=0.41.2",
"pytest-asyncio>=0.23.7",

View File

@ -220,6 +220,10 @@ class DeriverSettings(HonchoSettings):
MAX_INPUT_TOKENS: Annotated[int, Field(default=23000, gt=0, le=23000)] = 23000
# Template paths for prompt templates
CRITICAL_ANALYSIS_TEMPLATE: str = "deriver/critical_analysis.jinja"
PEER_CARD_TEMPLATE: str = "deriver/peer_card.jinja"
@model_validator(mode="after")
def validate_batch_tokens_vs_context_limit(self):
if self.REPRESENTATION_BATCH_MAX_TOKENS > self.MAX_INPUT_TOKENS:
@ -263,6 +267,10 @@ class DialecticSettings(HonchoSettings):
int, Field(default=100_000, gt=10_000, le=200_000)
] = 100_000
# Template paths for prompt templates
DIALECTIC_TEMPLATE: str = "dialectic/dialectic.jinja"
QUERY_GENERATION_TEMPLATE: str = "dialectic/query_generation.jinja"
class SummarySettings(HonchoSettings):
model_config = SettingsConfigDict(env_prefix="SUMMARY_", extra="ignore") # pyright: ignore
@ -323,6 +331,9 @@ class DreamSettings(HonchoSettings):
MODEL: str = "gpt-4o-mini-2024-07-18"
MAX_OUTPUT_TOKENS: Annotated[int, Field(default=2000, gt=0, le=10_000)] = 2000
# Template path for prompt template
CONSOLIDATION_TEMPLATE: str = "dreamer/consolidation.jinja"
class AppSettings(HonchoSettings):
# No env_prefix for app-level settings

View File

@ -7,9 +7,10 @@ and reasoning tasks.
import datetime
from functools import cache
from inspect import cleandoc as c
from src.config import settings
from src.utils.representation import Representation
from src.utils.templates import render_template
from src.utils.tokens import estimate_tokens
@ -35,116 +36,17 @@ def critical_analysis_prompt(
Returns:
Formatted prompt string for critical analysis
"""
# Format the peer card as a string with newlines
peer_card_section = (
f"""
{peer_id}'s known biographical information:
<peer_card>
{chr(10).join(peer_card)}
</peer_card>
"""
if peer_card is not None
else ""
)
working_representation_section = (
f"""
Current understanding of {peer_id}:
<current_context>
{str(working_representation)}
</current_context>
"""
if not working_representation.is_empty()
else ""
)
new_turns_section = "\n".join(new_turns)
return c(
f"""
You are an agent who critically analyzes messages from {peer_id} through rigorous logical reasoning to produce only conclusions about them that are CERTAIN.
TARGET USER TO ANALYZE
You are analyzing: {peer_id}
The conversation may include messages from multiple participants, but you MUST focus ONLY on deriving conclusions about {peer_id}. Only use other participants' messages as context for understanding {peer_id}.
IMPORTANT NAMING RULES
When you write a conclusion about {peer_id}, always start the sentence with their name (e.g. "Anthony is 25 years old").
NEVER start a conclusion with generic phrases like "The user …" unless the user name is not known.
If you must reference a third person, use their explicit name, and add clarifiers such as "(third-party)" when confusion is possible.
Your goal is to IMPROVE understanding of {peer_id} through careful analysis. Your task is to arrive at truthful, factual conclusions via explicit and deductive reasoning.
Here are strict definitions for the reasoning modes you are to employ:
1. **EXPLICIT REASONING**:
- Conclusions about {peer_id} that MUST be true given premises ONLY of the following types:
- Recent messages
- Knowledge about the conversation history
- Current date and time (which is: {message_created_at})
- Timestamps from conversation history
- Follow strict literal necessity--if stated directly in message, extract a conclusion
- Latest message MUST be a premise, previous messages and timestamps may be used to contextualize
- Transforms a single message (premise) into ONE OR MULTIPLE conclusions
- Derive EVERYTHING that can be explicitly concluded
- Make sure EVERY conclusion is sufficiently contextualized, i.e. ensure each conclusion contains enough specific information about subjects and objects to make it self-contained and useful (e.g. instead of "Ann is nervous about the interview", use "Ann is nervous about the job interview at the pharmacy")
- When possible, always use absolute dates and times, and avoid relative dates and times (e.g. instead of 'Mary went to the store yesterday', use 'Mary went to the store on June 26, 2025')
2. **DEDUCTIVE REASONING**:
- Conclusions about {peer_id} that MUST be true given premises ONLY of the following types:
- Explicit conclusions
- Previous deductive conclusions
- General, open domain knowledge known to be true
- Current date and time (which is: {message_created_at})
- Timestamps for {peer_id}'s messages, and previous premises and conclusions
- Follow strict logical necessity--if premises are true, conclusion MUST be true
- Multiple premises may be used in a deduction, but only one conclusion may be drawn
- Complete ONLY as many deductions as needed to form useful and additive knowledge about {peer_id}
- May scaffold previous conclusions and known facts to do further deduction
- But MAY NOT use previous **probabilistic** deductive conclusions (including qualifiers like probably, likely, typically, may, etc) as premises in further deductions
- Use current timestamp as needed to provide absolute dates
Here are examples of the reasoning modes in action:
- **EXPLICIT REASONING EXAMPLES**
1. PREMISE(S): "I just had my 25th birthday last Saturday" (latest message), Current date is June 26, 2025 (timestamp) CONCLUSION(S): "Maria is 25 years old", "Maria's birthday is June 21st"
2. PREMISE(S): "I took my dog for a walk in a park near my house in NYC—it was such a beautiful day" (latest message) CONCLUSION(S): "Liam has a dog", "Liam took his dog for a walk", "Liam has a house in NYC", "Liam lives near a park", "Liam prefers to take advantage of nice weather to walk his dog"
3. PREMISE(S): "Whenever I think about my college experience I feel nostalgic" (latest message) CONCLUSION(S): "Aisha attended college", "Aisha feels nostalgic about her college experience"
4. PREMISE(S): "That's so cool!" (latest message), The speaker is reacting to learning the definition of Kant's categorical imperative (conversation knowledge) → CONCLUSION(S): "Carlos thinks Kant's categorical imperative is cool"
- **DEDUCTIVE REASONING EXAMPLES**
1. PREMISE(S): "Maria attended college" (explicit), All people who attended college have completed high school or equivalent (general) CONCLUSION: "Maria completed high school or equivalent education"
2. PREMISE(S): "Liam is 25 years old" (explicit), Current date is June 26, 2025 (timestamp), "Liam's birthday was last Saturday" (explicit) CONCLUSION: "Liam was born on June 21, 1998"
3. PREMISE(S): "Aisha has a dog" (explicit), "Aisha took her dog for a walk" (explicit), All dogs require regular walks for health (general) CONCLUSION: "Aisha provides care for her dog"
4. PREMISE(S): "Carlos prefers to take advantage of nice weather to walk his dog" (explicit), Message timestamp shows afternoon hours (timestamp), Nice weather is typically during daylight (general) CONCLUSION: "Carlos has flexibility in his schedule during typical work hours"
Based on our definitions and examples, here's a summary of the logical reasoning task:
**REASONING INTERACTIONS:**
- Message (required)/Conversation History (optional)/Temporal (optional) Explicit: Derive certain conclusions only from literal statements
- Explicit/Deductive/Temporal/General Deductive: When logical necessity allows certain conclusion
- Explicit/Deductive/Temporal/General Further Deductive: Can use certain conclusions and known facts to deduce additional certain conclusions
- Probabilistic Deductive Further Deductive: If a deductive conclusion includes probabilistic qualifiers (likely, potentially, typically, might, etc) it may NOT be used as a premise for further deductions
**INSTRUCTIONS:** Given the above, first think critically about what it means to do explicit and deductive reasoning, then consider how to apply that to the latest message, finally do explicit and deductive reasoning about the user to reach useful, contextually-rich conclusions.
{peer_card_section}
{working_representation_section}
Recent conversation history for context:
<history>
{history}
</history>
New conversation turns to analyze:
<new_turns>
{new_turns_section}
</new_turns>
"""
return render_template(
settings.DERIVER.CRITICAL_ANALYSIS_TEMPLATE,
{
"peer_id": peer_id,
"peer_card": peer_card,
"message_created_at": message_created_at,
"working_representation": str(working_representation),
"has_working_representation": not working_representation.is_empty(),
"history": history,
"new_turns": new_turns,
},
)
@ -163,54 +65,12 @@ def peer_card_prompt(
Returns:
Formatted prompt string for (re)generating the peer card JSON.
"""
old_peer_card_section = (
f"""
Current user biographical card:
{chr(10).join(old_peer_card)}
"""
if old_peer_card is not None
else """
User does not have a card. Create one with any key observations.
"""
)
return c(
f"""
You are an agent that creates a concise "biographical card" based on new observations for a user. A biographical card summarizes essential information like name, nicknames, location, age, occupation, interests/hobbies, and likes/dislikes.
The goal is to capture only the most important observations about the user. Value permanent properties over transient ones, and value concision over detail, preferring to omit details that are not essential to the user's identity. The card should give a broad overview of who the user is while not including details that are unlikely to be relevant in most settings.
For example, "User is from Chicago" is worth inclusion. "User has an Instagram account" is not.
"User is a software engineer" is worth inclusion. "User wrote Python today" is not.
Never infer or generalize traits from one-off behaviors. Never manipulate the text of an observation to make an action or behavior into a "permanent" trait.
When a new observation contradicts an existing one, update it, favoring new information.
Example 1:
{{
"card": [
"Name: Bob",
"Age: 24",
"Location: New York"
]
}}
Example 2:
{{
"card": [
"Name: Alice",
"Occupation: Artist",
"Interests: Painting, biking, cooking"
]
}}
{old_peer_card_section}
New observations:
{new_observations}
If there's no new key info, set "card" to null (or omit it) to signal no update. **NEVER** include notes or temporary information in the card itself, instead use the notes field. There are no mandatory fields -- if you can't find a value, just leave it out. **ONLY** include information that is **GIVEN**.
""" # nosec B608 <-- this is a really dumb false positive
return render_template(
settings.DERIVER.PEER_CARD_TEMPLATE,
{
"old_peer_card": old_peer_card,
"new_observations": new_observations,
},
)

View File

@ -1,4 +1,5 @@
from inspect import cleandoc as c
from src.config import settings
from src.utils.templates import render_template
def dialectic_prompt(
@ -18,126 +19,25 @@ def dialectic_prompt(
query: The specific question or request from the application about the user
working_representation: Conclusions from recent conversation analysis AND historical conclusions from the user's global representation
recent_conversation_history: Recent conversation history
peer_card: Known biographical information about the user
observer_peer_card: Known biographical information about the observer
observed_peer_card: Known biographical information about the target, if applicable
observer: Name of the observer peer
observed: Name of the observed peer
Returns:
Formatted prompt string for the dialectic model
"""
if observer != observed:
# this is a directional query from the observer's view of the observed
query_target = f"""The query is about user {observer}'s understanding of {observed}.
The user's known biographical information:
{chr(10).join(observer_peer_card) if observer_peer_card else "(none)"}
The target's known biographical information:
{chr(10).join(observed_peer_card) if observed_peer_card else "(none)"}
If the user's name or nickname is known, exclusively refer to them by that name.
If the target's name or nickname is known, exclusively refer to them by that name.
"""
else:
# this is a global query: honcho's omniscient view of the observed
query_target = f"""The query is about user {observed}.
The user's known biographical information:
{chr(10).join(observer_peer_card) if observer_peer_card else "(none)"}
If the user's name or nickname is known, exclusively refer to them by that name.
"""
recent_conversation_history_section = (
f"""
<recent_conversation_history>
{recent_conversation_history}
</recent_conversation_history>
"""
if recent_conversation_history
else ""
)
return c(
f"""
You are a context synthesis agent that operates as a natural language API for AI applications. Your role is to analyze application queries about users and synthesize relevant conclusions into coherent, actionable insights that directly address what the application needs to know.
## INPUT STRUCTURE
You receive three key inputs:
- **Query**: The specific question or request from the application about this user
- **Working Representation**: Current session conclusions from recent conversation analysis
- **Additional Context**: Historical conclusions from the user's global representation
Each conclusion contains:
- **Conclusion**: The derived insight
- **Premises**: Supporting evidence/reasoning
- **Type**: Either Explicit or Deductive
- **Temporal Data**: When conclusions were made
## CONCLUSION TYPE DEFINITIONS
**Explicit Conclusions** (Direct Facts)
- Direct, literal conclusions which were extracted from statements by the user in their messages
- No interpretation - only derived from what was explicitly written
**Deductive Conclusions** (Logical Certainties)
- Conclusions that MUST be true given the premises
- Built from premises that may include explicit conclusions, deductive conclusions, temporal premises, and/or general knowledge known to be true
## SYNTHESIS PROCESS
1. **Query Analysis**: Identify what specific information the application needs
2. **Conclusion Gathering**: Collect all conclusions relevant to the query
3. **Evidence Evaluation**: Assess conclusions quality based on:
- Reasoning type (explicit > deductive in certainty)
- Recency (newer = more current state)
- Premise strength (more supporting evidence = stronger)
- Qualifiers (likely, probably, typically, etc)
1. **Synthesis**: Build a coherent answer that:
- Directly addresses the query
- Provides additional useful context
- Connects related conclusions logically
- Acknowledges gaps or uncertainties
## SYNTHESIS PRINCIPLES
**Logical Chaining**:
- Connect conclusions across time to build deeper understanding
- Use general knowledge to bridge gaps between user observations
- Apply established user patterns from one domain to predict behavior in another
**Temporal Awareness**:
- Recent conclusions reflect current state
- Historical patterns show consistent traits
- Note when conclusions may be outdated
**Evidence Integration**:
- Multiple converging conclusions strengthen synthesis
- Contradictions require resolution (prioritize: recency > explicit > deductive)
- Build from certainties toward useful query answers
**Response Requirements**:
- Answer the specific question asked
- Ground responses in actual conclusions
## OUTPUT FORMAT
Provide a natural language response that:
1. Directly answers the application's query
2. Provides most useful context based on available conclusions
3. References the reasoning types and evidence strength when relevant
4. Maintains appropriate confidence levels based on conclusion types
5. Flags any limitations or gaps in available information
{query_target}
<query>{query}</query>
{recent_conversation_history_section}
<working_representation>{working_representation}</working_representation>
"""
return render_template(
settings.DIALECTIC.DIALECTIC_TEMPLATE,
{
"query": query,
"working_representation": working_representation,
"recent_conversation_history": recent_conversation_history,
"observer_peer_card": observer_peer_card,
"observed_peer_card": observed_peer_card,
"observer": observer,
"observed": observed,
},
)
@ -152,41 +52,10 @@ def query_generation_prompt(query: str, observed: str) -> str:
Returns:
Formatted prompt string for query generation
"""
return c(
f"""
You are a query expansion agent helping AI applications understand their users. The user's name is {observed}. Your job is to take application queries about this user and generate targeted search queries that will retrieve the most relevant observations using semantic search over an embedding store containing observations about the user.
## QUERY EXPANSION STRATEGY FOR SEMANTIC SIMILARITY
**Your Goal**: Generate 3-5 complementary search queries optimized for semantic similarity retrieval, that together will surface the most relevant observations to help answer the application's question.
**Semantic Similarity Optimization**:
1. **Analyze the Application Query**: What specific aspect of the user does the application want to understand?
2. **Think Conceptually**: What concepts, themes, and semantic fields relate to this question?
3. **Consider Language Patterns in Stored Observations**: Loosely match the structure of the observations we aim to retrieve - "[subject] [verb] [predicate] [additional context]" (e.g. "Mary went ice-skating with Peter and Lin on June 5th 2024", "John activities summer outdoors")
4. **Vary Semantic Scope** across the generated queries to ensure maximum coverage.
5. Ensure the queries are different enough to not be redundant.
**Vocabulary Expansion Techniques**:
- **Synonyms**: feedback/criticism/advice/suggestions/input/guidance
- **Related Actions**: receiving/getting/handling/processing/responding/reacting
- **Emotional Language**: sensitive/defensive/receptive/open/resistant/welcoming
- **Contextual Terms**: workplace/professional/personal/relationship/dynamic/interaction
- **Intensity Variations**: harsh/gentle/direct/subtle/constructive/blunt
- **Outcome Language**: improvement/growth/learning/development/change
**Remember**: Since observations come from natural conversations, use the vocabulary people actually use when discussing these topics, including casual language, emotional descriptors, and situational context.
## OUTPUT FORMAT
Respond with 3-5 search queries as a JSON object with a "queries" field containing an array of strings. Each query should target different aspects or reasoning levels to maximize retrieval coverage.
Format: `{{"queries": ["query1", "query2", "query3"]}}`
No markdown, no explanations, just the JSON object.
<query>{query}</query>
"""
return render_template(
settings.DIALECTIC.QUERY_GENERATION_TEMPLATE,
{
"query": query,
"observed": observed,
},
)

View File

@ -1,6 +1,6 @@
from inspect import cleandoc as c
from src.config import settings
from src.utils.representation import Representation
from src.utils.templates import render_template
def consolidation_prompt(
@ -17,10 +17,7 @@ def consolidation_prompt(
"""
representation_as_json = representation.model_dump_json(indent=2)
return c(
f"""
You are an agent that consolidates observations about an entity. You will be presented with a list of EXPLICIT and DEDUCTIVE observations. **Reduce** the number of observations, if possible, by combining similar observations. **ONLY** include information that is **GIVEN**. Create the highest-quality observations with the given information. Observations must always be maximally concise.
{representation_as_json}
"""
return render_template(
settings.DREAM.CONSOLIDATION_TEMPLATE,
{"representation_as_json": representation_as_json},
)

View File

@ -0,0 +1,92 @@
You are an agent who critically analyzes messages from {{ peer_id }} through rigorous logical reasoning to produce only conclusions about them that are CERTAIN.
TARGET USER TO ANALYZE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
You are analyzing: {{ peer_id }}
The conversation may include messages from multiple participants, but you MUST focus ONLY on deriving conclusions about {{ peer_id }}. Only use other participants' messages as context for understanding {{ peer_id }}.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
IMPORTANT NAMING RULES
• When you write a conclusion about {{ peer_id }}, always start the sentence with their name (e.g. "Anthony is 25 years old").
• NEVER start a conclusion with generic phrases like "The user …" unless the user name is not known.
• If you must reference a third person, use their explicit name, and add clarifiers such as "(third-party)" when confusion is possible.
Your goal is to IMPROVE understanding of {{ peer_id }} through careful analysis. Your task is to arrive at truthful, factual conclusions via explicit and deductive reasoning.
Here are strict definitions for the reasoning modes you are to employ:
1. **EXPLICIT REASONING**:
- Conclusions about {{ peer_id }} that MUST be true given premises ONLY of the following types:
- Recent messages
- Knowledge about the conversation history
- Current date and time (which is: {{ message_created_at }})
- Timestamps from conversation history
- Follow strict literal necessity--if stated directly in message, extract a conclusion
- Latest message MUST be a premise, previous messages and timestamps may be used to contextualize
- Transforms a single message (premise) into ONE OR MULTIPLE conclusions
- Derive EVERYTHING that can be explicitly concluded
- Make sure EVERY conclusion is sufficiently contextualized, i.e. ensure each conclusion contains enough specific information about subjects and objects to make it self-contained and useful (e.g. instead of "Ann is nervous about the interview", use "Ann is nervous about the job interview at the pharmacy")
- When possible, always use absolute dates and times, and avoid relative dates and times (e.g. instead of 'Mary went to the store yesterday', use 'Mary went to the store on June 26, 2025')
2. **DEDUCTIVE REASONING**:
- Conclusions about {{ peer_id }} that MUST be true given premises ONLY of the following types:
- Explicit conclusions
- Previous deductive conclusions
- General, open domain knowledge known to be true
- Current date and time (which is: {{ message_created_at }})
- Timestamps for {{ peer_id }}'s messages, and previous premises and conclusions
- Follow strict logical necessity--if premises are true, conclusion MUST be true
- Multiple premises may be used in a deduction, but only one conclusion may be drawn
- Complete ONLY as many deductions as needed to form useful and additive knowledge about {{ peer_id }}
- May scaffold previous conclusions and known facts to do further deduction
- But MAY NOT use previous **probabilistic** deductive conclusions (including qualifiers like probably, likely, typically, may, etc) as premises in further deductions
- Use current timestamp as needed to provide absolute dates
Here are examples of the reasoning modes in action:
- **EXPLICIT REASONING EXAMPLES**
1. PREMISE(S): "I just had my 25th birthday last Saturday" (latest message), Current date is June 26, 2025 (timestamp) → CONCLUSION(S): "Maria is 25 years old", "Maria's birthday is June 21st"
2. PREMISE(S): "I took my dog for a walk in a park near my house in NYC—it was such a beautiful day" (latest message) → CONCLUSION(S): "Liam has a dog", "Liam took his dog for a walk", "Liam has a house in NYC", "Liam lives near a park", "Liam prefers to take advantage of nice weather to walk his dog"
3. PREMISE(S): "Whenever I think about my college experience I feel nostalgic" (latest message) → CONCLUSION(S): "Aisha attended college", "Aisha feels nostalgic about her college experience"
4. PREMISE(S): "That's so cool!" (latest message), The speaker is reacting to learning the definition of Kant's categorical imperative (conversation knowledge) → CONCLUSION(S): "Carlos thinks Kant's categorical imperative is cool"
- **DEDUCTIVE REASONING EXAMPLES**
1. PREMISE(S): "Maria attended college" (explicit), All people who attended college have completed high school or equivalent (general) → CONCLUSION: "Maria completed high school or equivalent education"
2. PREMISE(S): "Liam is 25 years old" (explicit), Current date is June 26, 2025 (timestamp), "Liam's birthday was last Saturday" (explicit) → CONCLUSION: "Liam was born on June 21, 1998"
3. PREMISE(S): "Aisha has a dog" (explicit), "Aisha took her dog for a walk" (explicit), All dogs require regular walks for health (general) → CONCLUSION: "Aisha provides care for her dog"
4. PREMISE(S): "Carlos prefers to take advantage of nice weather to walk his dog" (explicit), Message timestamp shows afternoon hours (timestamp), Nice weather is typically during daylight (general) → CONCLUSION: "Carlos has flexibility in his schedule during typical work hours"
Based on our definitions and examples, here's a summary of the logical reasoning task:
**REASONING INTERACTIONS:**
- Message (required)/Conversation History (optional)/Temporal (optional) → Explicit: Derive certain conclusions only from literal statements
- Explicit/Deductive/Temporal/General → Deductive: When logical necessity allows certain conclusion
- Explicit/Deductive/Temporal/General → Further Deductive: Can use certain conclusions and known facts to deduce additional certain conclusions
- Probabilistic Deductive ↛ Further Deductive: If a deductive conclusion includes probabilistic qualifiers (likely, potentially, typically, might, etc) it may NOT be used as a premise for further deductions
**INSTRUCTIONS:** Given the above, first think critically about what it means to do explicit and deductive reasoning, then consider how to apply that to the latest message, finally do explicit and deductive reasoning about the user to reach useful, contextually-rich conclusions.
{% if peer_card %}
{{ peer_id }}'s known biographical information:
<peer_card>
{{ peer_card|join('\n') }}
</peer_card>
{% endif %}
{% if has_working_representation %}
Current understanding of {{ peer_id }}:
<current_context>
{{ working_representation }}
</current_context>
{% endif %}
Recent conversation history for context:
<history>
{{ history }}
</history>
New conversation turns to analyze:
<new_turns>
{{ new_turns|join('\n') }}
</new_turns>

View File

@ -0,0 +1,40 @@
You are an agent that creates a concise "biographical card" based on new observations for a user. A biographical card summarizes essential information like name, nicknames, location, age, occupation, interests/hobbies, and likes/dislikes.
The goal is to capture only the most important observations about the user. Value permanent properties over transient ones, and value concision over detail, preferring to omit details that are not essential to the user's identity. The card should give a broad overview of who the user is while not including details that are unlikely to be relevant in most settings.
For example, "User is from Chicago" is worth inclusion. "User has an Instagram account" is not.
"User is a software engineer" is worth inclusion. "User wrote Python today" is not.
Never infer or generalize traits from one-off behaviors. Never manipulate the text of an observation to make an action or behavior into a "permanent" trait.
When a new observation contradicts an existing one, update it, favoring new information.
Example 1:
{
"card": [
"Name: Bob",
"Age: 24",
"Location: New York"
]
}
Example 2:
{
"card": [
"Name: Alice",
"Occupation: Artist",
"Interests: Painting, biking, cooking"
]
}
{% if old_peer_card %}
Current user biographical card:
{{ old_peer_card|join('\n') }}
{% else %}
User does not have a card. Create one with any key observations.
{% endif %}
New observations:
{{ new_observations }}
If there's no new key info, set "card" to null (or omit it) to signal no update. **NEVER** include notes or temporary information in the card itself, instead use the notes field. There are no mandatory fields -- if you can't find a value, just leave it out. **ONLY** include information that is **GIVEN**.

View File

@ -0,0 +1,111 @@
You are a context synthesis agent that operates as a natural language API for AI applications. Your role is to analyze application queries about users and synthesize relevant conclusions into coherent, actionable insights that directly address what the application needs to know.
## INPUT STRUCTURE
You receive three key inputs:
- **Query**: The specific question or request from the application about this user
- **Working Representation**: Current session conclusions from recent conversation analysis
- **Additional Context**: Historical conclusions from the user's global representation
Each conclusion contains:
- **Conclusion**: The derived insight
- **Premises**: Supporting evidence/reasoning
- **Type**: Either Explicit or Deductive
- **Temporal Data**: When conclusions were made
## CONCLUSION TYPE DEFINITIONS
**Explicit Conclusions** (Direct Facts)
- Direct, literal conclusions which were extracted from statements by the user in their messages
- No interpretation - only derived from what was explicitly written
**Deductive Conclusions** (Logical Certainties)
- Conclusions that MUST be true given the premises
- Built from premises that may include explicit conclusions, deductive conclusions, temporal premises, and/or general knowledge known to be true
## SYNTHESIS PROCESS
1. **Query Analysis**: Identify what specific information the application needs
2. **Conclusion Gathering**: Collect all conclusions relevant to the query
3. **Evidence Evaluation**: Assess conclusions quality based on:
- Reasoning type (explicit > deductive in certainty)
- Recency (newer = more current state)
- Premise strength (more supporting evidence = stronger)
- Qualifiers (likely, probably, typically, etc)
1. **Synthesis**: Build a coherent answer that:
- Directly addresses the query
- Provides additional useful context
- Connects related conclusions logically
- Acknowledges gaps or uncertainties
## SYNTHESIS PRINCIPLES
**Logical Chaining**:
- Connect conclusions across time to build deeper understanding
- Use general knowledge to bridge gaps between user observations
- Apply established user patterns from one domain to predict behavior in another
**Temporal Awareness**:
- Recent conclusions reflect current state
- Historical patterns show consistent traits
- Note when conclusions may be outdated
**Evidence Integration**:
- Multiple converging conclusions strengthen synthesis
- Contradictions require resolution (prioritize: recency > explicit > deductive)
- Build from certainties toward useful query answers
**Response Requirements**:
- Answer the specific question asked
- Ground responses in actual conclusions
## OUTPUT FORMAT
Provide a natural language response that:
1. Directly answers the application's query
2. Provides most useful context based on available conclusions
3. References the reasoning types and evidence strength when relevant
4. Maintains appropriate confidence levels based on conclusion types
5. Flags any limitations or gaps in available information
{% if observer != observed %}
The query is about user {{ observer }}'s understanding of {{ observed }}.
The user's known biographical information:
{% if observer_peer_card %}
{{ observer_peer_card|join('\n') }}
{% else %}
(none)
{% endif %}
The target's known biographical information:
{% if observed_peer_card %}
{{ observed_peer_card|join('\n') }}
{% else %}
(none)
{% endif %}
If the user's name or nickname is known, exclusively refer to them by that name.
If the target's name or nickname is known, exclusively refer to them by that name.
{% else %}
The query is about user {{ observed }}.
The user's known biographical information:
{% if observer_peer_card %}
{{ observer_peer_card|join('\n') }}
{% else %}
(none)
{% endif %}
If the user's name or nickname is known, exclusively refer to them by that name.
{% endif %}
<query>{{ query }}</query>
{% if recent_conversation_history %}
<recent_conversation_history>
{{ recent_conversation_history }}
</recent_conversation_history>
{% endif %}
<working_representation>{{ working_representation }}</working_representation>

View File

@ -0,0 +1,34 @@
You are a query expansion agent helping AI applications understand their users. The user's name is {{ observed }}. Your job is to take application queries about this user and generate targeted search queries that will retrieve the most relevant observations using semantic search over an embedding store containing observations about the user.
## QUERY EXPANSION STRATEGY FOR SEMANTIC SIMILARITY
**Your Goal**: Generate 3-5 complementary search queries optimized for semantic similarity retrieval, that together will surface the most relevant observations to help answer the application's question.
**Semantic Similarity Optimization**:
1. **Analyze the Application Query**: What specific aspect of the user does the application want to understand?
2. **Think Conceptually**: What concepts, themes, and semantic fields relate to this question?
3. **Consider Language Patterns in Stored Observations**: Loosely match the structure of the observations we aim to retrieve - "[subject] [verb] [predicate] [additional context]" (e.g. "Mary went ice-skating with Peter and Lin on June 5th 2024", "John activities summer outdoors")
4. **Vary Semantic Scope** across the generated queries to ensure maximum coverage.
5. Ensure the queries are different enough to not be redundant.
**Vocabulary Expansion Techniques**:
- **Synonyms**: feedback/criticism/advice/suggestions/input/guidance
- **Related Actions**: receiving/getting/handling/processing/responding/reacting
- **Emotional Language**: sensitive/defensive/receptive/open/resistant/welcoming
- **Contextual Terms**: workplace/professional/personal/relationship/dynamic/interaction
- **Intensity Variations**: harsh/gentle/direct/subtle/constructive/blunt
- **Outcome Language**: improvement/growth/learning/development/change
**Remember**: Since observations come from natural conversations, use the vocabulary people actually use when discussing these topics, including casual language, emotional descriptors, and situational context.
## OUTPUT FORMAT
Respond with 3-5 search queries as a JSON object with a "queries" field containing an array of strings. Each query should target different aspects or reasoning levels to maximize retrieval coverage.
Format: `{"queries": ["query1", "query2", "query3"]}`
No markdown, no explanations, just the JSON object.
<query>{{ query }}</query>

View File

@ -0,0 +1,3 @@
You are an agent that consolidates observations about an entity. You will be presented with a list of EXPLICIT and DEDUCTIVE observations. **Reduce** the number of observations, if possible, by combining similar observations. **ONLY** include information that is **GIVEN**. Create the highest-quality observations with the given information. Observations must always be maximally concise.
{{ representation_as_json }}

81
src/utils/templates.py Normal file
View File

@ -0,0 +1,81 @@
"""
Template rendering utility for managing Jinja2 prompt templates.
This module provides a centralized template manager for rendering prompt templates
used across the application (deriver, dreamer, dialectic).
"""
from functools import lru_cache
from typing import Any
from jinja2 import Environment, PackageLoader, select_autoescape
class TemplateManager:
"""Manages Jinja2 template rendering for prompts."""
env: Environment
def __init__(self) -> None:
"""
Initialize the template manager.
Uses PackageLoader to load templates from the 'src' package's 'templates' directory.
This works both when running from source and when installed via pip.
"""
self.env = Environment(
loader=PackageLoader("src", "templates"),
autoescape=select_autoescape(enabled_extensions=[], default=False),
trim_blocks=True,
lstrip_blocks=True,
keep_trailing_newline=False,
)
# Add custom filters
def join_lines(items: Any) -> str:
"""Join items with newlines."""
return "\n".join(items) if items else ""
self.env.filters["join_lines"] = join_lines
def render(self, template_name: str, context: dict[str, Any]) -> str:
"""
Render a template with the given context.
Args:
template_name: Name of the template file (e.g., 'dreamer/consolidation.jinja')
context: Dictionary of variables to pass to the template
Returns:
Rendered template string with leading/trailing whitespace stripped
"""
template = self.env.get_template(template_name)
rendered = template.render(**context)
# Strip leading/trailing whitespace to match cleandoc behavior
return rendered.strip()
@lru_cache(maxsize=1)
def get_template_manager() -> TemplateManager:
"""
Get a cached instance of the template manager.
Returns:
Singleton TemplateManager instance
"""
return TemplateManager()
def render_template(template_name: str, context: dict[str, Any]) -> str:
"""
Convenience function to render a template using the default template manager.
Args:
template_name: Name of the template file (e.g., 'dreamer/consolidation.jinja')
context: Dictionary of variables to pass to the template
Returns:
Rendered template string
"""
manager = get_template_manager()
return manager.render(template_name, context)

View File

@ -721,6 +721,7 @@ dependencies = [
{ name = "greenlet" },
{ name = "groq" },
{ name = "httpx" },
{ name = "jinja2" },
{ name = "json-repair" },
{ name = "langfuse" },
{ name = "nanoid" },
@ -768,6 +769,7 @@ requires-dist = [
{ name = "greenlet", specifier = ">=3.0.3" },
{ name = "groq", specifier = ">=0.31.0" },
{ name = "httpx", specifier = ">=0.27.0" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "json-repair", specifier = ">=0.49.0" },
{ name = "langfuse", specifier = ">=3.3.2" },
{ name = "nanoid", specifier = ">=2.0.0" },