2026-03-19 10:36:55 +08:00
|
|
|
|
"""
|
|
|
|
|
|
ChatOrchestrator:AI 回复用户模块的编排层
|
2026-03-22 16:45:57 +08:00
|
|
|
|
负责路由(Profile vs Interview)、调用 Specialist Agent;持久化由 feature 层 ConversationHistoryStore 完成。
|
2026-03-19 10:36:55 +08:00
|
|
|
|
"""
|
2026-03-19 14:36:14 +08:00
|
|
|
|
|
2026-03-26 12:13:36 +08:00
|
|
|
|
import time
|
2026-04-30 09:17:01 +08:00
|
|
|
|
from collections.abc import Callable
|
2026-03-19 10:36:55 +08:00
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from typing import TYPE_CHECKING, List, Optional
|
|
|
|
|
|
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
2026-03-20 17:25:42 +08:00
|
|
|
|
from app.agents.chat.agent_turn import AgentChatTurn
|
2026-04-06 22:22:50 +08:00
|
|
|
|
from app.agents.chat.helpers import get_history_with_window
|
2026-03-19 10:36:55 +08:00
|
|
|
|
from app.agents.chat.interview_agent import InterviewAgent
|
2026-04-08 21:36:12 +08:00
|
|
|
|
from app.agents.chat.interview_state_hints import (
|
|
|
|
|
|
build_runtime_interview_state,
|
|
|
|
|
|
extract_scene_cues,
|
|
|
|
|
|
)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
from app.agents.chat.profile_agent import ProfileAgent
|
2026-03-26 12:13:36 +08:00
|
|
|
|
from app.agents.chat.stage_detection import (
|
|
|
|
|
|
detect_primary_life_stage,
|
|
|
|
|
|
life_stage_display_name,
|
|
|
|
|
|
)
|
2026-04-08 15:37:09 +08:00
|
|
|
|
from app.agents.state_schema import MemoirStateSchema
|
|
|
|
|
|
from app.core.agent_logging import agent_summary_enabled, log_agent_detail
|
2026-03-31 23:55:26 +08:00
|
|
|
|
from app.core.config import settings
|
2026-04-30 09:17:01 +08:00
|
|
|
|
from app.core.dependencies import get_embedding_provider
|
|
|
|
|
|
from app.core.llm_gateway import LlmGateway
|
2026-04-08 15:37:09 +08:00
|
|
|
|
from app.core.logging import get_logger
|
2026-04-06 22:22:50 +08:00
|
|
|
|
from app.features.conversation.input_normalize import normalize_chat_input_for_agent
|
2026-04-08 21:36:12 +08:00
|
|
|
|
from app.features.memoir.state_service import (
|
|
|
|
|
|
get_or_create_state,
|
|
|
|
|
|
save_interview_state_meta,
|
|
|
|
|
|
switch_stage,
|
|
|
|
|
|
)
|
2026-04-30 09:17:01 +08:00
|
|
|
|
from app.features.memory.prompt_adapter import MemoryPromptAdapter
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
2026-03-31 23:55:26 +08:00
|
|
|
|
|
|
|
|
|
|
def _llm_for_chat_input_normalize():
|
|
|
|
|
|
try:
|
2026-04-30 09:17:01 +08:00
|
|
|
|
return LlmGateway().langchain_llm_for()
|
2026-03-31 23:55:26 +08:00
|
|
|
|
except Exception:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 10:36:55 +08:00
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
|
from app.features.user.models import User
|
2026-04-30 09:17:01 +08:00
|
|
|
|
from app.ports.embedding import EmbeddingProvider
|
|
|
|
|
|
from app.ports.llm import LLMProvider
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
_UNAUTH_TURN_ZH = AgentChatTurn(
|
2026-03-20 17:25:42 +08:00
|
|
|
|
messages=["暂时没法继续对话,请先登录后再试。"], skip_tts=True
|
|
|
|
|
|
)
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
_UNAUTH_TURN_EN = AgentChatTurn(
|
|
|
|
|
|
messages=["You'll need to sign in again before we can continue."],
|
|
|
|
|
|
skip_tts=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _user_language(user: Optional["User"]) -> str:
|
|
|
|
|
|
if not user:
|
|
|
|
|
|
return "zh"
|
|
|
|
|
|
lang = getattr(user, "language_preference", None) or "zh"
|
|
|
|
|
|
return "en" if str(lang).lower() == "en" else "zh"
|
2026-03-20 17:25:42 +08:00
|
|
|
|
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
2026-04-22 16:56:28 +08:00
|
|
|
|
async def _fetch_interview_memory_bundle(
|
2026-03-31 23:55:26 +08:00
|
|
|
|
db: AsyncSession,
|
|
|
|
|
|
user_id: str,
|
|
|
|
|
|
user_message: str,
|
2026-04-30 09:17:01 +08:00
|
|
|
|
*,
|
|
|
|
|
|
get_embedding_provider_fn: Callable[[], "EmbeddingProvider"],
|
2026-04-22 16:56:28 +08:00
|
|
|
|
) -> tuple[dict | None, object | None]:
|
2026-04-30 09:17:01 +08:00
|
|
|
|
"""检索记忆 bundle(原始结构);是否进主 prompt 由 adapter 再筛。"""
|
2026-04-08 15:37:09 +08:00
|
|
|
|
from app.features.memory.retrieval_trace import (
|
|
|
|
|
|
chat_memory_retrieval_trace_from_bundle,
|
|
|
|
|
|
)
|
2026-03-31 23:55:26 +08:00
|
|
|
|
from app.features.memory.service import MemoryService
|
|
|
|
|
|
|
|
|
|
|
|
if not settings.chat_memory_retrieval_enabled:
|
2026-04-03 13:49:24 +08:00
|
|
|
|
logger.debug(
|
|
|
|
|
|
"event=chat_memory_retrieval_skip reason=disabled user_id={}", user_id
|
|
|
|
|
|
)
|
2026-04-22 16:56:28 +08:00
|
|
|
|
return None, None
|
2026-03-31 23:55:26 +08:00
|
|
|
|
msg = (user_message or "").strip()
|
|
|
|
|
|
if not msg:
|
2026-04-03 13:49:24 +08:00
|
|
|
|
logger.debug(
|
|
|
|
|
|
"event=chat_memory_retrieval_skip reason=empty user_id={}", user_id
|
|
|
|
|
|
)
|
2026-04-22 16:56:28 +08:00
|
|
|
|
return None, None
|
2026-03-31 23:55:26 +08:00
|
|
|
|
try:
|
2026-04-30 09:17:01 +08:00
|
|
|
|
emb = get_embedding_provider_fn()
|
2026-04-03 11:43:16 +08:00
|
|
|
|
ms = MemoryService(db, embedding_provider=emb)
|
2026-04-08 15:37:09 +08:00
|
|
|
|
top_k = settings.chat_memory_top_k
|
|
|
|
|
|
bundle = await ms.retrieve(user_id, msg, top_k=top_k)
|
2026-04-03 11:43:16 +08:00
|
|
|
|
bd = bundle.model_dump()
|
2026-04-08 15:37:09 +08:00
|
|
|
|
trace = chat_memory_retrieval_trace_from_bundle(
|
|
|
|
|
|
bd, top_k=top_k, query_len=len(msg)
|
|
|
|
|
|
)
|
2026-04-03 13:49:24 +08:00
|
|
|
|
logger.info(
|
2026-04-22 16:56:28 +08:00
|
|
|
|
"event=memory_retrieval_bundle user_id={} top_k={}",
|
2026-04-03 13:49:24 +08:00
|
|
|
|
user_id,
|
2026-04-22 16:56:28 +08:00
|
|
|
|
top_k,
|
2026-04-03 13:49:24 +08:00
|
|
|
|
)
|
2026-04-22 16:56:28 +08:00
|
|
|
|
return bd, trace
|
2026-03-31 23:55:26 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
try:
|
|
|
|
|
|
await db.rollback()
|
|
|
|
|
|
except Exception as rollback_error:
|
|
|
|
|
|
logger.warning("访谈记忆检索失败后回滚也失败: {}", rollback_error)
|
|
|
|
|
|
logger.warning("访谈记忆检索失败: {}", e)
|
2026-04-22 16:56:28 +08:00
|
|
|
|
return None, None
|
2026-03-31 23:55:26 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 10:36:55 +08:00
|
|
|
|
class ChatOrchestrator:
|
|
|
|
|
|
"""
|
2026-03-22 16:45:57 +08:00
|
|
|
|
聊天编排器:根据用户资料完成度路由到 ProfileAgent 或 InterviewAgent。
|
|
|
|
|
|
不直接写入 Redis/DB;由 WS pipeline / ConversationHistoryStore 落库并同步缓存。
|
2026-04-30 09:17:01 +08:00
|
|
|
|
|
|
|
|
|
|
``get_embedding_provider_fn`` / ``llm_provider`` 供测试或脚本注入;默认使用全局依赖。
|
2026-03-19 10:36:55 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-04-30 09:17:01 +08:00
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
*,
|
|
|
|
|
|
get_embedding_provider_fn: Callable[[], "EmbeddingProvider"] | None = None,
|
|
|
|
|
|
llm_provider: "LLMProvider | None" = None,
|
|
|
|
|
|
):
|
|
|
|
|
|
self._get_embedding_provider_fn = (
|
|
|
|
|
|
get_embedding_provider_fn or get_embedding_provider
|
|
|
|
|
|
)
|
|
|
|
|
|
self.profile_agent = ProfileAgent(llm_provider=llm_provider)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
self.interview_agent = InterviewAgent()
|
2026-04-30 09:17:01 +08:00
|
|
|
|
self.memory_prompt_adapter = MemoryPromptAdapter()
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
async def process_user_message(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
user_message: str,
|
|
|
|
|
|
user: Optional["User"],
|
|
|
|
|
|
conversation, # 用于更新 conversation_stage
|
|
|
|
|
|
is_from_voice: bool,
|
|
|
|
|
|
voice_session_id: Optional[str],
|
|
|
|
|
|
db: AsyncSession,
|
|
|
|
|
|
apply_extracted_profile_fn,
|
|
|
|
|
|
get_missing_profile_fields_fn,
|
|
|
|
|
|
get_filled_profile_fields_fn,
|
|
|
|
|
|
user_message_timestamp: Optional[datetime] = None,
|
2026-03-20 16:36:42 +08:00
|
|
|
|
audio_duration_seconds: Optional[int] = None,
|
2026-03-20 17:25:42 +08:00
|
|
|
|
) -> AgentChatTurn:
|
2026-03-19 10:36:55 +08:00
|
|
|
|
"""
|
2026-03-20 17:25:42 +08:00
|
|
|
|
处理用户消息,返回 AI 回复(分段 + 是否跳过 TTS)。
|
2026-03-22 16:45:57 +08:00
|
|
|
|
根据 missing_fields 路由到 ProfileAgent 或 InterviewAgent。
|
2026-03-19 10:36:55 +08:00
|
|
|
|
"""
|
2026-03-26 12:13:36 +08:00
|
|
|
|
t0 = time.perf_counter()
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language = _user_language(user)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
# --- 资料收集模式 ---
|
|
|
|
|
|
if user:
|
|
|
|
|
|
missing = get_missing_profile_fields_fn(user)
|
|
|
|
|
|
if missing:
|
2026-04-06 22:22:50 +08:00
|
|
|
|
hw_profile = await get_history_with_window(
|
|
|
|
|
|
conversation_id,
|
|
|
|
|
|
max_pairs=settings.chat_history_max_pairs,
|
|
|
|
|
|
max_chars=settings.chat_history_max_chars,
|
|
|
|
|
|
)
|
|
|
|
|
|
profile_turn_total = hw_profile.turn_total
|
|
|
|
|
|
if profile_turn_total >= settings.chat_profile_max_turns:
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"event=chat_profile_cap_skip conversation_id={} "
|
|
|
|
|
|
"turn_total={} cap={} missing_fields={}",
|
2026-03-26 12:13:36 +08:00
|
|
|
|
conversation_id,
|
2026-04-06 22:22:50 +08:00
|
|
|
|
profile_turn_total,
|
|
|
|
|
|
settings.chat_profile_max_turns,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
missing,
|
|
|
|
|
|
)
|
2026-04-06 22:22:50 +08:00
|
|
|
|
else:
|
|
|
|
|
|
try:
|
|
|
|
|
|
log_agent_detail(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
"ChatOrchestrator route=profile conversation_id={} "
|
|
|
|
|
|
"missing_fields={} user_msg_len={} profile_turn_total={}",
|
|
|
|
|
|
conversation_id,
|
|
|
|
|
|
missing,
|
|
|
|
|
|
len(user_message or ""),
|
|
|
|
|
|
profile_turn_total,
|
2026-04-03 10:12:59 +08:00
|
|
|
|
)
|
2026-04-06 22:22:50 +08:00
|
|
|
|
# Profile 阶段每轮都抽取:短确认语也可能带可推断资料,跳过抽取会导致槽位长期不更新
|
2026-04-03 10:12:59 +08:00
|
|
|
|
extracted = (
|
|
|
|
|
|
await self.profile_agent.extract_profile_from_message(
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
user_message,
|
|
|
|
|
|
missing,
|
|
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
language=language,
|
2026-04-03 10:12:59 +08:00
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
logger.info(
|
2026-04-06 22:22:50 +08:00
|
|
|
|
"event=chat_profile_extract conversation_id={} "
|
|
|
|
|
|
"extracted_keys={} missing_before={}",
|
2026-03-26 12:13:36 +08:00
|
|
|
|
conversation_id,
|
2026-04-06 22:22:50 +08:00
|
|
|
|
list(extracted.keys()) if extracted else [],
|
|
|
|
|
|
missing,
|
|
|
|
|
|
)
|
|
|
|
|
|
if extracted:
|
|
|
|
|
|
await apply_extracted_profile_fn(user, extracted, db)
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
st.current_stage, language=language
|
2026-04-06 22:22:50 +08:00
|
|
|
|
)
|
|
|
|
|
|
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,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=language,
|
2026-04-06 22:22:50 +08:00
|
|
|
|
)
|
|
|
|
|
|
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),
|
|
|
|
|
|
)
|
2026-04-08 15:37:09 +08:00
|
|
|
|
return AgentChatTurn(
|
2026-04-09 15:32:35 +08:00
|
|
|
|
messages=responses,
|
|
|
|
|
|
skip_tts=False,
|
|
|
|
|
|
memory_retrieval_trace=None,
|
2026-04-08 15:37:09 +08:00
|
|
|
|
)
|
2026-04-06 22:22:50 +08:00
|
|
|
|
except Exception as e:
|
feat(eval): memoir A/B chapter judging and eval-web parity with dialogue
- Judge baseline excerpt and library chapter separately; build_memoir_compare_summary for gate, nine-dim and leaf deltas.
- Memoir SSE chapter payload: baseline_judge, compare_summary, baseline_judge_error.
- MemoirJudgeOutput: loose score coercion and post-validate clamp; memoir judge prompt caps from settings.
- app-eval-web: two-column MemoirScoreCard layout, MemoirCompareSummary, chapter blocks and CSS.
- Add memoir_compare_summary, log_events, celery_log_context, memoir_pipeline_progress; tests and migration 0014.
- Misc: memory/evidence and enrichment paths, task/orchestrator updates, internal-eval docs, env examples.
2026-04-10 10:23:43 +08:00
|
|
|
|
logger.exception("资料收集处理失败: {}", e)
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
fb_msg = (
|
|
|
|
|
|
"Sorry, I missed that. Could you say it again?"
|
|
|
|
|
|
if language == "en"
|
|
|
|
|
|
else "不好意思刚才没接住,你再说一遍好吗?"
|
|
|
|
|
|
)
|
2026-04-06 22:22:50 +08:00
|
|
|
|
return AgentChatTurn(
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
messages=[fb_msg],
|
2026-04-06 22:22:50 +08:00
|
|
|
|
skip_tts=False,
|
2026-04-08 15:37:09 +08:00
|
|
|
|
memory_retrieval_trace=None,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
# --- 正式访谈模式 ---
|
|
|
|
|
|
user_id = user.id if user else None
|
|
|
|
|
|
if not user_id:
|
2026-03-26 12:13:36 +08:00
|
|
|
|
if agent_summary_enabled():
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
"ChatOrchestrator.process_user_message route=unauth "
|
|
|
|
|
|
"duration_ms={:.2f} conversation_id={}",
|
|
|
|
|
|
(time.perf_counter() - t0) * 1000,
|
|
|
|
|
|
conversation_id,
|
|
|
|
|
|
)
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
return _UNAUTH_TURN_EN if language == "en" else _UNAUTH_TURN_ZH
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
2026-03-26 12:13:36 +08:00
|
|
|
|
log_agent_detail(
|
|
|
|
|
|
logger,
|
|
|
|
|
|
"ChatOrchestrator route=interview conversation_id={} user_msg_len={}",
|
|
|
|
|
|
conversation_id,
|
|
|
|
|
|
len(user_message or ""),
|
|
|
|
|
|
)
|
2026-03-31 23:55:26 +08:00
|
|
|
|
llm_n = None
|
|
|
|
|
|
if settings.chat_input_normalize_enabled and (
|
|
|
|
|
|
(settings.chat_input_normalize_mode or "").strip().lower() == "llm"
|
|
|
|
|
|
):
|
|
|
|
|
|
llm_n = _llm_for_chat_input_normalize()
|
|
|
|
|
|
normalized_user_message = normalize_chat_input_for_agent(
|
|
|
|
|
|
user_message or "",
|
|
|
|
|
|
llm=llm_n,
|
2026-04-03 10:12:59 +08:00
|
|
|
|
is_from_voice=is_from_voice,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
state = await get_or_create_state(user_id, db)
|
2026-04-10 13:56:44 +08:00
|
|
|
|
stage_before = state.current_stage
|
2026-03-26 12:13:36 +08:00
|
|
|
|
detected = await detect_primary_life_stage(
|
2026-03-31 23:55:26 +08:00
|
|
|
|
normalized_user_message,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
state.current_stage,
|
|
|
|
|
|
self.interview_agent.llm,
|
|
|
|
|
|
)
|
2026-04-10 13:56:44 +08:00
|
|
|
|
stage_switched_this_turn = detected != stage_before
|
|
|
|
|
|
if stage_switched_this_turn:
|
2026-03-26 12:13:36 +08:00
|
|
|
|
state = await switch_stage(user_id, detected, db)
|
|
|
|
|
|
|
2026-03-19 10:36:55 +08:00
|
|
|
|
if conversation and conversation.conversation_stage != state.current_stage:
|
|
|
|
|
|
conversation.conversation_stage = state.current_stage
|
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
|
2026-03-31 23:55:26 +08:00
|
|
|
|
from app.agents.chat.background_voice import infer_background_voice
|
2026-03-19 10:54:48 +08:00
|
|
|
|
from app.agents.chat.prompts_profile import format_user_profile_context
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
user_profile_context = ""
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice = "default"
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation = ""
|
2026-03-19 10:36:55 +08:00
|
|
|
|
if user:
|
|
|
|
|
|
user_profile_context = format_user_profile_context(
|
|
|
|
|
|
birth_year=user.birth_year,
|
|
|
|
|
|
birth_place=user.birth_place,
|
|
|
|
|
|
grew_up_place=user.grew_up_place,
|
|
|
|
|
|
occupation=user.occupation,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=language,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice = infer_background_voice(user.occupation)
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation = user.occupation or ""
|
2026-03-31 23:55:26 +08:00
|
|
|
|
|
2026-04-22 16:56:28 +08:00
|
|
|
|
memory_bundle, mem_trace = await _fetch_interview_memory_bundle(
|
2026-04-30 09:17:01 +08:00
|
|
|
|
db,
|
|
|
|
|
|
user_id,
|
|
|
|
|
|
normalized_user_message,
|
|
|
|
|
|
get_embedding_provider_fn=self._get_embedding_provider_fn,
|
|
|
|
|
|
)
|
|
|
|
|
|
mem_slices = self.memory_prompt_adapter.slice_for_interview(
|
|
|
|
|
|
memory_bundle,
|
|
|
|
|
|
normalized_user_message,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
)
|
2026-04-22 16:56:28 +08:00
|
|
|
|
# 场景关键词仅作为 focus planner 的辅助输入,不直接拼进记忆块,避免抢过用户明确的关系/身份线索
|
|
|
|
|
|
scene_cues_for_planner = extract_scene_cues(normalized_user_message)
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
2026-04-08 15:37:09 +08:00
|
|
|
|
profile_birth_year = user.birth_year if user else None
|
|
|
|
|
|
profile_era_place = ""
|
|
|
|
|
|
if user:
|
2026-04-09 15:32:35 +08:00
|
|
|
|
profile_era_place = (user.birth_place or user.grew_up_place or "").strip()
|
2026-04-08 21:36:12 +08:00
|
|
|
|
prompt_state = build_runtime_interview_state(
|
|
|
|
|
|
state,
|
|
|
|
|
|
user_message=normalized_user_message,
|
|
|
|
|
|
active_stage=detected or state.current_stage,
|
|
|
|
|
|
birth_year=profile_birth_year,
|
|
|
|
|
|
birth_place=(user.birth_place or "").strip() if user else "",
|
|
|
|
|
|
grew_up_place=(user.grew_up_place or "").strip() if user else "",
|
|
|
|
|
|
occupation=occupation,
|
|
|
|
|
|
)
|
2026-04-08 15:37:09 +08:00
|
|
|
|
|
2026-03-26 12:13:36 +08:00
|
|
|
|
turn = await self.interview_agent.generate_response_with_state(
|
2026-03-19 10:36:55 +08:00
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
user_message=user_message,
|
2026-04-08 21:36:12 +08:00
|
|
|
|
memoir_state=prompt_state,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
user_profile_context=user_profile_context,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
detected_user_stage=detected,
|
2026-04-22 16:56:28 +08:00
|
|
|
|
memory_evidence_text=mem_slices.prompt_excerpt,
|
|
|
|
|
|
memory_anchor_source=mem_slices.anchor_source,
|
|
|
|
|
|
memory_planner_text=mem_slices.planner_preview,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice=background_voice,
|
|
|
|
|
|
normalized_user_message=normalized_user_message,
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation=occupation,
|
2026-04-08 15:37:09 +08:00
|
|
|
|
profile_birth_year=profile_birth_year,
|
|
|
|
|
|
profile_era_place=profile_era_place,
|
2026-04-10 13:56:44 +08:00
|
|
|
|
stage_switched_this_turn=stage_switched_this_turn,
|
2026-04-22 16:56:28 +08:00
|
|
|
|
scene_cues_for_planner=scene_cues_for_planner,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=language,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
2026-04-08 21:36:12 +08:00
|
|
|
|
recent_questions = prompt_state.recent_questions
|
|
|
|
|
|
if turn.interview_state_meta and isinstance(turn.interview_state_meta, dict):
|
|
|
|
|
|
raw_recent = turn.interview_state_meta.get("recent_questions")
|
|
|
|
|
|
if isinstance(raw_recent, list):
|
|
|
|
|
|
recent_questions = [
|
|
|
|
|
|
str(x).strip() for x in raw_recent if str(x).strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
await save_interview_state_meta(
|
|
|
|
|
|
user_id,
|
|
|
|
|
|
known_facts=prompt_state.known_facts,
|
|
|
|
|
|
persona_threads=prompt_state.persona_threads,
|
|
|
|
|
|
recent_questions=recent_questions,
|
|
|
|
|
|
db=db,
|
|
|
|
|
|
)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
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,
|
|
|
|
|
|
)
|
2026-04-08 15:37:09 +08:00
|
|
|
|
if mem_trace is not None:
|
|
|
|
|
|
return AgentChatTurn(
|
|
|
|
|
|
messages=turn.messages,
|
|
|
|
|
|
skip_tts=turn.skip_tts,
|
|
|
|
|
|
memory_retrieval_trace=mem_trace,
|
2026-04-08 21:36:12 +08:00
|
|
|
|
interview_state_meta=turn.interview_state_meta,
|
2026-04-08 15:37:09 +08:00
|
|
|
|
)
|
2026-03-26 12:13:36 +08:00
|
|
|
|
return turn
|
2026-03-19 10:36:55 +08:00
|
|
|
|
|
|
|
|
|
|
async def extract_profile_from_message(
|
|
|
|
|
|
self,
|
|
|
|
|
|
user_message: str,
|
|
|
|
|
|
missing_fields: List[str],
|
|
|
|
|
|
conversation_id: Optional[str] = None,
|
|
|
|
|
|
):
|
|
|
|
|
|
"""委托 ProfileAgent 提取资料"""
|
|
|
|
|
|
return await self.profile_agent.extract_profile_from_message(
|
|
|
|
|
|
user_message, missing_fields, conversation_id=conversation_id
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_profile_followup(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
user_message: str,
|
|
|
|
|
|
missing_fields: List[str],
|
|
|
|
|
|
filled_fields: dict,
|
|
|
|
|
|
nickname: str = "",
|
|
|
|
|
|
is_from_voice: bool = False,
|
|
|
|
|
|
voice_session_id: str | None = None,
|
|
|
|
|
|
user_message_timestamp: datetime | None = None,
|
2026-03-20 16:36:42 +08:00
|
|
|
|
audio_duration_seconds: int | None = None,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: str = "zh",
|
2026-03-19 10:36:55 +08:00
|
|
|
|
) -> List[str]:
|
2026-03-22 16:45:57 +08:00
|
|
|
|
"""委托 ProfileAgent 生成资料追问(持久化由调用方负责)。"""
|
|
|
|
|
|
return await self.profile_agent.generate_profile_followup(
|
2026-03-19 10:36:55 +08:00
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
user_message=user_message,
|
|
|
|
|
|
missing_fields=missing_fields,
|
|
|
|
|
|
filled_fields=filled_fields,
|
|
|
|
|
|
nickname=nickname,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=language,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_profile_greeting(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
missing_fields: List[str],
|
|
|
|
|
|
nickname: str = "",
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: str = "zh",
|
2026-03-19 10:36:55 +08:00
|
|
|
|
) -> List[str]:
|
2026-03-22 16:45:57 +08:00
|
|
|
|
"""委托 ProfileAgent 生成资料收集开场白(持久化由调用方负责)。"""
|
|
|
|
|
|
return await self.profile_agent.generate_profile_greeting(
|
2026-03-19 10:36:55 +08:00
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
missing_fields=missing_fields,
|
|
|
|
|
|
nickname=nickname,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=language,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_response_with_state(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
user_message: str,
|
|
|
|
|
|
memoir_state: MemoirStateSchema,
|
|
|
|
|
|
user_profile_context: str = "",
|
|
|
|
|
|
is_from_voice: bool = False,
|
|
|
|
|
|
voice_session_id: str | None = None,
|
|
|
|
|
|
user_message_timestamp: datetime | None = None,
|
2026-03-20 16:36:42 +08:00
|
|
|
|
audio_duration_seconds: int | None = None,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
detected_user_stage: str | None = None,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
memory_evidence_text: str = "",
|
2026-04-22 16:56:28 +08:00
|
|
|
|
memory_anchor_source: str = "",
|
|
|
|
|
|
memory_planner_text: str = "",
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice: str = "default",
|
|
|
|
|
|
normalized_user_message: str | None = None,
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation: str = "",
|
2026-04-08 15:37:09 +08:00
|
|
|
|
profile_birth_year: int | None = None,
|
|
|
|
|
|
profile_era_place: str = "",
|
2026-04-10 13:56:44 +08:00
|
|
|
|
stage_switched_this_turn: bool = False,
|
2026-04-22 16:56:28 +08:00
|
|
|
|
scene_cues_for_planner: Optional[list[str]] = None,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: str = "zh",
|
2026-03-20 17:25:42 +08:00
|
|
|
|
) -> AgentChatTurn:
|
2026-03-22 16:45:57 +08:00
|
|
|
|
"""委托 InterviewAgent 生成访谈回复(持久化由调用方负责)。"""
|
|
|
|
|
|
return await self.interview_agent.generate_response_with_state(
|
2026-03-19 10:36:55 +08:00
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
user_message=user_message,
|
|
|
|
|
|
memoir_state=memoir_state,
|
|
|
|
|
|
user_profile_context=user_profile_context,
|
2026-03-26 12:13:36 +08:00
|
|
|
|
detected_user_stage=detected_user_stage,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
memory_evidence_text=memory_evidence_text,
|
2026-04-22 16:56:28 +08:00
|
|
|
|
memory_anchor_source=memory_anchor_source,
|
|
|
|
|
|
memory_planner_text=memory_planner_text,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice=background_voice,
|
|
|
|
|
|
normalized_user_message=normalized_user_message,
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation=occupation,
|
2026-04-08 15:37:09 +08:00
|
|
|
|
profile_birth_year=profile_birth_year,
|
|
|
|
|
|
profile_era_place=profile_era_place,
|
2026-04-10 13:56:44 +08:00
|
|
|
|
stage_switched_this_turn=stage_switched_this_turn,
|
2026-04-22 16:56:28 +08:00
|
|
|
|
scene_cues_for_planner=scene_cues_for_planner,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=language,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def detect_user_stage(self, user_message: str) -> str:
|
|
|
|
|
|
"""委托 InterviewAgent 检测用户阶段"""
|
|
|
|
|
|
return self.interview_agent._detect_user_stage(user_message)
|
|
|
|
|
|
|
|
|
|
|
|
async def generate_opening_message(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
memoir_state: MemoirStateSchema,
|
|
|
|
|
|
user_profile_context: str = "",
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice: str = "default",
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation: str = "",
|
2026-04-08 17:10:09 +08:00
|
|
|
|
profile_birth_year: Optional[int] = None,
|
|
|
|
|
|
profile_era_place: str = "",
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language: str = "zh",
|
2026-03-19 10:36:55 +08:00
|
|
|
|
) -> List[str]:
|
2026-03-20 15:15:35 +08:00
|
|
|
|
"""
|
2026-03-22 16:45:57 +08:00
|
|
|
|
委托 InterviewAgent 生成访谈开场白(持久化由调用方 ConversationHistoryStore 负责)。
|
2026-03-20 15:15:35 +08:00
|
|
|
|
"""
|
2026-03-22 16:45:57 +08:00
|
|
|
|
return await self.interview_agent.generate_opening_message(
|
2026-03-19 10:36:55 +08:00
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
memoir_state=memoir_state,
|
|
|
|
|
|
user_profile_context=user_profile_context,
|
2026-03-31 23:55:26 +08:00
|
|
|
|
background_voice=background_voice,
|
2026-04-01 11:49:33 +08:00
|
|
|
|
occupation=occupation,
|
2026-04-08 17:10:09 +08:00
|
|
|
|
profile_birth_year=profile_birth_year,
|
|
|
|
|
|
profile_era_place=profile_era_place,
|
feat(i18n): persist language preference and thread through chat, memoir, TTS
- Add users.language_preference (Alembic 0018, default zh); capture at signup/SMS
only; expose on auth and profile APIs
- Lite English prompts for chat and memoir; localized stage labels and agent
names (Life Echo / 岁月知己)
- Tencent TTS: language-aware synthesis, ModelType=1 for 501004, English chunking
- WebSocket pipeline: emit all AGENT_RESPONSE segments when TTS cancels; INFO logs
for tts_this_turn and TTS decisions; on-demand TTS logging
- Expo: device language on auth, i18n tiers/agent name, [SPLIT] streaming UX fixes
- Tests for migration, prompts, pipeline, router tts_this_turn, reply segments
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:16:49 +08:00
|
|
|
|
language=language,
|
2026-03-19 10:36:55 +08:00
|
|
|
|
)
|
2026-05-07 15:39:33 +00:00
|
|
|
|
|
|
|
|
|
|
async def generate_re_greeting_message(
|
|
|
|
|
|
self,
|
|
|
|
|
|
conversation_id: str,
|
|
|
|
|
|
memoir_state: MemoirStateSchema,
|
|
|
|
|
|
idle_hours: float,
|
|
|
|
|
|
user_profile_context: str = "",
|
|
|
|
|
|
background_voice: str = "default",
|
|
|
|
|
|
occupation: str = "",
|
|
|
|
|
|
profile_birth_year: Optional[int] = None,
|
|
|
|
|
|
profile_era_place: str = "",
|
2026-05-12 11:02:58 +08:00
|
|
|
|
language: str = "zh",
|
2026-05-07 15:39:33 +00:00
|
|
|
|
) -> List[str]:
|
|
|
|
|
|
"""委托 InterviewAgent 生成老对话回访问候(持久化由调用方负责)。"""
|
|
|
|
|
|
return await self.interview_agent.generate_re_greeting_message(
|
|
|
|
|
|
conversation_id=conversation_id,
|
|
|
|
|
|
memoir_state=memoir_state,
|
|
|
|
|
|
idle_hours=idle_hours,
|
|
|
|
|
|
user_profile_context=user_profile_context,
|
|
|
|
|
|
background_voice=background_voice,
|
|
|
|
|
|
occupation=occupation,
|
|
|
|
|
|
profile_birth_year=profile_birth_year,
|
|
|
|
|
|
profile_era_place=profile_era_place,
|
2026-05-12 11:02:58 +08:00
|
|
|
|
language=language,
|
2026-05-07 15:39:33 +00:00
|
|
|
|
)
|