feat: inject remembered facts and similar summaries into chat system prompt

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V57jSQPqwkGG8BuAXg59X5
This commit is contained in:
Jonny 2026-09-13 15:39:31 +02:00
parent 33016bdf0a
commit 2d9bf6a42b
3 changed files with 50 additions and 11 deletions

View File

@ -731,8 +731,14 @@ async def execute_tool(name: str, tool_input: dict) -> str:
MAX_TOOL_ROUNDS = 5 MAX_TOOL_ROUNDS = 5
async def run_chat_completion(claude_messages: list): async def run_chat_completion(claude_messages: list, conversation_id: int):
system_prompt = f"{CLAUDE_SYSTEM_PROMPT}\n\n{CALENDAR_ASSISTANT_INSTRUCTIONS}" 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) messages = list(claude_messages)
total_input = 0 total_input = 0
total_output = 0 total_output = 0
@ -743,7 +749,7 @@ async def run_chat_completion(claude_messages: list):
model=CLAUDE_MODEL, model=CLAUDE_MODEL,
max_tokens=1024, max_tokens=1024,
system=system_prompt, system=system_prompt,
tools=CALENDAR_TOOLS, tools=CALENDAR_TOOLS + MEMORY_TOOLS,
messages=messages, messages=messages,
) )
total_input += completion.usage.input_tokens 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 = [{"role": m["role"], "content": m["content"]} for m in history]
claude_messages.append({"role": "user", "content": request.message}) 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) await save_message(conversation_id, DEFAULT_USER_ID, "assistant", response_text, output_tokens)

View File

@ -42,7 +42,10 @@ async def test_run_chat_completion_without_tool_use():
main.claude_client = MagicMock() main.claude_client = MagicMock()
main.claude_client.messages.create.return_value = completion 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 text == "Hallo!"
assert output_tokens == 5 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 = MagicMock()
main.claude_client.messages.create.side_effect = [first, second] 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( 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." 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( with patch.object(main, "list_upcoming_events", new=AsyncMock(return_value=[{"uid": "abc"}])), patch.object(
main, "update_event", new=AsyncMock(return_value={"uid": "abc"}) 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( 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." assert text == "Termin verschoben."
@ -128,9 +133,11 @@ async def test_run_chat_completion_lists_recent_emails():
main.claude_client = MagicMock() main.claude_client = MagicMock()
main.claude_client.messages.create.side_effect = [first, second] 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( 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." assert text == "Du hast 5 neue Mails."

View File

@ -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) context = await main.build_memory_context("Hallo", conversation_id=1)
assert context == "" 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