feat(conversation): push topic chips after each assistant turn

- Extract maybe_send_topic_chips_ws for WS connect + pipeline reuse
- Default memoir stage to childhood when empty for chip bank lookup
- Resend suggestions after normal assistant reply (English/Chinese)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-12 11:16:39 +08:00
parent d155e45a44
commit b97bb64b4a
3 changed files with 93 additions and 48 deletions

View File

@@ -11,13 +11,8 @@ from fastapi import WebSocket, WebSocketDisconnect, status
from starlette.websockets import WebSocketState
from app.agents.chat.background_voice import infer_background_voice
from app.agents.chat.prompts_conversation import build_topic_chips
from app.agents.chat.prompts_profile import format_user_profile_context
from app.agents.stage_constants import STAGE_TO_ORDER
from app.agents.state_schema import (
interview_control_state,
narrative_coverage_state,
)
from app.core.config import settings
from app.core.db import AsyncSessionLocal
from app.core.dependencies import get_asr_provider
@@ -44,6 +39,7 @@ from app.features.conversation.ws.pipeline import (
)
from app.features.conversation.ws.profile_collector import get_missing_profile_fields
from app.features.conversation.ws.quota_guard import check_ws_quota
from app.features.conversation.ws.topic_chips_push import maybe_send_topic_chips_ws
from app.features.memoir.state_service import get_or_create_state
from app.features.quota.service import QuotaService
from app.features.user.models import User
@@ -219,49 +215,13 @@ async def websocket_endpoint(
)
async def _maybe_send_topic_chips(reason: str) -> None:
"""根据当前阶段空 slot 生成 quick-start 话题 chips失败静默。"""
if not settings.chat_topic_chips_enabled:
return
# 资料未齐时不送 chipsprofile 收集走另一条流程chips 反而噪音
if get_missing_profile_fields(user):
return
try:
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
)
chips = build_topic_chips(
memoir_state.current_stage,
empty_slots,
max_chips=settings.chat_topic_chips_max,
language=user_language,
)
if not chips:
return
await manager.send_message(
conversation_id,
{
"type": MessageType.TOPIC_SUGGESTIONS,
"conversation_id": conversation_id,
"data": {
"reason": reason,
"stage": memoir_state.current_stage,
"suggestions": chips,
},
"timestamp": datetime.now(timezone.utc).isoformat(),
},
)
logger.info(
"event=ws_topic_chips_sent reason={} conversation_id={} "
"stage={} count={}",
reason,
conversation_id,
memoir_state.current_stage,
len(chips),
)
except Exception as e:
logger.warning("发送话题 chips 失败: {}", e)
await maybe_send_topic_chips_ws(
conversation_id,
user=user,
memoir_state=memoir_state,
reason=reason,
language=user_language,
)
if not history:
missing_profile = get_missing_profile_fields(user)