169 lines
6.0 KiB
Python
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)}")
|