포스트

LangGraph 순환 Agent가 무한 루프를 막아줄까: State, Checkpoint, Retry 상한

LangGraph는 에이전트의 순환과 상태를 표현해 줄 뿐, 무한 루프를 자동으로 막아 주지는 않습니다. 재시도 상한과 종료 조건, 외부 작업의 멱등성을 그래프 설계자가 상태와 엣지에 명시해야 합니다.

DAG로는 표현하기 어려운 일을 그래프로 만든다

일회성 RAG는 입력, 검색, 생성 순으로 끝나는 DAG로도 충분합니다. 그러나 코드를 작성하고 테스트한 뒤 실패하면 다시 수정하는 작업에는 되돌아가는 경로가 필요합니다. LangGraph는 Pregel에서 영감을 받은 그래프 실행 모델 위에 State, Node, Edge를 두어 이 순환을 명시합니다.

노드는 LLM 호출, 데이터베이스 조회나 도구 실행을 맡는 Python 함수입니다. 조건부 엣지는 현재 상태를 보고 다음 노드나 종료 지점을 선택합니다. 자유로운 에이전트처럼 보여도 실제 통제 지점은 엣지입니다. 실패 횟수, 검증 결과, 사람 승인 여부가 상태에 없다면 올바른 분기를 만들 수 없습니다.

State는 대화 기록 이상의 계약이다

원문에 나온 상태 스키마는 다음과 같습니다.

1
2
3
4
5
6
7
from typing import Annotated, TypedDict
import operator

class AgentState(TypedDict):
    messages: Annotated[list, operator.add]
    current_context: str
    retry_count: int

이것은 그래프 전체가 아니라 상태 정의만 보여 주는 핵심 조각입니다. 노드, 그래프 생성, 조건부 엣지, 체크포인터와 종료 규칙이 빠져 있어 단독으로 실행되는 에이전트 예제가 아닙니다.

Annotated[list, operator.add]는 노드의 새 메시지를 기존 목록에 더하는 리듀서입니다. 편리하지만 순환할 때마다 messages가 계속 길어집니다. retry_count가 있다고 자동으로 제한되는 것도 아닙니다. 개발자가 증가시키고, 특정 값에서 종료나 사람 검토로 보내는 엣지를 만들어야 합니다.

상태에는 다음 질문에 답할 값이 있어야 합니다.

  • 이번 작업은 몇 번 시도했는가
  • 어떤 검증이 통과하거나 실패했는가
  • 외부 효과가 있는 도구를 이미 호출했는가
  • 누가 어떤 시점에 승인을 했는가
  • 다음 재개 때 반드시 유지할 최소 문맥은 무엇인가

Checkpoint는 복구 지점이지 정답 보증이 아니다

SqliteSaverPostgresSaver 같은 체크포인터는 노드 사이의 상태를 저장합니다. 실행이 중단되면 이전 상태에서 재개하고, 특정 스냅샷으로 돌아가 값을 수정한 뒤 다시 진행하는 흐름을 만들 수 있습니다. 사람의 판단이 필요한 지점에서는 interrupt를 걸어 승인을 기다릴 수도 있습니다.

이 기능은 긴 작업의 복구와 감사를 쉽게 하지만 잘못된 상태도 충실히 저장합니다. 도구 호출이 결제나 쓰기처럼 되돌리기 어렵다면 체크포인트에서 재개할 때 같은 효과가 두 번 발생하지 않도록 멱등 키와 실행 기록이 필요합니다. 데이터베이스에 상태가 남는 것과 비즈니스 작업이 정확히 한 번 수행되는 것은 다른 문제입니다.

운영에서 먼저 부딪히는 세 가지 한계

첫째, 그래프가 커질수록 경로 조합이 늘어 디버깅이 어렵습니다. 상태 변화와 노드 입출력을 구조적으로 기록해야 하며, 원문은 관찰 도구로 LangSmith를 소개하는 동시에 의존성과 비용을 지적합니다.

둘째, 순환은 토큰을 불립니다. 모든 오류 로그와 대화를 계속 더하면 비용이 증가하고 오래된 문맥이 판단을 흐릴 수 있습니다. 재시도 상한과 함께 오래된 메시지 요약, 절단 규칙, 노드별 토큰 예산을 정해야 합니다.

