feat(api): vertical slice POST /v1/agent/chat and the Celery ping path
One request exercises every layer: JWT auth, quota reservation, a versioned prompt through the LLM gateway, a cost row, quota commit or refund, and the unified envelope. The trivial ping task proves the queue routing and the tk_task transitions before any real long-running task is written. Routes are mounted at the /api/v1 base path of 架构文档 §9.1; /v1/* is rewritten to it at the ASGI layer so both spellings work from one route table.
This commit is contained in:
Executable
+78
@@ -0,0 +1,78 @@
|
||||
"""FastAPI application factory (架构文档 §3.1 L3 API 层).
|
||||
|
||||
The API layer only orchestrates: auth, validation, idempotency, quota,
|
||||
unified response, OpenAPI. Business rules live in ``packages/<domain>``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from apps.api.deps import build_llm_gateway
|
||||
from apps.api.routers import agent, auth, health, quota, tasks
|
||||
from packages.shared.config import Settings, get_settings
|
||||
from packages.shared.db import dispose_engine
|
||||
from packages.shared.handlers import install_exception_handlers
|
||||
from packages.shared.logging import configure_logging, get_logger
|
||||
from packages.shared.middleware import RequestContextMiddleware, install_version_alias
|
||||
from packages.shared.observability import setup_tracing
|
||||
from packages.shared.redis_client import close_redis
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
settings: Settings = get_settings()
|
||||
app.state.llm_gateway = build_llm_gateway(settings)
|
||||
logger.info(
|
||||
"api_started",
|
||||
env=settings.app_env,
|
||||
api_prefix=settings.api_prefix,
|
||||
llm_primary=settings.llm_primary_provider,
|
||||
llm_model=settings.deepseek_model,
|
||||
)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
await app.state.llm_gateway.aclose()
|
||||
await close_redis()
|
||||
await dispose_engine()
|
||||
logger.info("api_stopped")
|
||||
|
||||
|
||||
def create_app(settings: Settings | None = None) -> FastAPI:
|
||||
cfg = settings or get_settings()
|
||||
configure_logging(
|
||||
level=cfg.log_level, json_output=cfg.log_json, extra_redact_keys=cfg.extra_redact_keys
|
||||
)
|
||||
|
||||
app = FastAPI(
|
||||
title="美搭 backend",
|
||||
version="0.1.0",
|
||||
description=(
|
||||
"AI 穿搭美学助手服务端。统一响应见架构文档 §9.2;"
|
||||
"所有业务接口在 " + cfg.api_prefix + " 之下(/v1 为等价别名)。"
|
||||
),
|
||||
openapi_url=f"{cfg.api_prefix}/openapi.json",
|
||||
docs_url=f"{cfg.api_prefix}/docs" if cfg.is_local else None,
|
||||
redoc_url=None,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
install_version_alias(app, cfg.api_prefix)
|
||||
app.add_middleware(RequestContextMiddleware)
|
||||
install_exception_handlers(app)
|
||||
setup_tracing(app, cfg)
|
||||
|
||||
app.include_router(health.router)
|
||||
for router in (auth.router, agent.router, quota.router, tasks.router):
|
||||
app.include_router(router, prefix=cfg.api_prefix)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
Reference in New Issue
Block a user