feat: extract and save memories after chat conversations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
56
cogs/chat.py
56
cogs/chat.py
@@ -3,6 +3,7 @@ import logging
|
|||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@@ -32,6 +33,8 @@ class ChatCog(commands.Cog):
|
|||||||
self._chat_history: dict[int, deque] = {}
|
self._chat_history: dict[int, deque] = {}
|
||||||
# Counter of messages seen since last proactive reply (per channel)
|
# Counter of messages seen since last proactive reply (per channel)
|
||||||
self._messages_since_reply: dict[int, int] = {}
|
self._messages_since_reply: dict[int, int] = {}
|
||||||
|
# Users whose profile has been updated and needs DB flush
|
||||||
|
self._dirty_users: set[int] = set()
|
||||||
|
|
||||||
def _get_active_prompt(self) -> str:
|
def _get_active_prompt(self) -> str:
|
||||||
"""Load the chat prompt for the current mode."""
|
"""Load the chat prompt for the current mode."""
|
||||||
@@ -39,6 +42,51 @@ class ChatCog(commands.Cog):
|
|||||||
prompt_file = mode_config.get("prompt_file", "chat_personality.txt")
|
prompt_file = mode_config.get("prompt_file", "chat_personality.txt")
|
||||||
return _load_prompt(prompt_file)
|
return _load_prompt(prompt_file)
|
||||||
|
|
||||||
|
async def _extract_and_save_memories(
|
||||||
|
self, user_id: int, username: str, conversation: list[dict[str, str]],
|
||||||
|
) -> None:
|
||||||
|
"""Background task: extract memories from conversation and save them."""
|
||||||
|
try:
|
||||||
|
current_profile = self.bot.drama_tracker.get_user_notes(user_id)
|
||||||
|
result = await self.bot.llm.extract_memories(
|
||||||
|
conversation, username, current_profile,
|
||||||
|
)
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Save expiring memories
|
||||||
|
for mem in result.get("memories", []):
|
||||||
|
if mem["expiration"] == "permanent":
|
||||||
|
continue # permanent facts go into profile_update
|
||||||
|
exp_days = {"1d": 1, "3d": 3, "7d": 7, "30d": 30}
|
||||||
|
days = exp_days.get(mem["expiration"], 7)
|
||||||
|
expires_at = datetime.now(timezone.utc) + timedelta(days=days)
|
||||||
|
await self.bot.db.save_memory(
|
||||||
|
user_id=user_id,
|
||||||
|
memory=mem["memory"],
|
||||||
|
topics=",".join(mem["topics"]),
|
||||||
|
importance=mem["importance"],
|
||||||
|
expires_at=expires_at,
|
||||||
|
source="chat",
|
||||||
|
)
|
||||||
|
# Prune if over cap
|
||||||
|
await self.bot.db.prune_excess_memories(user_id)
|
||||||
|
|
||||||
|
# Update profile if warranted
|
||||||
|
profile_update = result.get("profile_update")
|
||||||
|
if profile_update:
|
||||||
|
self.bot.drama_tracker.set_user_profile(user_id, profile_update)
|
||||||
|
self._dirty_users.add(user_id)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Extracted %d memories for %s (profile_update=%s)",
|
||||||
|
len(result.get("memories", [])),
|
||||||
|
username,
|
||||||
|
bool(profile_update),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to extract memories for %s", username)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_message(self, message: discord.Message):
|
async def on_message(self, message: discord.Message):
|
||||||
if message.author.bot:
|
if message.author.bot:
|
||||||
@@ -265,6 +313,14 @@ class ChatCog(commands.Cog):
|
|||||||
|
|
||||||
await message.reply(response, mention_author=False)
|
await message.reply(response, mention_author=False)
|
||||||
|
|
||||||
|
# Fire-and-forget memory extraction
|
||||||
|
if not image_attachment:
|
||||||
|
asyncio.create_task(self._extract_and_save_memories(
|
||||||
|
message.author.id,
|
||||||
|
message.author.display_name,
|
||||||
|
list(self._chat_history[ch_id]),
|
||||||
|
))
|
||||||
|
|
||||||
reply_type = "proactive" if is_proactive else "chat"
|
reply_type = "proactive" if is_proactive else "chat"
|
||||||
logger.info(
|
logger.info(
|
||||||
"%s reply in #%s to %s: %s",
|
"%s reply in #%s to %s: %s",
|
||||||
|
|||||||
Reference in New Issue
Block a user