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:
@@ -45,6 +45,8 @@ from app.features.conversation.ws.profile_collector import (
|
||||
get_filled_profile_fields,
|
||||
get_missing_profile_fields,
|
||||
)
|
||||
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.memoir.background_runner import BackgroundTaskRunner
|
||||
from app.features.memoir.ingest_scheduler import MemoirIngestScheduler
|
||||
from app.features.user.models import User
|
||||
@@ -1107,6 +1109,19 @@ async def process_user_message(
|
||||
if i < n - 1:
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
if user is not None:
|
||||
try:
|
||||
fresh_memoir = await get_or_create_state(user.id, db)
|
||||
await maybe_send_topic_chips_ws(
|
||||
conversation_id,
|
||||
user=user,
|
||||
memoir_state=fresh_memoir,
|
||||
reason="after_assistant_turn",
|
||||
language=user_language,
|
||||
)
|
||||
except Exception as chip_err:
|
||||
logger.warning("after-turn topic chips skipped: {}", chip_err)
|
||||
|
||||
if tts_urls:
|
||||
await store.attach_ai_tts_audio_urls(
|
||||
conversation_id,
|
||||
|
||||
@@ -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
|
||||
# 资料未齐时不送 chips:profile 收集走另一条流程,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)
|
||||
|
||||
70
api/app/features/conversation/ws/topic_chips_push.py
Normal file
70
api/app/features/conversation/ws/topic_chips_push.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""WebSocket 下发话题 chips(opening / resume / 每轮助手回复后)。"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.agents.chat.prompts_conversation import build_topic_chips
|
||||
from app.agents.state_schema import (
|
||||
MemoirStateSchema,
|
||||
interview_control_state,
|
||||
narrative_coverage_state,
|
||||
)
|
||||
from app.core.config import settings
|
||||
from app.core.logging import get_logger
|
||||
from app.features.conversation.ws.connection_manager import manager
|
||||
from app.features.conversation.ws.message_types import MessageType
|
||||
from app.features.conversation.ws.profile_collector import get_missing_profile_fields
|
||||
from app.features.user.models import User
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
||||
async def maybe_send_topic_chips_ws(
|
||||
conversation_id: str,
|
||||
*,
|
||||
user: User,
|
||||
memoir_state: MemoirStateSchema,
|
||||
reason: str,
|
||||
language: str,
|
||||
) -> None:
|
||||
"""资料齐备且开关开启时,按当前回忆录阶段下发 topic_suggestions。失败静默。"""
|
||||
if not settings.chat_topic_chips_enabled:
|
||||
return
|
||||
if get_missing_profile_fields(user):
|
||||
return
|
||||
stage = (memoir_state.current_stage or "").strip() or "childhood"
|
||||
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, stage
|
||||
)
|
||||
chips = build_topic_chips(
|
||||
stage,
|
||||
empty_slots,
|
||||
max_chips=settings.chat_topic_chips_max,
|
||||
language=language,
|
||||
)
|
||||
if not chips:
|
||||
return
|
||||
await manager.send_message(
|
||||
conversation_id,
|
||||
{
|
||||
"type": MessageType.TOPIC_SUGGESTIONS,
|
||||
"conversation_id": conversation_id,
|
||||
"data": {
|
||||
"reason": reason,
|
||||
"stage": stage,
|
||||
"suggestions": chips,
|
||||
},
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
)
|
||||
log.info(
|
||||
"event=ws_topic_chips_sent reason={} conversation_id={} stage={} count={}",
|
||||
reason,
|
||||
conversation_id,
|
||||
stage,
|
||||
len(chips),
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning("发送话题 chips 失败: {}", e)
|
||||
Reference in New Issue
Block a user