셋째, LLM은 비결정적입니다. 어제 통과한 경로가 오늘 다른 출력을 만들 수 있습니다. 조건은 가능한 한 구조화된 검증 결과를 사용하고, 중요한 외부 작업 앞에는 사람 승인이나 결정적 검사기를 둬야 합니다.

첫 그래프는 세 갈래면 충분하다

처음부터 다중 에이전트를 만들기보다 “생성 → 검증 → 종료 또는 한 번 수정” 흐름으로 시작하십시오. 실패가 반복되면 세 번째 경로인 사람 검토로 보냅니다. 각 노드 전에 입력 상태를, 이후에는 변경된 필드와 비용을 기록하고 체크포인트에서 실제 재개도 시험합니다.

LangGraph가 잘 맞는 일은 여러 단계가 오래 이어지고, 중간 실패에서 재개해야 하며, 사람이 개입할 명확한 지점이 있는 작업입니다. 호출 몇 번으로 끝나는 선형 파이프라인이라면 그래프의 학습, 관찰 비용이 이득보다 클 수 있습니다. “자율성”보다 종료 조건을 먼저 그릴 수 있을 때 도입하는 것이 맞습니다.

State에는 사실과 실행 기록을 분리해 담는다

대화 메시지 하나에 목표, 중간 결과와 도구 실행 여부를 모두 묻어 두면 조건부 엣지가 자연어를 다시 해석해야 합니다. 작업 ID, 현재 단계, 검증 결과, 재시도 횟수, 승인 상태와 외부 효과 ID를 구조화된 필드로 두면 분기와 감사가 명확해집니다. 메시지는 모델이 참고할 문맥이고 상태 필드는 시스템이 통제할 계약으로 나누는 방식입니다.

각 노드는 필요한 필드만 읽고 자신이 바꿀 필드만 반환하게 하는 편이 좋습니다. 두 노드가 같은 값을 서로 다른 의미로 덮어쓰면 재개 시점에 상태를 믿기 어렵습니다. 상태 스키마가 바뀔 때 오래된 체크포인트를 읽을 수 있는지, 기본값이 안전한 종료 쪽인지도 확인해야 합니다.

오류 정보에는 원문 전체를 계속 쌓기보다 종류, 요약, 마지막 발생 위치와 원본 로그 참조를 남길 수 있습니다. 같은 긴 스택을 매 순환마다 모델에 넣으면 토큰이 늘고 최신 지시가 묻힙니다. 외부 저장소의 상세 로그와 모델이 볼 최소 상태를 분리하면 복구 가능성과 문맥 비용을 함께 관리할 수 있습니다.

Checkpoint 재개가 중복 실행을 만들지 않게 하려면

체크포인트는 노드 전후의 상태를 저장하지만 외부 시스템의 효과까지 되돌리지 않습니다. 결제 요청, 이메일 발송, 파일 쓰기나 PR 생성 뒤에 프로세스가 멈추면 재개한 노드가 같은 작업을 다시 할 수 있습니다. 외부 호출에 작업 ID 기반 멱등 키를 넘기고 결과 ID를 상태에 기록한 뒤, 재실행 전에 이미 완료됐는지 확인해야 합니다.

중단 지점도 의도적으로 정합니다. 외부 쓰기 직전에는 입력과 승인 상태를 저장하고, 쓰기 후에는 결과를 바로 체크포인트에 반영합니다. 두 저장 사이에서 장애가 났을 때 어떤 조회로 실제 완료 여부를 복구할지 문서화합니다. “exactly once”를 기대하기보다 중복 요청에도 최종 결과가 하나가 되도록 설계하는 편이 현실적입니다.

사람 승인 토큰에도 유효 기간과 대상 상태가 필요합니다. 이전 코드 버전에 대한 승인을 재개 후 바뀐 패치에 그대로 사용하면 안 됩니다. 승인할 입력의 해시나 버전과 승인자를 저장하고, 내용이 달라지면 다시 interrupt로 보내야 합니다.

순환 예산은 어떤 값으로 제한할까

