feat(memory): complete M.2-M.5 memory upgrade phases with tests

- M.2: ForgettingCurve, MemoryDecay, MemoryReinforcement (selective forgetting)
- M.3: DailyDigestGenerator, ReminderScheduler, ProactiveInformer (proactive reminders)
- M.4: MemoryExtractor with LLM-based memory extraction from conversations
- M.5: MemoryRecallInjector with token budget control for prompt injection
- All phases include comprehensive unit tests (109 tests passing)
- Updated checklist.md to mark all tasks complete
This commit is contained in:
2026-04-05 14:09:51 +08:00
parent 9bfa0dcc11
commit 11160ec4d2
22 changed files with 4117 additions and 186 deletions

View File

@@ -331,6 +331,40 @@ class AgentService:
async with async_session() as session:
await memory_service.try_auto_summarize(session, user_id, conversation_id)
# ———— M.4: 主动记忆提取 ————
async def _extract_memories_background(self, user_id: str, conversation_id: str) -> None:
"""Background task to extract memories from conversation after response."""
from app.services.memory.memory_extractor import MemoryExtractor
from sqlalchemy import select
from app.models.conversation import Message
try:
async with async_session() as db:
# Load last 10 messages from conversation
result = await db.execute(
select(Message)
.where(Message.conversation_id == conversation_id)
.order_by(Message.created_at.desc())
.limit(10)
)
messages = list(result.scalars().all())
if len(messages) < 2:
return
extractor = MemoryExtractor()
new_memories = await extractor.extract_from_conversation(
db, user_id, conversation_id, messages
)
if new_memories:
await extractor.save_memories(db, user_id, conversation_id, new_memories)
logger.info(
f"[MemoryExtractor] Extracted {len(new_memories)} new memories from conversation {conversation_id}"
)
except Exception as e:
logger.exception(f"[MemoryExtractor] Extraction failed: {e}")
def _build_progress_event(
self,
stage: str,
@@ -543,6 +577,13 @@ class AgentService:
self.db, user_id, conversation_id, message
)
# M.5: Inject recall memories into context (before LLM call)
from app.services.memory.recall_injector import MemoryRecallInjector
recall_ctx = await MemoryRecallInjector().build_context(self.db, user_id, message)
if recall_ctx:
memory_ctx = f"{memory_ctx}\n{recall_ctx}" if memory_ctx else recall_ctx
assistant_msg = Message(
conversation_id=conversation_id,
role="assistant",
@@ -735,6 +776,8 @@ class AgentService:
except Exception:
logger.exception("save_assistant_message_failed")
asyncio.create_task(self._try_auto_summarize_background(user_id, conversation_id))
# M.4: Extract memories from conversation
asyncio.create_task(self._extract_memories_background(user_id, conversation_id))
return conversation_id, assistant_msg.id, run_agent()
@@ -807,6 +850,13 @@ class AgentService:
self.db, user_id, conversation_id, message
)
# M.5: Inject recall memories into context (before LLM call)
from app.services.memory.recall_injector import MemoryRecallInjector
recall_ctx = await MemoryRecallInjector().build_context(self.db, user_id, message)
if recall_ctx:
memory_ctx = f"{memory_ctx}\n{recall_ctx}" if memory_ctx else recall_ctx
set_current_user(user_id)
try:
graph = get_agent_graph()