langgraph에서 벗어나기

Posted on October 1, 2025 by 주형
Tags: ,

llm api 를 사용한 제품을 만들면서 처음엔 langgraph 를 도입했습니다. 이후에 langgraph 를 벗어나 직접 채팅 내역 관리와 상태 관리를 구현했습니다. 이 과정에서 느꼈던 점들과 배웠던 점을 정리했어요.

막막할 때 구원의 손길 langgraph

처음 챗봇을 만들어야 했을 때 langgraph 가 큰 도움이었습니다. 유저가 질문하면 이미 알고 있는 정보를 바탕으로 답변을 하는 챗봇을 만들어야 했어요. 은행 앱에 있는 챗봇과 비슷합니다. 이 때 비슷한 작업을 해본 적이 없어서 막막했습니다. 사용자가 질문을 하면 그 질문으로부터 적절한 검색어를 추출해야했어요. 이 검색어로 디비에 있는 정보를 검색해야했습니다. 검색한 결과를 잘 다듬어서 유저의 질문에 맞는 답으로 제공해야했어요. 각각이 어려웠습니다.

langgraph 가 제공해주는 tutorial1 을 따라하면서 최소한의 원하는 기능을 쉽게 구현할 수 있었습니다. 모르던 많은 개념들도 익히게 되었어요. rag2 가 무엇인지, 어떻게 원본 정보를 쪼개고 vector화 해서 저장하는지, 어떻게 다시 쿼리하는지 등에 대해서 알게 되었습니다. 아무 것도 모르는 상태로도 동작하는 걸 만들 수 있었고, 이를 분석하면서 많이 배웠어요.

langgraph 를 벗어나게 된 이유 - graph 구조의 답답함

가장 큰 이유는 langgraph 의 graph 구조가 답답했기 때문이었습니다. langgraph에서는 여러 작업을 graph 형태로 표현합니다. 시작 node에서 끝 node로 이동하면서 llm이나 db 호출들을 진행해요. 끝 노드에 도착해서 나오는 결과물을 chatbot output 으로 사용합니다.

예를 들어서 자산을 이체하거나, 자산을 조회하는 기능을 가진 챗봇을 가정할게요. alice가 bob에게 500원을 이체해달라는 요청을 챗봇에게 했어요. 이 때 이렇게 그래프의 노드를 정의할 수 있습니다.

  1. alice 의 의도 파악
  2. 얼마의 자산을, 누구에게 이체할지 정보 추출
  3. 서버 api를 사용해서 자산을 실제 전송
  4. alice 에게 전달할 채팅 메시지 생성

이렇게 graph로 코드의 흐름을 표현하면 코드의 실행 흐름을 파악하기가 어려웠어요. 특히 각 노드들 사이에 의존 관계가 있는 경우 이 의존 관계가 잘 정의되어있는지 파악하기 어려웠습니다. 에러 처리를 하는 것도 까다로웠어요. 어떤 에러들이 발생할 수 있는지 알고 잘 처리하려면 사실상 langgraph 소스코드를 읽어야했습니다.

코드흐름을 쉽게 파악하면서 에러처리도 잘 해내려면 if/else/while 을 쓰는 게 더 편하다고 판단했어요. 우리 팀이 구현해야했던 요구사항 정도는 분기와 반복문으로도 충분히 간결하게 표현이 가능했어요. 앞선 단계에서 뒤로 전달해야하는 정보도 함수의 반환값과 인자로 표현할 수 있어 명확했구요. 에러처리도 익숙한 try catch 로 원하는 걸 쉽게 구현할 수 요었습니다.

langgraph 를 벗어나게 된 이유 - 기능 확장의 어려움

채팅 내역을 관리하는 것과 rag(지식 검색)의 확장이 까다로운 것도 langgraph에서 빠져나오게 된 추가적인 이유들이었어요. langgraph에서는 채팅 내역을 자동으로 postgresql 에 저장할 수 있어요. 처음은 쉽지만 조금 쓰다 보면 문제가 생겨요. 계속해서 채팅 내역이 쌓이기 때문에 context 제한을 넘쳐버리는 문제가 생겨요. 히스토리를 삭제하거나 요약해야하는데 이게 꽤 까다로웠습니다. remove message 를 만들어서 langgraph library에 전달해야해요. 그런데 단순하게 history를 지우는 코드를 작성하면 실행시점에 에러가 발생했어요. 항상 같이 지워져야하는 message들이 있었습니다. tool call과 tool call result message 는 항상 동시에 지워져야했어요. 이 중 하나만 지우면 langgraph 에서 나중에 에러가 나구요. 디비에 저장하는 구조도 꽤 복잡한 편이라서 디비 도구로 현재 history를 직접 쿼리하는 것도 어려웠어요. 우리 팀에 필요한 요구사항만 포함하도록 chatting history를 저장하도록 직접 구현하는 게 langgraph를 잘 쓰기 위한 노력보다 쉬웠습니다.

rag 를 확장하는 것도 까다로웠어요. rag 에 신뢰할 수 있는 소수의 정보를 저장할 때는 잘 동작했어요. llm 에 더 많은 정보를 제공하기 위해서 특정 시점에만 유효했던 정보나, 신뢰도가 낮은 정보들을 추가하게 되면서 문제가 어려워졌어요. langgraph가 제공해주는 rag에서는 검색할 때 정보의 출처에 따라 검색 순위를 바꾸거나 시간에 따라 점수를 부여하는 게 까다로웠습니다. 디비에 정보를 저장할 때 추가정보를 저장해야하는데 langgraph에서는 db schema에 json 형태로 meta data를 저장하게 하더라구요. 디비 스키마에 칼럼을 따로 두고 관리하고 싶은 입장에서 꽤 불편했어요. rag 역시 필요한 요구사항만을 지원하는 가벼운 시스템을 직접 구현하는 게 비용이 덜 들겠다는 판단이 들었습니다.

결론

langgraph 는 아무것도 모르는 상황에서 간단한 prototype 을 만들고, 이를 바탕으로 학습을 하는데 큰 도움을 준 도구였습니다. 다만 제가 작업하는 요구사항에 비해 기능이 과다했어요. 필요한 요구사항만을 만족시키는 코드를 작성하는 게 더 쉽고 미래에도 좋겠다는 판단을 했고 langgraph가 해주는 일들을 직접 구현했습니다.


  1. 아래 두 튜토리얼을 따라하고 분석하면서 많은 걸 배웠어요. langgraph의 rag tutorial, langgraph의 run agent tutorial↩︎

  2. rag는 디비에 다양한 참고자료를 저장한 뒤 llm 이 필요할 때 백터 검색으로 찾아다 쓰는 기술을 의미합니다.↩︎