LangGraph와 Langfuse를 활용한 자가 교정(Self-Correction) RAG 파이프라인 설계기
단순한 검색을 넘어, 지식 그래프와 실시간 LLM-as-a-judge를 결합해 스스로 품질을 평가하고 재생성하는 RAG 아키텍처 구축 경험.
최근 실무에서 학습자 진단부터 맞춤형 리포트 생성, 그리고 Q&A까지 이어지는 RAG 파이프라인 POC를 진행했다. 초기에는 단순히 “문서를 찾아서 프롬프트에 넣고 LLM을 호출한다”는 1차원적인 접근으로 시작했지만, 파이프라인이 복잡해질수록 다음과 같은 문제들에 부딪혔다.
- 검색의 한계: 벡터 검색만으로는 “A를 배우기 위해 필요한 선수 지식 B” 같은 명확한 위계 관계를 정확히 가져오지 못했다.
- 오케스트레이션: 여러 단계(진단 → 처방 → 리포트 생성)가 얽히면서, 단순한 체인(Chain) 방식으로는 예외 처리나 분기 로직을 감당하기 어려웠다.
- 품질 보증: LLM이 생성한 리포트나 답변이 “진짜 제공된 컨텍스트에 기반한 것인지(Faithfulness)” 보장할 수 없었다. 눈으로 일일이 확인할 수도 없는 노릇이었다.
이번 포스트에서는 이러한 문제들을 해결하기 위해 지식 그래프(GraphRAG), LangGraph, 그리고 Langfuse를 엮어 어떻게 ‘스스로 평가하고 교정하는’ 파이프라인을 설계했는지 정리해보고자 한다.
1. 전체 아키텍처: 데이터, 오케스트레이션, 그리고 평가의 분리#
파이프라인이 무거워지면서 가장 먼저 한 일은 역할을 명확하게 분리하는 것이었다. 내부 프로젝트의 상세한 도메인 로직은 덜어내고, 시스템의 큰 뼈대만 살펴보면 크게 세 가지 플레인(Plane)으로 나뉜다.
- 데이터 플레인 (Data Plane)
- Neo4j (Knowledge Graph): 개념 간의 위계, 선수 학습 관계 등 엄격한 스키마가 필요한 데이터를 담당한다.
- Weaviate (Vector DB): 교육과정 가이드라인이나 문서의 의미론적(Semantic) 검색을 담당한다.
- 이 둘을 결합해 하이브리드 검색(Graph + Vector)을 수행한다.
- 오케스트레이션 플레인 (App/Logic)
- LangGraph: 전체 워크플로우를 상태(State) 기반의 노드(Node)와 엣지(Edge)로 관리한다. 에러 발생 시의 폴백(Fallback)이나 조건부 분기를 깔끔하게 처리할 수 있다.
- 평가 및 모니터링 플레인 (Observability)
- Langfuse: LLM의 프롬프트 버전 관리, 전체 트레이스 수집, 그리고 실시간 평가(LLM-as-a-judge)를 담당한다.
- (추가적으로 딥러닝 기반의 진단 등 전통적인 ML 모델의 트레이싱은 MLflow로 분리해 역할 충돌을 막았다.)
2. LLM-as-a-judge: 단순한 대시보드를 넘어선 ‘평가 노드’#
어려웠던 부분은 LLM이 생성한 결과물의 품질을 어떻게 통제할 것인가였다. 개발 초기에는 결과가 나온 뒤에 대시보드에서 사후적으로 평가 점수를 확인하는 데 그쳤다. 하지만 우리는 이 평가를 파이프라인의 런타임 안으로 끌고 들어오고 싶었다.
그래서 LangGraph의 워크플로우 안에 evaluate_report(또는 evaluate_answer)라는 별도의 노드를 두었다.
[검색 노드] → [초안 생성 노드(LLM)] → [평가 노드(Langfuse/Judge)] ↓ (점수 미달?) / \ [재생성 노드(수정)] [최종 확정 노드]이 평가 노드는 LLM이 만들어낸 초안과 원본 컨텍스트를 비교하여 Faithfulness 등을 평가한다. 여기서 Langfuse가 핵심적인 역할을 한다. Langfuse 서버에 정의된 Evaluators를 통해 실시간으로 점수를 매기고, 이 점수가 기준치(예: 0.8)를 넘지 못하면 그래프는 다시 regenerate 노드로 돌아간다. 예를 들어, 로컬 모델 특성상 한국어 답변에 영어가 섞여 나오는 고질적인 문제도 언어 평가기를 둬서 실시간으로 필터링하고, “100% 한국어로 다시 작성할 것”이라는 피드백과 함께 재생성 루프를 태울 수 있다. 이러한 최대 1회의 재생성 루프를 통해 할루시네이션이나 내용 누락을 스스로 방어하는 구조다.
3. 안정성을 높이는 다중 검색과 폴백(Fallback) 전략#
LLM의 생성 품질 못지않게 중요한 것이 ‘검색’의 안정성이다. 에이전트가 특정 도구를 선택해 검색을 시도했을 때, 결과가 없거나 LLM이 생성한 검색 키워드가 부정확할 수 있다.
이를 방어하기 위해 에이전트(Tool Calling Agent)에게 다양한 도구를 쥐여주고 우회로를 만들어주었다.
graph_search_tool: 선수 지식이나 명확한 관계가 필요할 때vector_search_tool: 문서 내 구체적인 정의나 가이드가 필요할 때
만약 에이전트가 잘못된 키워드를 추출하여 그래프 탐색 결과가 0건으로 나오는 등 노드가 끊기면, 시스템은 질문 원문을 그대로 벡터화하여 유사도 검색하는 방식 으로 폴백한다. 즉, LLM이 만든 키워드가 틀렸더라도 원래 질문의 의미론적(Semantic) 유사도를 기반으로 검색 복원 경로를 시스템이 스스로 찾아가는 것이다. 이 과정 역시 LangGraph의 엣지 분기 로직과 Langfuse의 트레이스를 통해 촘촘하게 기록된다.
4. Langfuse로 구축한 실무 MLOps 환경#
이번 프로젝트를 통해 Langfuse를 단순한 로깅 툴이 아닌, 프롬프트와 평가 룰을 통합 관리하는 허브로 사용하게 되었다.
- 프롬프트 외부화: 코드 내에 하드코딩하던 프롬프트를 Langfuse로 옮겼다. 코드를 수정하지 않고도 대시보드에서 프롬프트를 개선하고 즉시 배포할 수 있게 되었다.
- 자동화된 채점 (Rules): 데이터셋에 Experiment를 돌리면 사전에 등록해둔 7종의 Judge Evaluator가 자동으로 채점을 수행한다.
- Trace 딥링크: 최종 리포트 메타데이터에 Langfuse의
trace_url을 심어두어, 결과물이 이상할 때 대시보드에서 일일이 찾을 필요 없이 해당 결과물이 생성된 노드별 트레이스 화면으로 즉시 점프할 수 있도록 구성했다.
What I Learned#
- 평가는 모니터링이 아니라 파이프라인의 일부다. 점수를 대시보드에 띄워두고 보는 것보다, 그 점수를 그래프의 상태(State)로 받아 분기 처리에 활용하는 ‘자가 교정(Self-Correction)’ 루프가 훨씬 강력하다.
- 도구와 역할의 분리: 모델 서빙이나 딥러닝 추론은 MLflow로, 복잡한 워크플로우와 단계별 분기 처리는 LangGraph로, LLM 생성 이력과 프롬프트는 Langfuse로 명확히 분리하니 시스템 확장성이 크게 좋아졌다.
- 검색 실패를 가정할 것: 에이전트는 언제든 엉뚱한 키워드를 만들어낼 수 있다. 벡터 진입과 질문 원문 폴백 같은 안전장치는 선택이 아니라 필수다.
결과적으로 지식 그래프의 ‘정확성’과 벡터 검색의 ‘유연성’, 그리고 LangGraph+Langfuse가 제공하는 ‘통제력’이 결합되었을 때 비로소 프로덕션 레벨에 가까운 견고한 파이프라인을 얻을 수 있다는 점을 배울 수 있었다.