""" Extraction sidecar — FastAPI app running LangExtract. Internal service, not exposed externally. Called by the Fastify extraction-service. Default port: 4006. """ from __future__ import annotations import os import structlog import uvicorn from fastapi import FastAPI, HTTPException, Request from .cache import extraction_cache from .extractor import extract from .models import ( BatchExtractRequest, ExtractRequest, ExtractResponse, HealthResponse, ) logger = structlog.get_logger(__name__) app = FastAPI( title="Extraction Sidecar", description="Internal LangExtract sidecar for extraction-service", version="0.1.0", ) @app.get("/health") async def health(): base = HealthResponse() return {**base.model_dump(), "cache": extraction_cache.stats} @app.post("/extract", response_model=ExtractResponse) async def extract_endpoint(req: ExtractRequest, request: Request) -> ExtractResponse: request_id = request.headers.get("x-request-id", "") logger.info( "extract_request", task_id=req.task_id, model_id=req.model_id, text_length=len(req.text), request_id=request_id, ) # Check cache first cached = extraction_cache.get(req.text, req.task_id, req.model_id) if cached is not None: logger.info("cache_hit", task_id=req.task_id, request_id=request_id) return cached try: result = await extract( text=req.text, task_prompt=req.task_prompt, examples=[e.model_dump() for e in req.examples] if req.examples else None, model_id=req.model_id, extraction_passes=req.extraction_passes, max_workers=req.max_workers, max_char_buffer=req.max_char_buffer, ) extraction_cache.put(req.text, req.task_id, req.model_id, result) return result except Exception as exc: logger.error("extract_failed", error=str(exc), request_id=request_id) raise HTTPException(status_code=500, detail=str(exc)) from exc @app.post("/extract/batch", response_model=list[ExtractResponse]) async def extract_batch_endpoint( batch: BatchExtractRequest, request: Request, ) -> list[ExtractResponse]: request_id = request.headers.get("x-request-id", "") logger.info( "batch_extract_request", input_count=len(batch.requests), request_id=request_id, ) results: list[ExtractResponse] = [] for req in batch.requests: try: result = await extract( text=req.text, task_prompt=req.task_prompt, examples=[e.model_dump() for e in req.examples] if req.examples else None, model_id=req.model_id, extraction_passes=req.extraction_passes, max_workers=req.max_workers, max_char_buffer=req.max_char_buffer, ) results.append(result) except Exception as exc: logger.error("batch_item_failed", error=str(exc), request_id=request_id) raise HTTPException(status_code=500, detail=str(exc)) from exc return results def main() -> None: port = int(os.environ.get("SIDECAR_PORT", "4006")) host = os.environ.get("SIDECAR_HOST", "0.0.0.0") logger.info("sidecar_starting", port=port, host=host) uvicorn.run( "src.app:app", host=host, port=port, reload=os.environ.get("NODE_ENV") == "development", log_level="info", ) if __name__ == "__main__": main()