- 访谈:新增 interview_state_hints,联动 orchestrator 与提示词 - 回忆录:story_pipeline_sync/state/memory/post_commit 与 Celery 任务调整 - 基建:开发用 celery broker、compose/development 脚本、依赖注入 - eval-web:移除数据集/实验/版本等页面与流式轮询,突出 Playground - 文档与单测同步
119 lines
4.2 KiB
Python
119 lines
4.2 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 pathlib import Path
|
||
|
||
from app.core.logging import get_logger, setup_logging
|
||
|
||
setup_logging()
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import HTMLResponse
|
||
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)
|
||
|
||
|
||
@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 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>"""
|
||
|
||
|
||
@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:
|
||
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)
|
||
|
||
|
||
@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"}
|