配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
136 lines
4.6 KiB
Python
136 lines
4.6 KiB
Python
"""
|
||
内部回归评测 API 入口:与 app.main 进程隔离部署。
|
||
|
||
启动示例(在 api/ 目录;端口与 INTERNAL_EVAL_PORT 一致,development.sh 默认 7999)::
|
||
|
||
uv run uvicorn app.internal_main:internal_app --host 0.0.0.0 --port 7999
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from contextlib import asynccontextmanager
|
||
from pathlib import Path
|
||
|
||
from app.core.logging import get_logger, setup_logging
|
||
|
||
setup_logging()
|
||
|
||
from app.core.config import settings
|
||
from app.core.runtime_constants import otel_defaults
|
||
from app.core.telemetry import instrument_fastapi_app, setup_telemetry
|
||
|
||
setup_telemetry(
|
||
service_name=otel_defaults.service_name or "life-echo-internal-api",
|
||
)
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import HTMLResponse
|
||
from fastapi.staticfiles import StaticFiles
|
||
|
||
from app.core.errors import register_exception_handlers
|
||
from app.core.middleware import RequestIdMiddleware
|
||
from app.features.evaluation import models as _eval_models # noqa: F401
|
||
from app.features.evaluation.router import router as eval_router
|
||
from app.features.evaluation.constants import eval_cfg
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
import asyncio
|
||
|
||
from app.core.alembic_startup import run_alembic_upgrade_at_startup
|
||
|
||
logger.info("内部评测 API 启动中…")
|
||
await asyncio.to_thread(run_alembic_upgrade_at_startup)
|
||
try:
|
||
from app.core.celery_broker_dev import maybe_purge_celery_broker_on_startup
|
||
from app.core.redis import redis_service
|
||
|
||
_redis = await redis_service.get_client()
|
||
logger.info("Redis 已连接(评测任务可用)")
|
||
await maybe_purge_celery_broker_on_startup(_redis)
|
||
except Exception as e:
|
||
logger.warning("Redis 连接失败: {}", e)
|
||
|
||
yield
|
||
|
||
logger.info("内部评测 API 关闭中…")
|
||
try:
|
||
from app.core.telemetry import shutdown_telemetry
|
||
|
||
shutdown_telemetry()
|
||
except Exception as e:
|
||
logger.warning("关闭 OpenTelemetry 失败: {}", e)
|
||
try:
|
||
from app.core.redis import redis_service
|
||
|
||
await redis_service.close()
|
||
except Exception as e:
|
||
logger.warning("关闭 Redis 失败: {}", e)
|
||
|
||
|
||
internal_app = FastAPI(
|
||
title="Life Echo Internal Evaluation API",
|
||
version="0.1.0",
|
||
docs_url="/docs" if eval_cfg.internal_enable_docs else None,
|
||
redoc_url="/redoc" if eval_cfg.internal_enable_docs else None,
|
||
openapi_url="/openapi.json" if eval_cfg.internal_enable_docs else None,
|
||
lifespan=lifespan,
|
||
)
|
||
|
||
instrument_fastapi_app(internal_app)
|
||
|
||
internal_app.add_middleware(RequestIdMiddleware)
|
||
_origins = [
|
||
o.strip()
|
||
for o in (eval_cfg.internal_cors_origins or "").split(",")
|
||
if o.strip()
|
||
]
|
||
_allow_creds = bool(_origins)
|
||
internal_app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=_origins if _origins else ["*"],
|
||
allow_credentials=_allow_creds,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
register_exception_handlers(internal_app)
|
||
|
||
|
||
@internal_app.get("/", include_in_schema=False, response_class=HTMLResponse)
|
||
async def internal_eval_landing():
|
||
"""浏览器打开内部评测 API 根路径时提示:界面在 Vite(默认 5174),本进程仅为 API。"""
|
||
docs_hint = (
|
||
'<p><a href="/docs">OpenAPI 文档 /docs</a></p>'
|
||
if eval_cfg.internal_enable_docs
|
||
else "<p>(未开启文档;在 config/development.toml 的 [eval] internal_enable_docs = true)</p>"
|
||
)
|
||
return f"""<!DOCTYPE html>
|
||
<html lang="zh-CN"><head><meta charset="utf-8"/><title>内部评测 API</title></head>
|
||
<body style="font-family:system-ui,sans-serif;max-width:44rem;margin:2rem auto;line-height:1.5">
|
||
<h1>Life Echo · 内部回归评测 API</h1>
|
||
<p>这里是 <strong>HTTP API</strong>(端口由启动命令决定),<strong>没有内置网页</strong>。
|
||
浏览「回归评测台」请在仓库执行 <code>./development.sh</code> 或 <code>cd app-eval-web && npm run dev</code>,
|
||
在终端里打开 Vite 给出的地址(一般为 <strong>http://127.0.0.1:5174/</strong>)。</p>
|
||
<p>健康检查:<a href="/health">/health</a></p>
|
||
{docs_hint}
|
||
<p>会话与对比接口前缀:<code>/internal/api/evaluation/</code></p>
|
||
</body></html>"""
|
||
|
||
|
||
internal_app.include_router(eval_router, prefix="/internal/api/evaluation")
|
||
|
||
_static_dir = Path(__file__).resolve().parent.parent / "static"
|
||
if _static_dir.is_dir():
|
||
internal_app.mount(
|
||
"/static", StaticFiles(directory=str(_static_dir)), name="static"
|
||
)
|
||
|
||
|
||
@internal_app.get("/health", include_in_schema=False)
|
||
async def health():
|
||
return {"status": "ok", "service": "internal-eval"}
|