LangChain에서 배우는 교훈: 프로덕션급 에이전트를 위한 신뢰할 수 있는 런타임 설계
요약
데모 수준의 에이전트와 실제 프로덕션 시스템 사이에는 '런타임 격차(Runtime gap)'가 존재하며, 이를 극복하기 위해서는 모델의 성능보다 안정적인 런타임 설계가 중요합니다. 비즈니스급 에이전트는 네트워크 장애, 도구 타임아웃, 상태 유지, 사용자 승인 대기 등 복잡한 실행 환경을 견딜 수 있는 복구 가능하고 관찰 가능한 시스템을 갖춰야 합니다.
핵심 포인트
- 에이전트의 실패는 모델의 환각뿐만 아니라 네트워크 장애, API 타임아웃, 프로세스 중단 등 다양한 런타임 요인에 의해 발생한다.
- 프로덕션 환경에서는 작업의 상태(state)를 지속적으로 유지하고 관리하는 능력이 필수적이다.
- 비즈니스급 에이전트의 핵심 경쟁력은 단순한 루프가 아닌 상태 관리, 권한, 복구, 관찰 가능성, 인간과의 협업 능력에 있다.
- 단순한 프롬프트 최적화를 넘어 복잡하고 불안정한 워크로드를 견딜 수 있는 런타임 설계가 필요하다.
🙋 저는 Agent 엔지니어링을 분석하고 AI가 교육에 어떻게 적용될 수 있는지 탐구해 온 개발자 Luhui Dev입니다. 저는 Agent Harness, LLM 애플리케이션 엔지니어링 (LLM application engineering), 수학을 위한 AI (AI for Math), 그리고 교육 SaaS의 제품화에 집중하고 있습니다. Agent 데모는 흥미를 끌기 쉽습니다. 모델 하나, 몇 가지 도구, 프롬프트 (prompt), 그리고 이를 루프 (loop)로 감싸기만 하면, 갑자기 검색을 하고 파일을 쓰며 API를 호출하는 무언가가 탄생합니다. 하지만 데모와 프로덕션 시스템 (production system) 사이에는 긴 간극이 존재합니다. 저는 이를 런타임 격차 (Runtime gap)라고 부르며, 이를 가로지르는 것은 더 똑똑한 모델이 아닙니다. 그것은 실제 환경에서 복잡하고, 불안정하며, 중단 가능하고, 복구 가능한 워크로드 (workloads)를 견뎌낼 수 있는 런타임 (runtime)입니다. 실제로 비즈니스 맥락에 배포하고 나면, Agent는 몇 분 또는 수십 분 동안 실행될 수 있습니다. 이는 여러 외부 시스템을 호출하고, 사용자의 승인이 필요할 수 있으며, 네트워크 장애, 도구 타임아웃 (tool timeouts), 모델 출력의 드리프트 (drifting model output), 권한 누락, 실행 중 사용자 중단, 프로세스 재시작, 그리고 버전 업그레이드 등을 겪을 수 있습니다. 설상가상으로, Agent는 상태 (state)를 유지합니다. 즉, 작업이 어디까지 진행되었는지, 무엇이 이미 쿼리되었는지, 어떤 중간 파일이 작성되었는지, 어떤 결론이 아직 확인되지 않았는지, 이 사용자가 주어진 데이터셋에 접근할 수 있는지 등을 관리해야 합니다. 이 시점에서 프롬프트 (prompts)를 최적화하는 것만으로는 근본적인 문제를 해결할 수 없습니다. Agent에게 필요한 것은 실행 프로세스를 하나로 묶어주는 런타임, 즉 복잡하고, 불안정하며, 중단 가능하고, 복구 가능한 런타임입니다. 프로덕션급 딥 에이전트 (production deep agents)와 그 런타임에 관한 LangChain의 최근 글은 Agent 제품을 만드는 모든 이들에게 공유할 가치가 있습니다. 이는 유용한 상기 사항을 제공합니다. 비즈니스급 Agent의 해자 (moat)는 단순히 더 예쁜 Agent 루프 (agent loop)가 아닙니다. 상태 (state), 권한 (permissions), 복구 (recovery), 관찰 가능성 (observability), 그리고 인간과의 협업 (human collaboration)을 안정적인 기반으로 만들 수 있느냐 하는 것입니다.
- 비즈니스급 Agent의 실패는 모델이 틀렸을 때만 발생하는 것이 아니다
사람들이 Agent의 신뢰성 (reliability)을 생각할 때, 가장 먼저 떠올리는 것은 보통 환각 (hallucinations)입니다. 그것도 중요하지만, 비즈니스 시스템에서 실패의 표면 (failure surface)은 훨씬 더 넓습니다.
긴 작업의 8단계에서 프로세스가 충돌할 수도 있습니다. 재실행은 비용을 낭비하고 외부 API를 중복 호출하여 지저분한 데이터(dirty data)를 남길 수 있습니다. 도구가 실패할 수도 있습니다. API 타임아웃, 로드되지 않는 페이지, 오류를 던지는 데이터베이스 쿼리 등 — 재시도(retries), 폴백(fallbacks), 상태 지속성(state persistence)이 없다면 전체 작업은 단 한 번의 기회에 모든 것을 거는 도박이 됩니다. 인간의 승인을 기다리는 동안 컨텍스트(context)를 잃어버릴 수도 있습니다. 사용자가 30분 후에 돌아와 "확인"을 클릭했을 때, 시스템이 사용자가 어떤 단계를 확인하고 있었는지 기억하지 못할 수 있습니다. 상호작용 계층(interaction layer)에서 제어력을 잃을 수도 있습니다. 에이전트(Agent)는 여전히 실행 중인데, 사용자가 "잠깐, 그 방향은 틀렸어요 — 계획 B로 전환하세요"라고 입력할 수 있습니다. 시스템은 이를 대기열에 넣어야 할까요, 중단해야 할까요, 재시작해야 할까요, 아니면 거부해야 할까요? 명확한 정책이 없다면 사용자 경험은 무너집니다. 따라서 프로덕션급 에이전트의 신뢰성은 최소한 여섯 가지 측면을 갖춰야 합니다: 실행 신뢰성(execution reliability), 상태 신뢰성(state reliability), 상호작용 신뢰성(interaction reliability), 권한 신뢰성(permission reliability), 관찰 가능성(observability), 그리고 운영 신뢰성(operational reliability)입니다. 런타임(Runtime)의 가치는 모든 팀이 이 문제들을 처음부터 직접 구현(hand-roll)하게 두는 대신, 이를 제품화하고 프레임워크화하는 데 있습니다.
- 하네스(Harness)와 런타임(Runtime)의 분리
현재 저의 관점에서 한 가지 중요한 차이점은 이것입니다: 하네스(Harness)와 런타임(Runtime)은 같은 것이 아닙니다. 하네스는 에이전트의 행동적 껍데기(behavioral shell)입니다. 이는 작업이 어떻게 계획되는지, 프롬프트(prompt)가 어떻게 작성되는지, 어떤 도구(tools)를 호출할 수 있는지, 하위 작업(sub-tasks)이 생성되는지, 파일 시스템이 존재하는지, 하위 에이전트(sub-Agents)가 사용되는지, 컨텍스트가 어떻게 압축되는지를 관리합니다. 이 계층은 에이전트가 얼마나 똑똑해 보이는지에 직접적인 영향을 미칩니다. 런타임은 더 낮은 계층입니다. 이는 에이전트가 어떻게 실행되고, 지속(persisted)되며, 복구(recovered)되고, 중단(interrupted)되며, 관찰(observed)되고, 스케줄링(scheduled)되는지, 사용자 간에 어떻게 격리(isolated)되는지, 그리고 동시 요청(concurrent requests)이 어떻게 처리되는지를 관리합니다. 이 계층은 에이전트가 실제로 비즈니스 시스템을 지원할 수 있는지 여부에 직접적인 영향을 미칩니다. 많은 오픈 소스 에이전트에서는 모든 것이 하네스에 쑤셔 넣어집니다: 프롬프트 내의 규칙, 도구 호출 내부의 try-catch, 데이터베이스의 임시 상태(ad-hoc state), 프론트엔드의 로딩 스피너 등이 그러합니다.
그것은 단기적으로만 작동합니다. 시간이 흐르면 아무도 유지보수하고 싶지 않은 복잡하게 얽힌 로직이 되어버립니다. LangChain의 런타임 (Runtime) 접근 방식은 이러한 횡단 관심사 (cross-cutting capabilities)를 에이전트 루프 (agent-loop) 컨텍스트로부터 분리하여 끌어올리는 것입니다.
- 내구성이 있는 실행 (Durable Execution): 신뢰성의 첫 번째 토대
만약 제가 LangChain 런타임에서 단 하나의 설계 원칙만을 배울 수 있다면, 저는 내구성이 있는 실행 (durable execution)부터 시작할 것입니다. 일반적인 웹 요청은 수명이 짧습니다. 요청이 들어오고, 작업을 수행하고, 응답하면 끝입니다. 에이전트는 다릅니다. 비즈니스 에이전트는 작업을 이해하고, 이를 세분화하고, 자료를 검색하고, 도구를 호출하고, 중간 파일을 작성하고, 승인을 기다리고, 계속 진행하여 보고서를 생성하는 등 많은 단계를 거칠 수 있습니다. 이 프로세스는 자연스럽게 여러 번의 모델 호출 (model calls), 도구 호출 (tool calls), 그리고 사용자 상호작용 (user interactions)에 걸쳐 이어집니다. 작업이 길어지면 시스템은 한 가지 질문에 답해야 합니다. '만약 중간에 충돌(crash)이 발생하면 어떻게 될 것인가?'
LangChain/LangGraph의 해답은 체크포인팅 (checkpointing)입니다. 실행 중인 핵심 상태 (key states)가 지속적으로 영속화 (persisted)됩니다. 복구 시, 처음부터 다시 시작하는 것이 아니라 가장 최근의 합리적인 상태에서 재개합니다. 비즈니스 시스템에서 이는 단순히 비용을 절감하는 문제가 아닙니다. 부수 효과 (side effects)의 중복을 방지하는 방법이기도 합니다.
이것은 실제로 어떻게 작동할까요? LangGraph는 에이전트 실행을 상태 그래프 (state graph)로 모델링합니다. 각 노드 (node)는 하나의 단계, 즉 모델 호출, 도구 호출, 또는 조건부 실행입니다. 상태는 노드 사이를 흐르며, 각 단계가 끝난 후 전체 그래프의 현재 스냅샷 (snapshot)이 체크포인터 (checkpointer)에 직렬화 (serialized)됩니다.
여기서 파헤쳐 볼 가치가 있는 몇 가지 설계 선택 사항이 있습니다. 첫째, 체크포인팅의 단위는 함수 호출 (function-call) 경계가 아니라 노드 (node) 경계입니다. 스트리밍 모델 호출이 출력 도중에 중단되면, 복구 시 호출 전체를 다시 실행합니다. 둘째, 상태는 블랙박스 형태의 피클 (pickle)이 아니라 구조화되어 있습니다. LangGraph는 상태를 이름이 지정된 채널 (messages, plan, scratchpad)로 나누고, 각 채널을 리듀서 (reducer) (messages의 경우 append, plan의 경우 overwrite)와 쌍을 이루도록 요구합니다. 이를 통해 체크포인트는 구조화된 차이 (structured diffs)가 되어, 추적 가능하고, 재생 가능하며, 어떤 단계로든 시간 여행 (time-travelable)이 가능해집니다. 셋째, 체크포인트는 선형이 아니라 트리 (tree) 구조를 형성합니다. 모든 체크포인트는 부모 참조 (parent reference)를 가집니다.
어떤 과거 노드에서든 분기(branch off)하여 다시 실행할 수 있습니다. 사용자의 질문을 수정하거나, 승인(approval) 단계를 건너뛰거나, 다른 도구(tool)를 시도하는 등의 모든 작업은 동일한 트리에서 새로운 가지를 형성하며 확장됩니다. 네 번째로, 중단(interrupt)과 체크포인트(checkpoint)는 동일한 메커니즘을 공유합니다. 노드 전후의 중단은 본질적으로 해당 시점에 기록된 체크포인트와 그에 따른 일시 정지(pause)입니다. 사람의 승인(human approval), 사용자의 편집, 외부 웨이크업 신호(wake-up signals) 모두 동일한 영속성 계층(persistence layer)을 재사용합니다. 이것이 바로 HITL(Human-in-the-loop)이 UI 로직이 아닌 런타임(Runtime) 기능이 될 수 있는 이유입니다. 다섯 번째로, 백엔드는 플러그인 방식(pluggable)으로 교체 가능합니다. 개발 시에는 인메모리(in-memory)나 SQLite를 사용하고, 프로덕션(production) 환경에서는 Postgres나 Redis를 사용하십시오. 에이전트(Agent)의 신뢰성 계층은 비즈니스 규모에 따라 확장할 수 있으며, 첫날부터 무거운 인프라를 구축할 필요는 없습니다. 기업 고객을 위해 연구 보고서를 생성하는 에이전트를 상상해 보십시오. 에이전트는 자료 수집, 경쟁사 요약, 보고서 초안 작성을 마쳤으며, 이제 내부 CRM 데이터를 가져올지 여부에 대해 사용자의 확인을 기다리고 있습니다. 만약 이 시점에서 서비스가 재시작된다면, 이상적인 결과는 에이전트가 처음부터 다시 검색하게 하거나 사용자가 요구 사항을 다시 설명하게 만드는 것이 아닙니다. 바로 "확인 대기 중" 상태에서 재개하는 것입니다. 이것이 내구성 있는 실행(durable execution)의 핵심입니다. 즉, 에이전트의 실행을 일회성 함수 호출(one-shot function call)에서 실제 라이프사이클(lifecycle)을 가진 저장 가능하고, 복구 가능하며, 재개 가능한 작업(task)으로 전환하는 것입니다. 여전히 답할 가치가 있는 구체적인 질문들이 남아 있습니다. 예를 들어, 각 에이전트 단계에서 정확히 무엇을 복구 가능한 경계(recoverable boundary)로 간주할 것인가? 비즈니스 시스템에 대한 쓰기(write) 작업을 안전하게 반복할 수 있는가? 하는 점들입니다.
- 상태를 계층화하십시오: 단기 상태(short-term state), 장기 메모리(long-term memory), 그리고 비즈니스 데이터는 혼합되어서는 안 됩니다.
복잡한 에이전트 작업은 상태(state)를 생성합니다. 하지만 상태가 하나의 커다란 찌개처럼 뒤섞여서는 안 됩니다. 단기 상태는 현재 작업의 컨텍스트(context)입니다. 즉, 계획이 무엇인지, 실행이 어디까지 도달했는지, 중간 결과물은 무엇인지, 어떤 도구 호출(tool calls)이 완료되었고 무엇이 확인을 기다리고 있는지 등을 의미합니다. 이러한 종류의 상태는 스레드(threads), 런(runs), 그리고 체크포인트에 종속되어야 합니다.
장기 기억 (Long-term memory)은 세션 간의 컨텍스트 (cross-session context)입니다: 사용자 선호도, 조직 규칙, 일반적인 워크플로우 (workflows), 반복되는 제약 조건, 재사용 가능한 지식 등이 이에 해당합니다. 이는 사용자, 조직, 애플리케이션, 어시스턴트 등으로 네임스페이스 (namespaced) 처리되어 장기 저장소 (long-term store)에 존재해야 합니다. 비즈니스 데이터는 또 다른 계층입니다: 주문, 문제, 교훈, 고객 기록, 조직 자산, 권한 모델 등이 포함됩니다. 이러한 데이터는 일반적으로 에이전트 런타임 (Agent Runtime)에 무심코 흡수되어서는 안 됩니다. 데이터의 소유권은 비즈니스 시스템에 유지되어야 하며, 에이전트는 제어된 도구 (tools)를 통해 이에 접근해야 합니다. LangChain의 설계는 이 지점에서 시사하는 바가 큽니다. LangChain은 스레드 체크포인트 (thread checkpoints)를 장기 저장소와 분리하면서도, 심층 에이전트 (deep agents)가 가상 파일 시스템 (virtual filesystem)과 같은 것을 통해 다양한 상태 계층에 접근할 수 있도록 합니다. 상위의 에이전트 입장에서는 파일과 메모리를 읽고 쓰는 것이 자연스럽게 느껴지지만, 하위 시스템 입장에서는 상태의 경계가 여전히 명확하게 유지됩니다. 이는 실제 비즈니스 시스템에서 매우 중요합니다. 초기 에이전트 제품 중 상당수는 채팅 기록, 도구 결과, 사용자 선호도, 비즈니스 데이터를 단일 대화 메모리에 모두 쌓아둡니다. 구현은 간단하지만, 나중에 권한, 비용, 검색 품질 (retrieval quality), 데이터 정리, 컴플라이언스 감사 (compliance audit) 측면에서 한꺼번에 문제가 터지게 됩니다. 더 견고한 패턴은 다음과 같습니다: 단기 상태 (short-term state)는 작업 복구 (task recovery)를 위해 사용되고, 장기 기억 (long-term memory)은 경험의 연속성 (experience continuity)을 위해 사용되며, 비즈니스 데이터는 비즈니스 시스템 내부에 머무릅니다. 에이전트는 오직 권한이 제어된 도구를 통해서만 해당 데이터에 도달할 수 있습니다.
- 휴먼 인 더 루프 (Human-in-the-loop)는 장식이 아니라 신뢰성 메커니즘입니다
프로덕션급 에이전트 (Production-grade Agents)를 완전히 자동화하는 것은 어렵습니다. 특히 쓰기 작업, 외부 시스템 호출, 중요한 결정, 유료 리소스 또는 사용자 개인정보와 관련된 모든 것은 인간과의 협업이 필수적인 안전 밸브 (safety valve) 역할을 합니다. 핵심은 단순히 확인 대화 상자를 띄우는 것이 아닙니다. 진짜 엔지니어링 관점에서의 질문은 다음과 같습니다: 에이전트가 어떻게 일시 중지(pause)하는가? 일시 중지하는 동안 어떤 상태가 저장되는가? 사용자가 나중에 언제든 돌아와서 작업을 계속할 수 있는가? 사용자가 에이전트가 생성한 계획을 수정할 수 있는가? 수정 후에는 어디서부터 재개되는가?
승인 기록은 감사(auditable) 가능한가? LangChain Runtime은 중단/재개(interrupt/resume)를 애플리케이션 계층이 임시방편(ad-hoc)으로 판단하는 것이 아니라, 런타임(runtime) 자체의 기능으로 만듭니다. 만약 인간 개입(HITL, Human-in-the-loop)이 프론트엔드 상호작용 계층에만 존재한다면, 이는 빠르게 UI 로직으로 전락하기 때문입니다. 작업이 프로세스, 워커(worker), 그리고 시간을 가로질러 확장되면 프론트엔드는 이를 유지할 수 없습니다. 비즈니스 에이전트(Agent)의 수많은 시나리오에서 이것이 필요합니다. 비용 보고서를 제출하려는 금융 에이전트는 인간의 확인이 필요합니다. 수업 계획을 대량으로 생성하는 교육 에이전트는 교사가 교수 스타일을 선택해야 합니다. 환불을 처리하는 고객 서비스 에이전트는 관리자의 승인이 필요합니다. 민감한 필드에 접근하려는 데이터 분석 에이전트는 일회성 사용자 권한 부여(authorization)가 필요합니다. 이것들은 일반적인 채팅 경험이 아니라 비즈니스 워크플로(workflow)입니다. 만약 런타임이 일시 중지, 재개, 승인 및 상태 지속성(state persistence)을 네이티브로 지원한다면, 에이전트의 신뢰성은 확실히 한 단계 도약할 것입니다.
- 권한 및 멀티 테넌시(multi-tenancy): 에이전트는 마스터 키를 들고 돌아다녀서는 안 된다
프로덕션급 에이전트의 가장 큰 위험 요소 중 하나는 권한(permissions)입니다. 일반적인 앱에서는 사용자가 버튼을 클릭하고 API를 호출하면 서버가 권한을 확인하므로, 그 흐름이 비교적 명확합니다. 하지만 에이전트가 개입하면 복잡해집니다. 모델이 어떤 도구(tool)를 호출할지 결정하고, 그 도구가 외부 시스템에 접근할 수 있으며, 해당 외부 시스템은 사용자 권한 부여를 요구할 수 있고, 에이전트는 중간 결과물을 장기 메모리(long-term memory)에 기록할 수도 있습니다. LangChain의 접근 방식은 신원(identity)과 권한을 계층별로 분리하는 것입니다. 즉, 최종 사용자가 누구인지, 해당 사용자가 어떤 스레드와 리소스에 접근할 수 있는지, 에이전트가 사용자를 대신하여 어떤 외부 시스템에 접근할 수 있는지, 그리고 팀원들이 플랫폼 자체에서 무엇을 할 수 있는지를 구분합니다. 이 설계에서 에이전트는 백엔드 슈퍼 관리자(super-admin)가 아닙니다. 에이전트는 현재 사용자, 현재 조직, 그리고 현재 작업의 범위 내에서만 행동할 수 있도록 허용된 위임된 실행자(delegated executor)에 가깝습니다.
만약 여러분만의 프로덕션급 에이전트 (Agent)를 위한 런타임 (Runtime)을 설계하고 있다면, 최소한 다음과 같은 경계 (boundaries)에 대해 고민해야 합니다: 사용자 식별 정보 (User identity)가 실행 컨텍스트 (run context)에 포함되어야 합니다. 모든 에이전트 실행은 현재 누구를 대신하여 행동하고 있는지를 알고 있어야 합니다. 리소스 접근 (Resource access)은 스레드 (thread), 파일 (file), 프로젝트 (project), 그리고 조직 (organization) 단위로 격리되어야 합니다. 모델에게 타인의 데이터에 손대지 말라고 프롬프트 (prompt)로 지시하는 것에 의존해서는 안 됩니다. 외부 도구 권한 부여 (External tool authorization)는 별도로 관리되어야 합니다. GitHub, Slack, CRM, 오브젝트 스토리지 (object storage), 데이터베이스 (databases) 등의 장기 키 (long-term keys)를 에이전트의 실행 환경 (execution environment)에 직접 전달해서는 안 됩니다. 장기 메모리 (Long-term memory)에는 네임스페이스 (namespaces)가 필요합니다. 그렇지 않으면 에이전트가 기억하는 사용자 설정 (user preferences)이 멀티 테넌트 (multi-tenant) 환경에서 쉽게 데이터 오염 (data pollution)이 될 수 있습니다. 고위험 도구 (High-risk tools)에는 승인 또는 정책 가로채기 (policy interception)가 필요합니다. 삭제 (Deletes), 전송 (sends), 결제 (payments),
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기