Files
llm-gateway-sub-backup/workspace/routers/ocr_router.py
2025-08-11 18:56:38 +09:00

169 lines
6.0 KiB
Python

import logging
import httpx
from config.setting import (
MINIO_BUCKET_NAME,
OCR_API_URL,
)
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.responses import JSONResponse
from utils.checking_files import validate_all_files
from utils.checking_keys import create_key
from utils.logging_utils import EndpointLogger
from utils.minio_utils import upload_file_to_minio_v2 # ✅ MinIO 유틸 함수 import
router = APIRouter(prefix="/ocr", tags=["OCR"])
logger = logging.getLogger(__name__)
@router.post(
"",
summary="문서 OCR 요청 (비동기)",
description="""### **요약**
문서 파일(PDF, 이미지 등)을 받아 텍스트를 추출하는 OCR(광학 문자 인식) 작업을 비동기적으로 요청합니다.
### **작동 방식**
1. **요청 접수**: `file`을 받아 고유 `request_id`를 생성하고 즉시 반환합니다.
2. **백그라운드 처리**:
- 업로드된 파일을 내부 저장소(MinIO)에 저장합니다.
- 별도의 OCR 서버에 텍스트 추출 작업을 요청합니다.
3. **상태 및 결과 확인**: 반환된 `request_id`를 사용하여 `GET /ocr/progress/{request_id}`로 작업 상태를, `GET /ocr/result/{request_id}`로 최종 텍스트 결과를 조회할 수 있습니다.
### **입력 (multipart/form-data)**
- `file` (**필수**): 텍스트를 추출할 문서 파일.
- 지원 형식: `.pdf`, `.jpg`, `.png`, `.jpeg` 등 OCR 서버가 지원하는 형식.
### **출력 (application/json)**
- **초기 응답**:
```json
[
{
"request_id": "고유한 요청 ID",
"status": "작업 접수",
"message": "아래 URL을 통해 작업 상태 및 결과를 확인하세요."
}
]
```
- **최종 결과**: `GET /ocr/result/{request_id}`를 통해 확인 가능.
""",
)
async def ocr_only(
file: UploadFile = File(...),
endpoint_logger: EndpointLogger = Depends(EndpointLogger),
):
validate_all_files(file)
results = []
endpoint_logger.log(
model="paddle-ocr",
input_filename=file.filename,
context_length=0, # OCR은 context_length가 필요하지 않음
)
async with httpx.AsyncClient() as client:
# ✅ 1. 고유 ID 생성
request_id = create_key()
bucket_name = MINIO_BUCKET_NAME
object_name = f"{request_id}/{file.filename}"
# ✅ 2. MinIO에 파일 업로드 후 presigned URL 생성
# presigned_url = upload_file_to_minio(file, request_id)
presigned_url = upload_file_to_minio_v2(
file=file, bucket_name=bucket_name, object_name=object_name
)
logger.info(f"[MinIO] ✅ presigned URL 생성 완료: {presigned_url}")
try:
# ✅ 3. OCR API에 presigned URL 전달
resp = await client.post(
OCR_API_URL,
json=[
{
"file_url": presigned_url,
"filename": file.filename,
}
],
timeout=None,
)
resp.raise_for_status()
# except httpx.ReadTimeout:
# logger.error("[OCR] OCR 서버 지연 가능성")
# raise HTTPException(
# status_code=504, detail="OCR 서버 응답이 지연되고 있습니다."
# )
# except httpx.HTTPStatusError as e:
# logger.error(
# f"[OCR] ❌ HTTP 에러 발생: {e.response.status_code} - {e.response.text}"
# )
# raise HTTPException(
# status_code=e.response.status_code, detail="OCR 서버 오류 발생"
# )
except Exception:
logger.exception("[OCR] ❌ 예기치 못한 오류 발생")
raise HTTPException(
status_code=500, detail="OCR 요청 처리 중 내부 오류 발생"
)
# ✅ 4. OCR 응답에서 request_id 추출
for item in resp.json().get("results", []):
ocr_request_id = item.get("request_id")
result_item = {
"request_id": ocr_request_id,
"status": "작업 접수",
"message": "아래 URL을 통해 작업 상태 및 결과를 확인하세요.",
}
results.append(result_item)
return JSONResponse(content=results)
@router.get(
"/progress/{request_id}",
summary="OCR 작업 상태 조회",
description="""### **요약**
`POST /ocr` 요청 시 반환된 `request_id`를 사용하여 OCR 작업의 현재 진행 상태를 조회합니다.
### **작동 방식**
- `request_id`를 OCR 서버에 전달하여 해당 작업의 상태를 가져옵니다.
- 상태는 보통 'PENDING', 'IN_PROGRESS', 'SUCCESS', 'FAILURE' 등으로 표시됩니다.
### **입력**
- `request_id`: 조회할 OCR 작업의 고유 ID.
### **출력 (application/json)**
- **성공 시**:
```json
{
"request_id": "요청 시 사용된 ID",
"progress_logs": [
{ "timestamp": "...", "status": "OCR 시작", "details": "..." },
{ "timestamp": "...", "status": "입력 길이 검사 시작", "details": "..." },
{ "timestamp": "...", "status": "LLM 추론 시작", "details": "..." },
{ "timestamp": "...", "status": "LLM 추론 완료 및 후처리 시작", "details": "..." },
{ "timestamp": "...", "status": "후처리 완료 및 결과 반환"", "details": "..." }
],
"final_result": {
"filename": "입력 파일",
"parsed": "OCR 결과 내용"
}
}
```
- **ID가 유효하지 않을 경우 (404 Not Found)**:
```json
{
"detail": "Meeting ID {request_id} 작업 없음"
}
```
""",
)
async def get_pipeline_status(request_id: str):
try:
async with httpx.AsyncClient() as client:
response = await client.get(f"{OCR_API_URL}/progress/{request_id}")
return JSONResponse(content=response.json(), status_code=response.status_code)
except Exception as e:
raise HTTPException(status_code=500, detail=f"OCR 상태 조회 실패: {str(e)}")