2026-03-27 16:01:28 +08:00
|
|
|
|
"""
|
|
|
|
|
|
证据包组装:跨 memory + story 的检索结果合并(业务层,非纯 repo)。
|
|
|
|
|
|
|
2026-04-30 16:22:55 +08:00
|
|
|
|
Memory evidence 只保留 async 单链路:chunk 原文为首要证据,结构化事实/
|
2026-04-30 14:11:46 +08:00
|
|
|
|
摘要/故事均按本次 query 命中进入 evidence,不再做 rolling/recent 历史降级。
|
2026-03-27 16:01:28 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
|
|
from app.features.memory.repo import (
|
|
|
|
|
|
list_summaries_for_evidence_async,
|
|
|
|
|
|
search_facts_for_user_async,
|
|
|
|
|
|
)
|
2026-04-30 14:11:46 +08:00
|
|
|
|
from app.features.story.repo import list_recent_stories_for_evidence
|
2026-04-03 11:43:16 +08:00
|
|
|
|
|
2026-03-27 16:01:28 +08:00
|
|
|
|
EMPTY_EVIDENCE_BUNDLE: dict = {
|
|
|
|
|
|
"relevant_chunks": [],
|
|
|
|
|
|
"relevant_summaries": [],
|
|
|
|
|
|
"relevant_facts": [],
|
|
|
|
|
|
"relevant_stories": [],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _facts_to_dicts(facts) -> list[dict]:
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": f.id,
|
|
|
|
|
|
"fact_type": f.fact_type,
|
|
|
|
|
|
"subject": f.subject,
|
|
|
|
|
|
"predicate": f.predicate,
|
|
|
|
|
|
"object_json": f.object_json,
|
|
|
|
|
|
}
|
|
|
|
|
|
for f in facts
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _stories_to_dicts(story_rows) -> list[dict]:
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": s.id,
|
|
|
|
|
|
"title": s.title,
|
|
|
|
|
|
"summary": s.summary,
|
|
|
|
|
|
"stage": s.stage,
|
|
|
|
|
|
"story_type": s.story_type,
|
|
|
|
|
|
}
|
|
|
|
|
|
for s in story_rows
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def fetch_evidence_metadata_async(
|
|
|
|
|
|
db: AsyncSession, user_id: str, q: str, top_k: int
|
|
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""非 chunk 证据(async)。"""
|
|
|
|
|
|
facts = await search_facts_for_user_async(db, user_id, q, top_k)
|
|
|
|
|
|
relevant_summaries = await list_summaries_for_evidence_async(
|
|
|
|
|
|
db, user_id=user_id, q=q, limit=top_k
|
|
|
|
|
|
)
|
|
|
|
|
|
story_rows = await list_recent_stories_for_evidence(
|
|
|
|
|
|
db, user_id=user_id, query=q, limit=top_k
|
|
|
|
|
|
)
|
|
|
|
|
|
return {
|
|
|
|
|
|
"relevant_facts": _facts_to_dicts(facts),
|
|
|
|
|
|
"relevant_summaries": relevant_summaries,
|
|
|
|
|
|
"relevant_stories": _stories_to_dicts(story_rows),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def retrieve_evidence_bundle_async(
|
|
|
|
|
|
db: AsyncSession,
|
|
|
|
|
|
user_id: str,
|
|
|
|
|
|
query: str,
|
|
|
|
|
|
*,
|
|
|
|
|
|
top_k: int = 10,
|
|
|
|
|
|
merged_chunk_dicts: list[dict],
|
|
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""
|
2026-04-03 11:43:16 +08:00
|
|
|
|
异步路径:chunk 已由调用方(如 HybridRetriever)向量检索填入;此处只拼元数据。
|
2026-03-27 16:01:28 +08:00
|
|
|
|
|
|
|
|
|
|
merged_chunk_dicts: [{"id","content","chunk_index"}, ...]
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not query or not query.strip():
|
|
|
|
|
|
return dict(EMPTY_EVIDENCE_BUNDLE)
|
|
|
|
|
|
q = query.strip()
|
|
|
|
|
|
meta = await fetch_evidence_metadata_async(db, user_id, q, top_k)
|
|
|
|
|
|
return {
|
|
|
|
|
|
"relevant_chunks": merged_chunk_dicts,
|
|
|
|
|
|
**meta,
|
|
|
|
|
|
}
|