feat(api+app): 对话阶段化、回忆录流水线与客户端会话体验
- DB: segments 用户输入文本(Alembic 0002) - Chat: 阶段检测/阶段提示/回复限制,编排与访谈/画像 prompts 调整 - Memoir: 忠实度检查 agent,叙事与分类等链路更新 - Core: agent 日志、Alembic 启动、LangChain/日志/配置等 - Story: time_hints;Memory 检索与相关测试 - Expo: 助手头像、会话页与消息拆分、实时会话与文案/i18n - Docs/scripts/tests: 迁移脚本、LLM JSON/记忆检索文档、新增单测
This commit is contained in:
@@ -3,6 +3,7 @@ ChatOrchestrator:AI 回复用户模块的编排层
|
||||
负责路由(Profile vs Interview)、调用 Specialist Agent;持久化由 feature 层 ConversationHistoryStore 完成。
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
@@ -12,8 +13,13 @@ from app.agents.chat.agent_turn import AgentChatTurn
|
||||
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.features.memoir.state_service import get_or_create_state
|
||||
from app.agents.chat.stage_detection import (
|
||||
detect_primary_life_stage,
|
||||
life_stage_display_name,
|
||||
)
|
||||
from app.features.memoir.state_service import get_or_create_state, switch_stage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.features.user.models import User
|
||||
@@ -54,12 +60,21 @@ class ChatOrchestrator:
|
||||
处理用户消息,返回 AI 回复(分段 + 是否跳过 TTS)。
|
||||
根据 missing_fields 路由到 ProfileAgent 或 InterviewAgent。
|
||||
"""
|
||||
t0 = time.perf_counter()
|
||||
|
||||
# --- 资料收集模式 ---
|
||||
if user:
|
||||
missing = get_missing_profile_fields_fn(user)
|
||||
if missing:
|
||||
try:
|
||||
log_agent_detail(
|
||||
logger,
|
||||
"ChatOrchestrator route=profile conversation_id={} "
|
||||
"missing_fields={} user_msg_len={}",
|
||||
conversation_id,
|
||||
missing,
|
||||
len(user_message or ""),
|
||||
)
|
||||
extracted = await self.profile_agent.extract_profile_from_message(
|
||||
user_message, missing, conversation_id=conversation_id
|
||||
)
|
||||
@@ -68,13 +83,26 @@ class ChatOrchestrator:
|
||||
|
||||
remaining = get_missing_profile_fields_fn(user)
|
||||
filled = get_filled_profile_fields_fn(user)
|
||||
interview_stage_hint = ""
|
||||
if not remaining:
|
||||
st = await get_or_create_state(user.id, db)
|
||||
interview_stage_hint = life_stage_display_name(st.current_stage)
|
||||
responses = await self.profile_agent.generate_profile_followup(
|
||||
conversation_id=conversation_id,
|
||||
user_message=user_message,
|
||||
missing_fields=remaining,
|
||||
filled_fields=filled,
|
||||
nickname=user.nickname or "",
|
||||
interview_stage_hint=interview_stage_hint,
|
||||
)
|
||||
if agent_summary_enabled():
|
||||
logger.info(
|
||||
"ChatOrchestrator.process_user_message route=profile "
|
||||
"duration_ms={:.2f} conversation_id={} response_segments={}",
|
||||
(time.perf_counter() - t0) * 1000,
|
||||
conversation_id,
|
||||
len(responses),
|
||||
)
|
||||
return AgentChatTurn(messages=responses, skip_tts=False)
|
||||
except Exception as e:
|
||||
logger.error(f"资料收集处理失败: {e}", exc_info=True)
|
||||
@@ -82,9 +110,30 @@ class ChatOrchestrator:
|
||||
# --- 正式访谈模式 ---
|
||||
user_id = user.id if user else None
|
||||
if not user_id:
|
||||
if agent_summary_enabled():
|
||||
logger.info(
|
||||
"ChatOrchestrator.process_user_message route=unauth "
|
||||
"duration_ms={:.2f} conversation_id={}",
|
||||
(time.perf_counter() - t0) * 1000,
|
||||
conversation_id,
|
||||
)
|
||||
return _UNAUTH_TURN
|
||||
|
||||
log_agent_detail(
|
||||
logger,
|
||||
"ChatOrchestrator route=interview conversation_id={} user_msg_len={}",
|
||||
conversation_id,
|
||||
len(user_message or ""),
|
||||
)
|
||||
state = await get_or_create_state(user_id, db)
|
||||
detected = await detect_primary_life_stage(
|
||||
user_message,
|
||||
state.current_stage,
|
||||
self.interview_agent.llm,
|
||||
)
|
||||
if detected != state.current_stage:
|
||||
state = await switch_stage(user_id, detected, db)
|
||||
|
||||
if conversation and conversation.conversation_stage != state.current_stage:
|
||||
conversation.conversation_stage = state.current_stage
|
||||
await db.commit()
|
||||
@@ -100,12 +149,24 @@ class ChatOrchestrator:
|
||||
occupation=user.occupation,
|
||||
)
|
||||
|
||||
return await self.interview_agent.generate_response_with_state(
|
||||
turn = await self.interview_agent.generate_response_with_state(
|
||||
conversation_id=conversation_id,
|
||||
user_message=user_message,
|
||||
memoir_state=state,
|
||||
user_profile_context=user_profile_context,
|
||||
detected_user_stage=detected,
|
||||
)
|
||||
if agent_summary_enabled():
|
||||
logger.info(
|
||||
"ChatOrchestrator.process_user_message route=interview "
|
||||
"duration_ms={:.2f} conversation_id={} stage={} response_segments={} skip_tts={}",
|
||||
(time.perf_counter() - t0) * 1000,
|
||||
conversation_id,
|
||||
state.current_stage,
|
||||
len(turn.messages),
|
||||
turn.skip_tts,
|
||||
)
|
||||
return turn
|
||||
|
||||
async def extract_profile_from_message(
|
||||
self,
|
||||
@@ -162,6 +223,7 @@ class ChatOrchestrator:
|
||||
voice_session_id: str | None = None,
|
||||
user_message_timestamp: datetime | None = None,
|
||||
audio_duration_seconds: int | None = None,
|
||||
detected_user_stage: str | None = None,
|
||||
) -> AgentChatTurn:
|
||||
"""委托 InterviewAgent 生成访谈回复(持久化由调用方负责)。"""
|
||||
return await self.interview_agent.generate_response_with_state(
|
||||
@@ -169,6 +231,7 @@ class ChatOrchestrator:
|
||||
user_message=user_message,
|
||||
memoir_state=memoir_state,
|
||||
user_profile_context=user_profile_context,
|
||||
detected_user_stage=detected_user_stage,
|
||||
)
|
||||
|
||||
def detect_user_stage(self, user_message: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user