feat: assemble memory context from facts and similar summaries
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V57jSQPqwkGG8BuAXg59X5
This commit is contained in:
parent
72d2bc379c
commit
33016bdf0a
|
|
@ -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():
|
def _caldav_calendar():
|
||||||
dav_root = NEXTCLOUD_CALDAV_URL.split("/calendars/")[0] + "/"
|
dav_root = NEXTCLOUD_CALDAV_URL.split("/calendars/")[0] + "/"
|
||||||
client = caldav.DAVClient(url=dav_root, username=NEXTCLOUD_USER, password=NEXTCLOUD_APP_PASSWORD)
|
client = caldav.DAVClient(url=dav_root, username=NEXTCLOUD_USER, password=NEXTCLOUD_APP_PASSWORD)
|
||||||
|
|
|
||||||
|
|
@ -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"})):
|
with patch.object(main, "forget_fact", new=AsyncMock(return_value={"deleted": True, "content": "X"})):
|
||||||
result = await main.execute_tool("forget_fact", {"query": "X"})
|
result = await main.execute_tool("forget_fact", {"query": "X"})
|
||||||
assert json.loads(result) == {"deleted": True, "content": "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 == ""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue