commit 7217d3cbaaf312adcd529dbdf1e641506622023c Author: ai-cell-a100-1 Date: Mon Aug 11 18:56:38 2025 +0900 원 레포랑 완전 분리 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