feat: summarize conversations in the background after each turn
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V57jSQPqwkGG8BuAXg59X5
This commit is contained in:
parent
2d9bf6a42b
commit
c879ffc625
|
|
@ -783,6 +783,41 @@ async def run_chat_completion(claude_messages: list, conversation_id: int):
|
|||
)
|
||||
|
||||
|
||||
SUMMARY_SYSTEM_PROMPT = (
|
||||
"Fasse das folgende Gespraech in 2-3 Saetzen auf Deutsch zusammen, damit "
|
||||
"ein spaeterer Chat den Kontext wiedererkennt. Gib nur die Zusammenfassung "
|
||||
"aus, ohne Einleitung."
|
||||
)
|
||||
|
||||
|
||||
async def update_conversation_summary(conversation_id: int):
|
||||
try:
|
||||
history = await get_messages(conversation_id)
|
||||
claude_messages = [{"role": m["role"], "content": m["content"]} for m in history]
|
||||
completion = await asyncio.to_thread(
|
||||
claude_client.messages.create,
|
||||
model=CLAUDE_MODEL,
|
||||
max_tokens=200,
|
||||
system=SUMMARY_SYSTEM_PROMPT,
|
||||
messages=claude_messages,
|
||||
)
|
||||
summary_text = "".join(b.text for b in completion.content if b.type == "text")
|
||||
embedding = await get_embedding(summary_text)
|
||||
await upsert_conversation_summary(conversation_id, summary_text, embedding)
|
||||
except Exception as e:
|
||||
logger.error(f"Conversation summary update failed for conversation {conversation_id}: {e}")
|
||||
|
||||
|
||||
_background_tasks: set = set()
|
||||
|
||||
|
||||
def _spawn_background_task(coro):
|
||||
task = asyncio.create_task(coro)
|
||||
_background_tasks.add(task)
|
||||
task.add_done_callback(_background_tasks.discard)
|
||||
return task
|
||||
|
||||
|
||||
# ============ MODELS ============
|
||||
class ChatRequest(BaseModel):
|
||||
conversation_id: Optional[int] = None
|
||||
|
|
@ -910,6 +945,8 @@ async def chat(request: ChatRequest):
|
|||
|
||||
await save_message(conversation_id, DEFAULT_USER_ID, "assistant", response_text, output_tokens)
|
||||
|
||||
_spawn_background_task(update_conversation_summary(conversation_id))
|
||||
|
||||
return ChatResponse(
|
||||
conversation_id=conversation_id,
|
||||
response=response_text,
|
||||
|
|
|
|||
|
|
@ -212,3 +212,49 @@ async def test_run_chat_completion_includes_memory_context_in_system_prompt():
|
|||
_, kwargs = main.claude_client.messages.create.call_args
|
||||
assert "Hund heisst Bruno" in kwargs["system"]
|
||||
assert kwargs["tools"] == main.CALENDAR_TOOLS + main.MEMORY_TOOLS
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_conversation_summary_upserts_summary_and_embedding():
|
||||
history = [
|
||||
{"role": "user", "content": "Wie geht es meinem Hund?", "tokens_used": None, "created_at": None},
|
||||
{"role": "assistant", "content": "Bruno geht es gut.", "tokens_used": 5, "created_at": None},
|
||||
]
|
||||
completion = MagicMock()
|
||||
text_block = MagicMock()
|
||||
text_block.type = "text"
|
||||
text_block.text = "Nutzer fragte nach seinem Hund Bruno."
|
||||
completion.content = [text_block]
|
||||
|
||||
main.claude_client = MagicMock()
|
||||
main.claude_client.messages.create.return_value = completion
|
||||
|
||||
with patch.object(main, "get_messages", new=AsyncMock(return_value=history)), patch.object(
|
||||
main, "get_embedding", new=AsyncMock(return_value=[0.1, 0.2])
|
||||
), patch.object(main, "upsert_conversation_summary", new=AsyncMock()) as mock_upsert:
|
||||
await main.update_conversation_summary(conversation_id=3)
|
||||
|
||||
mock_upsert.assert_called_once_with(3, "Nutzer fragte nach seinem Hund Bruno.", [0.1, 0.2])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_conversation_summary_swallows_exceptions():
|
||||
with patch.object(main, "get_messages", new=AsyncMock(side_effect=RuntimeError("DB down"))):
|
||||
await main.update_conversation_summary(conversation_id=3)
|
||||
# kein Raise - das ist der Test
|
||||
|
||||
|
||||
def test_spawn_background_task_tracks_and_releases_task():
|
||||
import asyncio as aio
|
||||
|
||||
async def _run():
|
||||
async def noop():
|
||||
return "done"
|
||||
|
||||
task = main._spawn_background_task(noop())
|
||||
assert task in main._background_tasks
|
||||
result = await task
|
||||
assert result == "done"
|
||||
assert task not in main._background_tasks
|
||||
|
||||
aio.run(_run())
|
||||
|
|
|
|||
Loading…
Reference in New Issue