같은 RAG 파이프라인, Langflow로 다시 만들어보니 — 코드 없이 되는 것과 안 되는 것

같은 RAG 파이프라인, Langflow로 다시 만들어보니 — 코드 없이 되는 것과 안 되는 것
요약 — 1편에서 LangChain 코드로 만든 사내 매뉴얼 RAG 파이프라인(청킹 → 하이브리드 검색 → 리랭킹)을 Langflow로 그대로 옮겨봤습니다. 결론부터: 파이프라인의 뼈대는 로우코드로 충분히 되지만, dense+sparse 하이브리드 쿼리와 RRF 융합처럼 세밀한 로직은 결국 커스텀 Python 컴포넌트로 빠져나가야 했습니다.

→ AI 에이전트 구현 시리즈 전체 보기

왜 같은 걸 두 번 만들었나

1편에서 만든 파이프라인은 코드로 짜여 있어서, 파라미터 하나 바꾸려면 코드를 열고 수정하고 재배포해야 했습니다. 이 파이프라인을 운영하는 게 저 혼자가 아니라면 — 예를 들어 청킹 크기나 리랭킹 후보 수를 현업 담당자가 직접 튜닝하고 싶어 한다면 — 코드 기반 구조는 접근성이 떨어집니다. Langflow 같은 로우코드 도구로 같은 파이프라인을 다시 만들어보면 이 트레이드오프가 어디서 갈리는지 명확해집니다.

전체 구조 비교

단계 1편 (LangChain 코드) Langflow
인제스천 ConfluenceLoader 직접 호출 Custom Component로 동일 로더 래핑
청킹 RecursiveCharacterTextSplitter Split Text 컴포넌트 (파라미터 동일 매핑)
임베딩(dense) FastEmbed 직접 호출 Embedding Model 컴포넌트
임베딩(sparse) + RRF 융합 커스텀 함수 네이티브 컴포넌트 없음 → Custom Component로 구현
벡터 저장 Qdrant 클라이언트 Vector Store 컴포넌트 (Qdrant 지원)
리랭킹 Cohere/bge-reranker 직접 호출 Reranker 컴포넌트 (지원 프로바이더 한정)
배포 FastAPI로 직접 감쌈 Flow as API로 즉시 엔드포인트 노출

표에서 보이듯 절반은 컴포넌트를 드래그해서 연결하는 것만으로 끝나고, 나머지 절반(특히 하이브리드 검색의 융합 로직)은 결국 코드로 내려가야 했습니다. 아래에서 단계별로 정리합니다.

1. 인제스천 — Custom Component로 기존 로더 재사용

Langflow에는 Confluence 전용 컴포넌트가 기본 제공되지 않습니다. 대신 Custom Component 블록 안에 1편에서 썼던 ConfluenceLoader 호출 코드를 그대로 넣으면 됩니다. Langflow의 커스텀 컴포넌트는 입력/출력 타입만 Langflow 규격(Data, Message 등)에 맞추면 내부 로직은 순수 Python이라, 기존 코드를 거의 그대로 재사용할 수 있었습니다.

from langflow.custom import Component
from langflow.io import Output
from langflow.schema import Data
from langchain_community.document_loaders import ConfluenceLoader

class ConfluenceIngestComponent(Component):
    display_name = "Confluence Ingest"
    outputs = [Output(display_name="Documents", name="docs", method="load_docs")]

    def load_docs(self) -> Data:
        loader = ConfluenceLoader(url=self.confluence_url, token=self.api_token)
        docs = loader.load(space_key=self.space_key, include_attachments=True, limit=50)
        return Data(data={"documents": docs})

증분 동기화(version.when 비교) 로직도 이 컴포넌트 안에 그대로 넣었습니다. Langflow가 대신해주는 건 “이 컴포넌트를 캔버스 위에 올려두고 다음 컴포넌트와 선으로 연결하는 것”뿐, 동기화 전략 자체는 여전히 직접 설계해야 합니다.

2. 청킹 — Split Text 컴포넌트로 파라미터만 노출

이 단계는 로우코드가 확실히 이깁니다. Split Text 컴포넌트는 chunk_size, chunk_overlap, 분할 기준(문자/토큰/구분자)을 UI 필드로 그대로 노출합니다. 1편에서 코드로 넣었던 값(chunk_size=1024, chunk_overlap=128)을 필드에 입력하는 것만으로 끝났습니다.

여기서 얻는 실무적 이득은 값 하나 바꿀 때마다 배포 파이프라인을 돌릴 필요가 없다는 점입니다. 현업 담당자가 “요즘 답변이 문맥을 놓친다”고 하면, 개발자 없이도 캔버스에서 chunk_overlap 값을 올려보고 바로 테스트할 수 있습니다.

3. 임베딩과 하이브리드 검색 — 여기서 로우코드의 한계가 드러남

Dense 임베딩은 Embedding Model 컴포넌트로 문제없이 처리됩니다. 문제는 sparse 임베딩과 RRF(Reciprocal Rank Fusion) 융합입니다.

Langflow의 Vector Store 컴포넌트는 dense 벡터 검색을 전제로 설계돼 있고, dense+sparse를 동시에 질의해서 RRF로 합치는 하이브리드 검색은 네이티브 컴포넌트로 노출돼 있지 않았습니다. 일부 벡터 DB 연동 컴포넌트(예: Astra DB)는 하이브리드 검색 + 리랭커 조합을 사전 설정으로 제공하지만, Qdrant 기준으로는 1편에서 쓴 RRF 융합 쿼리를 Custom Component로 다시 넣어야 했습니다.[1]

