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:
@@ -31,7 +31,11 @@ from app.agents.chat.reply_limits import (
|
||||
)
|
||||
from app.agents.chat.reply_planner import maybe_refine_turn_plan_with_llm
|
||||
from app.agents.chat.stage_detection import keyword_fallback_primary_stage
|
||||
from app.agents.state_schema import MemoirStateSchema
|
||||
from app.agents.state_schema import (
|
||||
MemoirStateSchema,
|
||||
interview_control_state,
|
||||
narrative_coverage_state,
|
||||
)
|
||||
from app.core.agent_logging import (
|
||||
agent_span,
|
||||
log_agent_payload,
|
||||
@@ -154,14 +158,14 @@ class InterviewAgent:
|
||||
text_for_model = self._resolve_text_for_model(
|
||||
user_message, normalized_user_message
|
||||
)
|
||||
empty_slots = memoir_state.prompt_empty_slots_for_current_stage()
|
||||
filled_slots = {
|
||||
key: value.snippet
|
||||
for key, value in memoir_state.slots.get(
|
||||
memoir_state.current_stage, {}
|
||||
).items()
|
||||
if value.snippet
|
||||
}
|
||||
narrative_state = narrative_coverage_state(memoir_state)
|
||||
control_state = interview_control_state(memoir_state)
|
||||
empty_slots = control_state.prompt_empty_slots_for_stage(
|
||||
narrative_state, memoir_state.current_stage
|
||||
)
|
||||
filled_slots = narrative_state.filled_slots_for_stage(
|
||||
memoir_state.current_stage
|
||||
)
|
||||
if detected_user_stage is not None:
|
||||
du = detected_user_stage
|
||||
else:
|
||||
@@ -173,7 +177,7 @@ class InterviewAgent:
|
||||
)
|
||||
recent_questions = extract_recent_questions(hw.window)
|
||||
conversation_turn_total = hw.turn_total
|
||||
all_stages_coverage = memoir_state.all_stages_coverage()
|
||||
all_stages_coverage = narrative_state.all_stages_coverage()
|
||||
persona = normalize_interview_persona(settings.chat_interview_persona)
|
||||
max_segments = int(settings.chat_interview_max_segments)
|
||||
max_tokens = int(settings.chat_interview_max_tokens)
|
||||
@@ -406,7 +410,11 @@ class InterviewAgent:
|
||||
if not self.llm:
|
||||
return ["你好呀~ 又见面了。今天想从人生里哪一小段回忆开始聊聊?"]
|
||||
try:
|
||||
empty_slots = memoir_state.prompt_empty_slots_for_current_stage()
|
||||
narrative_state = narrative_coverage_state(memoir_state)
|
||||
control_state = interview_control_state(memoir_state)
|
||||
empty_slots = control_state.prompt_empty_slots_for_stage(
|
||||
narrative_state, memoir_state.current_stage
|
||||
)
|
||||
empty_slots_readable = [SLOT_NAME_MAP.get(s, s) for s in empty_slots]
|
||||
persona = normalize_interview_persona(settings.chat_interview_persona)
|
||||
prompt = get_opening_prompt(
|
||||
|
||||
@@ -5,13 +5,18 @@ from __future__ import annotations
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
|
||||
# 与 `apply_duplicate_question_guard` 中整段替换句一致;用于判定是否需触发二次生成。
|
||||
DUPLICATE_QUESTION_GUARD_FALLBACK_ZH = "这一段我记住了。"
|
||||
|
||||
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
||||
from langchain_core.messages import AIMessage, BaseMessage
|
||||
|
||||
from app.agents.stage_constants import STAGE_DISPLAY_ZH, STAGE_SLOT_KEYS
|
||||
from app.agents.state_schema import KnownFact, MemoirStateSchema, PersonaThread
|
||||
from app.agents.state_schema import (
|
||||
KnownFact,
|
||||
MemoirStateSchema,
|
||||
PersonaThread,
|
||||
narrative_coverage_state,
|
||||
)
|
||||
|
||||
# 与 `apply_duplicate_question_guard` 中整段替换句一致;用于判定是否需触发二次生成。
|
||||
DUPLICATE_QUESTION_GUARD_FALLBACK_ZH = "这一段我记住了。"
|
||||
|
||||
_QUESTION_SPLIT_RE = re.compile(r"[??]+")
|
||||
_SENTENCE_SPLIT_RE = re.compile(r"(?<=[。!?!?])")
|
||||
@@ -220,10 +225,11 @@ def build_runtime_interview_state(
|
||||
)
|
||||
|
||||
persona_additions: list[PersonaThread] = []
|
||||
narrative_state = narrative_coverage_state(state)
|
||||
haystack = " ".join(
|
||||
[msg]
|
||||
+ [fact.value for fact in state.known_facts[-8:]]
|
||||
+ list(state.filled_slots_for_stage(active_stage).values())[:4]
|
||||
+ list(narrative_state.filled_slots_for_stage(active_stage).values())[:4]
|
||||
)
|
||||
for trait, markers in _TRAIT_HINTS:
|
||||
for marker in markers:
|
||||
|
||||
@@ -48,13 +48,3 @@ def get_interview_persona_tone_hint(persona: str) -> str:
|
||||
"短句像微信,一次最多一个具体问题,不重复上文已清楚的事;底色仍要**随和、像聊天伙伴**,别像考官盘问。"
|
||||
"允许用一两句场景感的短描写承接对方画面,不要只用干巴巴的确认句;情绪重时承接要有半句并肩,勿只回嗯。"
|
||||
)
|
||||
|
||||
|
||||
def get_interview_persona_block(persona: str) -> str:
|
||||
"""兼容旧名:返回空串,请改用 get_interview_persona_tone_hint。"""
|
||||
return ""
|
||||
|
||||
|
||||
def get_opening_persona_line(persona: str) -> str:
|
||||
"""兼容旧名:与访谈轮次共用一句性格提示。"""
|
||||
return get_interview_persona_tone_hint(persona)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from app.agents.chat.schemas import StageDetectionOutput
|
||||
from app.agents.chat.stage_prompts import (
|
||||
@@ -23,12 +23,6 @@ from app.core.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def normalize_life_stage(raw: Optional[str], fallback: str) -> str:
|
||||
"""兼容旧名:统一走 normalize_chat_stage。"""
|
||||
return normalize_chat_stage(raw, fallback)
|
||||
|
||||
|
||||
def keyword_fallback_primary_stage(user_message: str) -> str:
|
||||
"""多阶段打分,取最高分;平局按 CHAT_STAGES 逆序优先(与历史 tie_order 派生一致,可能有小幅行为差异)。"""
|
||||
if not (user_message or "").strip():
|
||||
@@ -100,5 +94,4 @@ __all__ = [
|
||||
"detect_primary_life_stage",
|
||||
"keyword_fallback_primary_stage",
|
||||
"life_stage_display_name",
|
||||
"normalize_life_stage",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user