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

@@ -27,7 +27,6 @@ from app.agents.memoir.story_route_agent import (
StoryRouteAgent,
default_append_target_story_id,
)
from app.core.business_telemetry import business_span
from app.agents.stage_constants import (
CATEGORY_TO_CHAT_STAGE,
CHAPTER_CATEGORIES,
@@ -35,6 +34,7 @@ from app.agents.stage_constants import (
STAGE_TO_ORDER,
)
from app.agents.state_schema import MemoirStateSchema
from app.core.business_telemetry import business_span
from app.core.config import settings
from app.core.logging import get_logger
from app.features.conversation.lineage_schemas import aggregate_lineage_from_segments
@@ -61,6 +61,8 @@ from app.features.memoir.repo import (
)
from app.features.memory.evidence_format import format_evidence_chunks_for_prompt
from app.features.story.models import Story, StoryVersion
from app.features.memoir.constants import memoir
from app.features.story.constants import story
from app.features.story.sync_write import (
append_story_version_sync,
count_story_versions_sync,
@@ -267,7 +269,7 @@ def _title_slots_filtered_for_generation(
slot_snippets: dict[str, str], *, md: str, oral_scope: str
) -> dict[str, str]:
"""仅保留与正文或本批口述有文本重叠的 slot降低档案/历史 slot 串台到标题。"""
if not settings.memoir_title_slots_require_body_or_oral_match:
if not memoir.title_slots_require_body_or_oral_match:
return dict(slot_snippets)
hay = f"{(md or '').strip()}\n{(oral_scope or '').strip()}"
if not hay.strip():
@@ -311,7 +313,7 @@ def _strip_ungrounded_title_segments(
"""
按 · / • 分节丢弃含未落地履历短语的小节;全部丢弃则占位。
"""
if not settings.memoir_title_hay_grounding_strict_phrases_enabled:
if not memoir.title_hay_grounding_strict_phrases_enabled:
return (title or "").strip() or _placeholder_title(
chapter_category, language=language
)
@@ -358,7 +360,7 @@ def _maybe_generate_title(
) -> str:
"""Generate a title only when body is long enough; otherwise return placeholder."""
body_len = len((md or "").strip())
if body_len < settings.story_title_min_body_chars:
if body_len < story.title_min_body_chars:
return _placeholder_title(chapter_category, language=language)
content_excerpt = (md or "").strip()[:300]
merged_slots = _title_slots_filtered_for_generation(
@@ -392,8 +394,8 @@ def _route_segment_texts(category_segments: list) -> list[tuple[str, str]]:
for seg in category_segments:
raw = seg.user_input_text or ""
if (
settings.memoir_oral_normalize_enabled
and (settings.memoir_oral_normalize_mode or "rules").strip().lower()
memoir.oral_normalize_enabled
and (memoir.oral_normalize_mode or "rules").strip().lower()
!= "off"
):
t = apply_oral_rules(raw)
@@ -435,7 +437,7 @@ def _gate_narrative_fidelity(
from app.agents.memoir.fidelity_check_agent import FidelityCheckAgent
check_llm = fidelity_llm if fidelity_llm is not None else llm
if not settings.memoir_fidelity_check_enabled or not check_llm:
if not memoir.fidelity_check_enabled or not check_llm:
return narrative_raw, "none"
agent = FidelityCheckAgent()
ex = (existing_canonical or "").strip() or None
@@ -471,7 +473,7 @@ def _apply_narrative_body_safety(
m = (md or "").strip()
ex = (existing_for_narrative or "").strip()
o = (oral or "").strip()
min_len = int(settings.memoir_narrative_evidence_overlap_min_chars)
min_len = int(memoir.narrative_evidence_overlap_min_chars)
ev_plain = strip_evidence_for_overlap_check(evidence_text)
if m and body_contains_prompt_artifact(m):
logger.warning(
@@ -494,7 +496,7 @@ def _apply_narrative_body_safety(
"evidence_leak_heuristic"
)
if (
settings.memoir_evidence_scene_anchor_check_enabled
memoir.evidence_scene_anchor_check_enabled
and m
and evidence_text.strip()
and evidence_scene_anchor_leak(m, ev_plain, o, ex)
@@ -667,8 +669,8 @@ def _resolve_append_target(
memoir_correlation_id: str | None,
) -> tuple[str | None, str, str]:
"""Resolve append target and return (target_story_id, existing_for_narrative, decision_source)."""
max_chars = int(settings.story_append_max_canonical_chars)
max_ver = int(settings.story_append_max_versions)
max_chars = int(story.append_max_canonical_chars)
max_ver = int(story.append_max_versions)
target_story_id: str | None = None
existing_for_narrative = ""
@@ -696,7 +698,7 @@ def _resolve_append_target(
and candidate_stories
and decision_source not in FALLBACK_NEW_STORY_REASONS
and len(oral_norm)
<= int(settings.memoir_story_route_append_guardrail_oral_chars)
<= int(memoir.story_route_append_guardrail_oral_chars)
):
tid_g = default_append_target_story_id(candidate_stories, story_meta, settings)
if tid_g:
@@ -817,7 +819,13 @@ def _execute_narrative_unit(
if target_story_id:
sid_s = str(target_story_id)
ver = append_story_version_sync(session, sid_s, md)
try:
ver = append_story_version_sync(session, sid_s, md)
except ValueError as exc:
logger.warning(
"append_story_version_sync failed story_id={}: {}", sid_s, exc
)
return None
_persist_story_lineage_sync(
session,
story_id=sid_s,
@@ -1049,9 +1057,9 @@ def _run_story_pipeline_batch_inner(
source_ids = [seg.id for seg in category_segments]
n_units = len(category_segments)
top_k = int(settings.evidence_top_k_default)
if n_units > int(settings.evidence_large_batch_threshold):
top_k = int(settings.evidence_top_k_large_batch)
top_k = int(story.evidence_top_k_default)
if n_units > int(story.evidence_large_batch_threshold):
top_k = int(story.evidence_top_k_large_batch)
def _oral_job() -> tuple[str, float]:
with business_span("memoir.story_pipeline.oral_normalize"):
@@ -1178,7 +1186,7 @@ def _run_story_pipeline_batch_inner(
plan is None
and single_route is not None
and single_route.reason in FALLBACK_NEW_STORY_REASONS
and bool(settings.memoir_route_defer_enabled)
and bool(memoir.route_defer_enabled)
):
defer_ids = [str(s.id) for s in category_segments]
logger.info(