🎯 LangGraph의 Human-in-the-Loop 패턴을 학습하고 제조/에너지 현장 품질 관리에 적용한 프로젝트
이 프로젝트는 LangGraph의 HITL(Human-in-the-Loop) 패턴을 학습하고, 이를 제조/에너지 현장의 품질 관리(QA) 시스템에 적용한 실습 코드입니다.
현대 제조 현장에서는 AI가 빠르게 데이터를 분석하지만, 중요한 결정은 여전히 인간 전문가의 판단이 필요합니다. 특히:
- 🏭 가동 중지 결정: AI가 과열을 감지했어도, 전문가가 최종 승인
⚠️ 안전 프로토콜: 위험한 조치는 반드시 인간 검토 필요- 🔧 유지보수 스케줄링: AI 권장 조치를 전문가가 현장 상황에 맞게 조정
1. ✅ LangGraph의 `interrupt()` 및 `Command` 패턴 이해
2. ✅ 기본 승인 워크플로우 구현 (`lg_approval.py`)
3. ✅ 실제 제조 현장 시나리오에 적용 (`lg_app_qa.py`)
4. ✅ 동기/비동기 실행 모드 비교 학습
# 승인/거부에 따른 조건부 라우팅
approved = interrupt({"question": "이 작업을 승인하시겠습니까?"})
if approved:
return Command(goto="do", update={"status": "approved"})
else:
return Command(goto="cancel", update={"status": "rejected"})특징:
- 💡
interrupt()를 통한 사용자 입력 대기 - 🔀
Command로 동적 라우팅 (승인 → do, 거부 → cancel) - ⚡ 동기/비동기 실행 모드 지원
- 🐛 비동기 스트리밍 디버깅 포함
# 위험도에 따른 조건부 승인
if risk_level == "LOW":
return Command(goto="execute_action", update={"human_approval": True})
else:
# HIGH/CRITICAL은 전문가 검토 필수
approval_data = interrupt({
"type": "expert_approval_required",
"risk_level": risk_level,
"sensor_data": sensor_data
})특징:
- 🤖 AI 센서 분석: 온도/압력/진동/유량/전력 모니터링
- 📊 위험도 자동 분류: LOW/HIGH/CRITICAL
- 👤 조건부 전문가 검토:
- LOW → 자동 승인 (전문가 개입 불필요)
- HIGH/CRITICAL → 전문가 검토 필수
- ✏️ 전문가 Override: AI가 "긴급 중지"를 권장해도 전문가가 "부하 감소"로 변경 가능
- 🎬 4가지 시나리오: 정상/과열/압력급증/진동이상
- 📝 완전한 감사 추적: 타임스탬프, 전문가 의견, 실행 조치 기록
# Python 3.10 이상 필요
python --version
# 가상환경 생성 (권장)
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 의존성 설치
pip install langgraph langchain-core typing-extensionspython lg_approval.py대화형 프롬프트:
실행 모드를 선택하세요:
1. 비동기 스트리밍 방식 (권장)
2. 동기 방식 (간단)
선택 (1 or 2): 2
=== 첫 번째 실행 ===
새로운 세션 ID: abc123...
질문: 이 작업을 승인하시겠습니까?
작업: 서버 재부팅
승인하시겠습니까? (yes/no): yes
=== 'True'로 재개 ===
--- 작업 실행: 서버 재부팅 ---
최종 상태: completed
```bash
python lg_app_qa.py
```
대화형 프롬프트:
🏭 제조/에너지 현장 HITL 모니터링 시스템
시나리오를 선택하세요:
1. normal - 정상 작동
2. overheating - 과열 감지
3. pressure_spike - 압력 급증
4. vibration_anomaly - 진동 이상
선택 (1-4): 2
실행 모드:
1. 동기 방식 (권장)
2. 비동기 방식
선택 (1-2): 1
############################################################
🏭 설비 모니터링 시작
############################################################
설비 ID: PLANT-A1B2C3D4
시나리오: overheating
############################################################
🤖 AI 분석 완료 - 2025-02-10 14:30:25
============================================================
위험도: CRITICAL
분석 결과:
🚨 긴급 상황 감지!
온도 위험: 95.8°C (임계값: 90.0°C)
권장 조치: IMMEDIATE_SHUTDOWN
============================================================
⏸️ 전문가 검토 대기 중...
승인하시겠습니까? (yes/no): yes
의견을 입력하세요: 현장 확인 완료, 즉시 가동 중지 승인
⚙️ 조치 실행: IMMEDIATE_SHUTDOWN
🛑 긴급 가동 중지 실행
전문가 의견: 현장 확인 완료, 즉시 가동 중지 승인
############################################################
📋 최종 리포트
############################################################
설비 ID: PLANT-A1B2C3D4
위험도: CRITICAL
전문가 승인: ✅ 예
전문가 의견: 현장 확인 완료, 즉시 가동 중지 승인
실행된 조치: IMMEDIATE_SHUTDOWN
타임스탬프: 2025-02-10T14:30:25.123456
############################################################
```
---
## 💡 사용 예시
### 예시 1: 정상 작동 (자동 승인)
```bash
# 시나리오 1 선택
선택 (1-4): 1
🤖 AI 분석: 온도 72.5°C → 위험도 LOW
✅ 위험도 낮음 - 자동 승인
👁️ 모니터링 계속
전문가 의견: 자동 승인 (정상 범위)
✅ 정상 작동 - 전문가 개입 불필요
```
### 예시 2: 과열 감지 (전문가 승인)
```bash
# 시나리오 2 선택
선택 (1-4): 2
🤖 AI 분석: 온도 95.8°C → 위험도 CRITICAL
⏸️ 전문가 검토 대기 중...
승인하시겠습니까? (yes/no): yes
의견: 긴급 가동 중지 승인
⚙️ 조치 실행: IMMEDIATE_SHUTDOWN
🛑 긴급 가동 중지 실행
```
### 예시 3: 전문가 Override
```bash
# 시나리오 2 선택
선택 (1-4): 2
🤖 AI 분석: 온도 95.8°C → 위험도 CRITICAL
AI 권장 조치: IMMEDIATE_SHUTDOWN
승인하시겠습니까? (yes/no): yes
의견: 부하만 감소하여 점검
조치를 수정하시겠습니까? (yes/no): yes
가능한 조치:
1. IMMEDIATE_SHUTDOWN
2. CONTROLLED_SHUTDOWN
3. REDUCE_LOAD
4. MAINTENANCE_ALERT
5. CONTINUE_MONITORING
선택: 3
⚠️ 전문가가 AI 권장 조치를 수정했습니다
원래 권장 조치: IMMEDIATE_SHUTDOWN
수정된 조치: REDUCE_LOAD
⚙️ 조치 실행: REDUCE_LOAD
📉 부하 감소 실행
```
---
## 🧠 핵심 개념
### 1. `interrupt()` vs `interrupt_before`
| 특징 | `interrupt()` | `interrupt_before` |
|------|---------------|-------------------|
| 위치 | 노드 내부 | 컴파일 시 설정 |
| 조건부 실행 | ✅ 가능 | ❌ 불가능 |
| 데이터 전달 | ✅ 가능 | ❌ 제한적 |
| 사용 난이도 | ⚠️ 중급 | ✅ 쉬움 |
**`interrupt()` 예시 (본 프로젝트):**
```python
def expert_approval_node(state):
if state["risk_level"] == "LOW":
return Command(goto="execute", update={...}) # 자동 승인
# HIGH/CRITICAL만 interrupt
approval = interrupt({"question": "승인?"})
return Command(goto="execute", update={...})
interrupt_before 예시:
graph = builder.compile(
checkpointer=memory,
interrupt_before=["approval_node"] # 항상 이 노드 전에 멈춤
)def approval_node(state):
decision = interrupt({"question": "승인?"})
# 조건에 따라 다른 노드로 이동
if decision["approved"]:
return Command(goto="execute", update={"status": "approved"})
elif decision.get("override"):
return Command(goto="override", update={"action": decision["override"]})
else:
return Command(goto="cancel", update={"status": "rejected"})# 위험도가 낮으면 자동 승인, 높으면 전문가 검토
if risk_level == "LOW":
return auto_approve()
else:
return request_expert_approval()**상황:**
- AI가 설비 과열 감지 (95.8°C)
- 정상 범위: 70-80°C
- 임계값: 90°C
**워크플로우:**
1. 🤖 AI 센서 분석 → CRITICAL 위험도 감지
2. ⏸️ 그래프 중단, 전문가 알림 전송
3. 👤 전문가 현장 확인
4. ✅ 전문가 승인 → 긴급 가동 중지 실행
5. 📝 감사 기록: "전문가 A, 2025-02-10 14:30, 승인, 긴급 가동 중지"
**상황:**
- AI가 압력 급증 감지 (125.5 kPa)
- AI 권장: IMMEDIATE_SHUTDOWN
**워크플로우:**
1. 🤖 AI 분석 → HIGH 위험도
2. ⏸️ 전문가 검토 요청
3. 👤 전문가 판단: "즉시 중지보다 부하 감소가 적절"
4. ✏️ 전문가 Override → REDUCE_LOAD 선택
5. 📉 부하 감소 실행
6. 📝 감사 기록: "AI 권장 수정, 전문가 B, 부하 감소"
**상황:**
- 모든 센서 정상 범위
- 위험도: LOW
**워크플로우:**
1. 🤖 AI 분석 → LOW 위험도
2. ✅ 자동 승인 (전문가 개입 없이 바로 실행)
3. 👁️ 모니터링 계속
4. 📝 자동 기록: "자동 승인, 정상 범위"
| 카테고리 | 기술 |
|----------|------|
| **언어** | Python 3.10+ |
| **프레임워크** | LangGraph |
| **상태 관리** | LangGraph Checkpointer (MemorySaver) |
| **비동기** | asyncio |
| **타입** | typing, typing_extensions |
```txt
langgraph>=0.2.0
langchain-core>=0.3.0
typing-extensions>=4.5.0
```
-
✅ HITL 기본 패턴
interrupt()로 사용자 입력 대기Command(resume=value)로 재개
-
✅ 조건부 라우팅
Command(goto="node_name")로 동적 이동- 상태에 따른 분기 처리
-
✅ 비동기 스트리밍
astream()의 이벤트 언패킹__interrupt__감지 및 처리
-
✅ 상태 관리
- Checkpointer를 통한 세션 유지
thread_id로 세션 식별
문제: 비동기 스트리밍에서 interrupt 감지 안 됨
# ❌ 잘못된 언패킹
async for mode, *rest in graph.astream(...):
chunk = rest[0] # 구조 불일치
# ✅ 올바른 언패킹
async for event in graph.astream(...):
if isinstance(event, tuple):
_, content = event
chunk = content[1] if isinstance(content, tuple) else content
else:
chunk = event-
SQLite Checkpointer 전환
from langgraph.checkpoint.sqlite import SqliteSaver checkpointer = SqliteSaver.from_conn_string("qa_checkpoints.db")
- 세션 영구 저장
- 프로세스 재시작 후 복구
-
에러 처리 강화
- 센서 연결 실패 처리
- 타임아웃 설정
- 재시도 로직
-
알림 시스템 통합
# 전문가에게 이메일/Slack 알림 send_notification( expert="safety_officer@company.com", subject=f"긴급: {facility_id} 승인 필요", priority="HIGH" )
-
웹 UI 개발
- FastAPI 백엔드
- React 프론트엔드
- 실시간 센서 대시보드
-
실제 센서 연동
# SCADA/IoT 시스템 연결 sensor_data = scada_client.read_tags([ "TEMP_SENSOR_01", "PRESSURE_GAUGE_02" ])
-
다중 전문가 승인
# CRITICAL은 2명 승인 필요 approval_1 = interrupt({"expert": "shift_manager"}) approval_2 = interrupt({"expert": "safety_officer"})
-
ML 모델 통합
- 이상 징후 예측
- 임계값 자동 조정
-
PostgreSQL/MySQL 전환
- 엔터프라이즈급 DB
- 고가용성
-
감사 리포팅
- 승인 준수율 대시보드
- 의사결정 분석
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - 자유롭게 사용, 수정, 배포 가능
질문이나 제안사항이 있으시면 Issue를 열어주세요!
Happy Learning! 🚀