Files
Fu DaiandClaude Opus 4.8 1a585693be Suya OCR API — vLLM-backed, OpenAI-compatible OCR service
FastAPI service wrapping the Surya-OCR-2 model (datalab-to) served through vLLM:
legacy /v1/api/ai/* endpoints, an OpenAI-compatible /v1/chat/completions endpoint,
a coalescing request batcher, a local OCR CLI, Docker packaging, multilingual
example outputs, and quantization/concurrency benchmarks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:20:02 +04:00

98 lines
2.8 KiB
Python

import json
import logging
import logging.config
import time
import uuid
import yaml
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from surya.endpoint.legacy import router as legacy_router
from surya.endpoint.openai import router as openai_router
from surya.endpoint.service import _error_response
with open("logger.yaml", "r", encoding="utf-8") as f:
config = yaml.safe_load(f.read())
logging.config.dictConfig(config)
logger = logging.getLogger(__name__)
app = FastAPI(
title="OCR SERVICE API SERVICE",
version="1.0",
docs_url="/v1/api/ai/swagger",
openapi_url="/v1/api/ai/openapi.json",
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
request_id = getattr(request.state, "request_id", "-")
logger.warning(
"request_validation_failed request_id=%s method=%s path=%s errors=%s",
request_id,
request.method,
request.url.path,
exc.errors(),
)
return JSONResponse(
status_code=200,
content={
"data": [],
"message": json.dumps({"error info": exc.errors()}, default=str),
"code": 422,
},
)
@app.middleware("http")
async def allow_openapi_unauthorized(request: Request, call_next):
if request.url.path == "/v1/api/ai/openapi.json":
response = await call_next(request)
return response
return await call_next(request)
@app.middleware("http")
async def log_request_response(request, call_next):
request_id = uuid.uuid4().hex
request.state.request_id = request_id
start = time.perf_counter()
client = request.client.host if request.client else "-"
logger.info(
"request_start request_id=%s method=%s path=%s client=%s",
request_id,
request.method,
request.url.path,
client,
)
try:
response = await call_next(request)
except Exception as e:
duration_ms = (time.perf_counter() - start) * 1000
logger.exception(
"request_unhandled_exception request_id=%s method=%s path=%s duration_ms=%.2f",
request_id,
request.method,
request.url.path,
duration_ms,
exc_info=True,
)
return JSONResponse(status_code=500, content=_error_response(e))
duration_ms = (time.perf_counter() - start) * 1000
logger.info(
"request_end request_id=%s method=%s path=%s status_code=%s duration_ms=%.2f",
request_id,
request.method,
request.url.path,
response.status_code,
duration_ms,
)
return response
app.include_router(legacy_router)
app.include_router(openai_router)