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:
Sully
2026-05-22 13:44:50 +08:00
committed by GitHub
parent f09ae248f9
commit 53e0065e3e
298 changed files with 15247 additions and 4344 deletions

View File

@@ -24,7 +24,9 @@ from app.agents.state_schema import (
narrative_coverage_state,
)
from app.core.config import settings
from app.core.db import transactional, transactional_sync
from app.features.memoir.models import MemoirState as MemoirStateModel
from app.features.memoir.constants import memoir
def _slots_snapshot_for_merge(raw: Dict[str, Dict] | None) -> Dict[str, Dict]:
@@ -83,8 +85,8 @@ async def get_or_create_state(user_id: str, db: AsyncSession) -> MemoirStateSche
for k, v in default.slots.items()
},
)
db.add(state)
await db.commit()
async with transactional(db):
db.add(state)
await db.refresh(state)
return coerce_memoir_state(state)
@@ -101,7 +103,7 @@ def _apply_current_stage_policy(
state.current_stage = stage_norm
return
if not settings.memoir_extraction_updates_current_stage:
if not memoir.extraction_updates_current_stage:
return
cur_b = chat_bucket(state.current_stage or current_from_db)
new_b = chat_bucket(stage_norm)
@@ -140,20 +142,20 @@ async def update_slot(
if slot_name not in allowed_slot_names_for_stage(stage_norm, current_from_db):
return coerce_memoir_state(state)
slots = _slots_snapshot_for_merge(
state.slots if isinstance(state.slots, dict) else None
)
stage_slots = dict(slots.get(stage_norm, {}) or {})
existing = stage_slots.get(slot_name, {})
async with transactional(db):
slots = _slots_snapshot_for_merge(
state.slots if isinstance(state.slots, dict) else None
)
stage_slots = dict(slots.get(stage_norm, {}) or {})
existing = stage_slots.get(slot_name, {})
merged_segment_ids = list({*(existing.get("segment_ids") or []), *segment_ids})
stage_slots[slot_name] = SlotData(
snippet=snippet, segment_ids=merged_segment_ids
).model_dump()
slots[stage_norm] = stage_slots
state.slots = slots
_apply_current_stage_policy(state, stage_norm, memoir_batch=memoir_batch)
await db.commit()
merged_segment_ids = list({*(existing.get("segment_ids") or []), *segment_ids})
stage_slots[slot_name] = SlotData(
snippet=snippet, segment_ids=merged_segment_ids
).model_dump()
slots[stage_norm] = stage_slots
state.slots = slots
_apply_current_stage_policy(state, stage_norm, memoir_batch=memoir_batch)
await db.refresh(state)
return coerce_memoir_state(state)
@@ -168,19 +170,19 @@ async def mark_stage_complete(
if not state:
return await get_or_create_state(user_id, db)
covered = state.covered_stages or []
if stage not in covered:
covered.append(stage)
state.covered_stages = covered
async with transactional(db):
covered = state.covered_stages or []
if stage not in covered:
covered.append(stage)
state.covered_stages = covered
stage_order = state.stage_order or default_state().stage_order
if state.current_stage == stage:
try:
idx = stage_order.index(stage)
state.current_stage = stage_order[min(idx + 1, len(stage_order) - 1)]
except ValueError:
state.current_stage = default_state().current_stage
await db.commit()
stage_order = state.stage_order or default_state().stage_order
if state.current_stage == stage:
try:
idx = stage_order.index(stage)
state.current_stage = stage_order[min(idx + 1, len(stage_order) - 1)]
except ValueError:
state.current_stage = default_state().current_stage
await db.refresh(state)
return coerce_memoir_state(state)
@@ -205,11 +207,11 @@ async def switch_stage(
result = await db.execute(stmt)
state = result.scalar_one()
fb = state.current_stage or "childhood"
state.current_stage = normalize_chat_stage(
new_stage, fallback=fb, log_context={"user_id": user_id}
)
await db.commit()
async with transactional(db):
fb = state.current_stage or "childhood"
state.current_stage = normalize_chat_stage(
new_stage, fallback=fb, log_context={"user_id": user_id}
)
await db.refresh(state)
return coerce_memoir_state(state)
@@ -234,10 +236,10 @@ async def save_interview_state_meta(
result = await db.execute(stmt)
state = result.scalar_one()
state.known_facts_json = [x.model_dump() for x in known_facts]
state.persona_threads_json = [x.model_dump() for x in persona_threads]
state.recent_questions_json = list(recent_questions)
await db.commit()
async with transactional(db):
state.known_facts_json = [x.model_dump() for x in known_facts]
state.persona_threads_json = [x.model_dump() for x in persona_threads]
state.recent_questions_json = list(recent_questions)
await db.refresh(state)
return coerce_memoir_state(state)
@@ -261,8 +263,8 @@ def get_or_create_state_sync(user_id: str, db: Session) -> MemoirStateSchema:
for k, v in default.slots.items()
},
)
db.add(state)
db.commit()
with transactional_sync(db):
db.add(state)
db.refresh(state)
return coerce_memoir_state(state)
@@ -298,19 +300,19 @@ def update_slot_sync(
if slot_name not in allowed_slot_names_for_stage(stage_norm, current_from_db):
return coerce_memoir_state(state)
slots = _slots_snapshot_for_merge(
state.slots if isinstance(state.slots, dict) else None
)
stage_slots = dict(slots.get(stage_norm, {}) or {})
existing = stage_slots.get(slot_name, {})
with transactional_sync(db):
slots = _slots_snapshot_for_merge(
state.slots if isinstance(state.slots, dict) else None
)
stage_slots = dict(slots.get(stage_norm, {}) or {})
existing = stage_slots.get(slot_name, {})
merged_segment_ids = list({*(existing.get("segment_ids") or []), *segment_ids})
stage_slots[slot_name] = SlotData(
snippet=snippet, segment_ids=merged_segment_ids
).model_dump()
slots[stage_norm] = stage_slots
state.slots = slots
_apply_current_stage_policy(state, stage_norm, memoir_batch=memoir_batch)
db.commit()
merged_segment_ids = list({*(existing.get("segment_ids") or []), *segment_ids})
stage_slots[slot_name] = SlotData(
snippet=snippet, segment_ids=merged_segment_ids
).model_dump()
slots[stage_norm] = stage_slots
state.slots = slots
_apply_current_stage_policy(state, stage_norm, memoir_batch=memoir_batch)
db.refresh(state)
return coerce_memoir_state(state)