- [LLM] LangGraph에서 GraphRecursionError 해결하기: 올바른 상태 관리의 중요성2025년 05월 05일 01시 05분 33초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
LangGraph에서 GraphRecursionError 해결하기: 올바른 상태 관리의 중요성
LangGraph를 사용하여 에이전트 워크플로우를 구축할 때 가장 자주 마주치는 오류 중 하나는
GraphRecursionError
이다. 이 오류는 그래프가 종료 조건에 도달하지 못하고 최대 반복 횟수를 초과할 때 발생한다. 오늘은 이 오류의 주요 원인과 해결 방법을 실제 사례를 통해 살펴보겠다.문제 상황: 무한 재귀 발생
LangGraph를 사용하여 계획 수립 및 실행 에이전트를 구현하는 중 다음과 같은 오류가 발생했다:
GraphRecursionError: Recursion limit of 10 reached without hitting a stop condition. You can increase the limit by setting the `recursion_limit` config key.
이 오류는 그래프가 종료 조건에 도달하지 못하고 계속해서 같은 노드를 순환하고 있음을 의미한다. 대부분의 경우, 이는 상태(state) 관리에 문제가 있다는 신호이다.
주요 원인 분석
코드를 분석해보니 다음과 같은 문제점들이 확인되었다:
1. 상태 업데이트 불완전:
PlanExecute
타입 정의 문제문제의 핵심은
PlanExecute
상태 객체와 그 업데이트 방식에 있었다. 원래 코드에서는:class PlanExecute(TypedDict): input: str plan: List[str] past_steps: List[Tuple[str, str]] response: str # 타입 힌트만 있고 실제로는 항상 채워지지 않음
이 상태 정의는
response
필드가 필수인 것처럼 보이지만, 실제로는 이 필드가 언제 채워지는지 명확하지 않았다. 더 큰 문제는 종료 조건이 이 필드에 의존한다는 점이었다:def should_end(state: PlanExecute): if "response" in state and state["response"]: return END else: return "agent"
2. 반환 값 누락:
execute_step
함수 문제또 다른 중요한 문제는
execute_step
함수가 실행된 계획 단계를 제거하지만, 업데이트된 계획을 반환하지 않는다는 점이었습니다:async def execute_step(state: PlanExecute): # ... 코드 생략 ... return { "past_steps": [(task, response_content)], # "plan": remaining_plan, # 이 라인 누락 }
이로 인해 상태의
plan
필드가 갱신되지 않고, 그래프는 계속해서 같은 단계를 수행하려고 시도했다.3. 애매한 응답 구조:
edited_planner
반환 값 문제마지막으로,
edited_planner
가 반환하는Act
객체의 구조가 명확하지 않아, 종료 조건을 제대로 트리거하지 못했다:class Act(BaseModel): action: Union[Response, Plan] = Field(...)
이 구조는 Union 타입을 사용하여 복잡성을 증가시켰고, 타입 체크가 제대로 이루어지지 않았다.
해결 방법: 명확한 상태 관리
이 문제를 해결하기 위한 핵심은 다음과 같다:
1. 타입 정의 개선:
Optional
타입 사용class PlanExecute(TypedDict): input: str plan: List[str] past_steps: List[Tuple[str, str]] response: Optional[str] # Optional로 명시하여 필수가 아님을 표시
이렇게 함으로써
response
필드가 있을 수도, 없을 수도 있다는 것을 명확히 했다.2. 상태 업데이트 보장: 모든 필요한 필드 반환
async def execute_step(state: PlanExecute): # ... 코드 생략 ... return { "past_steps": state.get("past_steps", []) + [(task, response_content)], "plan": remaining_plan, # 남은 계획을 명시적으로 반환 }
이제
execute_step
함수는 실행 후 남은 계획을 명시적으로 반환하므로, 상태가 올바르게 갱신된다.3. 명확한 응답 구조:
Act
클래스 개선class Act(BaseModel): action_type: str = Field(description="'response' 또는 'plan'") response: Optional[str] = Field(default=None) steps: Optional[List[str]] = Field(default=None) @property def action(self): if self.action_type == "response": return Response(response=self.response) else: return Plan(steps=self.steps)
이렇게 하면 응답 구조가 명확해지고, 종료 조건을 올바르게 트리거할 수 있다.
4. 종료 조건 강화
def should_end(state: PlanExecute): # response가 있고 빈 문자열이 아닌 경우에만 종료 if "response" in state and state["response"] and state["response"].strip(): return END # 또는 계획이 비어있으면 종료 (선택적) elif "plan" in state and len(state.get("plan", [])) == 0: return END else: return "agent"
더 엄격한 종료 조건을 추가하여 그래프가 적절히 종료되도록 했다.
결론
LangGraph를 사용하여 복잡한 에이전트 워크플로우를 구축할 때
GraphRecursionError
는 흔히 발생하는 오류이다. 이 오류를 해결하는 핵심은 상태 객체의 명확한 정의와 완전한 상태 업데이트를 보장하는 것이다. 특히 종료 조건에 사용되는 필드들이 올바르게 설정되고 업데이트되는지 확인하는 것이 중요하다.올바른 상태 관리만으로도 대부분의
GraphRecursionError
문제를 해결할 수 있으며, 더 안정적이고 예측 가능한 LangGraph 애플리케이션을 구축할 수 있다.728x90반응형'AI' 카테고리의 다른 글
[LLM] LangChain과 ChromaDB 사용 시 겪을 수 있는 두 가지 함정: 인코딩과 저장 문제 해결하기 (0) 2025.05.06 [LLM] 에이전트 리즈닝(Agent Reasoning) (0) 2025.04.29 [LLM] 왜 LangChain 같은 프레임워크를 사용할까? (1) 2025.04.29 BGE M3 임베딩 모델: 왜 사용하며 Llama 3와 무엇이 다른가? (0) 2025.04.24 이제 까먹지 마! 모델 컨텍스트 프로토콜(MCP) 쉽게 이해하기 (1) 2025.04.18 다음글이 없습니다.이전글이 없습니다.댓글