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>
This commit is contained in:
@@ -29,6 +29,31 @@ from app.ports.llm import LLMProvider
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_FOLLOWUP_FALLBACK_ZH = "谢谢分享!能再告诉我一些吗?"
|
||||
_FOLLOWUP_FALLBACK_EN = "Thanks for sharing — could you tell me a bit more?"
|
||||
_GREETING_FALLBACK_ZH = "你好!在开始之前,能告诉我你是哪一年出生的吗?"
|
||||
_GREETING_FALLBACK_EN = (
|
||||
"Hi! Before we get started, could you tell me what year you were born?"
|
||||
)
|
||||
_GREETING_FALLBACK_FULL_ZH = (
|
||||
"你好!在我们开始聊人生故事之前,能先简单介绍一下你自己吗?比如你是哪一年出生的?"
|
||||
)
|
||||
_GREETING_FALLBACK_FULL_EN = (
|
||||
"Hi! Before we dive into life stories, could you introduce yourself a little — for example, what year were you born?"
|
||||
)
|
||||
|
||||
|
||||
def _profile_followup_fallback(language: str) -> str:
|
||||
return _FOLLOWUP_FALLBACK_EN if language == "en" else _FOLLOWUP_FALLBACK_ZH
|
||||
|
||||
|
||||
def _profile_greeting_fallback(language: str) -> str:
|
||||
return _GREETING_FALLBACK_EN if language == "en" else _GREETING_FALLBACK_ZH
|
||||
|
||||
|
||||
def _profile_greeting_fallback_full(language: str) -> str:
|
||||
return _GREETING_FALLBACK_FULL_EN if language == "en" else _GREETING_FALLBACK_FULL_ZH
|
||||
|
||||
|
||||
class _ProviderBackedProfileGateway:
|
||||
def __init__(self, provider: LLMProvider) -> None:
|
||||
@@ -173,6 +198,7 @@ class ProfileAgent:
|
||||
user_message: str,
|
||||
missing_fields: List[str],
|
||||
conversation_id: Optional[str] = None,
|
||||
language: str = "zh",
|
||||
) -> Dict[str, Any]:
|
||||
"""从用户消息中提取资料字段,不持久化"""
|
||||
if not missing_fields:
|
||||
@@ -186,15 +212,20 @@ class ProfileAgent:
|
||||
)
|
||||
recent = hw.window[-4:] if len(hw.window) > 4 else hw.window
|
||||
parts = []
|
||||
user_label = "User" if language == "en" else "用户"
|
||||
asst_label = "Assistant" if language == "en" else "助手"
|
||||
for msg in recent:
|
||||
if isinstance(msg, HumanMessage):
|
||||
parts.append(f"用户: {msg.content}")
|
||||
parts.append(f"{user_label}: {msg.content}")
|
||||
elif isinstance(msg, AIMessage):
|
||||
parts.append(f"助手: {msg.content}")
|
||||
parts.append(f"{asst_label}: {msg.content}")
|
||||
recent_dialogue = "\n".join(parts) if parts else ""
|
||||
try:
|
||||
prompt = get_profile_extraction_prompt(
|
||||
user_message, missing_fields, recent_dialogue=recent_dialogue or None
|
||||
user_message,
|
||||
missing_fields,
|
||||
recent_dialogue=recent_dialogue or None,
|
||||
language=language,
|
||||
)
|
||||
parsed = await self._llm_gateway.json_object(
|
||||
prompt,
|
||||
@@ -241,6 +272,7 @@ class ProfileAgent:
|
||||
filled_fields: Dict[str, str],
|
||||
nickname: str = "",
|
||||
interview_stage_hint: str = "",
|
||||
language: str = "zh",
|
||||
) -> List[str]:
|
||||
"""生成资料追问回复,不持久化(由 Orchestrator 负责)"""
|
||||
try:
|
||||
@@ -249,6 +281,7 @@ class ProfileAgent:
|
||||
filled_fields,
|
||||
nickname,
|
||||
interview_stage_hint=interview_stage_hint,
|
||||
language=language,
|
||||
)
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
@@ -284,7 +317,7 @@ class ProfileAgent:
|
||||
response_text,
|
||||
max_segments=3,
|
||||
max_chars_per_segment=settings.chat_interview_max_chars_per_segment,
|
||||
fallback="谢谢分享!能再告诉我一些吗?",
|
||||
fallback=_profile_followup_fallback(language),
|
||||
)
|
||||
log_agent_summary(
|
||||
logger,
|
||||
@@ -295,17 +328,20 @@ class ProfileAgent:
|
||||
return segments
|
||||
except Exception as e:
|
||||
logger.error("生成资料跟进回复失败: {}", e)
|
||||
return ["谢谢分享!能再告诉我一些吗?"]
|
||||
return [_profile_followup_fallback(language)]
|
||||
|
||||
async def generate_profile_greeting(
|
||||
self,
|
||||
conversation_id: str,
|
||||
missing_fields: List[str],
|
||||
nickname: str = "",
|
||||
language: str = "zh",
|
||||
) -> List[str]:
|
||||
"""生成资料收集开场白,不持久化(由 Orchestrator 负责)"""
|
||||
try:
|
||||
prompt = get_profile_greeting_prompt(missing_fields, nickname)
|
||||
prompt = get_profile_greeting_prompt(
|
||||
missing_fields, nickname, language=language
|
||||
)
|
||||
hw = await get_history_with_window(
|
||||
conversation_id,
|
||||
max_pairs=settings.chat_history_max_pairs,
|
||||
@@ -313,12 +349,19 @@ class ProfileAgent:
|
||||
)
|
||||
messages: List[Any] = [SystemMessage(content=prompt)]
|
||||
messages.extend(hw.window)
|
||||
if hw.window:
|
||||
messages.append(
|
||||
HumanMessage(content="(请根据上文自然接话,继续资料收集开场。)")
|
||||
if language == "en":
|
||||
kickoff = (
|
||||
"(Continue from the context above and warmly carry on the profile-gathering opener.)"
|
||||
if hw.window
|
||||
else "(Please deliver your profile-gathering opener.)"
|
||||
)
|
||||
else:
|
||||
messages.append(HumanMessage(content="(请说出资料收集开场白。)"))
|
||||
kickoff = (
|
||||
"(请根据上文自然接话,继续资料收集开场。)"
|
||||
if hw.window
|
||||
else "(请说出资料收集开场白。)"
|
||||
)
|
||||
messages.append(HumanMessage(content=kickoff))
|
||||
log_agent_payload(
|
||||
logger,
|
||||
"ProfileAgent.greeting.prompt",
|
||||
@@ -345,7 +388,7 @@ class ProfileAgent:
|
||||
response_text,
|
||||
max_segments=2,
|
||||
max_chars_per_segment=settings.chat_interview_max_chars_per_segment,
|
||||
fallback="你好!在开始之前,能告诉我你是哪一年出生的吗?",
|
||||
fallback=_profile_greeting_fallback(language),
|
||||
)
|
||||
log_agent_summary(
|
||||
logger,
|
||||
@@ -356,6 +399,4 @@ class ProfileAgent:
|
||||
return segments
|
||||
except Exception as e:
|
||||
logger.error("生成资料收集开场白失败: {}", e)
|
||||
return [
|
||||
"你好!在我们开始聊人生故事之前,能先简单介绍一下你自己吗?比如你是哪一年出生的?"
|
||||
]
|
||||
return [_profile_greeting_fallback_full(language)]
|
||||
|
||||
Reference in New Issue
Block a user