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>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user