Spaces:
Runtime error
Runtime error
youdie006
commited on
Commit
·
aa58f77
1
Parent(s):
c36a566
Fix: Add-multi env
Browse files- Dockerfile +27 -19
- README.md +1 -1
- docker-compose.yml +43 -8
- main.py +242 -22
- start.sh +84 -0
Dockerfile
CHANGED
|
@@ -1,14 +1,13 @@
|
|
| 1 |
# Dockerfile
|
| 2 |
|
| 3 |
-
# 1. 베이스 이미지 설정
|
| 4 |
FROM python:3.10-slim
|
| 5 |
|
| 6 |
-
#
|
| 7 |
LABEL maintainer="[email protected]"
|
| 8 |
-
LABEL description="SimSimi-
|
| 9 |
LABEL version="1.0.0"
|
| 10 |
|
| 11 |
-
#
|
| 12 |
RUN apt-get update && apt-get install -y \
|
| 13 |
gcc \
|
| 14 |
g++ \
|
|
@@ -18,36 +17,45 @@ RUN apt-get update && apt-get install -y \
|
|
| 18 |
&& git lfs install \
|
| 19 |
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
|
| 21 |
-
# 4. 작업 디렉토리 설정
|
| 22 |
WORKDIR /app
|
| 23 |
|
| 24 |
-
#
|
| 25 |
ENV HF_HOME=/app/cache
|
| 26 |
ENV HF_DATASETS_CACHE=/app/cache
|
| 27 |
ENV TRANSFORMERS_CACHE=/app/cache
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
#
|
| 30 |
COPY requirements.txt .
|
| 31 |
RUN pip install --upgrade pip
|
| 32 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 33 |
|
| 34 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
RUN huggingface-cli download \
|
| 36 |
youdie006/simsimi-ai-agent-data \
|
| 37 |
--repo-type dataset \
|
| 38 |
--local-dir /app/data \
|
| 39 |
-
--local-dir-use-symlinks False
|
| 40 |
-
RUN chmod -R 777 /app/data /app/cache
|
| 41 |
|
| 42 |
-
#
|
| 43 |
-
COPY . .
|
|
|
|
| 44 |
|
| 45 |
-
#
|
| 46 |
-
EXPOSE
|
| 47 |
|
| 48 |
-
#
|
| 49 |
-
HEALTHCHECK --interval=
|
| 50 |
-
CMD curl -f http://localhost:
|
| 51 |
|
| 52 |
-
#
|
| 53 |
-
CMD ["
|
|
|
|
| 1 |
# Dockerfile
|
| 2 |
|
|
|
|
| 3 |
FROM python:3.10-slim
|
| 4 |
|
| 5 |
+
# 메타데이터
|
| 6 |
LABEL maintainer="[email protected]"
|
| 7 |
+
LABEL description="SimSimi AI Agent - Multi Environment Support"
|
| 8 |
LABEL version="1.0.0"
|
| 9 |
|
| 10 |
+
# 시스템 의존성 설치
|
| 11 |
RUN apt-get update && apt-get install -y \
|
| 12 |
gcc \
|
| 13 |
g++ \
|
|
|
|
| 17 |
&& git lfs install \
|
| 18 |
&& rm -rf /var/lib/apt/lists/*
|
| 19 |
|
|
|
|
| 20 |
WORKDIR /app
|
| 21 |
|
| 22 |
+
# 환경 변수 설정
|
| 23 |
ENV HF_HOME=/app/cache
|
| 24 |
ENV HF_DATASETS_CACHE=/app/cache
|
| 25 |
ENV TRANSFORMERS_CACHE=/app/cache
|
| 26 |
+
ENV PYTHONPATH=/app
|
| 27 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 28 |
+
ENV PYTHONUNBUFFERED=1
|
| 29 |
|
| 30 |
+
# Python 의존성 설치
|
| 31 |
COPY requirements.txt .
|
| 32 |
RUN pip install --upgrade pip
|
| 33 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 34 |
|
| 35 |
+
# 애플리케이션 코드 복사
|
| 36 |
+
COPY . .
|
| 37 |
+
|
| 38 |
+
# 필요한 디렉토리 생성
|
| 39 |
+
RUN mkdir -p /app/data /app/cache /app/logs /app/static
|
| 40 |
+
RUN chmod -R 777 /app/data /app/cache /app/logs
|
| 41 |
+
|
| 42 |
+
# 조건부 데이터 다운로드
|
| 43 |
RUN huggingface-cli download \
|
| 44 |
youdie006/simsimi-ai-agent-data \
|
| 45 |
--repo-type dataset \
|
| 46 |
--local-dir /app/data \
|
| 47 |
+
--local-dir-use-symlinks False || echo "⚠️ 데이터 다운로드 실패 - 런타임에 생성"
|
|
|
|
| 48 |
|
| 49 |
+
# 스마트 시작 스크립트 복사
|
| 50 |
+
COPY start.sh /app/start.sh
|
| 51 |
+
RUN chmod +x /app/start.sh
|
| 52 |
|
| 53 |
+
# 포트 노출 (7860 고정)
|
| 54 |
+
EXPOSE 7860
|
| 55 |
|
| 56 |
+
# 헬스체크
|
| 57 |
+
HEALTHCHECK --interval=30s --timeout=30s --start-period=300s --retries=3 \
|
| 58 |
+
CMD curl -f http://localhost:7860/api/v1/health || exit 1
|
| 59 |
|
| 60 |
+
# 🎯 스마트 시작 - 환경 자동 감지!
|
| 61 |
+
CMD ["/app/start.sh"]
|
README.md
CHANGED
|
@@ -6,7 +6,7 @@ emoji: 💙
|
|
| 6 |
colorFrom: purple
|
| 7 |
colorTo: blue
|
| 8 |
sdk: docker
|
| 9 |
-
app_port:
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
|
|
|
| 6 |
colorFrom: purple
|
| 7 |
colorTo: blue
|
| 8 |
sdk: docker
|
| 9 |
+
app_port: 7860
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
docker-compose.yml
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
services:
|
| 2 |
simsimi-ai-agent:
|
| 3 |
build:
|
|
@@ -5,26 +7,28 @@ services:
|
|
| 5 |
dockerfile: Dockerfile
|
| 6 |
container_name: simsimi_ai_agent
|
| 7 |
ports:
|
| 8 |
-
- "
|
| 9 |
volumes:
|
| 10 |
-
# 소스코드 실시간 반영 (개발용)
|
| 11 |
- ./src:/app/src
|
| 12 |
- ./scripts:/app/scripts
|
| 13 |
- ./main.py:/app/main.py
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
- ./data:/app/data
|
| 16 |
- ./logs:/app/logs
|
| 17 |
-
# [추가] 캐시 데이터 영구 저장
|
| 18 |
-
# 이렇게 하면 컨테이너를 껐다 켜도 매번 모델을 새로 다운로드하지 않습니다.
|
| 19 |
- ./cache:/app/cache
|
| 20 |
-
|
|
|
|
| 21 |
- ./.env:/app/.env:ro
|
| 22 |
environment:
|
| 23 |
- PYTHONPATH=/app
|
| 24 |
- PYTHONDONTWRITEBYTECODE=1
|
| 25 |
- PYTHONUNBUFFERED=1
|
| 26 |
-
# [추가] Hugging Face 캐시 디렉토리 환경 변수
|
| 27 |
- HF_HOME=/app/cache
|
|
|
|
|
|
|
| 28 |
env_file:
|
| 29 |
- .env
|
| 30 |
restart: unless-stopped
|
|
@@ -33,6 +37,37 @@ services:
|
|
| 33 |
networks:
|
| 34 |
- simsimi_network
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
networks:
|
| 37 |
simsimi_network:
|
| 38 |
-
driver: bridge
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# docker-compose.yml - 로컬 개발 최적화
|
| 2 |
+
|
| 3 |
services:
|
| 4 |
simsimi-ai-agent:
|
| 5 |
build:
|
|
|
|
| 7 |
dockerfile: Dockerfile
|
| 8 |
container_name: simsimi_ai_agent
|
| 9 |
ports:
|
| 10 |
+
- "7860:7860" # 허깅페이스와 동일한 포트
|
| 11 |
volumes:
|
| 12 |
+
# 🔄 소스코드 실시간 반영 (개발용)
|
| 13 |
- ./src:/app/src
|
| 14 |
- ./scripts:/app/scripts
|
| 15 |
- ./main.py:/app/main.py
|
| 16 |
+
- ./static:/app/static
|
| 17 |
+
|
| 18 |
+
# 💾 데이터 영구 저장
|
| 19 |
- ./data:/app/data
|
| 20 |
- ./logs:/app/logs
|
|
|
|
|
|
|
| 21 |
- ./cache:/app/cache
|
| 22 |
+
|
| 23 |
+
# 🔑 환경변수 (로컬에서만)
|
| 24 |
- ./.env:/app/.env:ro
|
| 25 |
environment:
|
| 26 |
- PYTHONPATH=/app
|
| 27 |
- PYTHONDONTWRITEBYTECODE=1
|
| 28 |
- PYTHONUNBUFFERED=1
|
|
|
|
| 29 |
- HF_HOME=/app/cache
|
| 30 |
+
- LOCAL_DEV=true # 🏠 로컬 개발 환경 표시
|
| 31 |
+
- DEVELOPMENT_MODE=true # 🔧 개발 모드 활성화
|
| 32 |
env_file:
|
| 33 |
- .env
|
| 34 |
restart: unless-stopped
|
|
|
|
| 37 |
networks:
|
| 38 |
- simsimi_network
|
| 39 |
|
| 40 |
+
# 🚀 프로덕션 테스트용 (옵션)
|
| 41 |
+
simsimi-production-test:
|
| 42 |
+
build:
|
| 43 |
+
context: .
|
| 44 |
+
dockerfile: Dockerfile
|
| 45 |
+
container_name: simsimi_production_test
|
| 46 |
+
ports:
|
| 47 |
+
- "7861:7860" # 다른 포트로 동시 실행
|
| 48 |
+
volumes:
|
| 49 |
+
- ./data:/app/data
|
| 50 |
+
- ./logs:/app/logs
|
| 51 |
+
- ./cache:/app/cache
|
| 52 |
+
- ./.env:/app/.env:ro
|
| 53 |
+
environment:
|
| 54 |
+
- PYTHONPATH=/app
|
| 55 |
+
- PYTHONDONTWRITEBYTECODE=1
|
| 56 |
+
- PYTHONUNBUFFERED=1
|
| 57 |
+
- HF_HOME=/app/cache
|
| 58 |
+
- PRODUCTION=true # 🏭 프로덕션 모드 테스트
|
| 59 |
+
env_file:
|
| 60 |
+
- .env
|
| 61 |
+
restart: unless-stopped
|
| 62 |
+
profiles:
|
| 63 |
+
- production-test # docker-compose --profile production-test up
|
| 64 |
+
networks:
|
| 65 |
+
- simsimi_network
|
| 66 |
+
|
| 67 |
networks:
|
| 68 |
simsimi_network:
|
| 69 |
+
driver: bridge
|
| 70 |
+
|
| 71 |
+
# 사용법:
|
| 72 |
+
# 개발: docker-compose up
|
| 73 |
+
# 프로덕션 테스트: docker-compose --profile production-test up
|
main.py
CHANGED
|
@@ -1,61 +1,281 @@
|
|
| 1 |
-
# main.py
|
| 2 |
|
| 3 |
-
"""
|
| 4 |
-
청소년 공감형 AI 챗봇 메인 서버
|
| 5 |
-
"""
|
| 6 |
-
from fastapi import FastAPI
|
| 7 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 8 |
-
from fastapi.responses import JSONResponse, HTMLResponse
|
| 9 |
-
from fastapi.staticfiles import StaticFiles
|
| 10 |
import os
|
| 11 |
import sys
|
| 12 |
from datetime import datetime
|
| 13 |
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# 환경 변수 로드
|
| 16 |
load_dotenv()
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
app = FastAPI(
|
| 20 |
title="💙 마음이 - 청소년 상담 챗봇",
|
| 21 |
-
description="13-19세 청소년을 위한 AI 공감 상담사",
|
| 22 |
-
version=os.getenv("VERSION", "2.0.0")
|
|
|
|
|
|
|
|
|
|
| 23 |
)
|
| 24 |
|
| 25 |
-
# CORS 설정
|
| 26 |
app.add_middleware(
|
| 27 |
CORSMiddleware,
|
| 28 |
-
allow_origins=["
|
| 29 |
allow_credentials=True,
|
| 30 |
allow_methods=["*"],
|
| 31 |
allow_headers=["*"],
|
| 32 |
)
|
| 33 |
|
| 34 |
-
# 정적 파일 서빙
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
try:
|
| 43 |
from src.api import chat, openai, vector
|
|
|
|
| 44 |
app.include_router(chat.router, prefix="/api/v1/chat", tags=["💙 Teen Chat"])
|
| 45 |
app.include_router(openai.router, prefix="/api/v1/openai", tags=["🤖 OpenAI GPT-4"])
|
| 46 |
app.include_router(vector.router, prefix="/api/v1/vector", tags=["🗄️ Vector Store"])
|
| 47 |
print("✅ API 라우터 등록 완료")
|
| 48 |
except ImportError as e:
|
| 49 |
print(f"⚠️ API 라우터 import 실패: {e}")
|
|
|
|
| 50 |
|
|
|
|
|
|
|
| 51 |
@app.get("/", response_class=HTMLResponse)
|
| 52 |
async def root():
|
|
|
|
| 53 |
html_file_path = "static/index.html"
|
| 54 |
if os.path.exists(html_file_path):
|
| 55 |
with open(html_file_path, "r", encoding="utf-8") as f:
|
| 56 |
return HTMLResponse(content=f.read())
|
| 57 |
-
return HTMLResponse(content=
|
|
|
|
| 58 |
|
| 59 |
@app.get("/api/v1/health")
|
| 60 |
async def health_check():
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# main.py - 환경 자동 감지 및 최적화
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import os
|
| 4 |
import sys
|
| 5 |
from datetime import datetime
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
+
from fastapi import FastAPI
|
| 8 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
+
from fastapi.responses import JSONResponse, HTMLResponse
|
| 10 |
+
from fastapi.staticfiles import StaticFiles
|
| 11 |
|
| 12 |
# 환경 변수 로드
|
| 13 |
load_dotenv()
|
| 14 |
|
| 15 |
+
|
| 16 |
+
# 🌍 환경 감지
|
| 17 |
+
class EnvironmentDetector:
|
| 18 |
+
@staticmethod
|
| 19 |
+
def detect_environment():
|
| 20 |
+
"""실행 환경 자동 감지"""
|
| 21 |
+
if os.getenv("SPACE_ID") or os.getenv("SPACE_AUTHOR_NAME"):
|
| 22 |
+
return "huggingface"
|
| 23 |
+
elif os.getenv("LOCAL_DEV") == "true" or os.getenv("DEVELOPMENT_MODE") == "true":
|
| 24 |
+
return "local_dev"
|
| 25 |
+
elif os.getenv("PRODUCTION") == "true":
|
| 26 |
+
return "production"
|
| 27 |
+
else:
|
| 28 |
+
return "default"
|
| 29 |
+
|
| 30 |
+
@staticmethod
|
| 31 |
+
def get_environment_config(env_type: str) -> dict:
|
| 32 |
+
"""환경별 설정 반환"""
|
| 33 |
+
configs = {
|
| 34 |
+
"huggingface": {
|
| 35 |
+
"debug": False,
|
| 36 |
+
"reload": False,
|
| 37 |
+
"log_level": "info",
|
| 38 |
+
"cors_origins": ["*"],
|
| 39 |
+
"description": "🤗 허깅페이스 Spaces에서 실행 중",
|
| 40 |
+
"features": {
|
| 41 |
+
"static_files": True,
|
| 42 |
+
"api_docs": True,
|
| 43 |
+
"debug_routes": False
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
"local_dev": {
|
| 47 |
+
"debug": True,
|
| 48 |
+
"reload": True,
|
| 49 |
+
"log_level": "debug",
|
| 50 |
+
"cors_origins": ["*"],
|
| 51 |
+
"description": "🏠 로컬 개발 환경에서 실행 중",
|
| 52 |
+
"features": {
|
| 53 |
+
"static_files": True,
|
| 54 |
+
"api_docs": True,
|
| 55 |
+
"debug_routes": True
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
"production": {
|
| 59 |
+
"debug": False,
|
| 60 |
+
"reload": False,
|
| 61 |
+
"log_level": "warning",
|
| 62 |
+
"cors_origins": ["https://yourdomain.com"],
|
| 63 |
+
"description": "🏭 프로덕션 환경에서 실행 중",
|
| 64 |
+
"features": {
|
| 65 |
+
"static_files": True,
|
| 66 |
+
"api_docs": False,
|
| 67 |
+
"debug_routes": False
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
"default": {
|
| 71 |
+
"debug": False,
|
| 72 |
+
"reload": False,
|
| 73 |
+
"log_level": "info",
|
| 74 |
+
"cors_origins": ["*"],
|
| 75 |
+
"description": "🔧 기본 환경에서 실행 중",
|
| 76 |
+
"features": {
|
| 77 |
+
"static_files": True,
|
| 78 |
+
"api_docs": True,
|
| 79 |
+
"debug_routes": False
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
return configs.get(env_type, configs["default"])
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# 환경 감지 및 설정
|
| 87 |
+
ENVIRONMENT = EnvironmentDetector.detect_environment()
|
| 88 |
+
CONFIG = EnvironmentDetector.get_environment_config(ENVIRONMENT)
|
| 89 |
+
|
| 90 |
+
print(f"🌍 감지된 환경: {ENVIRONMENT}")
|
| 91 |
+
print(f"📋 설정: {CONFIG['description']}")
|
| 92 |
+
|
| 93 |
+
# FastAPI 앱 생성 (환경별 설정 적용)
|
| 94 |
app = FastAPI(
|
| 95 |
title="💙 마음이 - 청소년 상담 챗봇",
|
| 96 |
+
description=f"13-19세 청소년을 위한 AI 공감 상담사 ({CONFIG['description']})",
|
| 97 |
+
version=os.getenv("VERSION", "2.0.0"),
|
| 98 |
+
docs_url="/docs" if CONFIG["features"]["api_docs"] else None,
|
| 99 |
+
redoc_url="/redoc" if CONFIG["features"]["api_docs"] else None,
|
| 100 |
+
debug=CONFIG["debug"]
|
| 101 |
)
|
| 102 |
|
| 103 |
+
# CORS 설정 (환경별)
|
| 104 |
app.add_middleware(
|
| 105 |
CORSMiddleware,
|
| 106 |
+
allow_origins=CONFIG["cors_origins"],
|
| 107 |
allow_credentials=True,
|
| 108 |
allow_methods=["*"],
|
| 109 |
allow_headers=["*"],
|
| 110 |
)
|
| 111 |
|
| 112 |
+
# 정적 파일 서빙 (환경별)
|
| 113 |
+
if CONFIG["features"]["static_files"]:
|
| 114 |
+
try:
|
| 115 |
+
static_dir = "static"
|
| 116 |
+
if not os.path.exists(static_dir):
|
| 117 |
+
os.makedirs(static_dir)
|
| 118 |
|
| 119 |
+
index_path = os.path.join(static_dir, "index.html")
|
| 120 |
+
if not os.path.exists(index_path):
|
| 121 |
+
create_default_html(index_path)
|
| 122 |
+
|
| 123 |
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
| 124 |
+
print("✅ 정적 파일 서빙 설정 완료")
|
| 125 |
+
except Exception as e:
|
| 126 |
+
print(f"⚠️ 정적 파일 설정 실패: {e}")
|
| 127 |
+
|
| 128 |
+
# 라우터 등록 (오류 처리 포함)
|
| 129 |
try:
|
| 130 |
from src.api import chat, openai, vector
|
| 131 |
+
|
| 132 |
app.include_router(chat.router, prefix="/api/v1/chat", tags=["💙 Teen Chat"])
|
| 133 |
app.include_router(openai.router, prefix="/api/v1/openai", tags=["🤖 OpenAI GPT-4"])
|
| 134 |
app.include_router(vector.router, prefix="/api/v1/vector", tags=["🗄️ Vector Store"])
|
| 135 |
print("✅ API 라우터 등록 완료")
|
| 136 |
except ImportError as e:
|
| 137 |
print(f"⚠️ API 라우터 import 실패: {e}")
|
| 138 |
+
add_demo_routes()
|
| 139 |
|
| 140 |
+
|
| 141 |
+
# 기본 라우트들
|
| 142 |
@app.get("/", response_class=HTMLResponse)
|
| 143 |
async def root():
|
| 144 |
+
"""메인 페이지"""
|
| 145 |
html_file_path = "static/index.html"
|
| 146 |
if os.path.exists(html_file_path):
|
| 147 |
with open(html_file_path, "r", encoding="utf-8") as f:
|
| 148 |
return HTMLResponse(content=f.read())
|
| 149 |
+
return HTMLResponse(content=get_default_html())
|
| 150 |
+
|
| 151 |
|
| 152 |
@app.get("/api/v1/health")
|
| 153 |
async def health_check():
|
| 154 |
+
"""헬스 체크 (환경 정보 포함)"""
|
| 155 |
+
return {
|
| 156 |
+
"status": "healthy",
|
| 157 |
+
"environment": ENVIRONMENT,
|
| 158 |
+
"config": CONFIG['description'],
|
| 159 |
+
"features": CONFIG['features'],
|
| 160 |
+
"timestamp": datetime.now().isoformat(),
|
| 161 |
+
"python_version": sys.version.split()[0],
|
| 162 |
+
"debug_mode": CONFIG["debug"]
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@app.get("/api/v1/environment")
|
| 167 |
+
async def get_environment_info():
|
| 168 |
+
"""환경 정보 조회"""
|
| 169 |
+
return {
|
| 170 |
+
"environment": ENVIRONMENT,
|
| 171 |
+
"config": CONFIG,
|
| 172 |
+
"env_vars": {
|
| 173 |
+
"SPACE_ID": bool(os.getenv("SPACE_ID")),
|
| 174 |
+
"LOCAL_DEV": os.getenv("LOCAL_DEV"),
|
| 175 |
+
"DEVELOPMENT_MODE": os.getenv("DEVELOPMENT_MODE"),
|
| 176 |
+
"PRODUCTION": os.getenv("PRODUCTION"),
|
| 177 |
+
"OPENAI_API_KEY_SET": bool(os.getenv("OPENAI_API_KEY"))
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
# 디버그 라우트 (개발 환경에서만)
|
| 183 |
+
if CONFIG["features"]["debug_routes"]:
|
| 184 |
+
@app.get("/api/v1/debug/reload")
|
| 185 |
+
async def debug_reload():
|
| 186 |
+
"""개발용: 서버 재시작 없이 모듈 리로드"""
|
| 187 |
+
return {"message": "개발 모드에서만 사용 가능", "environment": ENVIRONMENT}
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
@app.get("/api/v1/debug/logs")
|
| 191 |
+
async def debug_logs():
|
| 192 |
+
"""개발용: 최근 로그 조회"""
|
| 193 |
+
try:
|
| 194 |
+
with open("/app/logs/app.log", "r") as f:
|
| 195 |
+
logs = f.readlines()[-50:] # 최근 50줄
|
| 196 |
+
return {"logs": logs}
|
| 197 |
+
except FileNotFoundError:
|
| 198 |
+
return {"logs": ["로그 파일이 없습니다."]}
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def create_default_html(file_path: str):
|
| 202 |
+
"""기본 HTML 파일 생성"""
|
| 203 |
+
html_content = get_default_html()
|
| 204 |
+
with open(file_path, "w", encoding="utf-8") as f:
|
| 205 |
+
f.write(html_content)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def get_default_html() -> str:
|
| 209 |
+
"""환경별 기본 HTML 반환"""
|
| 210 |
+
env_emoji = {
|
| 211 |
+
"huggingface": "🤗",
|
| 212 |
+
"local_dev": "🏠",
|
| 213 |
+
"production": "🏭",
|
| 214 |
+
"default": "🔧"
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
return f'''
|
| 218 |
+
<!DOCTYPE html>
|
| 219 |
+
<html lang="ko">
|
| 220 |
+
<head>
|
| 221 |
+
<meta charset="UTF-8">
|
| 222 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 223 |
+
<title>마음이 AI | {CONFIG['description']}</title>
|
| 224 |
+
<style>
|
| 225 |
+
body {{ font-family: 'Noto Sans KR', sans-serif; background: #f4f7f6; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }}
|
| 226 |
+
.container {{ text-align: center; background: white; padding: 40px; border-radius: 20px; box-shadow: 0 8px 32px rgba(0,0,0,0.1); }}
|
| 227 |
+
.title {{ color: #8A2BE2; font-size: 2rem; margin-bottom: 20px; }}
|
| 228 |
+
.env {{ color: #666; font-size: 1rem; margin-bottom: 15px; }}
|
| 229 |
+
.message {{ color: #666; font-size: 1.1rem; margin-bottom: 30px; }}
|
| 230 |
+
.status {{ padding: 10px 20px; background: #e8f5e8; color: #4a5e4a; border-radius: 8px; display: inline-block; }}
|
| 231 |
+
{"" if not CONFIG['debug'] else ".debug { background: #fff3cd; color: #856404; padding: 10px; border-radius: 5px; margin-top: 20px; }"}
|
| 232 |
+
</style>
|
| 233 |
+
</head>
|
| 234 |
+
<body>
|
| 235 |
+
<div class="container">
|
| 236 |
+
<h1 class="title">💙 마음이 AI</h1>
|
| 237 |
+
<div class="env">{env_emoji.get(ENVIRONMENT, "🔧")} {CONFIG['description']}</div>
|
| 238 |
+
<p class="message">청소년 공감 상담 챗봇이 곧 시작됩니다!</p>
|
| 239 |
+
<div class="status">시스템 초기화 중...</div>
|
| 240 |
+
{"" if not CONFIG['debug'] else '<div class="debug">🔧 개발 모드 - 디버그 정보 활성화</div>'}
|
| 241 |
+
<script>
|
| 242 |
+
setTimeout(() => {{
|
| 243 |
+
window.location.reload();
|
| 244 |
+
}}, 5000);
|
| 245 |
+
</script>
|
| 246 |
+
</div>
|
| 247 |
+
</body>
|
| 248 |
+
</html>
|
| 249 |
+
'''
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def add_demo_routes():
|
| 253 |
+
"""데모용 라우터 추가"""
|
| 254 |
+
|
| 255 |
+
@app.post("/api/v1/chat/teen-chat")
|
| 256 |
+
async def demo_chat(request: dict):
|
| 257 |
+
return {
|
| 258 |
+
"response": f"안녕! 마음이가 곧 준비될 예정이야. ({ENVIRONMENT} 환경) 💙",
|
| 259 |
+
"status": "demo_mode",
|
| 260 |
+
"environment": ENVIRONMENT
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
# 환경별 실행 (스크립트로 직접 실행할 때)
|
| 265 |
+
if __name__ == "__main__":
|
| 266 |
+
import uvicorn
|
| 267 |
+
|
| 268 |
+
port = int(os.getenv("API_PORT", 7860))
|
| 269 |
+
|
| 270 |
+
print(f"🚀 {CONFIG['description']} 서버 시작")
|
| 271 |
+
print(f"📍 포트: {port}")
|
| 272 |
+
print(f"🔧 디버그 모드: {CONFIG['debug']}")
|
| 273 |
+
print(f"🔄 리로드: {CONFIG['reload']}")
|
| 274 |
+
|
| 275 |
+
uvicorn.run(
|
| 276 |
+
"main:app" if not CONFIG['reload'] else app,
|
| 277 |
+
host="0.0.0.0",
|
| 278 |
+
port=port,
|
| 279 |
+
reload=CONFIG['reload'],
|
| 280 |
+
log_level=CONFIG['log_level']
|
| 281 |
+
)
|
start.sh
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# start.sh - 환경 자동 감지 스마트 시작 스크립트
|
| 3 |
+
|
| 4 |
+
echo "🚀 마음이 AI 시작 중..."
|
| 5 |
+
|
| 6 |
+
# 환경 감지
|
| 7 |
+
if [ -n "$SPACE_ID" ] || [ -n "$SPACE_AUTHOR_NAME" ]; then
|
| 8 |
+
echo "🤗 허깅페이스 Spaces 환경 감지"
|
| 9 |
+
ENVIRONMENT="huggingface"
|
| 10 |
+
PORT=7860
|
| 11 |
+
WORKERS=1
|
| 12 |
+
RELOAD=""
|
| 13 |
+
elif [ "$DEVELOPMENT_MODE" = "true" ] || [ -f "/.dockerenv" ] && [ -n "$LOCAL_DEV" ]; then
|
| 14 |
+
echo "🏠 로컬 개발 환경 감지"
|
| 15 |
+
ENVIRONMENT="local_dev"
|
| 16 |
+
PORT=7860
|
| 17 |
+
WORKERS=1
|
| 18 |
+
RELOAD="--reload"
|
| 19 |
+
elif [ -n "$PRODUCTION" ]; then
|
| 20 |
+
echo "🏭 프로덕션 환경 감지"
|
| 21 |
+
ENVIRONMENT="production"
|
| 22 |
+
PORT=7860
|
| 23 |
+
WORKERS=2
|
| 24 |
+
RELOAD=""
|
| 25 |
+
else
|
| 26 |
+
echo "🔧 기본 환경으로 시작"
|
| 27 |
+
ENVIRONMENT="default"
|
| 28 |
+
PORT=7860
|
| 29 |
+
WORKERS=1
|
| 30 |
+
RELOAD=""
|
| 31 |
+
fi
|
| 32 |
+
|
| 33 |
+
echo "📊 환경 정보:"
|
| 34 |
+
echo " - 환경: $ENVIRONMENT"
|
| 35 |
+
echo " - 포트: $PORT"
|
| 36 |
+
echo " - 워커 수: $WORKERS"
|
| 37 |
+
echo " - 리로드: ${RELOAD:-"비활성화"}"
|
| 38 |
+
|
| 39 |
+
# Python 환경 확인
|
| 40 |
+
echo "🐍 Python 환경 확인:"
|
| 41 |
+
python --version
|
| 42 |
+
pip show fastapi uvicorn | grep Version
|
| 43 |
+
|
| 44 |
+
# 필수 디렉토리 확인
|
| 45 |
+
echo "📁 디렉토리 확인:"
|
| 46 |
+
mkdir -p /app/data /app/cache /app/logs /app/static
|
| 47 |
+
ls -la /app/
|
| 48 |
+
|
| 49 |
+
# 환경별 시작 방식
|
| 50 |
+
case $ENVIRONMENT in
|
| 51 |
+
"huggingface")
|
| 52 |
+
echo "🤗 허깅페이스 모드로 시작..."
|
| 53 |
+
exec python -m uvicorn main:app \
|
| 54 |
+
--host 0.0.0.0 \
|
| 55 |
+
--port $PORT \
|
| 56 |
+
--timeout-keep-alive 300 \
|
| 57 |
+
--log-level info
|
| 58 |
+
;;
|
| 59 |
+
"local_dev")
|
| 60 |
+
echo "🏠 로컬 개발 모드로 시작..."
|
| 61 |
+
exec python -m uvicorn main:app \
|
| 62 |
+
--host 0.0.0.0 \
|
| 63 |
+
--port $PORT \
|
| 64 |
+
--reload \
|
| 65 |
+
--log-level debug
|
| 66 |
+
;;
|
| 67 |
+
"production")
|
| 68 |
+
echo "🏭 프로덕션 모드로 시작..."
|
| 69 |
+
exec gunicorn main:app \
|
| 70 |
+
-w $WORKERS \
|
| 71 |
+
-k uvicorn.workers.UvicornWorker \
|
| 72 |
+
--bind 0.0.0.0:$PORT \
|
| 73 |
+
--timeout 300 \
|
| 74 |
+
--preload \
|
| 75 |
+
--log-level info
|
| 76 |
+
;;
|
| 77 |
+
*)
|
| 78 |
+
echo "🔧 기본 모드로 시작..."
|
| 79 |
+
exec python -m uvicorn main:app \
|
| 80 |
+
--host 0.0.0.0 \
|
| 81 |
+
--port $PORT \
|
| 82 |
+
--log-level info
|
| 83 |
+
;;
|
| 84 |
+
esac
|