인간 개입 루프
인간 개입 루프 (Human-in-the-loop)
에이전트가 자동으로 모든 걸 처리하면 편하지만, 파일을 지우거나 SQL을 실행하는 것 같은 위험한 동작은 사람의 확인을 거쳐야 안심이 되죠. 이런 상황에서 Human-in-the-loop (HITL) 미들웨어를 쓰면, 에이전트가 위험한 동작을 하기 전에 잠시 멈추고 사람의 결정을 기다려요. 이번 페이지에서는 그 방식과 설정법을 자세히 살펴볼게요.
HITL이 하는 일
Human-in-the-loop (HITL) 미들웨어는 에이전트의 도구 호출에 인간의 감독을 더해줘요. 모델이 검토가 필요한 동작(예: 파일 쓰기, SQL 실행)을 제안하면, 미들웨어가 실행을 멈추고 결정을 기다립니다.
동작 원리는 이렇게 흘러가요:
- 미들웨어가 각 도구 호출을 설정 가능한 정책(policy)과 대조합니다.
- 개입이 필요하면 미들웨어가 interrupt를 발행해 실행을 중단해요.
- 그래프 상태는 LangGraph의 영속성 계층(persistence layer)에 저장되어, 실행을 안전하게 멈췄다가 나중에 재개할 수 있어요.
- 인간의 결정이 다음 동작을 정합니다.
인터럽트 결정 유형
미들웨어는 인간이 인터럽트에 응답할 수 있는 네 가지 내장 방식을 정의해요.
| 결정 유형 | 설명 | 예시 사용 사례 |
|---|---|---|
✅ approve |
요청된 대로 동작을 승인 | 파일 삭제 승인 |
✏️ edit |
실행 전에 동작을 수정 | 도구 인자를 수정해 재전송 |
❌ reject |
도구 호출을 완전히 건너뛰고 거부 피드백을 에이전트에 반환 | 파일 삭제를 거부하고 이유 설명 |
💬 respond |
도구 실행을 건너뛰고 인간의 메시지를 합성 도구 결과로 직접 반환 — "사용자에게 묻기" 스타일 도구용 | "ask_user" 프롬프트에 직접 답변 |
각 도구에서 사용 가능한 결정 유형은 interrupt_on에 구성하는 정책에 따라 정해져요.
주의할 점이 몇 가지 있어요:
- 여러 도구 호출이 동시에 멈추면 각 동작마다 별도의 결정이 필요해요.
- 결정은 인터럽트 요청에 나타난 동작의 순서와 같은 순서로 제공해야 합니다.
reject는 인간이 요청된 동작을 거부할 때 사용하세요.respond는 인간이 도구 역할을 할 때(예:ask_user프롬프트에 답변)만 사용하세요.respond를 부수효과가 있는 도구를 거부하는 데 쓰면 안 돼요 — 그 메시지는 성공적인 도구 결과로 취급되거든요.
인터럽트 구성하기 (Configuring interrupts)
HITL을 사용하려면 에이전트를 만들 때 middleware 리스트에 미들웨어를 추가하면 됩니다.
# e.g., "Tool execution pending approval: execute_sql with query='DELETE FROM...'"
# Individual tools can override this by specifying a "description" in their interrupt config
description_prefix="Tool execution pending approval",
),
],
# Human-in-the-loop requires checkpointing to handle interrupts.
# In production, use a persistent checkpointer like AsyncPostgresSaver or MongoDBSaver.
checkpointer=InMemorySaver(), # [!code highlight]
)
인터럽트를 처리하려면 그래프 상태를 유지할 **체크포인터(checkpointer)**를 반드시 구성해야 해요. 에이전트를 호출할 때는 실행을 대화 스레드와 연결하는 thread ID를 포함한
config를 전달해야 합니다. 자세한 내용은 LangGraph interrupts 문서를 참고하세요.
구성 옵션 (Configuration options)
interrupt_on — 도구 이름과 승인 설정의 매핑. 값은 True(기본 설정으로 인터럽트), False(자동 승인), 또는 InterruptOnConfig 객체가 될 수 있어요.
allowed_decisions — 허용되는 결정 리스트: 'approve', 'edit', 'reject', 'respond'
description — 커스텀 설명을 위한 정적 문자열 또는 콜러블 함수
when — ToolCallRequest를 받아 인터럽트할지(True) 자동 승인할지(False) 반환하는 선택적 프레디킷. 호출의 인자를 기준으로 인터럽트를 걸고 싶을 때 사용해요. langchain>=1.3.3이 필요합니다.
조건부 인터럽트 (Conditional interrupts)
기본적으로 interrupt_on에 나열된 모든 도구 호출은 검토를 위해 멈춰요. 일부 호출만 멈추게 하려면 도구의 InterruptOnConfig에 when 프레디킷을 추가하면 됩니다. 프레디킷은 ToolCallRequest를 받아 인터럽트할지(True) 자동 승인할지(False) 반환하므로, 도구의 인자에 따라 멈출지 말지를 결정할 수 있어요.
조건부 인터럽트는
langchain>=1.3.3이 필요합니다.
인터럽트에 응답하기 (Responding to interrupts)
인간의 message는 성공한 ToolMessage로 에이전트에 반환되어요. respond는 도구가 인간 입력을 위한 자리표시자일 때(예: 명확화를 요청하는 ask_user 도구) 사용하세요. 제안된 동작을 거부하는 데 respond를 쓰면 안 됩니다 — 모델에게 도구가 성공적으로 완료됐다고 알려주는 셈이 되니까요.
HITL과 스트리밍 (Streaming with human-in-the-loop)
에이전트가 실행되고 인터럽트를 처리하는 동안 stream_events()를 사용해 실시간 업데이트를 스트리밍할 수 있어요. stream.messages로 LLM 토큰을 스트리밍하고, stream.values로 인터럽트가 있는지 에이전트 상태 스냅샷을 확인할 수 있습니다.
스트림 모드에 대한 자세한 내용은 Streaming 가이드를 참고하세요.
실행 수명주기 (Execution lifecycle)
인간 입력이 필요한 호출이 있으면 미들웨어는 action_requests와 review_configs를 담은 HITLRequest를 만들어 interrupt를 호출하고, 에이전트는 인간의 결정을 기다립니다. 그 다음 HITLResponse 결정을 바탕으로 미들웨어가 승인되거나 수정된 호출을 실행하고, 거부된 호출에 대해서는 ToolMessage를 합성하며, 인간의 답변을 직접 반환해요.
커스텀 HITL 로직 (Custom HITL logic)
더 전문화된 워크플로가 필요하면 interrupt 프리미티브와 미들웨어 추상화를 직접 사용해 커스텀 HITL 로직을 만들 수도 있어요.