Zum Hauptinhalt springen

개요

개요

Lumie AI는 학원 교육 플랫폼을 지원하는 4개의 독립적인 FastAPI 마이크로서비스로 구성됩니다. 각 서비스는 단일 책임 원칙에 따라 설계되었으며, Kubernetes 환경에서 독립적으로 배포·운영됩니다. 백엔드(lumie-backend)가 RabbitMQ 또는 HTTP를 통해 각 서비스를 오케스트레이션하며, 서비스 간 직접 호출은 없습니다.

기술 스택

항목버전 / 값
언어Python 3.11+
웹 프레임워크FastAPI 0.115
ASGI 서버Uvicorn
메시지 브로커RabbitMQ (aio_pika)
DB 연결 풀psycopg AsyncConnectionPool (pgbouncer 트랜잭션 모드)
컨테이너 레지스트리zot.lumie-infra.com
개발 환경Tilt (hot reload)

모든 서비스는 공통적으로 다음을 제공합니다.

  • GET /health — 헬스체크 엔드포인트
  • OpenTelemetry 트레이싱 (OTLP gRPC)
  • Prometheus 메트릭 (/metrics)
  • Uvicorn을 통한 ASGI 실행

프로젝트 구조

lumie-worker/
├── services/
│ ├── grading/ # OMR 자동 채점 (OpenCV, NumPy, RabbitMQ 소비자)
│ ├── report/ # 학생 리포트 생성 (Playwright, Jinja2, RabbitMQ 소비자)
│ ├── analysis/ # AI 피드백 생성 (OpenAI GPT-4o-mini, HTTP API)
│ └── chatbot/ # LangGraph 챗봇 (Gemini 2.5 Flash, SSE 스트리밍, HITL)
├── libs/
│ └── common/ # 공통 유틸리티 (mq.universal_process, HMAC 서명, 로깅)
└── Tiltfile # Tilt 개발 환경 설정

서비스 목록

채점 서비스 (grading-svc)

OMR(광학 마킹 인식) 방식으로 시험지 이미지를 분석하여 자동 채점을 수행합니다. RabbitMQ grading.omr-request 큐를 소비하고, 결과를 grading.omr.callback 라우팅 키로 백엔드에 콜백합니다.

  • 포트: 8000
  • 주요 라이브러리: OpenCV 4.8, NumPy, Pillow
  • 상세 문서: 채점 서비스

리포트 서비스 (report-svc)

학생별 시험 결과를 Jinja2 템플릿으로 렌더링한 뒤 Playwright Chromium을 통해 JPG 이미지로 출력합니다. RabbitMQ report.generation-request 큐를 소비하고, 결과를 report.generation.callback 라우팅 키로 백엔드에 콜백합니다.

  • 포트: 8000
  • 주요 라이브러리: Playwright 1.50.0, Jinja2 3.1.3, httpx 0.27.0
  • 상세 문서: 리포트 서비스

분석 서비스 (analysis-svc)

OpenAI GPT-4o-mini를 활용해 시험 전체 해설과 학생 개별 피드백을 한국어로 생성합니다. RabbitMQ 소비자 없이 HTTP API만 제공하며, lumie-backend 및 joossameng FE가 직접 호출합니다.

  • 포트: 8000
  • 주요 라이브러리: OpenAI SDK 1.82.0, Pydantic 2.10.0
  • 상세 문서: 분석 서비스

챗봇 서비스 (chatbot-svc)

LangGraph 에이전트 기반의 학원 운영 어시스턴트입니다. 2026-05-26 lumie-backend의 Spring AI 챗봇을 대체하여 추가되었습니다. Gemini 2.5 Flash 모델을 사용하며, SSE 스트리밍과 Human-in-the-Loop(HITL) 도구 승인을 지원합니다. RabbitMQ를 사용하지 않으며, lumie-backend가 HTTP로 직접 프록시합니다.

  • 포트: 8000
  • 주요 라이브러리: LangGraph 0.3.5, langchain-openai 0.3.23, psycopg-pool 3.2.4
  • DB: LangGraph 체크포인터 전용 Postgres (스키마 langgraph, 롤 lumie_langgraph)

