""" Test suite for fact extraction quality verification. This comprehensive test suite validates that the fact extraction system: 1. Preserves all information dimensions (emotional, sensory, cognitive, etc.) 2. Correctly converts relative dates to absolute dates 3. Properly classifies facts as agent vs world 4. Makes logical inferences to connect related information 5. Correctly attributes statements to speakers 6. Filters out irrelevant content (podcast intros/outros) These are quality/accuracy tests that verify the LLM-based extraction produces semantically correct and complete facts. """ from datetime import UTC, datetime import pytest from hindsight_api import LLMConfig from hindsight_api.engine.retain.fact_extraction import extract_facts_from_text # ============================================================================= # DIMENSION PRESERVATION TESTS # ============================================================================= class TestDimensionPreservation: """Tests that fact extraction preserves all information dimensions.""" @pytest.mark.asyncio async def test_emotional_dimension_preservation(self): """ Test that emotional states and feelings are preserved, not stripped away. Example: "I was thrilled to receive positive feedback" Should NOT become: "I received positive feedback" """ text = """ I was absolutely thrilled when I received such positive feedback on my presentation! Sarah seemed disappointed when she heard the news about the delay. Marcus felt anxious about the upcoming interview. """ context = "Personal journal entry" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) emotional_indicators = ["thrilled", "disappointed", "anxious", "positive feedback"] found_emotions = [word for word in emotional_indicators if word in all_facts_text] assert len(found_emotions) >= 2, ( f"Should preserve emotional dimension. " f"Found: {found_emotions}, Expected at least 2 from: {emotional_indicators}" ) @pytest.mark.asyncio async def test_sensory_dimension_preservation(self): """Test that sensory details (visual, auditory, etc.) are preserved.""" text = """ The coffee tasted bitter and burnt. She showed me her bright orange hair, which looked stunning under the lights. The music was so loud I could barely hear myself think. """ context = "Personal experience" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) sensory_indicators = ["bitter", "burnt", "bright orange", "loud", "stunning"] found_sensory = [word for word in sensory_indicators if word in all_facts_text] assert len(found_sensory) >= 2, ( f"Should preserve sensory details. " f"Found: {found_sensory}, Expected at least 2 from: {sensory_indicators}" ) @pytest.mark.asyncio async def test_cognitive_epistemic_dimension(self): """Test that cognitive states and certainty levels are preserved.""" text = """ I realized that the approach wasn't working. She wasn't sure if the meeting would happen. He's convinced that AI will transform healthcare. Maybe we should reconsider the timeline. """ context = "Team discussion" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) cognitive_indicators = ["realized", "wasn't sure", "convinced", "maybe", "reconsider"] found_cognitive = [word for word in cognitive_indicators if word in all_facts_text] assert len(found_cognitive) >= 2, ( f"Should preserve cognitive/epistemic dimension. " f"Found: {found_cognitive}" ) @pytest.mark.asyncio async def test_capability_skill_dimension(self): """Test that capabilities, skills, and limitations are preserved.""" text = """ I can speak French fluently. Sarah struggles with public speaking. He's an expert in machine learning. I'm unable to attend the conference due to scheduling conflicts. """ context = "Personal profile discussion" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) capability_indicators = ["can speak", "fluently", "struggles with", "expert in", "unable to"] found_capability = [word for word in capability_indicators if word in all_facts_text] assert len(found_capability) >= 2, ( f"Should preserve capability/skill dimension. " f"Found: {found_capability}" ) @pytest.mark.asyncio async def test_comparative_dimension(self): """Test that comparisons and contrasts are preserved.""" text = """ This approach is much better than the previous one. The new design is worse than expected. Unlike last year, we're ahead of schedule. """ context = "Project review" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) comparative_indicators = ["better than", "worse than", "unlike", "ahead of"] found_comparative = [word for word in comparative_indicators if word in all_facts_text] assert len(found_comparative) >= 1, ( f"Should preserve comparative dimension. " f"Found: {found_comparative}" ) @pytest.mark.asyncio async def test_attitudinal_reactive_dimension(self): """Test that attitudes and reactions are preserved.""" text = """ She's very skeptical about the new technology. I was surprised when he announced his resignation. Marcus rolled his eyes when the topic came up. She's enthusiastic about the opportunity. """ context = "Team meeting" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) attitudinal_indicators = ["skeptical", "surprised", "rolled his eyes", "enthusiastic"] found_attitudinal = [word for word in attitudinal_indicators if word in all_facts_text] assert len(found_attitudinal) >= 1, ( f"Should preserve attitudinal/reactive dimension. " f"Found: {found_attitudinal}" ) @pytest.mark.asyncio async def test_intentional_motivational_dimension(self): """Test that goals, plans, and motivations are preserved.""" text = """ I want to learn Mandarin before my trip to China. She aims to complete her PhD within three years. His goal is to build a sustainable business. I'm planning to switch careers because I'm not fulfilled in my current role. """ context = "Personal goals discussion" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) # Check for goal/intention related content intentional_indicators = [ "want", "aim", "goal", "plan", "because", "learn", "complete", "build", "switch", "career", "mandarin", "china", "phd", "business" ] found_intentional = [word for word in intentional_indicators if word in all_facts_text] assert len(found_intentional) >= 1, ( f"Should preserve intentional/motivational content. " f"Found: {found_intentional}" ) @pytest.mark.asyncio async def test_evaluative_preferential_dimension(self): """Test that preferences and values are preserved.""" text = """ I prefer working remotely to being in an office. She values honesty above all else. He hates being late to meetings. Family is the most important thing to her. """ context = "Personal values discussion" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) evaluative_indicators = ["prefer", "values", "hates", "important", "above all"] found_evaluative = [word for word in evaluative_indicators if word in all_facts_text] assert len(found_evaluative) >= 2, ( f"Should preserve evaluative/preferential dimension. " f"Found: {found_evaluative}" ) @pytest.mark.asyncio async def test_comprehensive_multi_dimension(self): """Test a realistic scenario with multiple dimensions in one fact.""" text = """ I was thrilled to receive such positive feedback on my presentation yesterday! I wasn't sure if my approach would resonate, but the audience seemed enthusiastic. I prefer presenting in person rather than virtually because I can read the room better. """ context = "Personal reflection" llm_config = LLMConfig.for_memory() event_date = datetime(2024, 11, 13) facts, _ = await extract_facts_from_text( text=text, event_date=event_date, context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) # Check emotional - should capture positive/thrilled sentiment has_emotional = any(term in all_facts_text for term in [ "thrilled", "positive feedback", "positive", "feedback", "enthusiastic" ]) assert has_emotional, "Should preserve emotional dimension" # Check no vague temporal terms prohibited_terms = ["recently", "soon", "lately"] found_prohibited = [term for term in prohibited_terms if term in all_facts_text] assert len(found_prohibited) == 0, \ f"Should NOT use vague temporal terms. Found: {found_prohibited}" # Check preference - should capture the in-person vs virtual preference has_preference = any(term in all_facts_text for term in [ "prefer", "rather than", "in person", "virtually", "read the room" ]) assert has_preference, "Should preserve preferential dimension" # ============================================================================= # TEMPORAL CONVERSION TESTS # ============================================================================= class TestTemporalConversion: """Tests for temporal extraction and date conversion.""" @pytest.mark.asyncio async def test_temporal_absolute_conversion(self): """ Test that relative temporal expressions are converted to absolute dates. Critical: "yesterday" should become "on November 12, 2024", NOT "recently" """ text = """ Yesterday I went for a morning jog for the first time in a nearby park. Last week I started a new project. I'm planning to visit Tokyo next month. """ context = "Personal conversation" llm_config = LLMConfig.for_memory() event_date = datetime(2024, 11, 13) facts, _ = await extract_facts_from_text( text=text, event_date=event_date, context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) # Should NOT contain vague temporal terms prohibited_terms = ["recently", "soon", "lately", "a while ago", "some time ago"] found_prohibited = [term for term in prohibited_terms if term in all_facts_text] assert len(found_prohibited) == 0, ( f"Should NOT use vague temporal terms. Found: {found_prohibited}" ) # Should contain specific date references temporal_indicators = ["november", "12", "early november", "week of", "december"] found_temporal = [term for term in temporal_indicators if term in all_facts_text] assert len(found_temporal) >= 1, ( f"Should convert relative dates to absolute. " f"Found: {found_temporal}, Expected month/date references" ) @pytest.mark.asyncio async def test_date_field_calculation_last_night(self): """ Test that the date field is calculated correctly for "last night" events. Ideally: If conversation is on August 14, 2023 and text says "last night", the date field should be August 13. We accept 13 or 14 as LLM may vary. """ text = """ Melanie: Hey Caroline! Last night was amazing! We celebrated my daughter's birthday with a concert surrounded by music, joy and the warm summer breeze. """ context = "Conversation between Melanie and Caroline" llm_config = LLMConfig.for_memory() event_date = datetime(2023, 8, 14, 14, 24) facts, _ = await extract_facts_from_text( text=text, event_date=event_date, context=context, llm_config=llm_config, agent_name="Melanie" ) assert len(facts) > 0, "Should extract at least one fact" birthday_fact = None for fact in facts: if "birthday" in fact.fact.lower() or "concert" in fact.fact.lower(): birthday_fact = fact break assert birthday_fact is not None, "Should extract fact about birthday celebration" fact_date_str = birthday_fact.occurred_start assert fact_date_str is not None, "occurred_start should not be None for temporal events" if 'T' in fact_date_str: fact_date = datetime.fromisoformat(fact_date_str.replace('Z', '+00:00')) else: fact_date = datetime.fromisoformat(fact_date_str) assert fact_date.year == 2023, "Year should be 2023" assert fact_date.month == 8, "Month should be August" # Accept day 13 (ideal: last night) or 14 (conversation date) as valid assert fact_date.day in (13, 14), ( f"Day should be 13 or 14 (around Aug 14 event), but got {fact_date.day}." ) @pytest.mark.asyncio async def test_date_field_calculation_yesterday(self): """Test that the date field is calculated correctly for "yesterday" events.""" text = """ Yesterday I went for a morning jog for the first time in a nearby park. """ context = "Personal diary" llm_config = LLMConfig.for_memory() event_date = datetime(2024, 11, 13) facts, _ = await extract_facts_from_text( text=text, event_date=event_date, context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" jogging_fact = facts[0] fact_date_str = jogging_fact.occurred_start if 'T' in fact_date_str: fact_date = datetime.fromisoformat(fact_date_str.replace('Z', '+00:00')) else: fact_date = datetime.fromisoformat(fact_date_str) assert fact_date.year == 2024, "Year should be 2024" assert fact_date.month == 11, "Month should be November" # Accept day 12 (ideal: yesterday) or 13 (conversation date) as valid assert fact_date.day in (12, 13), ( f"Day should be 12 or 13 (around Nov 13 event), but got {fact_date.day}." ) all_facts_text = " ".join([f.fact.lower() for f in facts]) assert "first time" in all_facts_text or "first" in all_facts_text, \ "Should preserve 'first time' qualifier" assert "recently" not in all_facts_text, \ "Should NOT convert 'yesterday' to 'recently'" assert any(term in all_facts_text for term in ["november", "12", "nov"]), \ "Should convert 'yesterday' to absolute date in fact text" @pytest.mark.asyncio async def test_extract_facts_with_relative_dates(self): """Test that relative dates are converted to absolute dates.""" reference_date = datetime(2024, 3, 20, 14, 0, 0, tzinfo=UTC) llm_config = LLMConfig.for_memory() text = """ Yesterday I went hiking in Yosemite. Last week I started my new job at Google. This morning I had coffee with Alice. """ facts, _ = await extract_facts_from_text( text=text, event_date=reference_date, llm_config=llm_config, agent_name="TestUser", context="Personal diary" ) assert len(facts) > 0, "Should extract at least one fact" for fact in facts: assert fact.fact, "Each fact should have 'fact' field" # Check that facts were extracted - dates may or may not be populated # depending on LLM behavior dates = [f.occurred_start for f in facts if f.occurred_start] # If dates were extracted, they should ideally be different for different events if len(dates) >= 2: unique_dates = set(dates) # Just verify we got dates, don't require them to be unique @pytest.mark.asyncio async def test_extract_facts_with_no_temporal_info(self): """Test that facts without temporal info are still extracted.""" reference_date = datetime(2024, 3, 20, 14, 0, 0, tzinfo=UTC) llm_config = LLMConfig.for_memory() text = "Alice works at Google. She loves Python programming." facts, _ = await extract_facts_from_text( text=text, event_date=reference_date, llm_config=llm_config, agent_name="TestUser", context="General info" ) assert len(facts) > 0, "Should extract at least one fact" # For facts without temporal info, occurred_start may be None or set to reference date # We just verify that facts were extracted with content for fact in facts: assert fact.fact, "Each fact should have text content" @pytest.mark.asyncio async def test_extract_facts_with_absolute_dates(self): """Test that absolute dates in text are preserved.""" reference_date = datetime(2024, 3, 20, 14, 0, 0, tzinfo=UTC) llm_config = LLMConfig.for_memory() text = """ On March 15, 2024, Alice joined Google. Bob will start his vacation on April 1st. """ facts, _ = await extract_facts_from_text( text=text, event_date=reference_date, llm_config=llm_config, agent_name="TestUser", context="Calendar events" ) assert len(facts) > 0, "Should extract at least one fact" for fact in facts: assert fact.occurred_start, f"Fact should have a date: {fact.fact}" # ============================================================================= # LOGICAL INFERENCE TESTS # ============================================================================= class TestLogicalInference: """Tests that the system makes logical inferences to connect related information.""" @pytest.mark.asyncio async def test_logical_inference_identity_connection(self): """ Test that the system extracts key information about loss and relationships. The LLM should extract facts about losing a friend and about Karlie. Ideally it connects them, but we accept extracting both separately. """ text = """ Deborah: The roses and dahlias bring me peace. I lost a friend last week, so I've been spending time in the garden to find some comfort. Jolene: Sorry to hear about your friend, Deb. Losing someone can be really tough. How are you holding up? Deborah: Thanks for the kind words. It's been tough, but I'm comforted by remembering our time together. It reminds me of how special life is. Jolene: Memories can give us so much comfort and joy. Deborah: Memories keep our loved ones close. This is the last photo with Karlie which was taken last summer when we hiked. It was our last one. We had such a great time! Every time I see it, I can't help but smile. """ context = "Conversation between Deborah and Jolene" llm_config = LLMConfig.for_memory() event_date = datetime(2023, 2, 23) facts, _ = await extract_facts_from_text( text=text, event_date=event_date, context=context, llm_config=llm_config, agent_name="Deborah" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) # Check that key information is extracted (Karlie and the loss) has_karlie = "karlie" in all_facts_text has_loss = any(word in all_facts_text for word in ["lost", "death", "passed", "died", "losing", "friend"]) has_hike = "hike" in all_facts_text or "hiking" in all_facts_text or "photo" in all_facts_text # At minimum, we should capture Karlie and either the loss or the hike memory assert has_karlie or has_loss, ( f"Should mention either Karlie or the loss in facts. Facts: {[f.fact for f in facts]}" ) # Check if inference was made (bonus - not required for pass) connected_fact_found = False for fact in facts: fact_text = fact.fact.lower() if "karlie" in fact_text and any(word in fact_text for word in ["lost", "death", "passed", "died", "losing", "friend"]): connected_fact_found = True break # This is informational - test passes even without perfect inference if not connected_fact_found and has_karlie and has_loss: pass # Acceptable: facts extracted separately @pytest.mark.asyncio async def test_logical_inference_pronoun_resolution(self): """ Test that pronouns are resolved to their referents. Example: "I started a project" + "It's challenging" -> "The project is challenging" """ text = """ I started a new machine learning project last month. It's been really challenging but very rewarding. I've learned so much from it. """ context = "Personal update" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), context=context, llm_config=llm_config, agent_name="TestUser" ) assert len(facts) > 0, "Should extract at least one fact" all_facts_text = " ".join([f.fact.lower() for f in facts]) has_project = "project" in all_facts_text has_qualities = any(word in all_facts_text for word in ["challenging", "rewarding", "learned"]) assert has_project, "Should mention the project" assert has_qualities, "Should mention the qualities/learning" connected_fact_found = False for fact in facts: fact_text = fact.fact.lower() if "project" in fact_text and any(word in fact_text for word in ["challenging", "rewarding"]): connected_fact_found = True break assert connected_fact_found, ( "Should resolve 'it' to 'the project' and connect characteristics in the same fact. " f"Facts: {[f.fact for f in facts]}" ) # ============================================================================= # FACT CLASSIFICATION TESTS # ============================================================================= class TestFactClassification: """Tests that facts are correctly classified as agent vs world.""" @pytest.mark.asyncio async def test_agent_facts_from_podcast_transcript(self): """ Test that when context identifies someone as 'you', their actions are classified as agent facts. This test addresses the issue where podcast transcripts with context like "this was podcast episode between you (Marcus) and Jamie" were extracting all facts as 'world' instead of properly identifying Marcus's statements as 'bank'. """ transcript = """ Marcus: I've been working on AI safety research for the past six months. Jamie: That's really interesting! What specifically are you focusing on? Marcus: I'm investigating interpretability methods. I believe we need to understand how models make decisions before we can trust them in critical applications. Jamie: I completely agree with that approach. Marcus: I published a paper on this topic last month, and I'm presenting it at the conference next week. Jamie: Congratulations! I'd love to read it. """ context = "Podcast episode between you (Marcus) and Jamie discussing AI research" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=transcript, event_date=datetime(2024, 11, 13), llm_config=llm_config, agent_name="Marcus", context=context ) assert len(facts) > 0, "Should extract at least one fact from the transcript" # Check that we extracted meaningful content about AI research all_facts_text = " ".join([f.fact.lower() for f in facts]) has_ai_content = any(term in all_facts_text for term in [ "ai", "safety", "interpretability", "research", "paper", "conference", "models" ]) assert has_ai_content, f"Should extract AI research content. Facts: {[f.fact for f in facts]}" # Check fact type classification (flexible - may vary by LLM) agent_facts = [f for f in facts if f.fact_type == "agent"] experience_facts = [f for f in facts if f.fact_type == "experience"] # Accept either agent or experience facts as valid for first-person statements first_person_facts = agent_facts + experience_facts # If we have agent facts, verify they use first person for agent_fact in agent_facts: fact_text = agent_fact.fact # Allow flexibility - fact may or may not start with "I" if fact_text.startswith("I ") or " I " in fact_text: pass # Good - uses first person @pytest.mark.asyncio async def test_agent_facts_without_explicit_context(self): """Test that when 'you' is used in the text itself, it gets properly classified.""" text = """ I completed the project on machine learning interpretability last week. My colleague Sarah helped me with the data analysis. We presented our findings to the team yesterday. """ context = "Personal work log" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=text, event_date=datetime(2024, 11, 13), llm_config=llm_config, agent_name="TestUser", context=context ) assert len(facts) > 0, "Should extract facts" agent_facts = [f for f in facts if f.fact_type == "agent"] assert len(agent_facts) >= 0 # Just verify classification works @pytest.mark.asyncio async def test_speaker_attribution_predictions(self): """ Test that predictions made by different speakers are correctly attributed. This addresses the issue where Jamie's prediction of "Niners 27-13" was being incorrectly attributed to Marcus (the agent) in the extracted facts. """ transcript = """ Marcus: [excited] I'm calling it now, Rams will win twenty seven to twenty four, their defense is too strong! Jamie: [laughs] No way, I predict the Niners will win twenty seven to thirteen, comfy win at home. Marcus: [angry] That's ridiculous, I stand by my Rams prediction. Jamie: [teasing] We'll see who's right, my Niners pick is solid. """ context = "podcast episode on match prediction of week 10 - Marcus (you) and Jamie - 14 nov" agent_name = "Marcus" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=transcript, event_date=datetime(2024, 11, 14), context=context, llm_config=llm_config, agent_name=agent_name ) assert len(facts) > 0, "Should extract at least one fact" # Check that predictions were extracted all_facts_text = " ".join([f.fact.lower() for f in facts]) # Should capture at least some prediction content has_prediction_content = any(term in all_facts_text for term in [ "rams", "niners", "49ers", "prediction", "win", "predict" ]) assert has_prediction_content, f"Should extract prediction content. Facts: {[f.fact for f in facts]}" # Ideally, Marcus's prediction should be in agent facts, but we accept # any reasonable extraction of the predictions agent_facts = [f for f in facts if f.fact_type == "agent"] if agent_facts: agent_facts_text = " ".join([f.fact.lower() for f in agent_facts]) # If agent facts exist, they should relate to Marcus's statements # (but we don't fail if classification varies) @pytest.mark.asyncio async def test_skip_podcast_meta_commentary(self): """ Test that podcast intros, outros, and calls to action are skipped. This addresses the issue where podcast outros like "that's all for today, don't forget to subscribe" were being extracted as facts. """ transcript = """ Marcus: Welcome everyone to today's episode! Before we dive in, don't forget to subscribe and leave a rating. Marcus: Today I want to talk about my research on interpretability in AI systems. I've been working on this for about a year now. Jamie: That sounds really interesting! What made you focus on that area? Marcus: I believe it's crucial for AI safety. We need to understand how these models make decisions before we can trust them in critical applications. Jamie: I completely agree with that approach. Marcus: Well, I think that's gonna do it for us today! Thanks for listening everyone. Don't forget to tap follow or subscribe, tell a friend, and drop a quick rating so the algorithm learns to box out. See you next week! """ context = "Podcast episode between you (Marcus) and Jamie about AI" llm_config = LLMConfig.for_memory() facts, _ = await extract_facts_from_text( text=transcript, event_date=datetime(2024, 11, 13), llm_config=llm_config, agent_name="Marcus", context=context ) assert len(facts) > 0, "Should extract at least one fact" # The main goal is to extract substantive content about AI research # Meta-commentary filtering is ideal but not strictly required all_facts_text = " ".join([f.fact.lower() for f in facts]) # Should extract the actual AI research content has_substantive_content = any(term in all_facts_text for term in [ "interpretability", "ai", "safety", "research", "models", "decisions" ]) assert has_substantive_content, \ f"Should extract substantive AI research content. Facts: {[f.fact for f in facts]}" # ============================================================================= # DISPOSITION INFERENCE TESTS # ============================================================================= class TestDispositionInference: """Tests for LLM-based disposition trait inference from background.""" @pytest.mark.asyncio async def test_background_merge_with_disposition_inference(self, memory): """Test that background merge infers disposition traits by default.""" import uuid bank_id = f"test_infer_{uuid.uuid4().hex[:8]}" result = await memory.merge_bank_background( bank_id, "I am a creative software engineer who loves innovation and trying new technologies", update_disposition=True ) assert "background" in result assert "disposition" in result background = result["background"] disposition = result["disposition"] assert "creative" in background.lower() or "innovation" in background.lower() # Check that new traits are present with valid values (1-5) required_traits = ["skepticism", "literalism", "empathy"] for trait in required_traits: assert trait in disposition assert 1 <= disposition[trait] <= 5 @pytest.mark.asyncio async def test_background_merge_without_disposition_inference(self, memory): """Test that background merge skips disposition inference when disabled.""" import uuid bank_id = f"test_no_infer_{uuid.uuid4().hex[:8]}" initial_profile = await memory.get_bank_profile(bank_id) initial_disposition = initial_profile["disposition"] result = await memory.merge_bank_background( bank_id, "I am a data scientist", update_disposition=False ) assert "background" in result assert "disposition" not in result final_profile = await memory.get_bank_profile(bank_id) final_disposition = final_profile["disposition"] assert initial_disposition == final_disposition @pytest.mark.asyncio async def test_disposition_inference_for_lawyer(self, memory): """Test disposition inference for lawyer profile (high skepticism, high literalism).""" import uuid bank_id = f"test_lawyer_{uuid.uuid4().hex[:8]}" result = await memory.merge_bank_background( bank_id, "I am a lawyer who focuses on contract details and never takes claims at face value", update_disposition=True ) disposition = result["disposition"] # Lawyers should have higher skepticism and literalism assert disposition["skepticism"] >= 3 assert disposition["literalism"] >= 3 @pytest.mark.asyncio async def test_disposition_inference_for_therapist(self, memory): """Test disposition inference for therapist profile (high empathy).""" import uuid bank_id = f"test_therapist_{uuid.uuid4().hex[:8]}" result = await memory.merge_bank_background( bank_id, "I am a therapist who deeply understands and connects with people's emotional struggles", update_disposition=True ) disposition = result["disposition"] # Therapists should have higher empathy assert disposition["empathy"] >= 3 @pytest.mark.asyncio async def test_disposition_updates_in_database(self, memory): """Test that inferred disposition is actually stored in database.""" import uuid bank_id = f"test_db_update_{uuid.uuid4().hex[:8]}" result = await memory.merge_bank_background( bank_id, "I am an innovative designer", update_disposition=True ) inferred_disposition = result["disposition"] profile = await memory.get_bank_profile(bank_id) db_disposition = profile["disposition"] # Compare values (db_disposition is a Pydantic model) assert db_disposition.skepticism == inferred_disposition["skepticism"] assert db_disposition.literalism == inferred_disposition["literalism"] assert db_disposition.empathy == inferred_disposition["empathy"] @pytest.mark.asyncio async def test_multiple_background_merges_update_disposition(self, memory): """Test that each background merge can update disposition.""" import uuid bank_id = f"test_multi_merge_{uuid.uuid4().hex[:8]}" result1 = await memory.merge_bank_background( bank_id, "I am a software engineer", update_disposition=True ) disposition1 = result1["disposition"] result2 = await memory.merge_bank_background( bank_id, "I love creative problem solving and innovation", update_disposition=True ) disposition2 = result2["disposition"] assert "engineer" in result2["background"].lower() or "software" in result2["background"].lower() assert "creative" in result2["background"].lower() or "innovation" in result2["background"].lower() @pytest.mark.asyncio async def test_background_merge_conflict_resolution_with_disposition(self, memory): """Test that conflicts are resolved and disposition reflects final background.""" import uuid bank_id = f"test_conflict_{uuid.uuid4().hex[:8]}" await memory.merge_bank_background( bank_id, "I was born in Colorado and prefer stability", update_disposition=True ) result = await memory.merge_bank_background( bank_id, "You were born in Texas and are very skeptical of people", update_disposition=True ) background = result["background"] disposition = result["disposition"] assert "texas" in background.lower() # Higher skepticism expected from "very skeptical of people" assert disposition["skepticism"] >= 3