재시도 횟수 하나만으로는 충분하지 않습니다. 한 번의 노드가 매우 긴 모델 호출이나 비싼 도구 실행을 할 수 있으므로 총 토큰, 벽시계 시간, 도구 호출 수와 외부 변경 수에도 상한을 둡니다. 어느 한도를 넘든 실패 상태와 지금까지 확인한 근거를 남기고 사람에게 넘깁니다.

진행 여부를 판단하는 값도 필요합니다. 검증 오류 수가 줄지 않거나 같은 도구 인수로 반복 호출하거나 상태 요약이 바뀌지 않으면 횟수가 남아 있어도 중단할 수 있습니다. 반대로 새로운 검증 결과가 생긴 경우에만 한 번 더 수정하도록 하면 무의미한 순환을 줄입니다.

메시지 축약은 오래됐다는 이유만으로 앞부분을 버리지 않습니다. 최종 목표, 사용자 제약, 승인과 되돌리기 어려운 실행 기록은 항상 유지하고 중간 탐색과 중복 오류만 요약합니다. 요약 뒤에도 다음 노드가 같은 분기를 선택하는지 회귀 테스트를 해 문맥 절단이 행동을 바꾸지 않는지 확인합니다.

그래프 경로는 어떻게 테스트할까

LLM 출력 대신 고정된 가짜 노드를 사용하면 조건부 엣지와 상태 변화를 결정적으로 시험할 수 있습니다. 첫 시도 성공, 검증 실패 뒤 성공, 재시도 상한, 사람 거부, 도구 시간 초과와 체크포인트 재개 경로를 각각 만듭니다. 모든 경로가 종료점이나 명시된 대기점에 도달하는지 확인해야 숨은 무한 순환을 찾을 수 있습니다.

그다음 실제 모델을 넣고 같은 입력을 여러 번 실행해 경로 분포를 봅니다. 성공률만 아니라 평균, 최대 노드 수, 토큰, 사람이 개입한 비율과 잘못된 외부 호출을 기록합니다. 모델 버전이나 프롬프트가 바뀌면 대표 경로를 다시 실행해 이전보다 재시도가 늘지 않았는지 비교합니다.

장애 시험에서는 체크포인트 저장소 연결을 끊고, 노드 실행 중 프로세스를 종료하며, 외부 도구가 성공했지만 응답만 유실되는 상황을 만듭니다. 재개 후 상태와 실제 외부 결과가 일치하고 중복 효과가 없는지가 복구 기능의 핵심입니다.

언제 LangGraph가 과한 선택일까

한 번의 검색과 생성으로 끝나고 실패 시 전체를 다시 실행해도 싼 작업에는 선형 함수가 더 읽기 쉽습니다. 상태가 몇 개 없고 사람 승인이나 장기 재개가 필요하지 않다면 그래프 런타임, 체크포인트 저장소와 관찰 도구가 새 운영 비용이 됩니다. 먼저 일반 코드로 종료 규칙을 설명할 수 있는지 살펴보는 편이 좋습니다.

반대로 여러 시간 이어지는 작업, 외부 도구와 사람 승인이 섞인 작업, 실패한 지점에서 재개해야 하는 업무라면 명시적 그래프가 유리합니다. 도입 근거는 에이전트가 더 자율적으로 보이는지가 아니라 모든 순환, 쓰기, 대기 상태를 열거하고 복구할 수 있다는 점입니다.

그래프를 선택한 뒤에도 노드 수를 성숙도의 지표로 삼지 않습니다. 적은 노드와 분명한 계약으로 실패 경로를 통제하는 구성이 많은 전문 에이전트를 연결한 구성보다 운영하기 쉽습니다. 첫 파일럿에서 종료와 재개가 안정적인지 확인한 뒤 필요한 경로만 추가해야 합니다.

참고 자료:

원문과 버전 확인

함께 읽으면 이해가 이어지는 글

THE END / OPSOAI

여기까지 읽었습니다

핵심 장면을 한 번 더 떠올려 보세요. 이해가 남았다면 이 책은 제 역할을 다했습니다.

다른 책 고르기
표지 1

키와 좌우 스와이프를 지원합니다. 읽던 페이지는 이 기기에 저장됩니다.

CONTENTS

이 책의 목차

    12개 장 19 분읽는 시간