diff --git a/Claude outputs/main.py b/Claude outputs/main.py index 2e67671..ac4bcc3 100644 --- a/Claude outputs/main.py +++ b/Claude outputs/main.py @@ -731,8 +731,14 @@ async def execute_tool(name: str, tool_input: dict) -> str: MAX_TOOL_ROUNDS = 5 -async def run_chat_completion(claude_messages: list): - system_prompt = f"{CLAUDE_SYSTEM_PROMPT}\n\n{CALENDAR_ASSISTANT_INSTRUCTIONS}" +async def run_chat_completion(claude_messages: list, conversation_id: int): + latest_user_message = claude_messages[-1]["content"] + memory_context = await build_memory_context(latest_user_message, conversation_id) + + system_prompt = f"{CLAUDE_SYSTEM_PROMPT}\n\n{CALENDAR_ASSISTANT_INSTRUCTIONS}\n\n{MEMORY_ASSISTANT_INSTRUCTIONS}" + if memory_context: + system_prompt = f"{system_prompt}\n\n{memory_context}" + messages = list(claude_messages) total_input = 0 total_output = 0 @@ -743,7 +749,7 @@ async def run_chat_completion(claude_messages: list): model=CLAUDE_MODEL, max_tokens=1024, system=system_prompt, - tools=CALENDAR_TOOLS, + tools=CALENDAR_TOOLS + MEMORY_TOOLS, messages=messages, ) total_input += completion.usage.input_tokens @@ -900,7 +906,7 @@ async def chat(request: ChatRequest): claude_messages = [{"role": m["role"], "content": m["content"]} for m in history] claude_messages.append({"role": "user", "content": request.message}) - response_text, output_tokens, tokens_used = await run_chat_completion(claude_messages) + response_text, output_tokens, tokens_used = await run_chat_completion(claude_messages, conversation_id) await save_message(conversation_id, DEFAULT_USER_ID, "assistant", response_text, output_tokens) diff --git a/Claude outputs/tests/test_chat_tools.py b/Claude outputs/tests/test_chat_tools.py index 8d1595f..13f5699 100644 --- a/Claude outputs/tests/test_chat_tools.py +++ b/Claude outputs/tests/test_chat_tools.py @@ -42,7 +42,10 @@ async def test_run_chat_completion_without_tool_use(): main.claude_client = MagicMock() main.claude_client.messages.create.return_value = completion - text, output_tokens, total_tokens = await main.run_chat_completion([{"role": "user", "content": "Hi"}]) + with patch.object(main, "build_memory_context", new=AsyncMock(return_value="")): + text, output_tokens, total_tokens = await main.run_chat_completion( + [{"role": "user", "content": "Hi"}], conversation_id=1 + ) assert text == "Hallo!" assert output_tokens == 5 @@ -64,9 +67,11 @@ async def test_run_chat_completion_executes_tool_and_returns_followup(): main.claude_client = MagicMock() main.claude_client.messages.create.side_effect = [first, second] - with patch.object(main, "list_upcoming_events", new=AsyncMock(return_value=[])): + with patch.object(main, "list_upcoming_events", new=AsyncMock(return_value=[])), patch.object( + main, "build_memory_context", new=AsyncMock(return_value="") + ): text, output_tokens, total_tokens = await main.run_chat_completion( - [{"role": "user", "content": "Was steht diese Woche an?"}] + [{"role": "user", "content": "Was steht diese Woche an?"}], conversation_id=1 ) assert text == "Naechste Woche steht nichts an." @@ -102,9 +107,9 @@ async def test_run_chat_completion_handles_two_sequential_tool_calls(): with patch.object(main, "list_upcoming_events", new=AsyncMock(return_value=[{"uid": "abc"}])), patch.object( main, "update_event", new=AsyncMock(return_value={"uid": "abc"}) - ): + ), patch.object(main, "build_memory_context", new=AsyncMock(return_value="")): text, output_tokens, total_tokens = await main.run_chat_completion( - [{"role": "user", "content": "Verschiebe den Termin X"}] + [{"role": "user", "content": "Verschiebe den Termin X"}], conversation_id=1 ) assert text == "Termin verschoben." @@ -128,9 +133,11 @@ async def test_run_chat_completion_lists_recent_emails(): main.claude_client = MagicMock() main.claude_client.messages.create.side_effect = [first, second] - with patch.object(main, "list_recent_emails", new=AsyncMock(return_value=[])): + with patch.object(main, "list_recent_emails", new=AsyncMock(return_value=[])), patch.object( + main, "build_memory_context", new=AsyncMock(return_value="") + ): text, output_tokens, total_tokens = await main.run_chat_completion( - [{"role": "user", "content": "Was ist neu im Postfach?"}] + [{"role": "user", "content": "Was ist neu im Postfach?"}], conversation_id=1 ) assert text == "Du hast 5 neue Mails." diff --git a/Claude outputs/tests/test_memory.py b/Claude outputs/tests/test_memory.py index 1bf7447..e260f3f 100644 --- a/Claude outputs/tests/test_memory.py +++ b/Claude outputs/tests/test_memory.py @@ -186,3 +186,29 @@ async def test_build_memory_context_skips_summaries_when_embedding_fails(): context = await main.build_memory_context("Hallo", conversation_id=1) assert context == "" + + +@pytest.mark.asyncio +async def test_run_chat_completion_includes_memory_context_in_system_prompt(): + completion = MagicMock() + completion.stop_reason = "end_turn" + text_block = MagicMock() + text_block.type = "text" + text_block.text = "Bruno geht es gut." + completion.content = [text_block] + usage = MagicMock() + usage.input_tokens = 10 + usage.output_tokens = 5 + completion.usage = usage + + main.claude_client = MagicMock() + main.claude_client.messages.create.return_value = completion + + with patch.object( + main, "build_memory_context", new=AsyncMock(return_value="Bekannte Fakten ueber den Nutzer:\n- Hund heisst Bruno") + ): + await main.run_chat_completion([{"role": "user", "content": "Wie geht es meinem Hund?"}], conversation_id=1) + + _, kwargs = main.claude_client.messages.create.call_args + assert "Hund heisst Bruno" in kwargs["system"] + assert kwargs["tools"] == main.CALENDAR_TOOLS + main.MEMORY_TOOLS