콘텐츠로 이동

AI 검색

AI가 매뉴얼 전체에서 답변을 찾아드립니다.

운영 배포 가이드

LLM Search Service를 프로덕션 환경에 배포하기 위한 가이드입니다.

구성특징권장 환경
StandaloneFastAPI 단독 실행개발, 소규모
Docker컨테이너 기반중규모, 클라우드
DDEV 통합기존 DDEV 환경에 추가si-data 프로젝트

  • Python 3.11+
  • OpenAI API 키 (프로덕션)
  • Elasticsearch 7.x (RAG Extended 사용 시)
  • Ollama (로컬 LLM 사용 시)
  • Docker (컨테이너 배포 시)

Terminal window
cd services/llm-search
# 가상환경 생성
python3 -m venv .venv
source .venv/bin/activate
# 의존성 설치
pip install -r requirements.txt
Terminal window
# .env 파일 생성
cp .env.example .env
# 필수 설정 편집
nano .env

.env 설정 예시:

Terminal window
# LLM Provider (openai 권장)
LLM_PROVIDER=openai
# OpenAI 설정
OPENAI_API_KEY=sk-proj-your-api-key-here
OPENAI_MODEL=gpt-4o
OPENAI_TIMEOUT=30
OPENAI_MAX_RETRIES=3
# Elasticsearch (RAG Extended 사용 시)
ES_HOST=localhost
ES_PORT=9200
ES_DATA_INDEX=elasticsearch_index_datasi_new_si
# 서비스 설정
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8000
DEBUG=false
LOG_LEVEL=INFO
Terminal window
# 개발 모드
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
# 프로덕션 모드 (Gunicorn)
pip install gunicorn
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
/etc/systemd/system/llm-search.service
[Unit]
Description=LLM Search Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/llm-search
Environment="PATH=/opt/llm-search/.venv/bin"
ExecStart=/opt/llm-search/.venv/bin/gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Terminal window
sudo systemctl daemon-reload
sudo systemctl enable llm-search
sudo systemctl start llm-search

# services/llm-search/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 애플리케이션 복사
COPY app/ ./app/
# 환경변수
ENV PYTHONUNBUFFERED=1
ENV SERVICE_HOST=0.0.0.0
ENV SERVICE_PORT=8000
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
services/llm-search/docker-compose.yml
version: '3.8'
services:
llm-search:
build: .
ports:
- "8000:8000"
environment:
- LLM_PROVIDER=openai
- OPENAI_API_KEY=${OPENAI_API_KEY}
- OPENAI_MODEL=gpt-4o
- ES_HOST=elasticsearch
- ES_PORT=9200
depends_on:
- elasticsearch
restart: unless-stopped
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.14
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- es_data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
volumes:
es_data:
Terminal window
# 빌드 및 실행
docker-compose up -d
# 로그 확인
docker-compose logs -f llm-search
# 상태 확인
curl http://localhost:8000/health

.ddev/docker-compose.llm-search.yaml
version: '3.6'
services:
llm-search:
build:
context: ../services/llm-search
dockerfile: Dockerfile
container_name: ddev-${DDEV_SITENAME}-llm-search
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
environment:
- LLM_PROVIDER=openai
- OPENAI_API_KEY=${OPENAI_API_KEY}
- ES_HOST=elasticsearch
- ES_PORT=9200
- ES_DATA_INDEX=elasticsearch_index_datasi_new_si
expose:
- "8000"
external_links:
- ddev-router:${DDEV_SITENAME}.ddev.site
Terminal window
# .ddev/.env 또는 .ddev/config.yaml의 web_environment
ddev config --web-environment-add="OPENAI_API_KEY=sk-proj-your-key"
web/modules/custom/si_data/src/Service/LLMSearchService.php
namespace Drupal\si_data\Service;
use GuzzleHttp\ClientInterface;
class LLMSearchService {
protected $httpClient;
protected $baseUrl;
public function __construct(ClientInterface $http_client) {
$this->httpClient = $http_client;
$this->baseUrl = 'http://llm-search:8000';
}
public function expandKeywords(string $query, int $maxKeywords = 5): array {
try {
$response = $this->httpClient->post($this->baseUrl . '/api/keywords/expand', [
'json' => [
'query' => $query,
'max_keywords' => $maxKeywords,
'provider' => 'openai',
],
'timeout' => 30,
]);
return json_decode($response->getBody(), TRUE);
}
catch (\Exception $e) {
\Drupal::logger('si_data')->error('LLM Search error: @error', [
'@error' => $e->getMessage(),
]);
return ['expanded_keywords' => []];
}
}
}
web/modules/custom/si_data/si_data.services.yml
services:
si_data.llm_search:
class: Drupal\si_data\Service\LLMSearchService
arguments: ['@http_client']

Terminal window
# 기본 헬스 체크
curl http://localhost:8000/health
# 상세 설정 확인
curl http://localhost:8000/config
# Provider 연결 테스트
curl http://localhost:8000/api/keywords/test/all
Terminal window
# OpenAI 사용량 확인
curl http://localhost:8000/api/benchmark/usage-stats
# PageIndex 캐시 현황
curl http://localhost:8000/api/pageindex/cache/list
# app/main.py에 추가
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler("/var/log/llm-search/app.log"),
logging.StreamHandler()
]
)
# 비용 임계값 알림 예시
async def check_usage_threshold():
from app.services.openai_client import OpenAIClient
client = OpenAIClient()
stats = client.usage_stats
if stats['total_cost_usd'] > 10.0: # $10 초과 시
# 알림 전송 (Slack, Email 등)
pass

Terminal window
# .env 파일 권한 설정
chmod 600 .env
# Git에서 제외 확인
cat .gitignore | grep ".env"
app/config.py
cors_origins: List[str] = [
"https://si-data.seoul.go.kr",
"https://data.seoul.go.kr"
]
# pip install slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@router.post("/expand")
@limiter.limit("10/minute")
async def expand_keywords(request: Request, ...):
...

문제원인해결책
429 Too Many RequestsOpenAI rate limitdelay 증가, API 키 확인
Connection refusedES 미실행ES 서비스 시작
Timeout네트워크/서버 지연timeout 값 증가
Import error의존성 누락pip install -r requirements.txt
Terminal window
# 서비스 로그
tail -f /tmp/llm-search.log
# Docker 로그
docker-compose logs -f llm-search
# systemd 로그
journalctl -u llm-search -f
Terminal window
# .env에서 설정
DEBUG=true
LOG_LEVEL=DEBUG
# 또는 실행 시
uvicorn app.main:app --reload --log-level debug

Terminal window
# Provider
LLM_PROVIDER=openai
# OpenAI
OPENAI_API_KEY=sk-proj-xxx
OPENAI_MODEL=gpt-4o
OPENAI_TIMEOUT=30
OPENAI_MAX_RETRIES=3
# Elasticsearch
ES_HOST=elasticsearch
ES_PORT=9200
ES_DATA_INDEX=elasticsearch_index_datasi_new_si
# Service
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8000
DEBUG=false
LOG_LEVEL=INFO
# CORS (프로덕션 도메인만)
CORS_ORIGINS=["https://si-data.seoul.go.kr"]
사용량OpenAI BasicOpenAI RAG
일 100 쿼리~$0.07~$0.17
일 1,000 쿼리~$0.73~$1.70
월 10,000 쿼리~$7.30~$17.00

버전날짜내용
1.02026-01-26초기 작성