WIP: memory system improvements (in progress)

Interview/chat prompt layers, reply planner, style profiles, memory
injection, interview meta store, and related tests. Work not finished.

Made-with: Cursor
This commit is contained in:
Kevin
2026-04-22 16:56:28 +08:00
parent e848f26354
commit 3121d1384d
28 changed files with 2790 additions and 452 deletions

View File

@@ -52,14 +52,13 @@ _UNAUTH_TURN = AgentChatTurn(
)
async def _fetch_interview_memory_evidence(
async def _fetch_interview_memory_bundle(
db: AsyncSession,
user_id: str,
user_message: str,
) -> tuple[str, dict | None]:
"""按本轮用户话检索记忆:格式化短文本 + 可入库 trace稳定 id"""
) -> tuple[dict | None, object | None]:
"""检索记忆 bundle原始结构是否进主 prompt 由 `slice_interview_memory` 再筛"""
from app.core.dependencies import get_embedding_provider
from app.features.memory.evidence_format import format_evidence_chunks_for_prompt
from app.features.memory.retrieval_trace import (
chat_memory_retrieval_trace_from_bundle,
)
@@ -69,13 +68,13 @@ async def _fetch_interview_memory_evidence(
logger.debug(
"event=chat_memory_retrieval_skip reason=disabled user_id={}", user_id
)
return "", None
return None, None
msg = (user_message or "").strip()
if not msg:
logger.debug(
"event=chat_memory_retrieval_skip reason=empty user_id={}", user_id
)
return "", None
return None, None
try:
emb = get_embedding_provider()
ms = MemoryService(db, embedding_provider=emb)
@@ -85,30 +84,19 @@ async def _fetch_interview_memory_evidence(
trace = chat_memory_retrieval_trace_from_bundle(
bd, top_k=top_k, query_len=len(msg)
)
text = format_evidence_chunks_for_prompt(bd)
t = (text or "").strip()
if not t:
logger.debug(
"event=memory_evidence_for_prompt user_id={} formatted_chars=0",
user_id,
)
return "", trace
max_c = settings.chat_memory_evidence_max_chars
if len(t) > max_c:
t = t[: max_c - 3] + "..."
logger.info(
"event=memory_evidence_for_prompt user_id={} formatted_chars={}",
"event=memory_retrieval_bundle user_id={} top_k={}",
user_id,
len(t),
top_k,
)
return t, trace
return bd, trace
except Exception as e:
try:
await db.rollback()
except Exception as rollback_error:
logger.warning("访谈记忆检索失败后回滚也失败: {}", rollback_error)
logger.warning("访谈记忆检索失败: {}", e)
return "", None
return None, None
class ChatOrchestrator:
@@ -284,14 +272,14 @@ class ChatOrchestrator:
background_voice = infer_background_voice(user.occupation)
occupation = user.occupation or ""
memory_evidence_text, mem_trace = await _fetch_interview_memory_evidence(
from app.features.memory.chat_memory_injection import slice_interview_memory
memory_bundle, mem_trace = await _fetch_interview_memory_bundle(
db, user_id, normalized_user_message
)
scene_cues = extract_scene_cues(normalized_user_message)
if scene_cues:
cue_block = "\n".join(f"- {c}" for c in scene_cues)
scene_hint = f"\n\n[场景氛围提示——可借用这些感官细节自然接话,不要原样抄]\n{cue_block}"
memory_evidence_text = (memory_evidence_text or "") + scene_hint
mem_slices = slice_interview_memory(memory_bundle, normalized_user_message)
# 场景关键词仅作为 focus planner 的辅助输入,不直接拼进记忆块,避免抢过用户明确的关系/身份线索
scene_cues_for_planner = extract_scene_cues(normalized_user_message)
profile_birth_year = user.birth_year if user else None
profile_era_place = ""
@@ -313,13 +301,16 @@ class ChatOrchestrator:
memoir_state=prompt_state,
user_profile_context=user_profile_context,
detected_user_stage=detected,
memory_evidence_text=memory_evidence_text,
memory_evidence_text=mem_slices.prompt_excerpt,
memory_anchor_source=mem_slices.anchor_source,
memory_planner_text=mem_slices.planner_preview,
background_voice=background_voice,
normalized_user_message=normalized_user_message,
occupation=occupation,
profile_birth_year=profile_birth_year,
profile_era_place=profile_era_place,
stage_switched_this_turn=stage_switched_this_turn,
scene_cues_for_planner=scene_cues_for_planner,
)
recent_questions = prompt_state.recent_questions
if turn.interview_state_meta and isinstance(turn.interview_state_meta, dict):
@@ -411,12 +402,15 @@ class ChatOrchestrator:
audio_duration_seconds: int | None = None,
detected_user_stage: str | None = None,
memory_evidence_text: str = "",
memory_anchor_source: str = "",
memory_planner_text: str = "",
background_voice: str = "default",
normalized_user_message: str | None = None,
occupation: str = "",
profile_birth_year: int | None = None,
profile_era_place: str = "",
stage_switched_this_turn: bool = False,
scene_cues_for_planner: Optional[list[str]] = None,
) -> AgentChatTurn:
"""委托 InterviewAgent 生成访谈回复(持久化由调用方负责)。"""
return await self.interview_agent.generate_response_with_state(
@@ -426,12 +420,15 @@ class ChatOrchestrator:
user_profile_context=user_profile_context,
detected_user_stage=detected_user_stage,
memory_evidence_text=memory_evidence_text,
memory_anchor_source=memory_anchor_source,
memory_planner_text=memory_planner_text,
background_voice=background_voice,
normalized_user_message=normalized_user_message,
occupation=occupation,
profile_birth_year=profile_birth_year,
profile_era_place=profile_era_place,
stage_switched_this_turn=stage_switched_this_turn,
scene_cues_for_planner=scene_cues_for_planner,
)
def detect_user_stage(self, user_message: str) -> str: