From 7217d3cbaaf312adcd529dbdf1e641506622023c Mon Sep 17 00:00:00 2001 From: ai-cell-a100-1 Date: Mon, 11 Aug 2025 18:56:38 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9B=90=20=EB=A0=88=ED=8F=AC=EB=9E=91=20?= =?UTF-8?q?=EC=99=84=EC=A0=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.ollama | 18 + .gitattributes | 32 + .gitignore | 27 + .gitmodules | 3 + Dockerfile | 25 + LICENSE | 21 + README.md | 191 +++++ docker-compose_8888.yml | 61 ++ docker-compose_8889.yml | 24 + docker-compose_monitoring.yml | 75 ++ docker-compose_ollama.yml | 77 ++ docs/ADR-0.md | 84 ++ docs/ADR-1.md | 6 + docs/ADR-2.md | 67 ++ docs/ADR-3.md | 82 ++ docs/ADR-4.md | 121 +++ docs/ADR-5.md | 64 ++ docs/API_KEY_USAGE.md | 104 +++ docs/PRD.md | 143 ++++ docs/README_DEV.md | 115 +++ docs/summary_api.md | 198 +++++ log_config.yaml | 23 + loki-config.yaml | 63 ++ prometheus.yml | 27 + promtail-config.yaml | 44 ++ pyproject.toml | 46 ++ requirements.txt | 33 + start_ollama_gemma.sh | 19 + start_ollama_gpt_oss.sh | 19 + start_ollama_qwen.sh | 19 + swagger-ui | 1 + workspace/api.py | 136 ++++ workspace/config/__init__.py | 0 workspace/config/setting.py | 59 ++ workspace/interface/__init__.py | 0 workspace/interface/streamlit_ui.py | 34 + workspace/routers/__init__.py | 25 + workspace/routers/api_key_router.py | 42 + workspace/routers/download_router.py | 22 + workspace/routers/dummy_router.py | 68 ++ workspace/routers/extract_router.py | 728 ++++++++++++++++++ workspace/routers/general_router.py | 235 ++++++ workspace/routers/guide_router.py | 46 ++ workspace/routers/llm_summation.py | 86 +++ workspace/routers/model_router.py | 17 + workspace/routers/ocr_router.py | 168 ++++ workspace/routers/stt_router.py | 144 ++++ workspace/routers/yolo_router.py | 80 ++ workspace/services/__init__.py | 0 workspace/services/api_key_service.py | 167 ++++ workspace/services/download_service.py | 38 + workspace/services/dummy_service.py | 20 + workspace/services/inference_service.py | 281 +++++++ workspace/services/model_service.py | 69 ++ workspace/services/pipeline_runner.py | 292 +++++++ workspace/services/prompt.py | 36 + workspace/services/report.py | 198 +++++ workspace/static/dummy_response.json | 42 + workspace/static/html/extract_guide.html | 83 ++ .../html/extraction_structured_guide.html | 32 + workspace/static/html/general_guide.html | 176 +++++ workspace/static/html/schema_file_guide.html | 98 +++ .../FastAPI_extract_structured_swagger.png | Bin 0 -> 70571 bytes .../static/image/FastAPI_extract_swagger.png | Bin 0 -> 51868 bytes workspace/static/image/FastAPI_general.png | Bin 0 -> 110124 bytes .../image/FastAPI_general_JSONresult.png | Bin 0 -> 49903 bytes .../static/image/FastAPI_general_response.png | Bin 0 -> 77608 bytes .../static/image/FastAPI_general_result.png | Bin 0 -> 177515 bytes workspace/static/image/logo.png | Bin 0 -> 17141 bytes .../static/prompt/d6c_test_prompt_eng.txt | 30 + .../static/prompt/default_prompt_v0.1.txt | 30 + .../static/prompt/i18n_test_prompt_kor.txt | 24 + .../static/prompt/structured_prompt_v0.1.txt | 28 + workspace/static/structured_schema.json | 34 + workspace/utils/__init__.py | 0 workspace/utils/checking_files.py | 57 ++ workspace/utils/checking_keys.py | 78 ++ workspace/utils/image_converter.py | 35 + workspace/utils/logging_utils.py | 182 +++++ workspace/utils/minio_utils.py | 164 ++++ workspace/utils/prompt_cache.py | 32 + workspace/utils/redis_utils.py | 22 + workspace/utils/request_utils.py | 27 + workspace/utils/text_formatter.py | 21 + workspace/utils/text_generator.py | 479 ++++++++++++ workspace/utils/text_processor.py | 134 ++++ 86 files changed, 6631 insertions(+) create mode 100644 .env.ollama create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100755 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose_8888.yml create mode 100644 docker-compose_8889.yml create mode 100644 docker-compose_monitoring.yml create mode 100644 docker-compose_ollama.yml create mode 100644 docs/ADR-0.md create mode 100644 docs/ADR-1.md create mode 100644 docs/ADR-2.md create mode 100644 docs/ADR-3.md create mode 100644 docs/ADR-4.md create mode 100644 docs/ADR-5.md create mode 100644 docs/API_KEY_USAGE.md create mode 100644 docs/PRD.md create mode 100644 docs/README_DEV.md create mode 100644 docs/summary_api.md create mode 100644 log_config.yaml create mode 100644 loki-config.yaml create mode 100644 prometheus.yml create mode 100644 promtail-config.yaml create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 start_ollama_gemma.sh create mode 100644 start_ollama_gpt_oss.sh create mode 100644 start_ollama_qwen.sh create mode 160000 swagger-ui create mode 100644 workspace/api.py create mode 100644 workspace/config/__init__.py create mode 100644 workspace/config/setting.py create mode 100644 workspace/interface/__init__.py create mode 100644 workspace/interface/streamlit_ui.py create mode 100644 workspace/routers/__init__.py create mode 100644 workspace/routers/api_key_router.py create mode 100644 workspace/routers/download_router.py create mode 100644 workspace/routers/dummy_router.py create mode 100644 workspace/routers/extract_router.py create mode 100644 workspace/routers/general_router.py create mode 100644 workspace/routers/guide_router.py create mode 100644 workspace/routers/llm_summation.py create mode 100644 workspace/routers/model_router.py create mode 100644 workspace/routers/ocr_router.py create mode 100644 workspace/routers/stt_router.py create mode 100644 workspace/routers/yolo_router.py create mode 100644 workspace/services/__init__.py create mode 100644 workspace/services/api_key_service.py create mode 100644 workspace/services/download_service.py create mode 100644 workspace/services/dummy_service.py create mode 100644 workspace/services/inference_service.py create mode 100644 workspace/services/model_service.py create mode 100644 workspace/services/pipeline_runner.py create mode 100644 workspace/services/prompt.py create mode 100644 workspace/services/report.py create mode 100644 workspace/static/dummy_response.json create mode 100644 workspace/static/html/extract_guide.html create mode 100644 workspace/static/html/extraction_structured_guide.html create mode 100644 workspace/static/html/general_guide.html create mode 100644 workspace/static/html/schema_file_guide.html create mode 100644 workspace/static/image/FastAPI_extract_structured_swagger.png create mode 100644 workspace/static/image/FastAPI_extract_swagger.png create mode 100644 workspace/static/image/FastAPI_general.png create mode 100644 workspace/static/image/FastAPI_general_JSONresult.png create mode 100644 workspace/static/image/FastAPI_general_response.png create mode 100644 workspace/static/image/FastAPI_general_result.png create mode 100644 workspace/static/image/logo.png create mode 100644 workspace/static/prompt/d6c_test_prompt_eng.txt create mode 100644 workspace/static/prompt/default_prompt_v0.1.txt create mode 100644 workspace/static/prompt/i18n_test_prompt_kor.txt create mode 100644 workspace/static/prompt/structured_prompt_v0.1.txt create mode 100644 workspace/static/structured_schema.json create mode 100644 workspace/utils/__init__.py create mode 100644 workspace/utils/checking_files.py create mode 100644 workspace/utils/checking_keys.py create mode 100644 workspace/utils/image_converter.py create mode 100644 workspace/utils/logging_utils.py create mode 100644 workspace/utils/minio_utils.py create mode 100644 workspace/utils/prompt_cache.py create mode 100644 workspace/utils/redis_utils.py create mode 100644 workspace/utils/request_utils.py create mode 100644 workspace/utils/text_formatter.py create mode 100644 workspace/utils/text_generator.py create mode 100644 workspace/utils/text_processor.py diff --git a/.env.ollama b/.env.ollama new file mode 100644 index 0000000..e8104b0 --- /dev/null +++ b/.env.ollama @@ -0,0 +1,18 @@ +# Gemma models +MODEL_GEMMA=gemma3:27b + +# Qwen models +MODEL_QWEN_30b=qwen3:30b +MODEL_QWEN_8b=qwen3:8b + +# DeepSeek models +MODEL_DEEPSEEK_32b=deepseek-r1:32b +MODEL_DEEPSEEK_8b=deepseek-r1:8b + +# GPT-OSS models +MODEL_GPT_OSS_120b=gpt-oss:120b +MODEL_GPT_OSS_20b=gpt-oss:20b + +# Other models +MODEL_PHI=phi4:14b +MODEL_DEEPSEEK_32b=deepseek-r1:32b \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b60060f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,32 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71ff4b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# 캐시 및 임시 파일 무시 +__pycache__/ +**/__pycache__/ +**/**/__pycache__/ +*.py[cod] + +# 로그/업로드 디렉토리 무시 +workspace/static/html/generated/ +minio/ +logs/ +cached/ +temp_upload/ +test/ + +# Loki 관련 무시 +loki/ +**/loki/ + +# 기타 +.DS_Store +.env +api_keys.json +docker-compose_minio.yml + +# gitignore for specific environment files +.env.8888 +.env.8889 \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b699840 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "swagger-ui"] + path = swagger-ui + url = https://gitea.hmac.kr/kyy/swagger-ui.git diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..0113cc2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.10-slim + +WORKDIR /workspace + +# 시스템 패키지 설치 +RUN apt-get update && \ + apt-get install -y poppler-utils tesseract-ocr tree curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Python 의존성 설치 +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt \ + prometheus-fastapi-instrumentator + +RUN pip install flower + +# 코드 복사 +COPY workspace/ ./workspace/ + +ENV PYTHONPATH=/workspace/workspace + +# ✅ uvicorn 실행 시 --log-config 옵션 추가 +CMD ["sh", "-c", "uvicorn api:app --workers 4 --host 0.0.0.0 --port ${PORT:-8888} --log-config log_config.yaml"] +# -w 4 -k uvicorn.workers.UvicornWorker \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4af313f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Lectom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..63c0c14 --- /dev/null +++ b/README.md @@ -0,0 +1,191 @@ +# LLM 게이트웨이 + +## 프로젝트 개요 + +이 프로젝트는 스캔된 PDF 문서에서 텍스트와 구조를 자동으로 추출하고, 대규모 언어 모델(LLM)을 활용하여 내용을 분석하고 요약한 뒤, 최종 결과를 JSON 형식으로 제공하는 문서 분석 시스템입니다. + +시스템은 OCR, LLM 추론, 음성 변환 등 각 기능을 독립적인 API로 구성하여 모듈성과 확장성을 높였습니다. +사용자는 FastAPI로 구현된 API 엔드포인트를 통해 서비스를 이용하여, 문서를 업로드하고 결과를 확인할 수 있습니다. + +## 주요 기능 + +- **PDF 및 이미지 처리**: PDF, 이미지 등 다양한 형식의 문서에서 텍스트와 좌표 정보를 추출합니다. +- **다중 모델 지원**: Ollama를 통해 Gemma, Qwen 등 다양한 로컬 LLM 모델을 지원하며, 외부 상용 LLM API 연동도 가능합니다. +- **비동기 처리**: 대용량 문서나 복잡한 분석 작업을 비동기적으로 처리하여 사용자 응답 시간을 단축합니다. +- **구조화된 데이터 추출**: 사용자 정의 JSON 스키마에 따라 문서에서 필요한 정보를 정확히 추출합니다. +- **음성-텍스트 변환 (STT)**: 음성 파일을 텍스트로 변환하는 STT 기능을 API 게이트웨이를 통해 중계합니다. +- **모니터링**: Loki, Prometheus, Grafana를 활용하여 시스템 로그와 성능 지표를 실시간으로 모니터링합니다. + +## 설치 및 실행 방법 + +이 프로젝트는 Docker Compose를 사용하여 각 서비스를 컨테이너 환경에서 실행합니다. + +1. **Docker 설치**: 시스템에 Docker와 Docker Compose가 설치되어 있어야 합니다. + +2. **환경 변수 설정**: 프로젝트 루트의 `.env` 파일을 각 환경에 맞게 설정합니다. (예: `.env.8888`, `.env.ollama`) + +3. **서비스 실행**: 필요에 따라 아래 명령어를 사용하여 서비스를 실행합니다. + + - **LLM 게이트웨이 API (포트: 8888)** + + ```bash + docker-compose -f docker-compose_8888.yml up -d + ``` + + - **Streamlit UI (포트: 8889)** + + ```bash + docker-compose -f docker-compose_8889.yml up -d + ``` + + - **Ollama 모델 서버** (예: Gemma 모델 실행) + + ```bash + ./start_ollama_gemma.sh + ``` + + - **모니터링 시스템** + ```bash + docker-compose -f docker-compose_monitoring.yml up -d + ``` + +4. **서비스 종료**: 실행 중인 서비스를 중지하려면 아래 명령어를 사용합니다. + ```bash + docker-compose -f <사용된-compose-파일.yml> down + ``` + +## 프로젝트 구조 + +``` +. +├── Dockerfile # 메인 서비스용 Dockerfile +├── README.md # 프로젝트 설명서 +├── docker-compose_8888.yml # LLM 게이트웨이 API 서비스용 docker-compose +├── docker-compose_8889.yml # LLM 게이트웨이 Streamlit UI용 docker-compose +├── docker-compose_monitoring.yml # 모니터링(Loki, Prometheus, Grafana) 구성 +├── docker-compose_ollama.yml # Ollama 모델 서버용 docker-compose +├── docs # 아키텍처 결정 기록(ADR) 및 문서 +├── log_config.yaml # 로그 설정 파일 +├── pyproject.toml # Python 프로젝트 및 의존성 관리 파일 +├── requirements.txt # 의존성 목록 +├── swagger-ui # Swagger UI 프론트엔드 모듈 +└── workspace # LLM 게이트웨이 메인 서비스 코드 + ├── api.py # FastAPI 애플리케이션 진입점 + ├── config # 설정 관련 모듈 + ├── interface # 사용자 인터페이스 (Streamlit 등) + ├── routers # FastAPI 라우터 (API 엔드포인트 정의) + ├── services # 비즈니스 로직 처리 서비스 + ├── static # 정적 파일 (예제, 템플릿 등) + └── utils # 공용 유틸리티 모듈 +``` + +## API 엔드포인트 + +### 상태 확인 및 가이드 + +| 경로 | 메서드 | 설명 | +| -------------------- | ------ | ------------------------------------ | +| `/health/*` | GET | 서버 상태 확인 | +| `/info` | GET | 사용 가능한 내부/외부 모델 목록 조회 | +| `/schema_file_guide` | GET | 스키마 파일 작성 가이드 HTML 제공 | +| `/general_guide` | GET | 범용 추론 가이드 HTML 제공 | +| `/extract_guide` | GET | 문서 추출 가이드 HTML 제공 | + +### 파일 다운로드 + +| 경로 | 메서드 | 설명 | +| -------------------- | ------ | --------------------------------------- | +| `/default_prompt` | GET | 기본 프롬프트 템플릿 파일 다운로드 | +| `/structured_prompt` | GET | 구조화 추출용 프롬프트 템플릿 다운로드 | +| `/structured_schema` | GET | 구조화 추출용 JSON 스키마 예시 다운로드 | + +### 범용 추론 (General) + +| 경로 | 메서드 | 설명 | +| -------------------------------- | ------ | -------------------------------- | +| `/general/inner` | POST | 내부 LLM 기반 범용 추론 (비동기) | +| `/general/outer` | POST | 외부 LLM 기반 범용 추론 (비동기) | +| `/general/progress/{request_id}` | GET | 범용 추론 작업 상태 및 결과 조회 | + +### 문서 정보 추출 (Extract) + +| 경로 | 메서드 | 설명 | +| -------------------------------- | ------ | ------------------------------------- | +| `/extract/inner` | POST | 내부 LLM 기반 문서 정보 추출 (비동기) | +| `/extract/outer` | POST | 외부 LLM 기반 문서 정보 추출 (비동기) | +| `/extract/progress/{request_id}` | GET | 문서 추출 작업 상태 및 결과 조회 | + +### 텍스트 요약 (Summary) + +| 경로 | 메서드 | 설명 | +| ------------------------- | ------ | ----------------------------------------------- | +| `/summary` | POST | 모든 모델을 사용하여 텍스트 요약 (동기) | +| `/ollama_summary` | POST | Ollama 모델을 사용하여 텍스트 요약 (동기) | +| `/task_summary` | POST | 모든 모델을 사용한 비동기 텍스트 요약 작업 생성 | +| `/task_summary/{task_id}` | GET | 비동기 요약 작업 상태 및 결과 조회 | + +### 음성-텍스트 변환 (STT) + +| 경로 | 메서드 | 설명 | +| ------------------------ | ------ | ---------------------------------------- | +| `/audio` | POST | 음성 파일을 STT API로 전달하여 변환 요청 | +| `/progress/{request_id}` | GET | STT 작업 진행 상태 조회 | +| `/result/{request_id}` | GET | STT 작업 결과 조회 | +| `/languages` | GET | STT 지원 언어 목록 조회 | + +### 텍스트 추출 (OCR) + +| 경로 | 메서드 | 설명 | +| ---------------------------- | ------ | -------------------------------- | +| `/ocr` | POST | 문서 파일 OCR 작업 요청 (비동기) | +| `/ocr/progress/{request_id}` | GET | OCR 작업 진행 상태 조회 | +| `/ocr/result/{request_id}` | GET | OCR 작업 결과 조회 | + +### 테스트용 + +| 경로 | 메서드 | 설명 | +| ---------------------- | ------ | ------------------------------- | +| `/dummy/extract/outer` | POST | 실제 추론 없이 더미 응답을 반환 | + +## 결과 JSON 구조 예시 + +문서 추출 API (`/extract/*`) 호출 시 반환되는 최종 결과의 JSON 구조입니다. + +```json +{ + "request_id": "요청 식별자", + "progress_logs": [ + {"status": "작업 접수", "timestamp": "2025-07-21T10:00:00Z"}, + {"status": "OCR 작업 시작", "timestamp": "2025-07-21T10:00:05Z"}, + {"status": "텍스트 추출 중", "timestamp": "2025-07-21T10:00:06Z"} + ], + "final_result": { + "filename": "example.pdf", + "model": { + "ocr_model": "tesseract", + "llm_model": "gemma:7b" + }, + "time": { + "duration_sec": 25.5, + "started_at": 1721556000.0, + "ended_at": 1721556025.5 + }, + "fields": { + "추출된 텍스트": [[x1, y1], [x2, y2], [x3, y3], [x4, y4]] + }, + "parsed": "OCR 모델로 추출한 원본 텍스트입니다.", + "generated": "LLM이 요약 및 번역한 텍스트입니다.", + "processed": { + "제목": "문서의 제목", + "한글제목": "번역된 한국어 제목", + "본문": "문서의 영문 본문", + "한글본문": "번역된 한국어 본문", + "날짜": "문서에 명시된 날짜", + "보낸사람": "발신인 정보", + "받는사람": "수신인 정보", + "연관공문": "참조 또는 연관된 문서", + "문서유형": "문서의 분류 (예: 보고서, 계약서)" + } + } +} +``` diff --git a/docker-compose_8888.yml b/docker-compose_8888.yml new file mode 100644 index 0000000..dd420b3 --- /dev/null +++ b/docker-compose_8888.yml @@ -0,0 +1,61 @@ +services: + pgn_api: + build: + context: . + image: pgn_api + container_name: pgn_api_8888 + volumes: + - ./:/workspace + ports: + - "8888:8888" + env_file: + - .env + environment: + - TZ=Asia/Seoul + stdin_open: true + restart: always + tty: true + networks: + - pgn_net + - monitor_net + depends_on: + pgn_redis: + condition: service_healthy + healthcheck: + test: + [ + "CMD-SHELL", + "curl -f http://localhost:8888/health/API && curl -f http://localhost:8888/health/Redis && curl -f http://localhost:8888/health/MinIO", + ] + interval: 60s + timeout: 5s + retries: 3 + start_period: 10s + + pgn_redis: + image: redis:7-alpine + container_name: pgn_redis + command: + [ + "redis-server", + "--maxmemory", + "256mb", + "--maxmemory-policy", + "allkeys-lru", + ] + ports: + - "6382:6379" + restart: always + networks: + - pgn_net + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +networks: + monitor_net: + driver: bridge + pgn_net: + external: true diff --git a/docker-compose_8889.yml b/docker-compose_8889.yml new file mode 100644 index 0000000..8f03c12 --- /dev/null +++ b/docker-compose_8889.yml @@ -0,0 +1,24 @@ +services: + api_2: + build: + context: . + image: pgn_api + container_name: pgn_api_8889 + volumes: + - ./:/workspace + ports: + - "8889:8889" + env_file: + - .env + environment: + - TZ=Asia/Seoul + stdin_open: true + restart: always + tty: true + networks: + - pgn_net + command: sh -c "uvicorn api:app --workers 4 --host 0.0.0.0 --port 8889 --log-config log_config.yaml" + +networks: + pgn_net: + external: true diff --git a/docker-compose_monitoring.yml b/docker-compose_monitoring.yml new file mode 100644 index 0000000..d87d972 --- /dev/null +++ b/docker-compose_monitoring.yml @@ -0,0 +1,75 @@ +services: + ollama_gemma: + image: ollama/ollama:0.10.0 + container_name: pgn_ollama_gemma + ports: + - "11534:11534" + volumes: + - ollama_data_gemma:/root/.ollama + - ./start_ollama_gemma.sh:/start_ollama_gemma.sh + env_file: + - .env.ollama + restart: always + entrypoint: ["/bin/sh", "/start_ollama_gemma.sh"] + networks: + - pgn_net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + + ollama_qwen_v1: + image: ollama/ollama:0.10.0 + container_name: pgn_ollama_qwen_v1 + ports: + - "11634:11634" + volumes: + - ollama_data_qwen_30b_v1:/root/.ollama + - ./start_ollama_qwen_v1.sh:/start_ollama_qwen_v1.sh + env_file: + - .env.ollama + restart: always + entrypoint: ["/bin/sh", "/start_ollama_qwen_v1.sh"] + networks: + - pgn_net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + + ollama_qwen_v2: + image: ollama/ollama:0.10.0 + container_name: pgn_ollama_qwen_v2 + ports: + - "11734:11734" + volumes: + - ollama_data_qwen_30b_v2:/root/.ollama + - ./start_ollama_qwen_v2.sh:/start_ollama_qwen_v2.sh + env_file: + - .env.ollama + restart: always + entrypoint: ["/bin/sh", "/start_ollama_qwen_v2.sh"] + networks: + - pgn_net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + +volumes: + ollama_data_gemma: + ollama_data_qwen_30b_v1: + ollama_data_qwen_30b_v2: + +networks: + pgn_net: + external: true diff --git a/docker-compose_ollama.yml b/docker-compose_ollama.yml new file mode 100644 index 0000000..f6e04b3 --- /dev/null +++ b/docker-compose_ollama.yml @@ -0,0 +1,77 @@ +services: + ollama_gemma: + image: ollama/ollama:0.11.2 + container_name: pgn_ollama_gemma + ports: + - "11534:11534" + volumes: + - ollama_data_gemma:/root/.ollama + - ./start_ollama_gemma.sh:/start_ollama_gemma.sh + environment: + - OLLAMA_NUM_PARALLEL=4 + env_file: + - .env.ollama + restart: always + entrypoint: ["/bin/sh", "/start_ollama_gemma.sh"] + networks: + - pgn_net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + + ollama_gpt_oss: + image: ollama/ollama:0.11.2 + container_name: pgn_ollama_gpt_oss + ports: + - "11634:11634" + volumes: + - ollama_data_gpt_oss:/root/.ollama + - ./start_ollama_gpt_oss.sh:/start_ollama_gpt_oss.sh + env_file: + - .env.ollama + restart: always + entrypoint: ["/bin/sh", "/start_ollama_gpt_oss.sh"] + networks: + - pgn_net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + + ollama_qwen: + image: ollama/ollama:0.11.2 + container_name: pgn_ollama_qwen + ports: + - "11734:11734" + volumes: + - ollama_data_qwen:/root/.ollama + - ./start_ollama_qwen.sh:/start_ollama_qwen.sh + env_file: + - .env.ollama + restart: always + entrypoint: ["/bin/sh", "/start_ollama_qwen.sh"] + networks: + - pgn_net + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + +volumes: + ollama_data_gemma: + ollama_data_gpt_oss: + ollama_data_qwen: + +networks: + pgn_net: + external: true diff --git a/docs/ADR-0.md b/docs/ADR-0.md new file mode 100644 index 0000000..46c1e15 --- /dev/null +++ b/docs/ADR-0.md @@ -0,0 +1,84 @@ +# LLM 게이트웨이 아키텍처 + +## 상태 + +제안됨 + +## 콘텍스트 + +본 프로젝트(`llm_gateway`)는 FastAPI를 기반으로 한 문서 추출 및 LLM 추론 API 시스템으로, PDF 및 이미지 기반의 문서 처리, LLM 기반 응답 생성, 다양한 다운로드 및 가이드 제공 기능을 제공한다. +이를 효율적으로 구현하기 위해 비동기 처리를 활용하고, 기능별 모듈화 및 서비스 계층을 분리하여 유지보수성과 확장성을 고려한 구조를 설계했다. + +## 아키텍처 구성 요소 + +### (1) API 계층 + +**FastAPI** + +- 클라이언트와 직접 상호작용하는 API 레이어. +- 비동기 요청을 처리하고 파일 업로드, LLM 추론, OCR, 다운로드 등의 엔드포인트를 제공. +- 주요 엔드포인트: + - `/extract`: 문서 텍스트 추출 및 LLM 응답 + - `/general/inner/outer`: 내부/외부 LLM 추론 + - `/ocr`: PDF/이미지 파일의 OCR 기반 텍스트 추출 + - `/download/*`: 프롬프트 및 스키마 다운로드 + - `/info`: 사용 가능한 모델 정보 제공 + - `/health`: 시스템 상태 확인 + +### (2) 서비스 계층 + +**서비스 모듈** + +- 라우터에서 호출되는 비즈니스 로직 처리 레이어. +- 주요 서비스: + - `InferenceService`: 텍스트 추출 및 LLM 호출 처리 + - `DownloadService`: 프롬프트 및 스키마 다운로드 처리 + - `ModelInfoService`: 모델 정보 반환 + - `PipelineRunner`: 추론 파이프라인 실행 로직 + - `DummyService`: 테스트용 API 처리 + +### (3) 유틸리티 및 처리 계층 + +**utils 모듈** + +- 공통 텍스트 추출(`extract_text_from_file`), LLM 응답 처리, 로깅 및 프롬프트 관리 기능 제공. +- OCR 처리의 경우 업로드된 파일을 `aiofiles`를 통해 임시 디스크에 저장한 후 `extract_text_from_file`을 호출하여 텍스트를 추출한다. +- 주요 모듈: + - `text_extractor`: 파일 기반 텍스트 추출 + - `text_processor`: OCR 및 후처리 + - `text_generator`: LLM 추론 (Ollama, Gemini, Claude, GPT 등) + - `logging_utils`, `prompt_cache`: 로깅 및 캐시 관리 + +### (4) 데이터 계층 + +**파일 저장소 및 정적 자원** + +- 업로드된 파일은 서버 내 `UPLOAD_DIR`에 저장. +- 정적 리소스(`/static/`)를 통해 HTML 가이드, 이미지, JSON 스키마 제공. +- 추론 및 다운로드 요청에 필요한 리소스를 관리하고, 서버 안정성을 위해 디스크 기반 임시 저장소 활용. + +## 결정 + +- FastAPI를 중심으로 기능별 라우터를 독립적으로 구성하고, 서비스 계층과 유틸리티 계층을 분리하여 유지보수성과 확장성을 확보한다. +- `/ocr` 요청 시 파일 업로드는 임시 디스크 저장(aiofiles) 후 `text_extractor` 호출로 처리하며, 이를 Mermaid 흐름도에 반영한다. +- 공통 텍스트 추출 모듈(`text_extractor`)은 `/extract`, `/general`, `/ocr`에서 재사용하도록 `utils` 계층에 위치시킨다. + +## 근거 + +- 비동기 요청 처리 및 모듈화된 구조는 대규모 문서 추출 및 LLM 처리 환경에서 성능과 유지보수성을 동시에 확보한다. +- `aiofiles` 기반 파일 저장은 메모리 상 `UploadFile` 객체를 안정적으로 처리하기 위해 필요하다. +- `utils` 계층 통합으로 공통 로직 재사용성을 높여 코드 중복을 방지하고 유지보수를 용이하게 한다. + +## 결과 + +- 클라이언트는 비동기 요청을 통해 빠르고 안정적인 문서 추출 및 LLM 응답을 받을 수 있다. +- 서비스 계층과 유틸리티 계층 분리를 통해 확장 가능하고 유지보수성이 높은 구조를 확보했다. +- Mermaid 다이어그램을 통해 아키텍처와 흐름을 명확하게 시각화하여 향후 리팩토링 및 개선 작업에 참고할 수 있도록 했다. + +## 결정자 + +AI cell / 김용연 연구원 + +## 결정 날짜 + +2025-05-29 diff --git a/docs/ADR-1.md b/docs/ADR-1.md new file mode 100644 index 0000000..086b222 --- /dev/null +++ b/docs/ADR-1.md @@ -0,0 +1,6 @@ +# ADR-001: LLM Gateway 초기 아키텍처 개요 + +**상태:** 대체됨 (Superseded) +**대체 문서:** [ADR-002](./ADR-2.md) + +> 본 ADR-1의 내용은 ADR-2에 작성되었으며, 해당 문서를 참조 바랍니다. diff --git a/docs/ADR-2.md b/docs/ADR-2.md new file mode 100644 index 0000000..4e85302 --- /dev/null +++ b/docs/ADR-2.md @@ -0,0 +1,67 @@ +## LLM Gateway 아키텍처 + +**문서 번호:** **ADR-002** + +- 제목: **LLM Gateway 비동기 아키텍처 채택** +- 날짜: **2025-06-02** +- 상태: **제안됨 (Proposed)** +- 작성자: **\[김용연 연구원]** + +--- + +### 1. 컨텍스트 (Context) + +**본 문서는 현재 운영 중인 OCR, LLM Service, STT 3개 AI 서비스의 통합 운영을 위해 선택한 멀티레포(Multi-Repository) 아키텍처를 정의함. 주요 요구사항은 다음과 같음** + +- **서비스별 독립 개발 및 배포**: OCR, LLM, STT 각각의 서비스는 별도 개발자에 의해 독립적으로 개발/운영되며, 서비스별 라이프사이클 관리가 필요함 +- **모놀리식 구조 지양**: 모든 서비스를 하나의 코드베이스로 통합하는 모노레포 방식은 코드 충돌, 배포 및 관리 복잡성 등의 단점을 내포함 +- **비동기 처리 및 작업 분산 지원**: Redis 및 Celery를 활용하여 STT, OCR 등 장시간 소요되는 비동기 작업을 효율적으로 처리하고, 사용자 요청에 대한 응답성을 유지함 +- **서비스간 라우팅 및 통합 API 게이트웨이**: 각 서비스는 독립적인 엔드포인트를 가지되, 공통적으로 요청을 분산 처리하는 라우터 계층 및 통합 API를 통해 외부 서비스와의 연동을 단순화함 + +--- + +### 2. 결정 (Decision) + +**OCR, LLM Service, STT 서비스를 멀티레포(Multi-Repository) 구조로 분리 관리함** +공통 기능 및 설정은 common-utils에 모듈화하여 서비스 간 연계 및 확장을 지원함 + +```bash +llm_gateway/ +├── ocr-service/ # OCR 서비스 (독립 레포) +├── stt-service/ # STT 서비스 (독립 레포) +└── common-utils/ # 공통 유틸 및 설정 (독립 레포) + ├── logging/ # 공통 로깅 설정 + └── config/ # 환경설정 및 공통 설정 모듈 +``` + +--- + +### 3. 고려된 대안 (Alternatives Considered) + +| 아키텍처 | 장점 | 단점 | +| --------------------------------- | ------------------------------------------------- | ----------------------------------------------------------- | +| **모노레포**(Monorepo) | 코드 관리와 통합 배포가 쉬움, 개발 환경 통일 가능 | 빌드 및 테스트 속도 저하, 서비스별 권한 및 배포 분리 어려움 | +| **멀티레포**(Multirepo) ✅ 선택됨 | 서비스별 독립 개발/배포 용이, 작업 충돌 최소화 | 공통 코드 관리 복잡, 서비스 간 연동 이슈 처리 필요 | + +--- + +### 4. 결과 (Consequences) + +- **장점:** + + - 서비스별로 독립적인 관리 체계를 유지하여 각 서비스의 개발, 배포 주기를 유연하게 설정 가능 + - Redis, Celery를 통한 비동기 처리 및 요청 분산으로 응답성과 효율성 확보 + - 공통 Router 계층을 통해 서비스 간 통합 요청 처리 가능, 아키텍처 확장 용이 + +- **단점:** + + - 공통 코드(`common-utils`)의 관리와 배포 자동화 필요, 버전 충돌 가능성 존재 + - 서비스 간 연계 로직 및 의존성 관리 복잡, 유지보수 시 추가 개발 필요 + - 레포 수 증가에 따른 중앙 관리 및 모니터링 시스템 필요, 통합 이슈 관리와 배포 체계 도입 필요 + +--- + +### 5. 추가 노트 (Optional) + +- **공통 코드 배포 방식:** `common-utils`는 git 서브모듈 또는 사설 PyPI 패키지로 배포 관리 예정. 버전 관리 체계 마련 필요 +- **서비스 확장 고려:** 새로운 AI 서비스 추가 시 현재 아키텍처의 확장성이 유리함 diff --git a/docs/ADR-3.md b/docs/ADR-3.md new file mode 100644 index 0000000..fac9118 --- /dev/null +++ b/docs/ADR-3.md @@ -0,0 +1,82 @@ +# **ADR-003** + +* 제목: **서비스 간 공통 모듈 연동 방식 결정** +* 날짜: **2025-06-12** +* 상태: 제안됨 (Proposed) +* 작성자: **\[김용연 연구원]** + +--- + +## 1. 컨텍스트 (Context) + +ADR-002를 통해 **멀티레포 아키텍처**로 결정되었으며, 이에 따라 각 서비스 간 **공통 모듈을 어떤 방식으로 연동할 것인지**에 대한 결정을 내려야 한다. 이번 논의는 다음 피드백을 기반으로 진행된다. + +> 1. 공통모듈이 많고 복잡한가? +> 2. 관리포인트가 늘어날 여지가 많은가? + +또한, Git 연동 방식 외에도 **Gitea Action 기반 자동 동기화 방식**을 함께 검토한다. 고려 요소는 다음과 같다. + +* 공통 모듈의 복잡도 및 변경 빈도 +* 유지보수 및 형상관리 난이도 +* 개발자 사용성 +* 자동화 가능성과 CI/CD 연계 +* 로컬/원격 환경 간의 충돌 여부 + +--- + +## 2. 결정 (Decision) + +현재 고려 중인 연동 방식은 다음과 같다. + +| 방법 | 장점 | 단점 | +| --------------------------- | ---------------------------- | ---------------------------- | +| **git-submodule** | 참조만으로 관리되어 가볍고 명확한 버전 고정 가능 | 초기 사용 복잡, 동기화 어려움, 실수 발생 가능성 | +| **git-subtree** ✅ | 병합, 업데이트 용이, 독립적인 서비스 유지 가능 | 충돌 시 수동 병합 필요, 코드 중복 위험 | +| **Gitea Action(강제 동기화)** | CI로 일관된 코드 유지 가능, 자동화 쉬움 | 설정 복잡, 브랜치 충돌 발생 가능성 있음 | +--- + +## ✅ 공통모듈 + +현재 공통적으로 사용되거나 예정된 모듈은 다음과 같다: + +| 영역 | 모듈 예시 | 설명 | +| ------------- | ---------------------------------- | ------------------------------------------ | +| 로깅 | `logger.py` | 공통 포맷의 로그 기록, Loki 기반 모니터링 대응 | +| 요청 로깅 추적 | `request_logger.py` | 클라이언트 IP, 요청 시간, API 경로 등을 기록, Loki/Grafana 연동 | +| 설정 관리 | `config_loader.py` | 환경 변수 및 설정 파일 통합 로딩 | +| 응답 구조 | `response.py` | API 응답 형식 표준화 | +| 예외 처리 | `exceptions.py` | 공통 예외 클래스로 서비스간 처리 로직 통일 | +| Celery 태스크 래퍼 | `celery_base.py` | 공통 Task 기반 클래스, 재시도/타임아웃/로깅 통일 | +| 작업 상태 추적 | `redis_job_tracker.py` | Redis 기반 작업 상태 추적 기능 | +| 모델 어댑터 | `llm_adapter.py`, `ocr_adapter.py` | 다양한 추론 서비스에 대한 공통 인터페이스 | +| 라우터 템플릿 | `base_router.py` | FastAPI 기반 라우터 템플릿 및 공통 설정 | +| 파일 유틸리티 | `file_utils.py` | 확장자 검사, 디렉토리 생성 등 파일 처리 로직 | +--- + +## 3. 고려된 대안 + +### 1. **git-submodule** + +* 경량화 및 버전 고정 측면에서 유리 +* 그러나 초기 진입장벽이 높고, 실수(예: 서브모듈 커밋 누락 등) 발생 가능 + +### 2. **git-subtree** + +* 현재 가장 유력한 방식 +* 외부 저장소의 디렉터리를 내부로 복사하여 통합된 이력 관리 가능 +* CI/CD 파이프라인 내에서도 동기화 명령 (`pull`, `push`, `split`)이 명확 + +### 3. **Gitea Action 통한 자동 동기화** + +* push 이벤트 발생 시 `main` 브랜치의 `common/` 디렉토리를 각 서비스의 `utils/`로 복사 +* 자동화는 강력하나, 충돌이나 병합 충돌 발생 시 수동 대응 필요 + +--- + +## 4. 향후 계획 + +* 실제 운영 중인 공통 모듈을 기준으로 **변경 빈도, 영향 범위 분석** +* `subtree` 및 `Gitea Action` 기반 연동 테스트 진행 +* 두 방식을 병행 테스트하여 장단점 비교 + +--- diff --git a/docs/ADR-4.md b/docs/ADR-4.md new file mode 100644 index 0000000..68a9bd6 --- /dev/null +++ b/docs/ADR-4.md @@ -0,0 +1,121 @@ +# **ADR-004** + +- 제목: **LLM Gateway의 비동기 추론 구조 개선: OCR 및 추론 파이프라인 요청 처리 방식 전환** +- 날짜: **2025-06-23** +- 상태: 채택됨 (Proposed) +- 작성자: **\[김용연 연구원]** + +--- + +## 1. 컨텍스트 (Context) + +기존 LLM Gateway에서는 `/ocr`, `/extract/*`, `/general/*` 요청 처리 시 다음과 같은 문제가 발생하였다: + +- Gateway가 OCR 결과를 **최대 85초간 polling**하여 기다린 뒤, 후속 작업(LLM 추론 또는 최종 결과 응답)을 수행 +- 이 구조는 Gateway가 **해당 요청을 점유**한 상태로 유지되므로, 동시에 여러 사용자의 요청을 처리하기 어려워짐 +- 클라이언트는 장시간 대기해야 하며, 요청 실패 시 다시 처음부터 재시도해야 하는 구조였음 + +--- + +## 2. 결정 (Decision) + +LLM Gateway는 **모든 OCR 및 LLM 요청을 비동기 백그라운드 처리 방식**으로 전환하여, Gateway가 요청을 즉시 반환하고 작업은 `asyncio.create_task()`를 통해 처리되도록 구조를 개선하였다. + +### 📌 asyncio.create_task()를 사용한 이유: + +- `asyncio.create_task()`는 FastAPI와 잘 통합되는 **간단한 백그라운드 실행 방식**으로, + + - Gateway의 요청 핸들러를 차단하지 않으면서 + - 요청 수신 즉시 응답을 반환하고 + - 이후 파이프라인 작업을 **비동기 이벤트 루프에서 독립적으로 처리**할 수 있게 한다. + +- FastAPI의 `BackgroundTasks`는 요청 스코프에 종속되어 **작업 진행 중 연결이 끊기면 실행되지 않을 수 있는 단점**이 있음. +- `create_task()`를 사용하면 상태 추적, 결과 저장 등 복합 처리가 필요한 **OCR + LLM 파이프라인**에도 유연하게 대응 가능. + +### 📌 주요 변경 사항: + +- 클라이언트 요청 시 `request_id`와 `progress_url`만 응답 +- 전체 파이프라인(OCR → LLM → 후처리)은 백그라운드에서 실행 +- 최종 결과는 Redis에 저장되며, 클라이언트는 /progress/{request_id}를 통해 상태 및 결과를 직접 조회 + +--- + +## ✅ `/ocr`에서 요청 + +### 변경 전 + +- Gateway가 OCR API에 요청한 후, **최대 85초간 polling** +- 완료 여부에 따라 최종 응답을 직접 반환 + +### 변경 후 + +- Gateway는 OCR API에 요청만 전송하고 다른 작업 요청 수행 +- 클라이언트에는 다음 정보만 반환: + + - `request_id`: OCR 작업 식별자 + - `status`: 작업 접수 + - `message`: 사용자 안내 문구 + - `status_check_url`: `/ocr/progress/{request_id}` + +- 클라이언트는 해당 URL로 결과 조회 + +--- + +## ✅ `/extract/*`, `/general/*`에서 요청 + +### 변경 전 + +- Gateway가 내부에서: + + 1. OCR API 요청 → 결과 polling + 2. 결과 수신 후 LLM API 요청 + 3. 최종 결과 응답 + +### 변경 후 + +- 전체 파이프라인(OCR → LLM → 후처리)을 `asyncio.create_task()`로 **백그라운드 비동기 실행** +- Gateway는 즉시 아래 정보 응답: + + - `message`: 작업이 백그라운드에서 실행 중 + - `request_id`: 상태 추적용 + - `status_check_url`: `/extract/progress/{request_id}` + +- 최종 결과는 Redis에 저장되고, `/extract/progress/{result_id}`로 확인 가능 + +``` +Client + │ + ├── POST /extract/outer (input + prompt) + │ ↓ + │ Gateway → OCR → LLM → Result + │ ↓ + └── return: request_id + progress URL + +백그라운드 작업 + ├── OCR API 호출 + ├── 추출 텍스트 Redis 저장 + ├── LLM API 호출 + └── 최종 결과 Redis 저장 +``` + +--- + +## 3. 고려된 대안 + +- **FastAPI BackgroundTasks** 사용: + → 요청 스코프와 연결되어 있어 상태 추적 및 작업 흐름 관리에 제한적 +- **Celery를 통한 전체 파이프라인 비동기화** + → 복잡성 증가, 구현 부담이 커서 1차 개선으로는 적절하지 않음 +- **기존 polling 방식 유지 + 타임아웃 설정 개선** + → 구조적 병목 문제 해결 불가 + +--- + +## 4. 결과 + +- Gateway가 작업을 점유하지 않고 **완전한 비동기 구조** 확보 +- 클라이언트 응답 속도 향상 및 사용자 경험 개선 +- Redis 기반 상태 추적 및 결과 조회 API 완비 +- 확장성 및 고부하 처리 능력 증가 + +--- diff --git a/docs/ADR-5.md b/docs/ADR-5.md new file mode 100644 index 0000000..0507e9c --- /dev/null +++ b/docs/ADR-5.md @@ -0,0 +1,64 @@ +# **ADR-005** + +- 제목: **LLM Gateway의 로깅 로직 개선: 클라이언트 IP 및 포트 추출 방식 보강** +- 날짜: **2025-07-29** +- 상태: 채택됨 (Proposed) +- 작성자: **\[김용연 연구원]** + +--- + +## 1. 컨텍스트 (Context) + +LLM Gateway에서 API 요청을 처리할 때, 로그에 기록되는 `ip`와 `swagger_port` 값이 프록시 환경에서 정확하지 않은 문제가 발생했다. + +- 외부망 요청 시 `request.client.host` 값이 **회사 공인 IP(프록시/NAT IP)** 로만 기록됨. +- `Host` 헤더 기반 포트 추출이 실패해 `"unknown"`으로 로그에 기록되는 사례 발생. + +--- + +## 2. 배경 (Background) + +- 로드밸런서나 리버스 프록시(Nginx, ALB 등)를 거치는 구조에서, 원래 클라이언트 정보를 담은 헤더(`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Port`)가 전달되지 않거나 무시되는 문제가 있었다. +- 그 결과, 모니터링과 디버깅 과정에서 **실제 사용자의 접속 정보(IP/포트)** 를 추적하기 어려웠다. + +--- + +## 3. 결정 (Decision) + +- **클라이언트 IP**: `X-Forwarded-For` → `X-Real-IP` → `request.client.host` 순으로 확인해 기록. +- **포트 정보**: `X-Forwarded-Port` → `request.url.port` → `Host` 헤더 → 기본 포트(443/80) 순으로 확인해 기록. + +--- + +## 4. 대안 (Alternatives) + +<<<<<<< Updated upstream + + +| 대안 | 장점 | 단점 | +| -------------------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------- | +| **기존 방식 유지**
`request.client.host`와 `Host`만 사용 | 코드 단순, 별도 설정 불필요 | 프록시 환경에서 실제 사용자 IP·포트 추적 불가,`"unknown"` 로그 발생 가능 | +| **프록시 헤더 활용 (채택)**
`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Port` 사용 | 외부망·내부망 모두에서 정확한 IP/포트 기록 가능 | 프록시 설정 누락 시 fallback 값(`request.client.host`, `443/80`)으로만 기록됨 | + +======= + + +| 대안 | 장점 | 단점 | +| -------------------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------- | +| **기존 방식 유지**
`request.client.host`와 `Host`만 사용 | 코드 단순, 별도 설정 불필요 | 프록시 환경에서 실제 사용자 IP·포트 추적 불가,`"unknown"` 로그 발생 가능 | +| **프록시 헤더 활용 (채택)**
`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Port` 사용 | 외부망·내부망 모두에서 정확한 IP/포트 기록 가능 | 프록시 설정 누락 시 fallback 값(`request.client.host`, `443/80`)으로만 기록됨 | + +>>>>>>> Stashed changes +>>>>>>> +>>>>>> +>>>>> +>>>> +>>> +>> + +--- + +## 5. 결과 (Consequences) + +- LLM Gateway의 로그에 실제 사용자 접속 정보(IP, 포트)가 일관되게 기록되어 **모니터링과 디버깅 품질 개선**. +- 프록시 설정(`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Port`)이 누락되면 여전히 기본값(`request.client.host`, `443/80`)으로만 기록될 수 있으므로, 인프라 설정 가이드와 함께 배포 필요 diff --git a/docs/API_KEY_USAGE.md b/docs/API_KEY_USAGE.md new file mode 100644 index 0000000..df088cc --- /dev/null +++ b/docs/API_KEY_USAGE.md @@ -0,0 +1,104 @@ +# API 키 사용 가이드 + +이 문서는 LLM Gateway의 API를 사용하기 위한 인증 방식, 특히 API 키의 발급, 관리 및 사용 방법에 대해 설명합니다. + +## 개요 + +LLM Gateway는 두 종류의 API 키를 사용하여 접근을 제어합니다. + +1. **관리자 키 (Admin Key):** + - **헤더 이름:** `X-Admin-KEY` + - **용도:** API 클라이언트 키를 생성, 조회, 폐기하는 관리용 API를 호출할 때 사용됩니다. + - **특징:** 시스템 관리자만 알고 있어야 하는 마스터 키입니다. 서버의 `.env` 파일에 `ADMIN_API_KEY`로 저장되어 있습니다. + +2. **클라이언트 키 (Client Key):** + - **헤더 이름:** `X-API-KEY` + - **용도:** 모델 추론, 파일 다운로드 등 일반적인 모든 API 서비스를 호출할 때 사용됩니다. + - **특징:** 관리자가 각 사용자 또는 서비스별로 발급해주는 고유한 키입니다. + +--- + +## 1. 관리자 가이드 + +### 1.1. 새로운 클라이언트 키 발급하기 + +새로운 사용자나 서비스를 위해 신규 `X-API-KEY`를 발급합니다. + +- **Endpoint:** `POST /manage/keys` +- **요청 예시 (`curl`):** + +```bash +# 'my-first-client' 라는 이름으로 키를 발급하는 예시 +# X-Admin-KEY 헤더에 자신의 관리자 키를 입력하세요. + +curl -X POST http://localhost:8000/manage/keys \ +-H "Content-Type: application/json" \ +-H "X-Admin-KEY: <여기에_관리자_키를_입력하세요>" \ +-d '{"client_name": "my-first-client"}' +``` + +- **성공 응답:** +```json +{ + "message": "API Key created successfully", + "key_info": { + "api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "client_name": "my-first-client", + "created_at": 1678886400, + "is_active": "true" + } +} +``` +> **중요:** 여기서 생성된 `api_key` 값을 사용자에게 전달해 주세요. + +### 1.2. 발급된 모든 키 목록 조회하기 + +현재 시스템에 등록된 모든 클라이언트 키의 정보를 확인합니다. + +- **Endpoint:** `GET /manage/keys` +- **요청 예시 (`curl`):** + +```bash +curl -X GET http://localhost:8000/manage/keys \ +-H "X-Admin-KEY: <여기에_관리자_키를_입력하세요>" +``` + +### 1.3. 클라이언트 키 폐기(삭제)하기 + +특정 클라이언트 키를 시스템에서 영구적으로 삭제하여 더 이상 사용할 수 없게 만듭니다. + +- **Endpoint:** `DELETE /manage/keys/{api_key}` +- **요청 예시 (`curl`):** + +```bash +# 폐기하려는 클라이언트 키를 URL 경로에 포함합니다. + +curl -X DELETE http://localhost:8000/manage/keys/sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ +-H "X-Admin-KEY: <여기에_관리자_키를_입력하세요>" +``` + +--- + +## 2. 일반 사용자/클라이언트 가이드 + +관리자로부터 발급받은 `X-API-KEY`를 사용하여 LLM Gateway의 일반 API를 호출할 수 있습니다. + +### 2.1. API 호출 방법 + +- **요구사항:** 모든 요청의 HTTP 헤더에 `X-API-KEY`를 포함해야 합니다. +- **예시: 모델 목록 조회 API 호출** + +```bash +# 발급받은 자신의 클라이언트 키를 X-API-KEY 헤더에 입력하세요. + +curl -X GET http://localhost:8000/api/v1/model/list \ +-H "X-API-KEY: <여기에_발급받은_클라이언트_키를_입력하세요>" +``` + +- **인증 실패 시:** +만약 키가 없거나 유효하지 않은 키를 사용하면, `401 Unauthorized` 또는 `403 Forbidden` 오류가 발생합니다. +```json +{ + "detail": "Invalid or missing API Key" +} +``` diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 0000000..8d4311e --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,143 @@ +## **LLM Gateway 프로젝트 제품 요구사항 문서 (PRD)** + +### 1\. 문서 개요 + +본 문서는 사내 다양한 서비스에서 발생하는 LLM(거대 언어 모델) 및 관련 AI 모델(OCR, STT 등) 수요를 중앙에서 효율적으로 처리하고 관리하기 위한 **LLM Gateway** 프로젝트의 요구사항을 정의합니다. + + * **프로젝트명:** LLM Gateway + * **작성일:** 2025년 8월 8일 + * **담당자/팀:** 한치영 - AI 팀장 + + +### 2\. 프로젝트 배경 및 목표 + +#### 2.1. 배경 및 문제 정의 + + * **리소스 중복:** 유사한 기능이 팀별로 중복 운영 + * **일관성 부재:** 모델 API 규격, 인증 방식, 성능 기준이 다르고, 코드 재활용이 어려움 + * **확장성 한계:** 트래픽 증가 시 유연한 수평 확장에 대한 통일된 전략이 부재 + * **외부 LLM 사용량 측정:** 외부 LLM API를 사용하는 경우 사용량 측정을 위한 목(통일된 진입점)이 필요 + +#### 2.2. 프로젝트 목표 + +LLM Gateway는 이러한 문제들을 해결하고자 아래와 같은 목표를 가짐 + + * **API 통합 및 표준화:** 사내 모든 LLM 및 AI 모델 접근을 위한 단일 End-point를 제공하여 API 규격을 표준화 + * **전처리 파이프라인 제공:** OCR, STT 등 자주 사용되는 전처리 기능을 파이프라인으로 제공하여 개발 생산성을 향상 + * **효율적인 자원 관리:** Docker 기반의 통합 인프라와 Ollama 런타임을 통해 모델 서버를 효율적으로 운영하고, 수평 확장이 용이한 구조를 마련 + * **중앙 집중 관리 및 모니터링:** API 사용량, 응답 시간, 오류 등을 중앙에서 모니터링하여 안정적인 운영을 지원 + + +### 3\. 기능 요구사항 + +| 기능 ID | 기능명 | 상세 설명 | 우선순위 | +| :--- | :--- | :--- | :--- | +| **F-001** | **Gateway API 서버** | FastAPI 기반으로 모든 요청을 수신하고 적절한 서비스로 라우팅. 인증, 로깅, 요청/응답 형식 변환을 처리 | **높음** | +| **F-002** | **LLM 추론 요청 처리** | Ollama로 구동되는 LLM에 대한 추론(Chat/Completion) 요청을 처리합니다. 향후 로드 밸런싱을 통해 여러 LLM 인스턴스로 요청을 분산 | **높음** | +| **F-003** | **비동기 OCR 처리** | 이미지 파일을 입력받아 PaddleOCR 서버에 비동기 처리를 요청하고, 처리 결과를 Redis에 저장. 사용자에게는 Job ID를 즉시 반환 | **높음** | +| **F-004** | **비동기 STT 처리** | 음성 파일을 입력받아 STT 모델(위스퍼) 서버에 비동기 처리를 요청 | **낮음** | +| **F-005** | **비동기 작업 결과 조회** | Job ID를 통해 Redis에 저장된 OCR, STT 등의 작업 상태(대기, 처리 중, 완료, 실패) 및 최종 결과를 조회하는 API를 제공 | **높음** | +| **F-006** | **모듈 추가 확장성** | 향후 새로운 AI 모델(번역, 문서 요약 등)이 추가될 때, 최소한의 설정으로 Gateway에 쉽게 통합할 수 있는 구조 | **중간** | +| **F-007** | **공문 분석** | 국내외 수발신 공문에 대한 데이터 추출 | **높음** | +| **F-008** | **문서 요약** | 문서에 대한 단문 요약 | **낮음** | + + +### 4\. 비기능 요구사항 + + * **성능:** 기준 응답 시간이 채팅 인터페이스를 통한 입력보다 3초 이상 길지 않아야 함. + * **확장성:** 모든 컴포넌트(Gateway, OCR, STT, LLM)는 Docker 컨테이너 기반으로 설계되어 트래픽 증가에 따라 `docker-compose up --scale =N` 를 통해 수평 확장 + * **보안:** 내부망 사용을 전제로 하되, 서비스 구분을 위한 API Key 기반의 인증 체계를 도입 + * **모니터링:** API 요청/응답, 처리 시간, 에러율 등 주요 지표를 Prometheus, Grafana 등을 활용해 시각화하고 모니터링 + +----- + +## **LLM Gateway 아키텍처** + +하이레벨 아키텍처 + +```mermaid +graph TD + User["👩‍💻 사내 서비스:PM 등"] + + subgraph "Gateway Layer" + Gateway["🚀 LLM Gateway (FastAPI)"] + Result_Redis + end + + Ollama + + subgraph "Worker Layer" + Broker[(Celery Broker)] + Worker_Redis["💾 Redis (Job Result Store)"] + Worker_OCR["👷 OCR Celery Worker - PaddleOCR"] + end + %% --- Data Flow --- + + %% Synchronous Flow (LLM Chat) + User --"1.요청"--> Gateway + Result_Redis -- "2.Job ID 즉시 반환" --> User + Gateway -- "3.작업 등록" --> Broker + Broker --> Worker_OCR --> Worker_Redis + Gateway -- "3-background 작업 결과 반복 확인" --> Worker_Redis + Gateway -- "4.LLM 추론 요청" --> Ollama + Ollama -- "5.Json 기반 응답" --> Gateway + Gateway -- "6.결과 확인 Cache 업데이트" --> Result_Redis + User -- "7.결과 확인" --> Result_Redis + + %% Style Definitions + classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px; + classDef special fill:#E8E8FF,stroke:#663399,stroke-width:2px; + class Gateway,LB_LLM,Broker,Redis special; +``` +----- + +시퀀스 기반 이해 + +```mermaid +sequenceDiagram + participant User as 👩‍💻 Project Master + participant Gateway as 🚀 LLM Gateway + participant Broker as Celery Broker + participant Worker as 👷 OCR Worker + participant Worker_Redis as 💾 Redis (Job Store) + participant Ollama + + User->>+Gateway: 1. 이미지 처리 요청 + Gateway->>-User: 2. Job ID 즉시 반환 + + Gateway->>Broker: 3. OCR 작업 등록 + activate Gateway + Broker->>Worker: OCR 작업 전달 + deactivate Gateway + + activate Worker + Worker->>Worker: PaddleOCR로 이미지 처리 + Worker->>Worker_Redis: 처리 결과 저장 + deactivate Worker + + loop 3-background. 작업 결과 반복 확인 + Gateway->>Worker_Redis: Job ID로 결과 확인 + end + + Note right of Gateway: OCR 작업 완료 확인 후 + + Gateway->>+Ollama: 4. OCR 결과 기반 LLM 추론 요청 + Ollama-->>-Gateway: 5. JSON 형식으로 응답 + + Note right of Gateway: 6. 최종 결과를
내부 캐시에 저장 (Result_Redis) + + User->>+Gateway: 7. Job ID로 최종 결과 확인 요청 + Gateway-->>-User: 최종 처리 결과 반환 +``` + + +### 아키텍처 설명 + +1. **User:** 사내의 다른 서비스나 개발자 등 Gateway의 API를 호출하는 주체 +2. **Gateway Layer:** 모든 요청의 진입점 FastAPI로 구현되어 동기/비동기 요청을 받아 적절한 백엔드 서비스로 분기 +3. **AI Model Layer:** + * **LLM Serving:** Ollama 런타임을 사용 +4. **Worker Layer:** OCR, STT 등 각 기능을 독립적인 Docker Stack으로 묶습니다. + * **Celery & Broker(Redis/RabbitMQ):** OCR, STT 같이 시간이 오래 걸리는 작업을 비동기로 처리하기 위한 메시지 큐 + * **Worker:** 실제 작업을 수행하는 프로세스. FastAPI로 랩핑해서 서비스 + * **Redis:** 비동기 작업의 최종 결과를 저장. 클라이언트는 Job ID를 통해 이곳에 저장된 결과를 조회 diff --git a/docs/README_DEV.md b/docs/README_DEV.md new file mode 100644 index 0000000..1d506d8 --- /dev/null +++ b/docs/README_DEV.md @@ -0,0 +1,115 @@ +# LLM Gateway API 가이드 + +## 1. 개요 + +LLM Gateway는 다양한 AI 모델(Ollama, OpenAI, Google, Anthropic 등)을 통합하여 일관된 인터페이스로 제공하는 API 서버입니다. 문서(PDF, 이미지) 처리, 텍스트 생성, 음성-텍스트 변환(STT), 객체 탐지(YOLO) 등 복잡한 AI 파이프라인을 비동기적으로 처리하고 관리하는 기능을 제공합니다. + +주요 기술 스택은 다음과 같습니다: + +- **프레임워크**: FastAPI +- **비동기 작업 관리**: Redis +- **파일 저장소**: MinIO +- **내부 LLM 호스팅**: Ollama +- **서비스 오케스트레이션**: Docker Compose + +--- + +## 2. 핵심 아키텍처 + +### 비동기 처리 모델 + +대부분의 AI 작업은 시간이 오래 걸리므로, 본 시스템은 비동기 파이프라인을 채택했습니다. + +1. **요청 접수**: 사용자가 파일을 포함하여 API를 호출하면, 시스템은 즉시 고유한 `request_id`를 발급하고 백그라운드 작업을 생성합니다. +2. **상태 추적**: `request_id`를 사용하여 `/extract/progress/{request_id}`와 같은 `progress` 엔드포인트에서 작업 상태를 폴링(polling)할 수 있습니다. +3. **결과 저장**: 작업이 완료되면, 최종 결과는 별도의 `result_id`와 매핑되어 Redis에 저장됩니다. `progress` 엔드포인트는 작업 완료 시 `final_result` 필드에 최종 결과를 포함하여 반환합니다. + +### 서비스 의존성 + +- **Redis**: 작업 큐, 상태 로그, 최종 결과, API 키 등을 저장하는 핵심 데이터베이스입니다. +- **MinIO**: 사용자가 업로드한 파일을 임시 저장하고, 각 서비스(OCR, STT 등)가 파일에 접근할 수 있도록 Presigned URL을 생성하는 역할을 합니다. +- **OCR API**: PDF, 이미지 등에서 텍스트를 추출하는 별도의 서비스입니다. `/extract`, `/general` 엔드포인트에서 내부적으로 호출됩니다. +- **STT API**: 음성 파일에서 텍스트를 추출하는 별도의 서비스입니다. `/stt` 엔드포인트가 프록시 역할을 합니다. +- **YOLO API**: 이미지에서 객체를 탐지하는 별도의 서비스입니다. `/yolo` 엔드포인트가 프록시 역할을 합니다. + +--- + + +## 3. API 엔드포인트 상세 설명 + +### Tag: `Extraction` + +문서(PDF, 이미지 등)에서 사전 정의된 프롬프트를 기반으로 정보를 추출합니다. + +* `POST /extract/inner`: **내부 LLM(Ollama)**을 사용합니다. +* `POST /extract/outer`: **외부 LLM(GPT, Gemini 등)**을 사용합니다. +* `POST /extract/structured`: **내외부 LLM을 사용해 JSON 형식 응답 생성**에 사용합니다.* **요청 (multipart/form-data)**:* `input_file`: 분석할 원본 문서 파일. + * `prompt_file` (선택): 기본 프롬프트를 대체할 `.txt` 파일. + * `model` (선택): 사용할 LLM 모델 이름. + * `schema_file` (structured용/필수): 형식을 지정할 `.json` 파일. + * **응답 (초기)**: `request_id`를 포함한 JSON 객체. +* `GET /extract/progress/{request_id}`: 정보 추출 작업의 진행 상태와 최종 결과를 조회합니다. + +### Tag: `General` + +문서 기반의 범용 질의응답을 수행합니다. 사용자가 직접 프롬프트를 제공해야 합니다. + +* `POST /general/inner`: **내부 LLM(Ollama)**을 사용합니다. +* `POST /general/outer`: **외부 LLM(GPT, Gemini 등)**을 사용합니다.* **요청 (multipart/form-data)**:* `input_file`: 분석할 원본 문서 파일. + * `prompt_file`: LLM에 전달할 명령어가 담긴 `.txt` 파일. + * `schema_file` (선택): 결과 포맷을 강제할 `.json` 스키마 파일. + * `model` (선택): 사용할 LLM 모델 이름. + * **응답 (초기)**: `request_id`를 포함한 JSON 객체. +* `GET /general/progress/{request_id}`: 범용 추론 작업의 진행 상태와 최종 결과를 조회합니다. + +### Tag: `OCR Gateway` + +문서에서 텍스트를 추출하는 OCR API를 프록시합니다. + +* `POST /ocr`: 문서를 OCR 서버에 전달하고 `request_id`를 받습니다. +* `GET /ocr/progress/{request_id}`: OCR 작업 상태를 조회합니다. + +### Tag: `STT Gateway` + +음성 파일을 텍스트로 변환하는 STT API를 프록시합니다. + +* `POST /audio`: 단일 음성 파일을 STT 서버로 전달합니다. +* `POST /dialog_processing`: 화자 분리가 포함된 음성 파일을 처리합니다. +* `GET /progress/{request_id}`: STT 작업 상태를 조회합니다. + +### Tag: `YOLO Gateway` + +이미지 내 객체를 탐지하는 YOLO API를 프록시합니다. + +* `POST /detect_view`: 이미지를 YOLO 서버로 전달하고 탐지 결과를 받습니다. +* `GET /detect_view/images/{request_id}`: 탐지 결과가 시각화된 이미지를 조회합니다. +* `GET /detect_view/results/{request_id}`: 탐지 결과 JSON을 조회합니다. + +### Tag: `summary` + +STT로 전사된 텍스트를 회의록으로 작성합니다. + +* `POST /summary`: 등록된 모든 모델을 사용하여 요약을 수행하고 결과를 종합합니다. +* `POST /ollama_summary`: 로컬 모델을 사용하여 요약을 수행하고 결과를 종합합니다. +* `POST /gemini_summary`: gemini flash 모델을 사용하여 요약을 수행하고 결과를 종합합니다. +* `POST /task_summary`: 비동기적으로 모든 모델의 요약을 수행하고 `task_id`를 반환합니다. +* `GET /task_summary/{task_id}`: 비동기 요약 작업의 결과를 조회합니다. + +### Tag: `API Key Management` + +API 키를 관리합니다. + +* `POST /manage/keys`: 새로운 API 키를 생성합니다. +* `GET /manage/keys`: 모든 API 키 목록을 조회합니다. +* `DELETE /manage/keys/{api_key}`: 지정된 API 키를 폐기합니다. + +### Tag: `Model Management` + +* `GET /info`: `/extract`, `/general` 엔드포인트에서 사용 가능한 내부/외부 모델 목록을 조회합니다. +* `GET /default_prompt`: 기본 프롬프트 파일을 다운로드합니다. +* `GET /structured_prompt`: 구조화 추출용 프롬프트 예시 파일을 다운로드합니다. +* `GET /structured_schema`: 구조화 추출용 스키마 예시 파일을 다운로드합니다. + +### Tag: `Guide Book` + +* `/schema_file_guide`, `/general_guide`, `/extract_guide`: 각 기능에 대한 HTML 가이드 문서를 제공합니다. diff --git a/docs/summary_api.md b/docs/summary_api.md new file mode 100644 index 0000000..376e28a --- /dev/null +++ b/docs/summary_api.md @@ -0,0 +1,198 @@ +# Summary API 기능 명세서 + +제공된 코드를 기반으로 `summary` 태그로 그룹화된 API에 대한 기능 명세서입니다. + +--- + +## 1. 통합 요약 생성 (STT 요약) + +여러 LLM 모델을 동시에 호출하여 입력된 텍스트에 대한 요약 결과를 통합하여 반환합니다. + +* **Endpoint:** `/summary` +* **Method:** `POST` +* **Tag:** `summary` +* **Description:** + * 하나의 텍스트 입력을 받아 내부적으로 구성된 여러 모델(gpt-4.1-mini, qwen3:custom, gemini-2.5-flash, claude-3-7-sonnet-latest 등)을 통해 요약을 실행하고, 그 결과를 종합하여 반환합니다. + * 동기적으로 처리되며, 모든 모델의 요약이 완료될 때까지 응답을 대기합니다. + +### Request Body + +| 필드명 | 타입 | 필수 여부 | 설명 | +| :--- | :--- | :--- | :--- | +| `text` | `string` | Y | 요약할 원본 텍스트 | + +### Response Body + +| 필드명 | 타입 | 설명 | +| :--- | :--- | :--- | +| `summary_results` | `object` | 각 모델별 요약 결과가 포함된 객체 | + +### cURL Example + +```bash +curl -X 'POST' \ + 'http://localhost:8888/summary' \ + -H 'Content-Type: application/json' \ + -d '{ + "text": "오늘 회의에서는 3분기 신제품 출시 계획에 대해 논의했습니다. 주요 안건은..." + }' +``` + +--- + +## 2. Ollama 모델 요약 생성 + +Ollama를 통해 호스팅되는 `qwen` 모델을 사용하여 텍스트를 요약합니다. + +* **Endpoint:** `/ollama_summary` +* **Method:** `POST` +* **Tag:** `summary` +* **Description:** + * 지정된 `qwen` 모델만을 사용하여 텍스트 요약을 수행합니다. + +### Request Body + +| 필드명 | 타입 | 필수 여부 | 설명 | +| :--- | :--- | :--- | :--- | +| `text` | `string` | Y | 요약할 원본 텍스트 | + +### Response Body + +| 필드명 | 타입 | 설명 | +| :--- | :--- | :--- | +| `summary_results` | `string` | `qwen` 모델의 요약 결과 | + +### cURL Example + +```bash +curl -X 'POST' \ + 'http://localhost:8888/ollama_summary' \ + -H 'Content-Type: application/json' \ + -d '{ + "text": "Ollama QWEN 모델을 테스트하기 위한 샘플 텍스트입니다." + }' +``` + +--- + +## 3. Gemini 모델 요약 생성 + +Gemini 모델을 사용하여 텍스트를 요약합니다. + +* **Endpoint:** `/gemini_summary` +* **Method:** `POST` +* **Tag:** `summary` +* **Description:** + * Gemini 모델만을 사용하여 텍스트 요약을 수행합니다. + +### Request Body + +| 필드명 | 타입 | 필수 여부 | 설명 | +| :--- | :--- | :--- | :--- | +| `text` | `string` | Y | 요약할 원본 텍스트 | + +### Response Body + +| 필드명 | 타입 | 설명 | +| :--- | :--- | :--- | +| `summary_results` | `string` | Gemini 모델의 요약 결과 | + +### cURL Example + +```bash +curl -X 'POST' \ + 'http://localhost:8888/gemini_summary' \ + -H 'Content-Type: application/json' \ + -d '{ + "text": "Gemini 모델을 테스트하기 위한 샘플 텍스트입니다." + }' +``` + +--- + +## 4. 비동기 요약 작업 생성 + +모든 모델을 사용하여 텍스트 요약을 수행하는 백그라운드 작업을 시작합니다. + +* **Endpoint:** `/task_summary` +* **Method:** `POST` +* **Tag:** `summary` +* **Description:** + * 요약 작업을 백그라운드에서 실행하도록 요청하고, 즉시 작업 ID(`task_id`)를 반환합니다. + * 실제 요약 결과는 반환된 `task_id`를 사용하여 `/task_summary/{task_id}` 엔드포인트에서 조회해야 합니다. + +### Request Body + +| 필드명 | 타입 | 필수 여부 | 설명 | +| :--- | :--- | :--- | :--- | +| `text` | `string` | Y | 요약할 원본 텍스트 | + +### Response Body + +| 필드명 | 타입 | 설명 | +| :--- | :--- | :--- | +| `task_id` | `string` | 생성된 백그라운드 작업의 고유 ID | + +### cURL Example + +```bash +curl -X 'POST' \ + 'http://localhost:8888/task_summary' \ + -H 'Content-Type: application/json' \ + -d '{ + "text": "이것은 비동기 요약 작업을 테스트하기 위한 긴 텍스트입니다..." + }' +``` + +--- + +## 5. 비동기 요약 작업 결과 조회 + +`task_summary`를 통해 생성된 백그라운드 작업의 상태와 결과를 조회합니다. + +* **Endpoint:** `/task_summary/{task_id}` +* **Method:** `GET` +* **Tag:** `summary` +* **Description:** + * `task_id`를 사용하여 특정 요약 작업의 진행 상태(예: 처리 중, 완료) 및 완료 시 요약 결과를 확인합니다. + +### Path Parameters + +| 파라미터명 | 타입 | 필수 여부 | 설명 | +| :--- | :--- | :--- | :--- | +| `task_id` | `string` | Y | 조회할 작업의 고유 ID | + +### Response Body + +* **작업이 진행 중일 경우:** + ```json + { + "status": "processing", + "results": {} + } + ``` +* **작업이 완료되었을 경우:** + ```json + { + "status": "completed", + "results": { + "gpt_summary": "GPT 요약 결과...", + "qwen_summary": "QWEN 요약 결과...", + "gemini_summary": "Gemini 요약 결과...", + "claude_summary": "Claude 요약 결과..." + } + } + ``` +* **잘못된 `task_id`일 경우:** + ```json + { + "error": "Invalid task_id" + } + ``` + +### cURL Example + +```bash +curl -X 'GET' \ + 'http://localhost:8888/task_summary/your_generated_task_id' +``` \ No newline at end of file diff --git a/log_config.yaml b/log_config.yaml new file mode 100644 index 0000000..6480441 --- /dev/null +++ b/log_config.yaml @@ -0,0 +1,23 @@ +version: 1 +disable_existing_loggers: False + +formatters: + access: + (): uvicorn.logging.AccessFormatter + format: '%(asctime)s [%(levelname)s] %(client_addr)s - "%(request_line)s" %(status_code)s' + +filters: + health_check_filter: + (): workspace.utils.logging_utils.HealthCheckFilter + +handlers: + access: + class: logging.StreamHandler + formatter: access + filters: [health_check_filter] + +loggers: + uvicorn.access: + level: INFO + handlers: [access] + propagate: no diff --git a/loki-config.yaml b/loki-config.yaml new file mode 100644 index 0000000..8f4de2c --- /dev/null +++ b/loki-config.yaml @@ -0,0 +1,63 @@ +server: + http_listen_port: 3100 + http_server_read_timeout: 3m + http_server_write_timeout: 3m + grpc_server_max_send_msg_size: 2147483647 + grpc_server_max_recv_msg_size: 2147483647 + +common: # Loki의 공통 설정 + path_prefix: /loki # Loki의 데이터 저장 경로 + storage: + filesystem: + chunks_directory: /loki/chunks # 로그 chunk 데이터를 저장하는 디스크 경로 + rules_directory: /loki/rules # Loki alerting/rule 파일을 저장하는 디렉토리 + replication_factor: 1 + ring: + kvstore: + store: memberlist # inmemory + +schema_config: + configs: + - from: 2025-04-01 + store: boltdb-shipper + object_store: filesystem + schema: v11 + index: + prefix: index_ + period: 24h + +ruler: + enable_api: true + +limits_config: + enforce_metric_name: false + max_cache_freshness_per_query: 10m + reject_old_samples: true + reject_old_samples_max_age: 168h + split_queries_by_interval: 15m # 쿼리를 작은 단위로 나눠서 처리 → 요청량 분산 + per_stream_rate_limit: 512M + cardinality_limit: 200000 + ingestion_burst_size_mb: 1000 + ingestion_rate_mb: 10000 + max_entries_limit_per_query: 1000000 # 반환할 로그 개수 제한으로 리소스 초과 방지 + max_global_streams_per_user: 10000 + max_streams_per_user: 0 + max_query_parallelism: 32 # 단일 쿼리를 쪼개 병렬 처리 허용 수 제한 + max_label_value_length: 20480 + max_label_name_length: 10240 + max_label_names_per_series: 300 + +frontend: + max_outstanding_per_tenant: 8192 # 프론트에서 수용 가능한 요청 수 증가 + compress_responses: true + +querier: + max_concurrent: 8192 # 실행기 병렬 처리 확장 + +query_scheduler: + max_outstanding_requests_per_tenant: 8192 # 스케줄러 병목 해소 + +query_range: + split_queries_by_interval: 0 + + \ No newline at end of file diff --git a/prometheus.yml b/prometheus.yml new file mode 100644 index 0000000..1cbf784 --- /dev/null +++ b/prometheus.yml @@ -0,0 +1,27 @@ +global: + scrape_interval: 1m + evaluation_interval: 1m + +scrape_configs: + - job_name: "llm_gateway" + metrics_path: /metrics + static_configs: + - targets: ["pgn_api_8888:8888"] + + - job_name: "ocr" + metrics_path: /metrics + static_configs: + - targets: ["ocr_api_8890:8890"] + + - job_name: "stt" + metrics_path: /metrics + static_configs: + - targets: ["stt_fastapi:8899"] + + - job_name: "node" + static_configs: + - targets: ["pgn_node_exporter:9100"] + + - job_name: "gpu" + static_configs: + - targets: ["pgn_dcgm_exporter:9400"] diff --git a/promtail-config.yaml b/promtail-config.yaml new file mode 100644 index 0000000..2946a91 --- /dev/null +++ b/promtail-config.yaml @@ -0,0 +1,44 @@ +server: # Promtail 자체 HTTP 서버 (내부용, 포트 9080) + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: # 로그 읽은 위치 저장 (로그 재수집 방지용) + filename: /tmp/positions.yaml + +clients: # Loki로 로그 전송 (pgn_loki:3100) + - url: http://pgn_loki:3100/loki/api/v1/push + +scrape_configs: # 어떤 로그를 읽을지 설정 + - job_name: llm_gateway + static_configs: + - targets: + - localhost + labels: + job: llm_gateway + __path__: /var/lib/docker/containers/*/*.log + + pipeline_stages: + - docker: {} # 로그에서 도커 메타데이터 추출 + + - match: + selector: '{container_name=~"pgn_api_.*"}' + stages: + - json: # 로그 내용이 JSON이면 자동 파싱 (level, msg, time 추출) + expressions: + level: level + msg: message + time: timestamp + - labels: # level, container_name을 Loki에 라벨링 + level: + container_name: + job: + + - match: + selector: '{container_name=~"pgn_api_.*"}' + stages: + - regex: + expression: "^(?P