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)}")