2. LangGraph 기본기
실행을 멈추고 사람에게 묻기
이 차시를 마치면
사람 승인을 넣는 자리를 정하고, 승인 앞에 두면 안 되는 코드를 가려냅니다.
모델이 실제 행동을 하기 전에 사람이 승인하도록 만드는 방법입니다. 보안 특화 Agent 의 핵심이 여기 있습니다.
목적
Human-in-the-loop 의 목적은 모델이 실제 외부 action 을 수행하기 전에 사람의 승인, 수정, 추가 입력을 받는 것입니다.
기본 패턴 다섯 단계
- 그래프를 Checkpointer 와 함께 compile
thread_id를 설정- Node 내부에서
interrupt(payload) - caller 가 중단 payload 확인
Command(resume=value)로 같은 Thread 를 재실행
어디에 쓰는가
| 위험도 | 작업 |
|---|---|
| 높음 — 승인 필요 | 이메일 실제 발송, DB 삭제, 외부 게시, 결제, 권한 변경, 채용과 평가 등 민감 판단 |
| 낮음 — 승인 불필요 | 초안 생성, 요약, 분류 후보 제안, 검색어 추천 |
두 목록을 가르는 기준은 되돌릴 수 있는가입니다. 초안은 지우면 되지만 발송한 메일은 회수되지 않습니다. 새 기능을 추가할 때 이 질문 하나로 등급을 정할 수 있습니다. 분석
공식 문서가 특히 주의하라고 안내하는 세 가지
interrupt호출 순서 변경- 직렬화할 수 없는 payload 반환
interrupt전에 되돌릴 수 없는 side effect 를 수행
3번이 왜 위험한가
이메일을 먼저 보내고 그다음에 interrupt 를 걸면 어떻게 될까요.
재개 과정에서 Node 가 처음부터 다시 실행될 수 있습니다. 그러면 이메일이 두 번 나갑니다. 승인을 받기 위해 멈춘 것인데, 멈추기 전에 이미 되돌릴 수 없는 일이 벌어진 상태입니다.
비유
결재를 올리기 전에 먼저 돈을 보내고 나서 "이거 보내도 될까요?"라고 묻는 것과 같습니다. 답이 "안 됩니다"여도 돈은 이미 나갔습니다. 게다가 결재 시스템이 재시도를 하면 한 번 더 나갑니다.
안전한 순서
draft 생성 → interrupt(승인 요청) → 승인 후에만 side effect
실제 발송, 삭제, 결제 코드는 승인 뒤에 오는 별도 Node 안에 둡니다. 그리고 그 Node 는 같은 작업이 두 번 실행되지 않도록 실행 기록을 남겨 보호합니다.
from langgraph.types import interrupt
def approval(state) -> dict:
"""사람에게 승인을 요청하고 멈춘다.
payload 에는 직렬화 가능한 값만 넣는다.
"""
decision = interrupt(
{
"question": "이 작업을 실행할까요?",
"risk_level": state["risk_level"],
"draft": state["draft"],
"options": ["approve", "edit", "reject"],
}
)
return {"approved": decision == "approve"}
위 코드는 개념을 보이려고 줄인 것입니다. 다만 interrupt 로 멈추고 Command(resume=...) 로 이어지는 동작 자체는 2026-08-15 에 langgraph 1.2.11 로 확인했습니다. 승인·반려에 따라 경로가 갈리는 것까지 테스트 12개가 덮습니다. 돌아가는 전체 코드는 4번 트랙 5차시에, 직접 돌리는 방법은 8차시에 있습니다.
빠뜨리기 쉬운 것 — Checkpointer
승인 절차에는 Checkpointer 가 반드시 있어야 합니다. 그런데 이걸 빠뜨려도 처음에는 잘 되는 것처럼 보입니다. 여기서 많이 속습니다.
Checkpointer 없이 interrupt 를 부르면 멈추기는 합니다. 결과에 __interrupt__ 도 들어옵니다. 여기까지만 시험해 보고 "다 됐다"고 넘어가기 쉽습니다.
{'v': '', '__interrupt__': [Interrupt(value={'q': '승인?'}, id='351efe...')]}
문제는 사람이 승인한 뒤 이어서 하려는 순간 드러납니다.
RuntimeError: Cannot use Command(resume=...) without checkpointer
멈추는 것과 이어서 하는 것은 다른 일입니다. 멈추는 데는 저장이 필요 없지만, 이어서 하려면 멈춘 지점의 상태가 어딘가에 남아 있어야 합니다. 그 자리가 Checkpointer 입니다.
위 두 출력은 2026-08-15 에 langgraph 1.2.11 로 직접 확인한 것입니다. Checkpointer 없이 interrupt 만 부른 경우와, 그 뒤 Command(resume=...) 까지 부른 경우를 각각 돌렸습니다.
그래서 승인 절차를 시험할 때는 멈추는 것까지가 아니라 이어서 끝나는 것까지 확인해야 합니다. 4번 트랙 5차시의 그래프가 그렇게 되어 있습니다.
이 원칙은 규칙으로 굳어 있습니다
"승인 전에 되돌릴 수 없는 일을 하지 않는다"는 3번 트랙에서 절대 유지해야 하는 안전 불변조건으로 다시 나옵니다. prompt 를 고치기 편하다는 이유로 없애면 안 되는 항목입니다.
전체 구현은 4번 트랙 "보안 특화"에서 코드로 다룹니다.