feat(api)!: memory single chain — async MemoryService, strict eval closure
Route all memory ingest/retrieve/enrichment/compaction through async MemoryService. Remove legacy sync memory implementations (ingest/retrieve/compaction); Celery and memoir Phase2 call asyncio.run into MemoryService-backed helpers. Memoir Phase1 batch ingest uses MemoryService.ingest_transcripts_batch; drop chapters. evidence_bundle_json mirror (Alembic 0015). Evaluation uses snapshot/link-only bundles; raise EvidenceClosureMissing instead of partial/fallback lineage tiers. Split memoir state into NarrativeCoverageState and InterviewControlState; delete the _interview_meta_store adapter layer. Remove rolling-query and recent-fact fallback settings from config and evidence assembly. Update judges, docs, tests, and PlaygroundPage alignment. Made-with: Cursor
This commit is contained in:
@@ -11,7 +11,6 @@ from app.features.memoir.models import Chapter, ChapterStoryLink
|
||||
from app.features.memory.models import (
|
||||
MemoryChunk,
|
||||
MemoryFact,
|
||||
MemorySource,
|
||||
MemorySummary,
|
||||
TimelineEvent,
|
||||
)
|
||||
@@ -171,77 +170,6 @@ async def fetch_ai_messages_for_segments(
|
||||
return out
|
||||
|
||||
|
||||
async def fetch_memory_closure_for_conversations(
|
||||
db: AsyncSession, *, user_id: str, conversation_ids: list[str]
|
||||
) -> tuple[list[str], list[str], list[str], list[str]]:
|
||||
"""
|
||||
返回 (chunk_ids, fact_ids, timeline_event_ids, summary_ids),均限定 user_id。
|
||||
路径:MemorySource(conversation_id) -> chunks;facts by source_chunk_id;
|
||||
timeline by memory_source_id;summaries 仅 rolling + 与会话 chunk 有交集的(轻量近似)。
|
||||
"""
|
||||
if not conversation_ids:
|
||||
return [], [], [], []
|
||||
conv_set = list({c for c in conversation_ids if c})
|
||||
|
||||
src_stmt = select(MemorySource).where(
|
||||
MemorySource.user_id == user_id,
|
||||
MemorySource.conversation_id.in_(conv_set),
|
||||
)
|
||||
src_result = await db.execute(src_stmt)
|
||||
sources = list(src_result.scalars().all())
|
||||
source_ids = [s.id for s in sources]
|
||||
if not source_ids:
|
||||
return [], [], [], []
|
||||
|
||||
ch_stmt = select(MemoryChunk).where(
|
||||
MemoryChunk.user_id == user_id,
|
||||
MemoryChunk.source_id.in_(source_ids),
|
||||
MemoryChunk.is_excluded.is_(False),
|
||||
)
|
||||
ch_result = await db.execute(ch_stmt)
|
||||
chunks = list(ch_result.scalars().all())
|
||||
chunk_ids = [c.id for c in chunks]
|
||||
if not chunk_ids:
|
||||
fact_rows: list[MemoryFact] = []
|
||||
else:
|
||||
f_stmt = select(MemoryFact).where(
|
||||
MemoryFact.user_id == user_id,
|
||||
MemoryFact.source_chunk_id.in_(chunk_ids),
|
||||
or_(MemoryFact.status.is_(None), MemoryFact.status != "stale"),
|
||||
)
|
||||
f_result = await db.execute(f_stmt)
|
||||
fact_rows = list(f_result.scalars().all())
|
||||
fact_ids = [f.id for f in fact_rows]
|
||||
|
||||
te_stmt = select(TimelineEvent).where(
|
||||
TimelineEvent.user_id == user_id,
|
||||
TimelineEvent.memory_source_id.in_(source_ids),
|
||||
)
|
||||
te_result = await db.execute(te_stmt)
|
||||
ev_rows = list(te_result.scalars().all())
|
||||
timeline_ids = [e.id for e in ev_rows]
|
||||
|
||||
sum_stmt = (
|
||||
select(MemorySummary)
|
||||
.where(MemorySummary.user_id == user_id)
|
||||
.order_by(MemorySummary.updated_at.desc())
|
||||
.limit(12)
|
||||
)
|
||||
sum_result = await db.execute(sum_stmt)
|
||||
summaries = list(sum_result.scalars().all())
|
||||
chunk_set = set(chunk_ids)
|
||||
summary_ids: list[str] = []
|
||||
for sm in summaries:
|
||||
if sm.summary_type == "rolling":
|
||||
summary_ids.append(sm.id)
|
||||
continue
|
||||
scids = sm.source_chunk_ids or []
|
||||
if isinstance(scids, list) and chunk_set.intersection({str(x) for x in scids}):
|
||||
summary_ids.append(sm.id)
|
||||
|
||||
return chunk_ids, fact_ids, timeline_ids, summary_ids
|
||||
|
||||
|
||||
async def load_chunks_by_ids(
|
||||
db: AsyncSession, *, user_id: str, chunk_ids: list[str]
|
||||
) -> list[MemoryChunk]:
|
||||
|
||||
Reference in New Issue
Block a user