refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
This commit is contained in:
@@ -12,13 +12,11 @@ from starlette.websockets import WebSocketState
|
||||
|
||||
from app.agents.chat.background_voice import infer_background_voice
|
||||
from app.agents.chat.prompts_profile import format_user_profile_context
|
||||
from app.agents.stage_constants import STAGE_TO_ORDER
|
||||
from app.core.config import settings
|
||||
from app.core.db import AsyncSessionLocal
|
||||
from app.core.dependencies import get_asr_provider
|
||||
from app.core.logging import get_logger
|
||||
from app.core.security import verify_token
|
||||
from app.features.conversation.history_store import ConversationHistoryStore
|
||||
from app.features.conversation.service import ConversationService
|
||||
from app.features.conversation.ws.connection_manager import manager
|
||||
from app.features.conversation.ws.message_types import MessageType
|
||||
@@ -30,7 +28,6 @@ from app.features.conversation.ws.pipeline import (
|
||||
cleanup_segment_states,
|
||||
get_or_create_segment_state,
|
||||
handle_tts_request_on_demand,
|
||||
memoir_ingest_scheduler,
|
||||
process_audio_segment,
|
||||
process_conversation_segments,
|
||||
process_persisted_user_segment_response,
|
||||
@@ -40,9 +37,10 @@ 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.memoir.service import MemoirService
|
||||
from app.features.quota.service import QuotaService
|
||||
from app.features.user.models import User
|
||||
from app.features.user.service import UserService
|
||||
from app.features.conversation.constants import chat
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -92,7 +90,8 @@ async def websocket_endpoint(
|
||||
return
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
user = await db.get(User, user_id)
|
||||
user_service = UserService(db)
|
||||
user = await user_service.get_by_id(user_id)
|
||||
if not user:
|
||||
await websocket.close(
|
||||
code=status.WS_1008_POLICY_VIOLATION, reason="用户不存在"
|
||||
@@ -108,6 +107,7 @@ async def websocket_endpoint(
|
||||
|
||||
quota_service = QuotaService(db=db)
|
||||
conversation_service = ConversationService(db=db, quota_service=quota_service)
|
||||
memoir_service = MemoirService(db=db)
|
||||
|
||||
try:
|
||||
await manager.send_message(
|
||||
@@ -158,15 +158,10 @@ async def websocket_endpoint(
|
||||
|
||||
# 冷启动对齐 conversation_stage 与 MemoirState.current_stage;
|
||||
# 若对话行已有更靠前的人生阶段(STAGE_TO_ORDER 更大),不覆盖以免回退。
|
||||
memoir_state = await get_or_create_state(user_id, db)
|
||||
ms = (memoir_state.current_stage or "").strip()
|
||||
cs = (conversation.conversation_stage or "").strip()
|
||||
if ms:
|
||||
if not cs:
|
||||
conversation.conversation_stage = ms
|
||||
elif STAGE_TO_ORDER.get(ms, -1) >= STAGE_TO_ORDER.get(cs, -1):
|
||||
conversation.conversation_stage = ms
|
||||
await db.commit()
|
||||
memoir_state = await memoir_service.get_or_create_memoir_state(user_id)
|
||||
await conversation_service.align_conversation_stage_from_memoir(
|
||||
conversation, memoir_state.current_stage or ""
|
||||
)
|
||||
await db.refresh(conversation)
|
||||
|
||||
history = await conversation_service.ensure_redis_history_from_db(
|
||||
@@ -184,7 +179,7 @@ async def websocket_endpoint(
|
||||
"""统一:把一组 AI 消息落库并按 [SPLIT] 分段下发。"""
|
||||
if not texts:
|
||||
return
|
||||
ai_msg_id = await ConversationHistoryStore(db).record_ai_only_turn(
|
||||
ai_msg_id = await conversation_service.record_ai_only_turn(
|
||||
conversation_id, texts
|
||||
)
|
||||
if not ai_msg_id:
|
||||
@@ -271,9 +266,9 @@ async def websocket_endpoint(
|
||||
else:
|
||||
# 历史非空:判断是否需要回访问候(距上次消息超过阈值)
|
||||
idle_hours = _idle_hours_since(conversation.last_message_at)
|
||||
threshold = float(settings.chat_re_greeting_idle_hours)
|
||||
threshold = float(chat.re_greeting_idle_hours)
|
||||
if (
|
||||
settings.chat_re_greeting_enabled
|
||||
chat.re_greeting_enabled
|
||||
and not get_missing_profile_fields(user)
|
||||
and idle_hours is not None
|
||||
and idle_hours >= threshold
|
||||
@@ -383,11 +378,6 @@ async def websocket_endpoint(
|
||||
user_id,
|
||||
text_message,
|
||||
)
|
||||
await memoir_ingest_scheduler.queue_segment(
|
||||
conversation.user_id,
|
||||
segment.id,
|
||||
text_char_count=len(text_message.strip()),
|
||||
)
|
||||
|
||||
task = asyncio.create_task(
|
||||
process_persisted_user_segment_response(
|
||||
@@ -645,11 +635,6 @@ async def websocket_endpoint(
|
||||
audio_duration_seconds=ads if ads > 0 else None,
|
||||
)
|
||||
)
|
||||
await memoir_ingest_scheduler.queue_segment(
|
||||
conversation.user_id,
|
||||
segment.id,
|
||||
text_char_count=len((asr_text or "").strip()),
|
||||
)
|
||||
|
||||
if asr_text and not asr_text.startswith("转写失败"):
|
||||
task = asyncio.create_task(
|
||||
|
||||
Reference in New Issue
Block a user