ffb9ed3198
The proxy was creating a temporary httpx.AsyncClient within an async context manager. When returning StreamingResponse for SSE endpoints, the client was being closed before the streaming generator could use it, causing "client has been closed" errors. This change introduces a shared httpx.AsyncClient that persists for the application lifecycle, properly cleaned up during shutdown. This also improves performance by reusing TCP connections across requests. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
69 lines
1.9 KiB
Python
69 lines
1.9 KiB
Python
import logging
|
|
from collections.abc import AsyncGenerator
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from src.gateway.config import get_gateway_config
|
|
from src.gateway.routers import artifacts, models, proxy
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
"""Application lifespan handler."""
|
|
config = get_gateway_config()
|
|
logger.info(f"Starting API Gateway on {config.host}:{config.port}")
|
|
logger.info(f"Proxying to LangGraph server at {config.langgraph_url}")
|
|
yield
|
|
logger.info("Shutting down API Gateway")
|
|
# Close the shared HTTP client
|
|
await proxy.close_http_client()
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""Create and configure the FastAPI application.
|
|
|
|
Returns:
|
|
Configured FastAPI application instance.
|
|
"""
|
|
|
|
app = FastAPI(
|
|
title="DeerFlow API Gateway",
|
|
description="API Gateway for DeerFlow - proxies to LangGraph Server and provides custom endpoints",
|
|
version="0.1.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# Add CORS middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Include routers
|
|
# Models API is mounted at /api/models
|
|
app.include_router(models.router)
|
|
|
|
# Artifacts API is mounted at /api/threads/{thread_id}/artifacts
|
|
app.include_router(artifacts.router)
|
|
|
|
# Proxy router handles all LangGraph paths (must be last due to catch-all)
|
|
app.include_router(proxy.router)
|
|
|
|
@app.get("/health")
|
|
async def health_check() -> dict:
|
|
"""Health check endpoint."""
|
|
return {"status": "healthy", "service": "deer-flow-gateway"}
|
|
|
|
return app
|
|
|
|
|
|
# Create app instance for uvicorn
|
|
app = create_app()
|