챗봇 서비스 상세

아키텍처

lumie-backend가 프론트엔드 요청을 받아 chatbot-svc에 프록시합니다. chatbot-svc는 LangGraph 그래프를 실행하여 백엔드의 /internal/chatbot/** 엔드포인트를 통해 테넌트 데이터를 조회·조작합니다. chatbot-svc는 테넌트 테이블에 직접 접근하지 않으며, 모든 실행과 영속성은 모노리스에서 처리됩니다.

LangGraph 그래프 구조

  • scope_check: 프롬프트 인젝션 및 도메인 외 요청 사전 차단
  • agent: Gemini 2.5 Flash에 도구를 바인딩하여 응답 생성
  • tools: 읽기 도구(general_query)는 즉시 실행; 쓰기 도구는 interrupt()로 HITL 일시 중단
  • give_up: SQL 재시도 횟수(sql_max_retries=3) 초과 시 안내 메시지 반환
  • cancel: 사용자가 HITL에서 거부한 경우 취소 메시지 반환

HTTP API 엔드포인트

POST /api/chatbot/stream — SSE 스트리밍

LangGraph 에이전트 실행을 Server-Sent Events로 스트리밍합니다. 클라이언트가 연결을 끊어도 그래프 실행은 계속되어 결과가 백엔드에 저장됩니다(단절 안전 내구성).

요청 헤더

헤더설명
X-Tenant-Slug테넌트 식별자 (바디의 tenantSlug와 일치해야 함)

요청 바디 (JSON)

{
"tenantSlug": "academy-a",
"tenantId": 1,
"userId": 42,
"conversationId": null,
"message": "이번 달 결석 학생 목록 보여줘"
}

conversationIdnull이면 새 대화를 생성합니다.

SSE 이벤트 유형

이벤트데이터 형식설명
token\{"content": "..."}\어시스턴트 토큰 스트림
pending\{"messageId": 1, "toolName": "...", "description": "...", "arguments": \{\}\}HITL 승인 대기
done\{"conversationId": 1, "messageId": 5\}스트림 완료
error\{"message": "..."\}오류 발생

POST /api/chatbot/confirm — HITL 도구 승인

HITL로 일시 중단된 그래프를 재개합니다.

요청 바디 (JSON)

{
"tenantSlug": "academy-a",
"tenantId": 1,
"userId": 42,
"conversationId": 1,
"messageId": 5,
"confirmed": true
}

에이전트 도구 목록

도구유형설명
general_query읽기SELECT SQL을 백엔드 SqlValidator+RLS를 통해 실행
create_announcement쓰기 (HITL)공지사항 생성
send_sms쓰기 (HITL)SMS 발송
send_telegram쓰기 (HITL)텔레그램 발송
schedule_task쓰기 (HITL)반복·일회성 작업 예약

쓰기 도구는 interrupt()로 실행을 일시 중단하여 사용자 승인 후에만 실행됩니다.

DB 연결 풀 (AsyncConnectionPool)

LangGraph AsyncPostgresSaver 체크포인터는 단일 커넥션이 아닌 AsyncConnectionPool을 사용합니다. CNPG Pooler(pgbouncer 트랜잭션 모드)의 server_lifetime 순환·유휴 타임아웃·NAT 리셋으로 단일 커넥션이 끊어지면 재연결이 불가능하기 때문입니다.

pool = AsyncConnectionPool(
conninfo=settings.langgraph_db_dsn,
min_size=1,
max_size=5,
timeout=30.0,
max_idle=180.0, # pgbouncer server_idle_timeout(~600s)보다 낮게 설정
check=AsyncConnectionPool.check_connection, # 핸드아웃 전 SELECT 1 검증
kwargs={
"autocommit": True, # langgraph가 트랜잭션 직접 관리
"prepare_threshold": 0, # pgbouncer txn-mode: prepared statement 비활성화
"row_factory": dict_row, # AsyncPostgresSaver 요구사항
},
)

환경 변수

변수기본값설명
LLM_API_KEY— (필수)Gemini API 키
LLM_BASE_URLhttps://generativelanguage.googleapis.com/v1beta/openaiLLM 엔드포인트
LLM_MODELgemini-2.5-flash사용할 LLM 모델
LUMIE_BACKEND_URL— (필수)백엔드 URL
LUMIE_INTERNAL_HMAC_SECRET— (필수)HMAC 서명 비밀키
LANGGRAPH_DB_DSN— (필수)LangGraph 체크포인터 Postgres DSN
RECURSION_LIMIT10LangGraph 최대 재귀 스텝 수
SQL_MAX_RETRIES3SQL 생성→검증→실행 재시도 최대 횟수

공통 라이브러리 (libs/common)

libs/common 패키지는 서비스 간 공통으로 사용하는 유틸리티를 제공합니다.

모듈역할
mq.universal_processRabbitMQ DLQ + 재시도 + 콜백 생명주기
callback_mq.CallbackMqPublisher라우팅 키 사전 바인딩된 콜백 퍼블리셔
sign_request/internal/** 호출용 HMAC 서명
logging_config구조화된 JSON 로깅 (Loki json-stage 파싱 호환)
observabilityOTel 트레이싱 + Prometheus 메트릭 헬퍼

Kubernetes 리소스 현황

서비스메모리 요청/한도CPU 요청/한도K8s 배포 여부
grading-svc1Gi / 2Gi100m / 1000mO
report-svc512Mi / 2Gi100m / —O
chatbot-svc256Mi / 512Mi50m / —O
analysis-svcO (joossameng용)

채점 서비스는 OpenCV CPU 집약적 연산을 위해 CPU 한도가 높게 설정되어 있습니다. 챗봇 서비스는 LangGraph 그래프와 Postgres 연결 풀만 유지하므로 상대적으로 경량입니다.

DB 연결 풀 패턴

장기 실행 워커는 단일 psycopg.AsyncConnection 대신 반드시 AsyncConnectionPoolcheck=check_connection을 사용해야 합니다. CNPG Pooler(pgbouncer 트랜잭션 모드)는 server_lifetime 주기, 유휴 시간 초과, NAT 리셋 등으로 연결을 끊을 수 있으며, 단일 커넥션은 재연결되지 않습니다. 풀의 check_connection은 각 핸드아웃 전 SELECT 1로 유효성을 검증하여 끊어진 연결을 자동 교체합니다.

chatbot-svc가 이 패턴을 채택하고 있으며, max_idle=180s를 pgbouncer의 server_idle_timeout(약 600s)보다 낮게 설정해 클라이언트 측이 먼저 유휴 연결을 닫도록 합니다.

테넌트 전파

모든 서비스는 인바운드 RabbitMQ 메시지나 HTTP 요청에서 받은 X-Tenant-Slug를 백엔드 콜백 및 /internal/** 호출 시 그대로 전달합니다. chatbot-svc는 요청 헤더의 X-Tenant-Slug와 바디의 tenantSlug가 일치하지 않으면 400을 반환합니다.

개발 환경

모든 AI 서비스는 Tiltfile을 통해 로컬 Kubernetes 클러스터(lumie-dev 네임스페이스)에서 개발 서버를 실행합니다. 파일 변경 시 자동으로 컨테이너가 재빌드·재시작되는 hot reload를 지원합니다.

# 루트 디렉토리에서 Tilt 실행
.github/tilt-up.sh

포트 포워딩

개발 환경에서는 다음 포트로 각 서비스에 접근할 수 있습니다.

서비스로컬 포트클러스터 포트
grading-svc180008000
report-svc180018000
chatbot-svc180028000