
SSAFY에서 AI수업을 들으면서 LangChain과 LangGraph를 배웠다. 이전 프로젝트들을 진행하면서 LangChain은 사용해봤지만 LangGraph는 처음 다뤄봤다.
기존 RAG 챗봇을 만들 때 챗봇의 성능이 잘 안나오면 최종 답변을 받기 전에 query를 분석하는 sub LLM의 결과를 최종 LLM에 넣는 방식을 사용했는데 LangGraph를 사용하면 이를 편리하게 구조화 할 수 있다. 심지어 여러 LLM을 병렬적으로 실행시켜 답변을 얻을 수도 있었다.
이를 예제를 통해 정리해본다.
여러 task를 단일 LLM에서 답변을 얻으려 하다 보면 모두 잘 수행하는 경우도 있지만 몇몇 task를 미흡하게 수행하거나 빼먹는 경우가 종종 있다.
이를 해결하기 위해 최종 답변을 얻기 전 여러 LLM에서 미리 정보를 정리하고 이를 다시 LLM에 넣어 최종 답변을 얻는다.
┌─────────────────────────────────────────────────────────────────────┐
│ LangGraph State 흐름 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [State 객체] │
│ ┌─────────────────────────────────────────────┐ │
│ │ user_request: "3일 서울 여행..." │ │
│ │ plan: "" │ │
│ │ result: "" │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Planner Node │ ─── State 읽기: user_request │
│ │ (계획 수립) │ ─── State 쓰기: plan = "Step 1..." │
│ └─────────────────┘ │
│ │ │
│ ▼ (Edge: planner → worker) │
│ ┌─────────────────┐ │
│ │ Worker Node │ ─── State 읽기: user_request, plan │
│ │ (실행) │ ─── State 쓰기: result = "Day 1: ..." │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ [최종 State] │
│ ┌─────────────────────────────────────────────┐ │
│ │ user_request: "3일 서울 여행..." │ │
│ │ plan: "Step 1: 일정...\nStep 2: 맛집." │ ← Planner 결과 │
│ │ result: "Day 1: 경복궁(09:00)..." │ ← Worker 결과 │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
기존 LLM에서 특정 태스크를 분류해 Node로 구성하고 이를 Edge로 연결하는 구조이다.
그런데 이렇게 여러 노드를 구성하게 되면 결국 LLM을 호출하는 횟수만큼 지연시간도 늘게 되는데 이를 해결할 수 있는 방법은 없을까 라는 의문이 들었다.
이를 해결하기 위해서 LangGraph에서 독립적인 태스크는 병렬화 할 수 있게 해줬다.
사용자 요청
↓
[Supervisor Agent] → 작업 분배 (Fan-out)
↓
┌───────────┬───────────┐
│ Worker A │ Worker B │ ← 병렬 실행
│ (일정담당) │ (예산담당) │
└───────────┴───────────┘
↓
[Supervisor 통합] → 결과 통합 (Fan-in: 모든 Worker 완료 대기)
↓
최종 결과
기존 코드에서는
workflow.add_edge(START, task1)
workflow.add_edge(task1, task2)
workflow.add_edge(task2, END)
였다면
병렬로 한다면 아래처럼 할 수 있다.
workflow.add_edge(START, task1)
workflow.add_edge(START, task2)
workflow.add_edge(task1, supervisor)
workflow.add_edge(task2, supervisor)
workflow.add_edge(supervisor, END)
이렇게 LangGraph의 병렬처리로 여러 LLM의 결과를 종합해 최종적으로 품질 높은 답변을 얻는 방법을 알았다. 여기서 드는 의문점은 GPT같이 API를 호출해서 답변을 얻는 LLM은 토큰량이 많이 드는 문제는 있을 수 있지만 GPU메모리 문제는 없을 것이다. 하지만 로컬 LLM을 사용하는 경우에는 병렬 구조를 사용하는데 제한적이지 않을까 하는 생각이 들었다.