2026-04-03 14:44:46 +08:00
|
|
|
|
"""
|
|
|
|
|
|
内部回归评测 API 入口:与 app.main 进程隔离部署。
|
|
|
|
|
|
|
2026-04-08 15:37:09 +08:00
|
|
|
|
启动示例(在 api/ 目录;端口与 INTERNAL_EVAL_PORT 一致,development.sh 默认 7999)::
|
2026-04-03 14:44:46 +08:00
|
|
|
|
|
2026-04-08 15:37:09 +08:00
|
|
|
|
uv run uvicorn app.internal_main:internal_app --host 0.0.0.0 --port 7999
|
2026-04-03 14:44:46 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
from app.core.logging import get_logger, setup_logging
|
|
|
|
|
|
|
|
|
|
|
|
setup_logging()
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
2026-04-06 13:45:04 +08:00
|
|
|
|
from fastapi.responses import HTMLResponse
|
2026-04-03 14:44:46 +08:00
|
|
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
|
|
|
|
|
|
|
|
from app.core.config import settings
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
internal_app = FastAPI(
|
|
|
|
|
|
title="Life Echo Internal Evaluation API",
|
|
|
|
|
|
version="0.1.0",
|
|
|
|
|
|
docs_url="/docs" if settings.internal_eval_enable_docs else None,
|
|
|
|
|
|
redoc_url="/redoc" if settings.internal_eval_enable_docs else None,
|
|
|
|
|
|
openapi_url="/openapi.json" if settings.internal_eval_enable_docs else None,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
internal_app.add_middleware(RequestIdMiddleware)
|
|
|
|
|
|
_origins = [
|
|
|
|
|
|
o.strip()
|
|
|
|
|
|
for o in (settings.internal_eval_cors_origins or "").split(",")
|
|
|
|
|
|
if o.strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
# 浏览器不允许 Origin=* 与 credentials 同时出现;未配置显式白名单时关闭 credentials。
|
|
|
|
|
|
_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)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 13:45:04 +08:00
|
|
|
|
@internal_app.get("/", include_in_schema=False, response_class=HTMLResponse)
|
|
|
|
|
|
async def internal_eval_landing():
|
2026-04-08 15:37:09 +08:00
|
|
|
|
"""浏览器打开内部评测 API 根路径时提示:界面在 Vite(默认 5174),本进程仅为 API。"""
|
2026-04-06 13:45:04 +08:00
|
|
|
|
docs_hint = (
|
|
|
|
|
|
'<p><a href="/docs">OpenAPI 文档 /docs</a></p>'
|
|
|
|
|
|
if settings.internal_eval_enable_docs
|
|
|
|
|
|
else "<p>(未开启文档;设置 INTERNAL_EVAL_ENABLE_DOCS=1 后可访问 /docs)</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>./internal-eval.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>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-03 14:44:46 +08:00
|
|
|
|
@internal_app.on_event("startup")
|
|
|
|
|
|
async def _startup():
|
|
|
|
|
|
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:
|
2026-04-08 21:36:12 +08:00
|
|
|
|
from app.core.celery_broker_dev import maybe_purge_celery_broker_on_startup
|
2026-04-03 14:44:46 +08:00
|
|
|
|
from app.core.redis import redis_service
|
|
|
|
|
|
|
2026-04-08 21:36:12 +08:00
|
|
|
|
_redis = await redis_service.get_client()
|
2026-04-03 14:44:46 +08:00
|
|
|
|
logger.info("Redis 已连接(评测任务可用)")
|
2026-04-08 21:36:12 +08:00
|
|
|
|
await maybe_purge_celery_broker_on_startup(_redis)
|
2026-04-03 14:44:46 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning("Redis 连接失败: {}", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@internal_app.on_event("shutdown")
|
|
|
|
|
|
async def _shutdown():
|
|
|
|
|
|
logger.info("内部评测 API 关闭中…")
|
|
|
|
|
|
try:
|
|
|
|
|
|
from app.core.redis import redis_service
|
|
|
|
|
|
|
|
|
|
|
|
await redis_service.close()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning("关闭 Redis 失败: {}", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"}
|