From e663fbd730fc513edb689ca90056efbdf7c2933a Mon Sep 17 00:00:00 2001 From: Rajat Ahuja Date: Mon, 10 Nov 2025 16:38:18 -0500 Subject: [PATCH] fix: align templates + flatten params --- src/deriver/deriver.py | 11 +++++++--- src/deriver/prompts.py | 29 ++++++++++++++++----------- src/deriver/reasoners/deductive.py | 7 ++++--- src/templates/deriver/deductive.jinja | 10 ++++----- src/templates/deriver/explicit.jinja | 26 ++++++++++++------------ 5 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/deriver/deriver.py b/src/deriver/deriver.py index b4e746d3..c95105eb 100644 --- a/src/deriver/deriver.py +++ b/src/deriver/deriver.py @@ -331,10 +331,15 @@ class CertaintyReasoner: latest_message.created_at, ) - # Step 2: Deductive reasoning (receives explicit observations) + # Step 2: Deductive reasoning (receives atomic propositions) + # Extract atomic propositions from explicit and implicit observations + atomic_propositions = [ + obs.content for obs in explicit_observations.explicit + ] + [obs.content for obs in explicit_observations.implicit] + deductive_response = await self.deductive_reasoner.reason( working_representation=working_representation, - explicit_observations=explicit_observations, + atomic_propositions=atomic_propositions, history=history, speaker_peer_card=speaker_peer_card, ) @@ -357,7 +362,7 @@ class CertaintyReasoner: analysis_duration_ms = (time.perf_counter() - analysis_start) * 1000 accumulate_metric( f"deriver_{latest_message.id}_{self.observer}", - "critical_analysis_duration", + "reasoning_duration", analysis_duration_ms, "ms", ) diff --git a/src/deriver/prompts.py b/src/deriver/prompts.py index ff25be10..2998c9e5 100644 --- a/src/deriver/prompts.py +++ b/src/deriver/prompts.py @@ -100,7 +100,7 @@ def estimate_base_prompt_tokens() -> int: peer_card=None, message_created_at=datetime.datetime.now(datetime.timezone.utc), working_representation=Representation(), - explicit_observations=Representation(), + atomic_propositions=[], history="", new_turns=[], ) @@ -153,7 +153,7 @@ def deductive_reasoning_prompt( peer_card: list[str] | None, message_created_at: datetime.datetime, working_representation: Representation, - explicit_observations: Representation, + atomic_propositions: list[str], history: str, new_turns: list[str], ) -> str: @@ -165,23 +165,29 @@ def deductive_reasoning_prompt( peer_card (list[str] | None): The bio card of the user being analyzed. message_created_at (datetime.datetime): Timestamp of the message. working_representation (Representation): Current user understanding context. - explicit_observations (Representation): New explicit observations from current batch - (includes both explicit and implicit propositions). + atomic_propositions (list[str]): New atomic propositions from explicit reasoning + (includes both explicit and implicit observations as content strings). history (str): Recent conversation history. new_turns (list[str]): New conversation turns to analyze. Returns: Formatted prompt string for deductive reasoning """ - # Format atomic propositions (includes both explicit and implicit from ExplicitReasoner) - # as numbered list - combine both lists - all_atomic_propositions = [ - obs.content for obs in explicit_observations.explicit - ] + [obs.content for obs in explicit_observations.implicit] + # Format atomic propositions as numbered list atomic_propositions_section = "\n".join( - [f"{i}. {prop}" for i, prop in enumerate(all_atomic_propositions, 1)] + [f"{i}. {prop}" for i, prop in enumerate(atomic_propositions, 1)] ) + # Format existing deductions from working representation + # Uses the same format as Representation.__str__() for DEDUCTIVE section + existing_deductions_section = "" + if working_representation.deductive: + deduction_strings = [ + f"{i}. {deduction}" + for i, deduction in enumerate(working_representation.deductive, 1) + ] + existing_deductions_section = "\n".join(deduction_strings) + return render_template( settings.DERIVER.DEDUCTIVE_REASONING_TEMPLATE, { @@ -190,9 +196,8 @@ def deductive_reasoning_prompt( "message_created_at": message_created_at, "working_representation": str(working_representation), "has_working_representation": not working_representation.is_empty(), - "explicit_observations": str(explicit_observations), - "has_explicit_observations": not explicit_observations.is_empty(), "atomic_propositions_section": atomic_propositions_section, + "existing_deductions_section": existing_deductions_section, "history": history, "new_turns": new_turns, }, diff --git a/src/deriver/reasoners/deductive.py b/src/deriver/reasoners/deductive.py index cbe71d93..d5632615 100644 --- a/src/deriver/reasoners/deductive.py +++ b/src/deriver/reasoners/deductive.py @@ -55,7 +55,7 @@ class DeductiveReasoner(BaseReasoner): async def reason( self, working_representation: Representation, - explicit_observations: Representation, + atomic_propositions: list[str], history: str, speaker_peer_card: list[str] | None, ) -> DeductiveResponse: @@ -63,7 +63,8 @@ class DeductiveReasoner(BaseReasoner): Args: working_representation: Current representation context - explicit_observations: New explicit observations from current batch + atomic_propositions: New atomic propositions from explicit reasoning + (both explicit and implicit observations as content strings) history: Recent conversation history speaker_peer_card: Peer card for the observed peer @@ -81,7 +82,7 @@ class DeductiveReasoner(BaseReasoner): peer_card=speaker_peer_card, message_created_at=latest_message.created_at, working_representation=working_representation, - explicit_observations=explicit_observations, + atomic_propositions=atomic_propositions, history=history, new_turns=new_turns, ) diff --git a/src/templates/deriver/deductive.jinja b/src/templates/deriver/deductive.jinja index 666f68c2..319933c7 100644 --- a/src/templates/deriver/deductive.jinja +++ b/src/templates/deriver/deductive.jinja @@ -65,7 +65,7 @@ Therefore, you must ONLY generate deductions that: * "Carlos has a daughter starting kindergarten" + "Kindergarten typically starts at age 5" → "Carlos's daughter is approximately 5 years old" (domain-specific knowledge application) * "Elena graduated with a PhD in neuroscience" + "PhDs require bachelor's degrees" → "Elena completed a bachelor's degree in a relevant field" (non-obvious educational prerequisite) -**KEY PRINCIPLE:** Only generate deductions that add substantive information that is semantically differentiated from the atomic propositions. Ask yourself: +**KEY PRINCIPLE:** Only generate deductions that add substantive information that is semantically differentiated from the atomic propositions. Ask yourself: 1. "Would the explicit/implicit extraction have already captured this?" If yes, don't generate it. 2. "Does this conclusion connect or build on multiple propositions in a non-obvious way?" If no, don't generate it. 3. "Does this add meaningful context about who {{ peer_id }} is that isn't already present?" If no, don't generate it. @@ -118,7 +118,7 @@ Common valid deductive patterns include: **EXAMPLES OF VALID DEDUCTIONS:** Example 1 - Categorical Syllogism: -- PREMISES: +- PREMISES: * "Maria attended college" (atomic proposition) * All people who attended college completed high school or equivalent (general knowledge) - CONCLUSION: "Maria completed high school or equivalent education" @@ -148,7 +148,7 @@ Example 5 - Multi-step Scaffolding: * "Elena graduated with a PhD in neuroscience" (atomic proposition) * A PhD requires completing a bachelor's degree (general knowledge) * A bachelor's degree requires completing high school (general knowledge) -- CONCLUSIONS: +- CONCLUSIONS: * "Elena completed a bachelor's degree" * "Elena completed high school or equivalent education" @@ -177,7 +177,7 @@ Each deduction must be self-contained and include sufficient context: - Disambiguating details that make the conclusion independently meaningful - All necessary qualifiers to ensure accuracy -{{ peer_card }} +{{ peer_card|join('\n') }} {{ existing_deductions_section }} @@ -228,4 +228,4 @@ Generate ALL valid deductions that can be derived from the available premises. S }, ] } -``` \ No newline at end of file +``` diff --git a/src/templates/deriver/explicit.jinja b/src/templates/deriver/explicit.jinja index 809e68be..53fcf76f 100644 --- a/src/templates/deriver/explicit.jinja +++ b/src/templates/deriver/explicit.jinja @@ -6,7 +6,7 @@ Extract atomic propositions from the peer's message. An atomic proposition is: **The Critical Balance:** Each proposition must be atomic (indivisible) yet contain enough semantic context to be interpretable without reference to other propositions. -- ❌ TOO ATOMIC (lacks context): +- ❌ TOO ATOMIC (lacks context): * "Maria is happy" → Happy about what? * "James said hi" → Said hi to whom? In what context? * "Sarah went there" → Went where? @@ -27,7 +27,7 @@ Each proposition must be atomic (indivisible) yet contain enough semantic contex 1. **EXPLICIT EXTRACTION** - Directly stated facts: - Extract propositions directly asserted in the message - Each claim becomes a separate atomic proposition - + 2. **IMPLICIT EXTRACTION** - Clearly implied facts: - Extract propositions that are obviously implied by the message - Only include implications that are certain, not speculative @@ -96,7 +96,7 @@ Example 4 - Implicit Extraction: {{ peer_id }}'s known biographical information: -{{ peer_card }} +{{ peer_card|join('\n') }} Current understanding of {{ peer_id }}: @@ -111,23 +111,23 @@ Recent conversation history for context: New conversation turns to analyze: -{{ new_turns }} +{{ new_turns|join('\n') }} Extract ALL atomic propositions (both explicit and clearly implied) from the latest peer message. Output your response in JSON structured format: ```json { - "explicit":[ - "explicit proposition 1", - "explicit proposition 2", + "explicit": [ + {"content": "explicit proposition 1"}, + {"content": "explicit proposition 2"}, ... - "explicit proposition n" + {"content": "explicit proposition n"} ], - "implicit":[ - "implicit proposition 1", - "implicit proposition 2", + "implicit": [ + {"content": "implicit proposition 1"}, + {"content": "implicit proposition 2"}, ... - "implicit proposition n" + {"content": "implicit proposition n"} ] } -``` \ No newline at end of file +```