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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user