인간 개입 루프

인간 개입 루프 (Human-in-the-loop)

에이전트가 자동으로 모든 걸 처리하면 편하지만, 파일을 지우거나 SQL을 실행하는 것 같은 위험한 동작은 사람의 확인을 거쳐야 안심이 되죠. 이런 상황에서 Human-in-the-loop (HITL) 미들웨어를 쓰면, 에이전트가 위험한 동작을 하기 전에 잠시 멈추고 사람의 결정을 기다려요. 이번 페이지에서는 그 방식과 설정법을 자세히 살펴볼게요.

출처: LangChain 공식 문서 — human-in-the-loop

HITL이 하는 일

Human-in-the-loop (HITL) 미들웨어는 에이전트의 도구 호출에 인간의 감독을 더해줘요. 모델이 검토가 필요한 동작(예: 파일 쓰기, SQL 실행)을 제안하면, 미들웨어가 실행을 멈추고 결정을 기다립니다.

동작 원리는 이렇게 흘러가요:

  1. 미들웨어가 각 도구 호출을 설정 가능한 정책(policy)과 대조합니다.
  2. 개입이 필요하면 미들웨어가 interrupt를 발행해 실행을 중단해요.
  3. 그래프 상태는 LangGraph의 영속성 계층(persistence layer)에 저장되어, 실행을 안전하게 멈췄다가 나중에 재개할 수 있어요.
  4. 인간의 결정이 다음 동작을 정합니다.

인터럽트 결정 유형

미들웨어는 인간이 인터럽트에 응답할 수 있는 네 가지 내장 방식을 정의해요.

결정 유형 설명 예시 사용 사례
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 — 커스텀 설명을 위한 정적 문자열 또는 콜러블 함수

whenToolCallRequest를 받아 인터럽트할지(True) 자동 승인할지(False) 반환하는 선택적 프레디킷. 호출의 인자를 기준으로 인터럽트를 걸고 싶을 때 사용해요. langchain>=1.3.3이 필요합니다.

조건부 인터럽트 (Conditional interrupts)

기본적으로 interrupt_on에 나열된 모든 도구 호출은 검토를 위해 멈춰요. 일부 호출만 멈추게 하려면 도구의 InterruptOnConfigwhen 프레디킷을 추가하면 됩니다. 프레디킷은 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_requestsreview_configs를 담은 HITLRequest를 만들어 interrupt를 호출하고, 에이전트는 인간의 결정을 기다립니다. 그 다음 HITLResponse 결정을 바탕으로 미들웨어가 승인되거나 수정된 호출을 실행하고, 거부된 호출에 대해서는 ToolMessage를 합성하며, 인간의 답변을 직접 반환해요.

커스텀 HITL 로직 (Custom HITL logic)

더 전문화된 워크플로가 필요하면 interrupt 프리미티브와 미들웨어 추상화를 직접 사용해 커스텀 HITL 로직을 만들 수도 있어요.

더 알아보기 (Learn more)