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

282 lines
11 KiB
Python

import asyncio
import json
import logging
import os
import shutil
from pathlib import Path
from typing import List, Optional
from config.setting import (
DEFAULT_PROMPT_PATH,
MINIO_BUCKET_NAME,
PGN_REDIS_DB,
PGN_REDIS_HOST,
PGN_REDIS_PORT,
STRUCTURED_PROMPT_PATH,
UPLOAD_DIR,
)
from fastapi import HTTPException, UploadFile
from redis import Redis
from utils.logging_utils import log_user_request
from utils.minio_utils import save_result_to_minio, upload_file_to_minio_v2
from utils.prompt_cache import compute_file_hash, save_prompt_file_if_not_exists
from services.dummy_service import DummyService
from services.model_service import ModelInfoService
from services.pipeline_runner import PipelineRunner
# Redis 클라이언트 (LLM Gateway 전용)
redis_client = Redis(
host=PGN_REDIS_HOST, port=PGN_REDIS_PORT, db=PGN_REDIS_DB, decode_responses=True
)
logger = logging.getLogger(__name__)
class InferenceHandler:
# ☑️ /general-공통처리함수
@staticmethod
async def handle_general_background(
request_id: str,
result_id: str,
input_file: UploadFile,
prompt_file: UploadFile,
mode: str,
model: str,
api_key: str,
schema_file: Optional[UploadFile] = None,
request_info: Optional[str] = None,
):
logger.info(f"[INPUT_FILE_NAME]: {input_file.filename}")
try:
# ✅ prompt_file이 없으면 사용자 에러 응답 반환
if not prompt_file or not prompt_file.filename:
raise HTTPException(
status_code=400,
detail="❌ 프롬프트 파일(prompt_file)은 반드시 업로드해야 합니다.",
)
# ✅ 파일 저장
filename = input_file.filename
file_path = os.path.join(UPLOAD_DIR, filename)
with open(file_path, "wb") as f:
shutil.copyfileobj(input_file.file, f)
# 🔽 프롬프트 해시 처리 + 캐시 저장 + 내용 읽기
file_hash = compute_file_hash(prompt_file)
cached_prompt_path = save_prompt_file_if_not_exists(file_hash, prompt_file)
with open(cached_prompt_path, encoding="utf-8") as f:
prompt = f.read()
custom_mode = True
# ✅ schema_file 있으면 structured 모드로 전환 및 로딩
schema_override = None
if schema_file and schema_file.filename:
schema_override = json.loads(await schema_file.read())
mode = "structured" # override
# ✅ 모델 정보 수집
info_response = await ModelInfoService.get_model_info()
info = json.loads(info_response.body.decode("utf-8"))
inner_models = info["models"]["inner_model"]["model_list"]
outer_models = info["models"]["outer_model"]["model_list"]
model_url_map = await ModelInfoService.get_ollama_model_map()
# presigned_url = upload_file_to_minio(input_file, request_id)
presigned_url = upload_file_to_minio_v2(
file=input_file,
bucket_name=MINIO_BUCKET_NAME,
object_name=f"{request_id}/{filename}",
)
logger.info(f"[MinIO] presigned URL 생성 완료: {presigned_url}")
# ✅ run_pipeline 재사용 (schema_override는 일반 추론이므로 None)
results_minio = await PipelineRunner.run_pipeline(
request_info=request_info,
request_id=request_id,
file_path=presigned_url,
filename=filename,
prompt=prompt,
prompt_filename=Path(cached_prompt_path).name,
custom_mode=custom_mode,
mode=mode,
model=model,
inner_models=inner_models,
outer_models=outer_models,
model_url_map=model_url_map,
api_key=api_key,
schema_override=schema_override,
prompt_mode="general",
)
# ✅ 결과 Redis 저장
results_redis = {k: v for k, v in results_minio.items() if k != "fields"}
redis_key = f"pipeline_result:{result_id}"
redis_client.set(
redis_key, json.dumps(results_redis, ensure_ascii=False), ex=60 * 60
)
logger.info(f"[REDIS] 결과 Redis 저장 완료: {result_id}")
except Exception as e:
logger.error(f"[PIPELINE] ❌ result_id={result_id} 처리 실패: {e}")
redis_client.set(
f"pipeline_result:{result_id}",
json.dumps({"error": str(e)}),
ex=60 * 60,
)
# ✅ 결과 MinIO 저장 (전체본)
try:
minio_key = f"{request_id}/{input_file.filename.rsplit('.', 1)[0]}.json"
presigned_url = save_result_to_minio(
result_dict=results_minio,
object_name=minio_key,
)
logger.info(f"[MinIO] 결과 MinIO 저장 완료: {presigned_url}")
except Exception as e:
logger.error(f"[MinIO] 결과 저장 실패: {e}")
raise HTTPException(
status_code=500,
detail="결과 파일 저장 중 오류가 발생했습니다.",
)
@staticmethod
async def handle_extract_background(
request_id: str,
result_id: str,
input_file: UploadFile,
schema_file: Optional[UploadFile],
prompt_file: Optional[UploadFile],
mode: str,
model_list: List[str],
api_key: str,
request_info: Optional[str] = None,
):
# ✅ dummy 요청 처리
if model_list == ["dummy"]:
try:
log_user_request(
request_info=request_info,
endpoint="dummy/extract/outer",
input_filename="None",
model="dummy",
prompt_filename="None",
context_length=0,
api_key=api_key,
)
return await DummyService.extract_dummy()
except Exception as e:
logger.info(f"Failed to log 'dummy/extract/outer' request: {e}")
try:
if prompt_file and prompt_file.filename:
file_hash = compute_file_hash(prompt_file)
cached_prompt_path = save_prompt_file_if_not_exists(
file_hash, prompt_file
)
with open(cached_prompt_path, encoding="utf-8") as f:
prompt = f.read()
custom_mode = True
prompt_filename = Path(cached_prompt_path).name
else:
prompt_path = (
STRUCTURED_PROMPT_PATH
if mode == "structured"
else DEFAULT_PROMPT_PATH
)
with open(prompt_path, encoding="utf-8") as f:
prompt = f.read()
custom_mode = False
prompt_filename = Path(prompt_path).name
if schema_file and schema_file.filename:
# clone_upload_file()로 복제된 파일은 UploadFile과 달리 await .read() 지원 안 함
schema_content = schema_file.file.read() # 파일 핸들로 읽기
schema_override = json.loads(schema_content.decode("utf-8"))
else:
# with open(STRUCTURED_SCHEMA_PATH, "r", encoding="utf-8") as f:
# schema_override = json.load(f)
schema_override = None
info_response = await ModelInfoService.get_model_info()
info = json.loads(info_response.body.decode("utf-8"))
inner_models = info["models"]["inner_model"]["model_list"]
outer_models = info["models"]["outer_model"]["model_list"]
model_url_map = await ModelInfoService.get_ollama_model_map()
# presigned_url = upload_file_to_minio(input_file, request_id)
presigned_url = upload_file_to_minio_v2(
file=input_file,
bucket_name=MINIO_BUCKET_NAME,
object_name=f"{request_id}/{input_file.filename}",
)
logger.info(f"[MinIO] presigned URL 생성 완료: {presigned_url}")
tasks = []
for model in model_list:
tasks.append(
PipelineRunner.run_pipeline(
request_info=request_info,
request_id=request_id,
file_path=presigned_url,
filename=input_file.filename,
prompt=prompt,
prompt_filename=prompt_filename,
custom_mode=custom_mode,
mode=mode,
model=model,
inner_models=inner_models,
outer_models=outer_models,
model_url_map=model_url_map if model in inner_models else {},
api_key=api_key,
schema_override=schema_override,
prompt_mode="extract",
)
)
result_set = await asyncio.gather(*tasks)
results_minio = []
results_redis = []
for result in result_set:
results_minio.append(result)
# 'fields' 키 제외한 버전 생성
result_filtered = {k: v for k, v in result.items() if k != "fields"}
results_redis.append(result_filtered)
# ✅ 결과 Redis 저장 (요약본)
redis_key = f"pipeline_result:{result_id}"
redis_client.set(
redis_key,
json.dumps(results_redis, ensure_ascii=False),
ex=60 * 60,
)
logger.info(f"[REDIS] 결과 Redis 저장 완료: {result_id}")
except Exception as e:
logger.error(f"[PIPELINE] ❌ result_id={result_id} 처리 실패: {e}")
redis_client.set(
f"pipeline_result:{result_id}",
json.dumps({"error": str(e)}),
ex=60 * 60,
)
# ✅ 결과 MinIO 저장 (전체본)
try:
minio_key = f"{request_id}/{input_file.filename.rsplit('.', 1)[0]}.json"
presigned_url = save_result_to_minio(
result_dict=results_minio,
object_name=minio_key,
)
logger.info(f"[MinIO] 결과 MinIO 저장 완료: {presigned_url}")
except Exception as e:
logger.error(f"[MinIO] 결과 저장 실패: {e}")
raise HTTPException(
status_code=500,
detail="결과 파일 저장 중 오류가 발생했습니다.",
)