LangChain 에이전트의 Human-in-the-Loop 승인 구현 방법
요약
LangChain 에이전트가 이메일 전송이나 API 호출 등 부작용을 일으키는 도구를 사용할 때, 인간의 승인을 거치도록 구현하는 방법을 설명합니다. 프롬프트 지시 대신 도구 추상화 단계에서 구조적인 승인 게이트를 도입하여 안전성을 확보합니다.
핵심 포인트
- 프롬프트 기반 제어의 한계를 극복하기 위해 구조적 승인 게이트 도입
- BaseTool 서브클래스를 활용하여 도구 실행 전 일시 중지 및 폴링 구현
- 인간 검토자가 승인 전 내용을 수정할 수 있는 편집 기능 지원
- 승인된 최종 데이터(final_preview)를 사용하여 실행의 정확성 보장
40줄 미만의 코드로 모든 LangChain 에이전트 도구에 인간 승인 게이트를 추가하세요. 이는 사람이 초안을 승인할 때까지 부작용(side effects) 실행을 차단하며, 전체 편집 지원과 감사 기록(audit record) 기능을 제공합니다.
도구 호출(tool-calling) 에이전트의 문제점
ReAct 또는 함수 호출 루프를 사용하는 LangChain 에이전트는 단일 실행 과정에서 여러 번 도구를 호출할 수 있습니다. 이는 읽기 전용(read-only) 도구의 경우 괜찮습니다. 하지만 도구가 이메일을 보내거나, 댓글을 게시하거나, 레코드를 수정하거나, 외부 API를 호출하는 경우, 모든 호출은 영구적인 부작용입니다.
일반적인 완화 방법으로는 제한적인 시스템 프롬프트(system prompts), 드라이-런 플래그(dry-run flags), 상세 로깅(verbose logging) 등이 있습니다. 이러한 방법들은 실수의 빈도를 줄여줄 뿐, 근절하지는 못하며 강제된 게이트를 남기지 않습니다. 모델은 '전송 전에 확인해 주세요'와 같은 지시 사항을 스스로 논리적으로 우회할 수 있습니다. 사후 로깅(logging after the fact)으로는 이미 발생한 행동을 되돌릴 수 없습니다.
구조적으로 효과적인 방법은 도구의 직접 실행기(direct executor)를 인간의 결정 없이는 완료될 수 없는 버전으로 대체하는 것입니다. 에이전트는 정상적으로 작동하지만, 최종 행동에 도달할 수는 없습니다.
연결 지점 (Where to hook in)
LangChain의 도구 추상화(tool abstraction)가 적절한 통합 지점입니다. BaseTool 서브클래스는 에이전트가 도구를 호출할 때 발생하는 일을 제어합니다. 여기에는 일시 중지(pause), 외부 신호 대기, 그리고 그 신호를 기반으로 결과를 반환하는 기능이 포함됩니다.
패턴은 다음과 같습니다:
- 에이전트는 제안된 입력값(수신자, 제목, 본문 등, 행동에 필요한 모든 것)과 함께 도구를 호출합니다.
- 도구는 해당 제안을 Impri로 전송하고
action_id를 받습니다. - 도구는 사람이 결정할 때까지
GET /v1/actions/:id를 폴링(polling)합니다. - '승인됨(approved)' 상태일 경우, 인간이 만든 모든 편집 내용을 담고 있는
decision.final_preview를 사용하여 실행합니다. - '거부됨(rejected)' 또는 '만료됨(expired)' 상태일 경우, 결과를 설명하는 문자열을 반환하며 — 에이전트는 다음으로 무엇을 할지 결정할 수 있습니다.
핵심 속성은 다음과 같습니다: 실행은 모델이 논리적으로 무시할 수 있는 프롬프트 지침이 아니라 데이터 의존성(API가 `status:
에이전트에 다른 도구와 마찬가지로 연결하면 됩니다:
from langchain.agents import initialize_agent, AgentType
from langchain.chat_models import ChatOpenAI
...
에이전트는 이메일을 작성하고 send_email을 호출합니다. send_email은 일시 정지(pause)하고 승인을 기다리며, 사람이 '예'라고 말할 때만 진행됩니다. 에이전트의 추론 루프는 변경되지 않으며, 게이트가 구조적인 역할을 합니다.
사람의 수정 및 final_preview
"editable": ["preview.body"]를 설정하면 검토자가 승인하기 전에 본문 텍스트를 수정할 수 있습니다. 그들이 수정하면 decision.final_preview.body에 수정된 버전이 담기고, decision.diff에는 변경된 내용의 통합 diff(unified diff)가 담깁니다.
실행 시에는 항상 원본 body 인수가 아닌 final_preview.body를 사용해야 합니다. 이것이 사람이 실제로 승인한 버전입니다.
만료 및 폴링 동작
expires_in 필드는 Impri가 해당 작업을 열어두는 시간(초 단위; 최소 300, 최대 30일, 기본값 72시간)을 설정합니다. 만료된 후 상태는 expired가 되며 작업은 승인될 수 없습니다.
실시간 에이전트 루프의 경우, 짧은 만료 시간(1시간 이하)이 보통 적절합니다. 하룻밤 동안 방치된 초안은 컨텍스트를 잃었을 가능성이 높기 때문입니다. expired는 도구의 반환 값에서 rejected와 동일하게 취급하세요. 에이전트는 이 문자열을 보고 재시도, 에스컬레이션 또는 포기를 결정할 수 있습니다.
Impri가 아닌 것들
Impri는 제안된 작업을 저장하고, 인박스 카드(선택적으로 Slack, Discord 또는 Telegram 사용)를 통해 사람에게 알리고, 결정을 보류합니다. 자체적으로 콘텐츠를 생성하거나, 해당 작업이 무엇을 하는지 해석하거나, 무언가를 실행하지 않습니다.
ApprovedEmailTool이 에이전트가 이메일을 보내는 유일한 경로인 동안에만 진정한 게이트 역할을 합니다. 만약 에이전트가 원시 SMTP 자격 증명이나 이메일 전송 접근 권한을 가진 직접적인 API 키를 가지고 있다면, 이 래퍼(wrapper)를 우회할 수 있습니다. 부작용(side-effect) 자격 증명을 도구에 한정하고, 에이전트가 접근할 다른 방법을 주지 마세요.
다음 단계
다음 단계
- Quickstart — API 키와 5분 만에 첫 액션 수행하기
- Python SDK — 위 REST 호출을 감싸는 타입이 지정된 클라이언트
- Slack 및 Telegram 승인 — 받은 편지함 UI 대신 휴대폰에서 알림 받고 승인하기
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기