class HybridSearchComponent(Component):
    display_name = "Hybrid Search (Dense+Sparse RRF)"

    def search(self) -> Data:
        dense_hits = self.qdrant.query(collection, using="dense", query=dense_vec, limit=50)
        sparse_hits = self.qdrant.query(collection, using="sparse", query=sparse_vec, limit=50)
        fused = reciprocal_rank_fusion(dense_hits, sparse_hits, k=60)
        return Data(data={"results": fused[:10]})

즉 “하이브리드 검색이 필요 없는 단순 RAG”라면 Langflow 기본 컴포넌트만으로 충분하지만, 1편에서 다룬 수준의 정교한 검색을 그대로 옮기려면 결국 코드 한 조각은 남습니다. 로우코드 도구가 없애주는 건 보일러플레이트(연결, 로깅, API 서빙)지, 검색 품질을 결정하는 핵심 로직이 아니라는 걸 이 단계에서 체감했습니다.

4. 리랭킹 — 컴포넌트는 있지만 프로바이더가 제한적

Reranker 컴포넌트는 존재하지만, 지원하는 리랭커 프로바이더 목록이 정해져 있습니다. 1편에서 쓴 오픈소스 bge-reranker를 자체 호스팅해서 쓰려면 이것도 Custom Component로 HTTP 호출을 감싸야 했습니다. Cohere처럼 이미 지원 목록에 있는 프로바이더를 쓴다면 이 단계는 필드 몇 개 채우는 것으로 끝납니다 — 즉 “어떤 리랭커를 쓰느냐”가 로우코드 여부를 가르는 실질적 기준이었습니다.

5. Flow as API — 배포는 확실히 편해진다

파이프라인을 다 연결한 뒤 Flow as API 기능으로 즉시 REST 엔드포인트를 얻었습니다.[2] 1편에서는 FastAPI로 직접 서버를 감싸는 코드를 따로 작성했는데, 이 부분은 Langflow가 확실히 시간을 아껴줬습니다. 사내 챗봇 UI나 Slack 봇에서 이 엔드포인트를 호출하기만 하면 되므로, “파이프라인을 프로덕션에 연결하는” 마지막 단계는 로우코드 쪽이 명백히 유리했습니다.

코드 vs 로우코드, 언제 뭘 쓸까

기준 LangChain 코드 Langflow
검색 로직이 이미 정교하게 정해져 있음(하이브리드+RRF 등) 적합 커스텀 컴포넌트로 우회 필요
파라미터를 비개발자가 자주 튜닝해야 함 배포 필요 캔버스에서 즉시 조정
빠른 프로토타입, PoC 초기 설정 부담 빠름
API 서빙까지 직접 설계·통제하고 싶음 적합 Flow as API로 충분한 경우가 많음
버전 관리(Git diff로 리뷰) 자연스러움 Flow 파일(JSON) diff는 가독성이 떨어짐

자주 하는 실수

  • 모든 로직을 로우코드로 밀어붙이려 하기: 하이브리드 검색처럼 이미 코드로 검증된 로직이 있다면, 억지로 네이티브 컴포넌트에 끼워 맞추기보다 Custom Component로 그대로 옮기는 게 낫습니다.
  • Flow JSON을 형상관리 없이 UI에서만 관리하기: Langflow의 Flow는 JSON으로 export할 수 있습니다. 이걸 Git에 커밋해두지 않으면, 누가 캔버스에서 뭘 바꿨는지 추적할 방법이 없습니다.
  • 프로바이더 지원 여부를 먼저 확인하지 않고 설계하기: 리랭커·벡터DB처럼 “이미 통합돼 있는지”가 작업량을 크게 좌우하는 컴포넌트는, 파이프라인을 그리기 전에 지원 목록부터 확인하는 게 시간을 아낍니다.

정리

  • 파이프라인의 뼈대(인제스천 → 청킹 → 임베딩 → 검색 → 리랭킹 → 생성)는 Langflow 컴포넌트로 대부분 표현됩니다.
  • 다만 1편 수준의 정교한 하이브리드 검색·RRF 융합은 네이티브 컴포넌트만으로는 안 되고, Custom Component(Python)로 빠져야 했습니다.
  • 반대로 API 배포는 Langflow가 확실히 더 빠릅니다.
  • 결론: “검색 로직은 코드로 확정하고, 그 위의 조립·운영·배포는 로우코드로” 가져가는 하이브리드 구성이 가장 현실적이었습니다.

참고자료

질문이나 지적할 부분이 있으면 문의로 알려주세요.

Questions or corrections? Let us know via Contact.

AI

AI map Ontology

기업 IT·데이터 조직에서 20년 넘게 실무를 해온 사람이 씁니다. 모든 사례는 익명화·일반화합니다. 소개 보기 →

AI

AI map Ontology

Written by someone with 20+ years in enterprise IT and data. All cases are anonymized and generalized. About us →

다음으로 읽어볼 글

개념을 이해했다면, 실제 설계와 활용 방법을 이어서 살펴보세요.

온톨로지 Foundry AIP 기업 AI 전략

Keep reading

Once you understand the concept, continue on to real design and usage patterns.

Ontology Foundry AIP Enterprise AI Strategy