- [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 > AI와 일하기' 카테고리의 다른 글
[LLM] 대규모 임베딩 검색의 핵심, FAISS 알아보기 (1) 2025.05.15 [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 다음글이 없습니다.이전글이 없습니다.댓글