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

@@ -12,6 +12,7 @@ from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.core.db import transactional
from app.core.dependencies import (
EvalJudgeProvider,
build_eval_judge_llm_spec,
@@ -47,6 +48,8 @@ from app.features.evaluation.transcript_for_judge import (
from app.features.evaluation.user_export_fixtures import read_user_export_fixture
from app.features.memoir.repo import get_chapters_for_memoir_list
from app.features.story.repo import get_stories_for_user
from app.features.evaluation.constants import eval_cfg
from app.features.memoir.constants import memoir
logger = get_logger(__name__)
@@ -55,8 +58,7 @@ _MAX_EVAL_STORIES = 40 # memoir_snapshot 等仍限幅
_PRIOR_TRANSCRIPT_MAX_CHARS = 8000
_JUDGE_CONFIG_HINT = (
"评审未配置:智谱需 eval_judge_api_key 或 zhipu_api_key"
"DeepSeek 需 deepseek_api_key或 llm_api_key"
"评审未配置:智谱需 ZHIPU_API_KEYDeepSeek 需 DEEPSEEK_API_KEY"
)
@@ -126,7 +128,7 @@ def _clip_md_for_judge(text: str, max_chars: int | None = None) -> str:
cap = (
max_chars
if max_chars is not None
else max(1000, int(settings.eval_judge_memoir_body_max_chars))
else max(1000, int(eval_cfg.judge_memoir_body_max_chars))
)
s = (text or "").strip()
if len(s) <= cap:
@@ -170,11 +172,12 @@ class EvalJudgeManualService:
self, conversation_id: str, bundle: dict[str, Any]
) -> None:
try:
row = await conversation_repo.set_playground_conversation_judge_json(
conversation_id, self._db, bundle
)
if row is not None:
await self._db.commit()
async with transactional(self._db):
row = await conversation_repo.set_playground_conversation_judge_json(
conversation_id, self._db, bundle
)
if row is None:
return
except Exception:
logger.exception(
"persist playground_conversation_judge_json failed conversation_id={}",
@@ -717,7 +720,7 @@ class EvalJudgeManualService:
if (getattr(x, "canonical_markdown", None) or "").strip()
)
conc = max(1, min(32, int(settings.eval_judge_memoir_chapter_concurrency)))
conc = max(1, min(32, int(eval_cfg.judge_memoir_chapter_concurrency)))
logger.info(
"event=eval_memoir_judge_start user_id={} judge_provider={} judge_model={} "
"chapters_total={} chapters_nonempty={} chapter_concurrency={}",
@@ -741,7 +744,7 @@ class EvalJudgeManualService:
baseline_excerpt = _clip_md_for_judge(
bl.body,
max_chars=max(
1000, int(settings.eval_judge_memoir_evidence_max_chars)
1000, int(eval_cfg.judge_memoir_evidence_max_chars)
),
)
md = f"# 章节:{ch.title}\n\n{_clip_md_for_judge(body)}"
@@ -959,7 +962,7 @@ class EvalJudgeManualService:
baseline_excerpt = _clip_md_for_judge(
bl.body,
max_chars=max(
1000, int(settings.eval_judge_memoir_evidence_max_chars)
1000, int(eval_cfg.judge_memoir_evidence_max_chars)
),
)
md = f"# 章节:{ch.title}\n\n{_clip_md_for_judge(body)}"
@@ -999,7 +1002,7 @@ class EvalJudgeManualService:
yield {"event": "chapters_prepared", "count": len(prepared)}
conc = max(1, min(32, int(settings.eval_judge_memoir_chapter_concurrency)))
conc = max(1, min(32, int(eval_cfg.judge_memoir_chapter_concurrency)))
sem = asyncio.Semaphore(conc)
result_queue: asyncio.Queue[dict[str, Any] | None] = asyncio.Queue()