diff --git a/Claude outputs/main.py b/Claude outputs/main.py index 6ee6584..2e67671 100644 --- a/Claude outputs/main.py +++ b/Claude outputs/main.py @@ -328,6 +328,28 @@ async def search_similar_conversation_summaries(query_embedding: list, exclude_c ) +async def build_memory_context(user_message: str, conversation_id: int) -> str: + sections = [] + + facts = await get_all_memory_facts() + if facts: + facts_lines = "\n".join(f"- {f['content']}" for f in facts) + sections.append(f"Bekannte Fakten ueber den Nutzer:\n{facts_lines}") + + try: + query_embedding = await get_embedding(user_message) + summaries = await search_similar_conversation_summaries(query_embedding, conversation_id, limit=3) + except Exception as e: + logger.warning(f"Memory retrieval skipped, embedding/search failed: {e}") + summaries = [] + + if summaries: + summary_lines = "\n".join(f"- {s['summary']}" for s in summaries) + sections.append(f"Relevante fruehere Gespraeche:\n{summary_lines}") + + return "\n\n".join(sections) + + def _caldav_calendar(): dav_root = NEXTCLOUD_CALDAV_URL.split("/calendars/")[0] + "/" client = caldav.DAVClient(url=dav_root, username=NEXTCLOUD_USER, password=NEXTCLOUD_APP_PASSWORD) diff --git a/Claude outputs/tests/test_memory.py b/Claude outputs/tests/test_memory.py index fc12d5c..1bf7447 100644 --- a/Claude outputs/tests/test_memory.py +++ b/Claude outputs/tests/test_memory.py @@ -141,3 +141,48 @@ async def test_execute_tool_dispatches_remember_and_forget_fact(): with patch.object(main, "forget_fact", new=AsyncMock(return_value={"deleted": True, "content": "X"})): result = await main.execute_tool("forget_fact", {"query": "X"}) assert json.loads(result) == {"deleted": True, "content": "X"} + + +@pytest.mark.asyncio +async def test_build_memory_context_empty_when_nothing_stored(): + with patch.object(main, "get_all_memory_facts", new=AsyncMock(return_value=[])), patch.object( + main, "get_embedding", new=AsyncMock(return_value=[0.1]) + ), patch.object(main, "search_similar_conversation_summaries", new=AsyncMock(return_value=[])): + context = await main.build_memory_context("Hallo", conversation_id=1) + + assert context == "" + + +@pytest.mark.asyncio +async def test_build_memory_context_includes_facts_section(): + facts = [{"id": 1, "content": "Hund heisst Bruno"}] + with patch.object(main, "get_all_memory_facts", new=AsyncMock(return_value=facts)), patch.object( + main, "get_embedding", new=AsyncMock(return_value=[0.1]) + ), patch.object(main, "search_similar_conversation_summaries", new=AsyncMock(return_value=[])): + context = await main.build_memory_context("Wie geht es meinem Hund?", conversation_id=1) + + assert "Bekannte Fakten ueber den Nutzer" in context + assert "Hund heisst Bruno" in context + assert "Relevante fruehere Gespraeche" not in context + + +@pytest.mark.asyncio +async def test_build_memory_context_includes_summaries_section(): + summaries = [{"conversation_id": 2, "summary": "Ging um den Kalender", "distance": 0.05}] + with patch.object(main, "get_all_memory_facts", new=AsyncMock(return_value=[])), patch.object( + main, "get_embedding", new=AsyncMock(return_value=[0.1]) + ), patch.object(main, "search_similar_conversation_summaries", new=AsyncMock(return_value=summaries)): + context = await main.build_memory_context("Was war nochmal mit dem Termin?", conversation_id=1) + + assert "Relevante fruehere Gespraeche" in context + assert "Ging um den Kalender" in context + + +@pytest.mark.asyncio +async def test_build_memory_context_skips_summaries_when_embedding_fails(): + with patch.object(main, "get_all_memory_facts", new=AsyncMock(return_value=[])), patch.object( + main, "get_embedding", new=AsyncMock(side_effect=RuntimeError("Ollama down")) + ): + context = await main.build_memory_context("Hallo", conversation_id=1) + + assert context == ""