feat: 回忆录证据血缘与内部评测可追溯,顺带对齐本地评测台与 CI
数据库与模型:新增多版迁移(章节证据快照、对话血缘、记忆事实/时间线 lineage 等),把「成稿 ↔ 对话/记忆」的溯源信息落到表结构里。 业务链路:会话与 WS、回忆录/故事流水线、记忆写入与 enrichment 等跟着接上线索与快照;新增章节证据快照与评测侧 EvalTraceService 等模块,方便组评审用的证据包。 内部评测:自动化 run 与手工 memoir 评审共用可追溯证据;rubric/ judge 相关脚本与文档有配套调整。 app-eval-web:Memoir/实验详情里能展开看证据摘要与 evidence_trace(含对话轮次 id);Vite 代理与 development.sh 注入的 API 端口与当前默认内部评测端口一致,避免改端口后页面连错服务。 工程杂项:GitHub Actions / 仓库说明有更新;各适配器与支付/配额/plan 等多处为小改动或跟随主改动的收尾;新增/扩充了?
This commit is contained in:
@@ -13,15 +13,15 @@ from app.agents.chat.agent_turn import AgentChatTurn
|
||||
from app.agents.chat.helpers import get_history_with_window
|
||||
from app.agents.chat.interview_agent import InterviewAgent
|
||||
from app.agents.chat.profile_agent import ProfileAgent
|
||||
from app.agents.state_schema import MemoirStateSchema
|
||||
from app.core.agent_logging import agent_summary_enabled, log_agent_detail
|
||||
from app.core.logging import get_logger
|
||||
from app.agents.chat.stage_detection import (
|
||||
detect_primary_life_stage,
|
||||
life_stage_display_name,
|
||||
)
|
||||
from app.agents.state_schema import MemoirStateSchema
|
||||
from app.core.agent_logging import agent_summary_enabled, log_agent_detail
|
||||
from app.core.config import settings
|
||||
from app.core.dependencies import get_llm_provider
|
||||
from app.core.logging import get_logger
|
||||
from app.features.conversation.input_normalize import normalize_chat_input_for_agent
|
||||
from app.features.memoir.state_service import get_or_create_state, switch_stage
|
||||
|
||||
@@ -48,28 +48,35 @@ async def _fetch_interview_memory_evidence(
|
||||
db: AsyncSession,
|
||||
user_id: str,
|
||||
user_message: str,
|
||||
) -> str:
|
||||
"""按本轮用户话检索记忆,格式化为短文本;失败或未启用时返回空串。"""
|
||||
) -> tuple[str, dict | None]:
|
||||
"""按本轮用户话检索记忆:格式化短文本 + 可入库 trace(稳定 id)。"""
|
||||
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,
|
||||
)
|
||||
from app.features.memory.service import MemoryService
|
||||
|
||||
if not settings.chat_memory_retrieval_enabled:
|
||||
logger.debug(
|
||||
"event=chat_memory_retrieval_skip reason=disabled user_id={}", user_id
|
||||
)
|
||||
return ""
|
||||
return "", None
|
||||
msg = (user_message or "").strip()
|
||||
if not msg:
|
||||
logger.debug(
|
||||
"event=chat_memory_retrieval_skip reason=empty user_id={}", user_id
|
||||
)
|
||||
return ""
|
||||
return "", None
|
||||
try:
|
||||
emb = get_embedding_provider()
|
||||
ms = MemoryService(db, embedding_provider=emb)
|
||||
bundle = await ms.retrieve(user_id, msg, top_k=settings.chat_memory_top_k)
|
||||
top_k = settings.chat_memory_top_k
|
||||
bundle = await ms.retrieve(user_id, msg, top_k=top_k)
|
||||
bd = bundle.model_dump()
|
||||
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:
|
||||
@@ -77,7 +84,7 @@ async def _fetch_interview_memory_evidence(
|
||||
"event=memory_evidence_for_prompt user_id={} formatted_chars=0",
|
||||
user_id,
|
||||
)
|
||||
return ""
|
||||
return "", trace
|
||||
max_c = settings.chat_memory_evidence_max_chars
|
||||
if len(t) > max_c:
|
||||
t = t[: max_c - 3] + "..."
|
||||
@@ -86,14 +93,14 @@ async def _fetch_interview_memory_evidence(
|
||||
user_id,
|
||||
len(t),
|
||||
)
|
||||
return t
|
||||
return t, trace
|
||||
except Exception as e:
|
||||
try:
|
||||
await db.rollback()
|
||||
except Exception as rollback_error:
|
||||
logger.warning("访谈记忆检索失败后回滚也失败: {}", rollback_error)
|
||||
logger.warning("访谈记忆检索失败: {}", e)
|
||||
return ""
|
||||
return "", None
|
||||
|
||||
|
||||
class ChatOrchestrator:
|
||||
@@ -197,12 +204,15 @@ class ChatOrchestrator:
|
||||
conversation_id,
|
||||
len(responses),
|
||||
)
|
||||
return AgentChatTurn(messages=responses, skip_tts=False)
|
||||
return AgentChatTurn(
|
||||
messages=responses, skip_tts=False, memory_retrieval_trace=None
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"资料收集处理失败: {e}", exc_info=True)
|
||||
return AgentChatTurn(
|
||||
messages=["不好意思刚才没接住,你再说一遍好吗?"],
|
||||
skip_tts=False,
|
||||
memory_retrieval_trace=None,
|
||||
)
|
||||
|
||||
# --- 正式访谈模式 ---
|
||||
@@ -262,10 +272,17 @@ class ChatOrchestrator:
|
||||
background_voice = infer_background_voice(user.occupation)
|
||||
occupation = user.occupation or ""
|
||||
|
||||
memory_evidence_text = await _fetch_interview_memory_evidence(
|
||||
memory_evidence_text, mem_trace = await _fetch_interview_memory_evidence(
|
||||
db, user_id, normalized_user_message
|
||||
)
|
||||
|
||||
profile_birth_year = user.birth_year if user else None
|
||||
profile_era_place = ""
|
||||
if user:
|
||||
profile_era_place = (
|
||||
(user.birth_place or user.grew_up_place or "").strip()
|
||||
)
|
||||
|
||||
turn = await self.interview_agent.generate_response_with_state(
|
||||
conversation_id=conversation_id,
|
||||
user_message=user_message,
|
||||
@@ -276,6 +293,8 @@ class ChatOrchestrator:
|
||||
background_voice=background_voice,
|
||||
normalized_user_message=normalized_user_message,
|
||||
occupation=occupation,
|
||||
profile_birth_year=profile_birth_year,
|
||||
profile_era_place=profile_era_place,
|
||||
)
|
||||
if agent_summary_enabled():
|
||||
logger.info(
|
||||
@@ -287,6 +306,12 @@ class ChatOrchestrator:
|
||||
len(turn.messages),
|
||||
turn.skip_tts,
|
||||
)
|
||||
if mem_trace is not None:
|
||||
return AgentChatTurn(
|
||||
messages=turn.messages,
|
||||
skip_tts=turn.skip_tts,
|
||||
memory_retrieval_trace=mem_trace,
|
||||
)
|
||||
return turn
|
||||
|
||||
async def extract_profile_from_message(
|
||||
@@ -349,6 +374,8 @@ class ChatOrchestrator:
|
||||
background_voice: str = "default",
|
||||
normalized_user_message: str | None = None,
|
||||
occupation: str = "",
|
||||
profile_birth_year: int | None = None,
|
||||
profile_era_place: str = "",
|
||||
) -> AgentChatTurn:
|
||||
"""委托 InterviewAgent 生成访谈回复(持久化由调用方负责)。"""
|
||||
return await self.interview_agent.generate_response_with_state(
|
||||
@@ -361,6 +388,8 @@ class ChatOrchestrator:
|
||||
background_voice=background_voice,
|
||||
normalized_user_message=normalized_user_message,
|
||||
occupation=occupation,
|
||||
profile_birth_year=profile_birth_year,
|
||||
profile_era_place=profile_era_place,
|
||||
)
|
||||
|
||||
def detect_user_stage(self, user_message: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user