목차
[LLM 애플리케이션 개발] Haystack으로 사내 지식 검색 챗봇 구축 사례: RAG 파이프라인 개발기
안녕하세요, 13년차 인프라 엔지니어입니다. 요즘 제가 홈랩에서 몰두하고 있는 프로젝트가 하나 있는데, 바로 Haystack을 활용한 사내 지식 검색 챗봇이거든요. 회사에서 일하다 보면 “그 정보 어디 있지?”라며 헤매는 시간이 정말 많지 않으신가요? 저도 처음엔 그랬습니다. 수많은 문서와 슬랙 채널을 뒤지며 시간을 낭비하는 게 비효율적이라고 늘 생각했었거든요.
그러다 “이걸 LLM(Large Language Model, 거대 언어 모델)으로 해결할 수 있지 않을까?”라는 생각이 자꾸만 들었고, 결국 직접 만들어보기로 결심했습니다. 특히 RAG(Retrieval Augmented Generation, 검색 증강 생성) 기법을 쓰면 환각(Hallucination) 없이 정확한 답변을 얻을 수 있다는 점이 마음에 들었죠. 제가 선택한 도구는 Haystack입니다. 지금부터 제가 어떻게 이 프로젝트를 진행했는지, 그리고 어떤 삽질들을 겪었는지 자세히 풀어보겠습니다.
사내 지식 검색 챗봇의 RAG 파이프라인 아키텍처 개념도입니다. 질문이 들어오면 Haystack이 문서를 뒤져서 관련성 높은 정보를 가져오고, 그걸 LLM에 넘겨서 답변을 만들어내는 흐름이죠.
RAG와 Haystack: 왜 이 조합일까요?
먼저 RAG와 Haystack이 정확히 뭔지 짚고 넘어갈게요. 이미 잘 아시는 분도 계시겠지만, 저도 처음엔 개념을 제대로 이해하는 데 시간이 걸렸거든요. LLM 애플리케이션 개발은 빠르게 진화하고 있어서, 기본기를 탄탄히 하는 게 정말 중요하더라고요.
RAG (Retrieval Augmented Generation, 검색 증강 생성)
LLM이 똑똑하긴 하지만, 학습 데이터 시점 이후의 정보나 우리 회사 같은 특정 도메인 지식은 알 수가 없습니다. 그리고 가끔은 환각(Hallucination), 즉 없는 이야기를 지어내기도 하죠. 이 문제를 해결하기 위해 나온 게 RAG입니다. 간단히 말해서, 질문이 들어오면 LLM이 바로 답변하는 게 아니라, 먼저 외부 지식 베이스(우리 회사 문서)에서 관련성 높은 정보를 ‘검색(Retrieval)’해서 가져온 다음, 그 정보를 ‘참고해서(Augmented)’ 답변을 ‘생성(Generation)’하는 방식입니다. 이렇게 하면 LLM이 최신 정보나 특정 도메인 지식에 기반한 정확한 답변을 할 수 있게 되더라고요.
Haystack: RAG 파이프라인을 쉽게 구축하는 프레임워크
RAG 파이프라인을 밑바닥부터 직접 구현하려면 생각보다 복잡합니다. 문서 로딩(Document Loading), 임베딩(Embedding), 검색기(Retriever), 생성기(Generator) 등 여러 컴포넌트를 일일이 연결해야 하거든요. Haystack은 이런 복잡한 과정을 쉽고 유연하게 만들어주는 프레임워크입니다. 다양한 DocumentStore(문서 저장소), Retriever(검색기), Generator(생성기) 컴포넌트를 모듈식으로 제공해서, 마치 레고 블록처럼 조립하듯이 파이프라인을 구축할 수 있어요. 파이썬 기반이라 저 같은 개발자에게는 접근성도 정말 좋습니다.
실전 구현: Haystack RAG 파이프라인 만들기
이제 제가 어떻게 사내 지식 검색 챗봇을 만들었는지 단계별로 보여드리겠습니다. 홈랩에서 직접 해보면서 시행착오도 많았는데, 핵심만 쏙쏙 뽑아서 공유해드릴게요.
1. 환경 구성 및 Haystack 설치
가장 먼저 개발 환경을 설정하고 Haystack을 설치해야겠죠. 저는 Python 가상 환경을 사용해서 의존성 충돌을 방지합니다. 깔끔하게 시작해야 나중에 트러블슈팅이 쉬워거든요.
# 가상 환경 생성 및 활성화
python3 -m venv haystack_env
source haystack_env/bin/activate
# Haystack 및 필요한 라이브러리 설치
# PDF 문서를 처리할 예정이므로 pypdf도 설치합니다.
pip install farm-haystack[pdf,faiss]
# LLM 연동을 위해 OpenAI 라이브러리를 설치합니다.
# 저는 테스트용으로 OpenAI API를 사용했습니다. (실제 운영 시에는 보안 고려 필수!)
pip install openai
참고로 <code>farm-haystack[pdf,faiss]처럼 대괄호 안에 옵션을 넣으면 필요한 컴포넌트들을 한 번에 설치할 수 있어요. FAISS(Facebook AI Similarity Search)를 사용해 빠른 검색을 구현했고, 나중에 스케일 아웃을 고려해서 Elasticsearch도 고려했습니다.
2. 사내 문서 로딩 및 인덱싱
이제 챗봇이 참고할 지식 베이스를 구축해야 합니다. 회사 내부 문서(회의록, 기술 문서, FAQ 등)를 Haystack이 이해할 수 있는 형태로 변환하고 저장하는 과정이죠. 저는 주로 PDF나 텍스트 파일 형태의 문서를 사용했습니다. 여기서 중요한 건 문서 청킹(Document Chunking) 전략입니다. 너무 길면 LLM의 컨텍스트 윈도우(Context Window)를 초과하고, 너무 짧으면 문맥을 놓칠 수 있거든요. 이 부분에서 저도 삽질 좀 했습니다. 처음엔 문서를 통째로 넣었다가 “너무 길어요”라는 LLM의 반응에 깜짝 놀랐죠. ㅎㅎ
import os
from haystack.document_stores import FAISSDocumentStore
from haystack.nodes import TextConverter, PreProcessor
# 문서 저장소 초기화 (FAISS 사용)
# FAISS는 메모리 기반으로 빠르게 유사성 검색을 할 수 있게 해줍니다.
document_store = FAISSDocumentStore()
# 문서 변환 및 전처리 설정
text_converter = TextConverter(remove_numeric_tables=True, valid_languages=["ko"])
preprocessor = PreProcessor(
clean_empty_lines=True,
clean_whitespace=True,
split_by="word",
split_length=500,
split_overlap=50,
split_respect_sentence_boundary=True,
language="ko"
)
# 문서 로딩
from pathlib import Path
DOC_DIR = "data"
if not os.path.exists(DOC_DIR):
os.makedirs(DOC_DIR)
# PDF 파일들을 로드하고 전처리합니다.
doc_files = list(Path(DOC_DIR).glob("*.pdf"))
for file in doc_files:
docs = text_converter.run(file_paths=[str(file)])["documents"]
processed_docs = preprocessor.run(documents=docs)["documents"]
document_store.write_documents(processed_docs)
print(f"총 {document_store.get_document_count()}개의 문서(청크)가 인덱싱되었습니다.")
여기서 중요한 건 PreProcessor의 split_length와 split_overlap 파라미터예요. 이 값을 어떻게 설정하느냐에 따라 검색 결과의 품질이 크게 달라지더라고요. 회사 문서의 스타일이나 평균 문장 길이를 고려해서 여러 번 테스트하고 최적값을 찾는 게 중요합니다. 저도 이 부분에서 여러 번 값을 조절하며 시행착오를 겪었습니다.

Haystack에서 문서들을 로딩하고 인덱싱하는 과정입니다. 원본 문서가 텍스트로 변환되고, 의미 있는 조각(청크)으로 나뉘어 검색 가능한 형태로 저장되는 과정이죠.
3. RAG 파이프라인 구축: 검색기와 생성기의 조립
문서가 준비되었으니, 이제 질문이 들어왔을 때 관련 문서를 찾아주고 답변을 생성해주는 핵심 파이프라인을 만들 차례입니다. Haystack에서는 Pipeline 클래스를 사용해서 컴포넌트들을 연결해요. 저는 BM25Retriever와 PromptNode를 사용했습니다. BM25는 키워드 기반 검색에 강력하고, PromptNode는 LLM을 통합해 강력한 언어 생성 능력을 제공합니다.
from haystack.nodes import BM25Retriever, PromptNode, PromptTemplate
from haystack.pipelines import Pipeline
import os
# 1. Retriever (검색기) 설정
# DocumentStore에서 가장 관련성 높은 문서를 찾아옵니다.
retriever = BM25Retriever(document_store=document_store)
# 2. Generator (생성기) 설정 - LLM 연동
# PromptTemplate에 RAG를 위한 지시사항(프롬프트 엔지니어링)을 추가합니다.
rag_prompt_template = PromptTemplate(
prompt="""Given the following documents, answer the question in Korean.
If you don't know the answer, just say that you don't know, don't make up an answer.
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
Question: {{query}}
Answer:"""
)
# PromptNode를 사용하여 LLM을 통합합니다.
# model_name은 사용하려는 LLM 모델명입니다.
# api_key는 OpenAI API 키를 환경 변수에서 가져옵니다.
prompt_node = PromptNode(
model_name_or_path="gpt-3.5-turbo",
api_key=os.environ.get("OPENAI_API_KEY"),
default_prompt_template=rag_prompt_template,
max_length=500,
model_kwargs={"temperature": 0.1}
)
# 3. RAG 파이프라인 조립
rag_pipeline = Pipeline()
rag_pipeline.add_node(component=retriever, name="Retriever", inputs=["Query"])
rag_pipeline.add_node(component=prompt_node, name="PromptNode", inputs=["Retriever"])
print("RAG 파이프라인이 준비되었습니다!")
PromptTemplate이 정말 중요한데요. LLM이 가져온 문서를 바탕으로 어떤 답변을 해야 할지 지시하는 역할을 하거든요. “주어진 문서에서만 답변해라”, “모르면 모른다고 해라” 같은 지시를 명확히 주면 환각을 최소화할 수 있습니다. temperature 값도 중요한데, 낮을수록 정해진 사실에 기반한 답변을 유도합니다.
⚠️ 삽질 경험과 트러블슈팅: 제가 겪은 문제들
이 과정을 진행하면서 겪었던 몇 가지 삽질과 해결책을 공유합니다. 여러분은 저처럼 같은 실수를 반복하지 않으시길 바라요!
-
문제: “Context window exceeded” 오류
상황: 처음에는PreProcessor에서 문서를 너무 크게 청킹하거나, 청킹을 아예 하지 않고 통째로 LLM에 넘기려 했습니다. 그랬더니 “Context window exceeded” 에러가 나더군요. LLM마다 한 번에 처리할 수 있는 토큰(단어) 수가 정해져 있는데, 이 한도를 초과한 거죠.
해결:PreProcessor의split_length와split_overlap파라미터를 조절했습니다.split_length를 LLM의 컨텍스트 윈도우 한계와 검색할 문서의 양을 고려해서 적절히 줄이고,split_overlap을 설정해 청크 간의 문맥 연결성을 유지했어요. 특히split_respect_sentence_boundary=True옵션을 주면 문장 중간에 잘리는 것을 방지할 수 있습니다. -
문제: “관련 없는 문서 검색” 또는 “빈약한 답변”
상황: 질문에 대한 답변이 엉뚱하거나, 문서에 분명히 내용이 있는데도 “모르겠습니다”라고 하는 경우가 있었습니다. 검색기(Retriever)가 관련성 높은 문서를 제대로 찾아오지 못했거나, 찾아온 문서가 충분히 상세하지 않았기 때문이었죠.
해결:- 검색기 튜닝:
BM25Retriever외에DensePassageRetriever(DPR)나EmbeddingRetriever같은 임베딩 기반 검색기도 시도해봤어요. 특히 DPR은 문맥을 더 잘 이해해서 유사성 높은 문서를 찾아주는 데 효과적이더라고요. 다만, 임베딩 모델 선택과 GPU 자원이 필요할 수 있습니다. - 프롬프트 엔지니어링 강화:
PromptTemplate에 “주어진 문서 외의 정보는 사용하지 마세요”, “간결하게 답변해주세요” 등의 지시를 더 명확하게 추가했어요. - 문서 품질 개선: 결국 지식 베이스가 튼튼해야 합니다. 문서의 내용이 너무 두루뭉술하거나 핵심 정보가 부족하면 아무리 좋은 RAG 파이프라인이라도 좋은 답변을 내기 어렵거든요. 회사 내 문서들을 정리하고 표준화하는 작업도 병행했습니다.
- 검색기 튜닝:
검증 및 결과: 드디어 챗봇과 대화하다!
여러 번의 시행착오 끝에 드디어 만족스러운 답변을 내놓는 챗봇을 만들었어요! 파이프라인이 제대로 작동하는지 확인하는 순간은 정말 짜릿했습니다. 간단한 질문으로 테스트해볼 차례입니다.
# 챗봇에 질문하기
question = "2023년 하반기 사내 워크샵은 언제 어디서 개최되나요?"
result = rag_pipeline.run(query=question, params={"Retriever": {"top_k": 3}})
# 결과 출력
print(f"질문: {question}\n")
print(f"답변: {result['answers'][0].answer}\n")
print("-" * 30)
print("참조 문서:")
for doc in result.get("documents", []):
print(f"- {doc.meta.get('name', '제목 없음')} (ID: {doc.id[:8]}...)")
결과를 보면, LLM이 단순히 질문에 답하는 것을 넘어, 어떤 문서에서 정보를 가져왔는지 참조 문서(Source Documents)까지 알려주더라고요. 이게 바로 RAG의 강력한 장점입니다. 사용자는 답변의 근거를 확인할 수 있어 신뢰도가 높아져요. 실제로 회사 회의록이나 프로젝트 보고서 같은 문서들을 넣어 테스트해보니, 담당자를 일일이 찾지 않아도 필요한 정보를 빠르게 얻을 수 있어서 업무 효율성이 크게 향상될 거 같다는 확신이 들었습니다.

제가 개발한 사내 지식 검색 챗봇의 실제 동작 화면입니다. 질문에 대한 명확한 답변과 함께 어떤 문서에서 정보를 가져왔는지 출처까지 알려줘서 신뢰할 수 있죠.
Retriever 선택 가이드
제가 여러 검색기를 사용해보면서 느낀 점을 바탕으로, 어떤 상황에서 어떤 검색기가 유리한지 정리해봤습니다. 여러분의 환경과 필요에 맞춰 선택하시면 됩니다.
| 검색기 (Retriever) | 장점 | 단점 | 추천 사용 사례 |
|---|---|---|---|
| BM25Retriever | 설치 및 사용이 간편 키워드 매칭에 강력 적은 컴퓨팅 자원 |
의미론적 유사성 파악 어려움 동의어/유의어 처리 한계 |
초기 프로토타입 개발 명확한 키워드 기반 문서 제한된 컴퓨팅 자원 환경 |
| EmbeddingRetriever (DPR 포함) | 의미론적 유사성 검색 가능 문맥 이해도가 높음 질문 의도를 더 잘 파악 |
임베딩 모델 필요 (추가 설치/학습) GPU 또는 고성능 CPU 요구 초기 설정 복잡성 |
고품질 검색 결과 요구 의미가 복잡한 문서 충분한 컴퓨팅 자원 보유 |
저 같은 경우는 초기에는 BM25로 빠르게 시작하고, 성능 개선이 필요할 때 EmbeddingRetriever로 전환하는 전략을 사용했어요. 홈랩에서는 자원 제약이 있을 수 있으니, 이 표를 참고해서 현명한 선택을 하시길 바랍니다.
마무리: RAG 챗봇, 가능성을 엿보다
이번 Haystack 기반 RAG 파이프라인 개발은 저에게 LLM 애플리케이션 개발의 무한한 가능성을 보여준 값진 경험이었습니다. 처음엔 낯설고 복잡하게 느껴졌지만, 하나하나 직접 부딪히고 해결해나가면서 많은 걸 배울 수 있었어요. 특히 사내 지식 검색 챗봇은 업무 효율성을 혁신적으로 높일 수 있는 강력한 도구가 될 거라는 확신이 들었습니다. 물론 아직 개선할 부분은 많습니다. 사용자 인터페이스(UI)를 더 친숙하게 만들고, 검색 정확도를 높이기 위한 지속적인 모델 튜닝, 그리고 보안 강화 같은 과제들이 남아있거든요.
만약 여러분도 회사 내부의 비효율적인 정보 탐색 문제로 고민하고 계신다면, 저처럼 Haystack과 RAG를 활용해 사내 지식 검색 챗봇 구축에 도전해보시길 강력히 추천합니다. 처음엔 삽질 좀 하겠지만, 그 과정에서 얻는 경험과 지식은 분명 여러분의 엔지니어링 역량을 한 단계 끌어올려 줄 겁니다. 다음 글에서는 이 Haystack 챗봇을 Kubernetes 환경에 배포하는 과정이나, 더 다양한 검색기를 통합하는 방법에 대해 다뤄볼 예정이니 기대해주세요!

RAG 파이프라인과 Haystack의 주요 장점들을 요약한 이미지입니다. LLM의 한계를 극복하고 효율적인 애플리케이션 개발을 가능하게 하죠.
![[AI] Haystack으로 사내 지식 검색 챗봇 RAG 파이프라인 구축 사례](https://blog.pswq.net/wp-content/uploads/2026/09/haystack-internal-knowledge-chatbot-case-study-thumbnail.jpg)
![[Cloud] Sentry 메모리 누수 해결: 실전 디버깅 사례 연구](https://blog.pswq.net/wp-content/uploads/2026/08/sentry-production-memory-leak-debugging-case-study-thumbnail.jpg)




![[AI] LangChain으로 AI 에이전트 구축: 복잡한 작업 자동화 실전 가이드](https://blog.pswq.net/wp-content/uploads/2026/05/langchain-ai-agent-build-guide-thumbnail.jpg)
![[AI] vLLM 실전 가이드: 고성능 LLM 추론 및 API 서빙 최적화](https://blog.pswq.net/wp-content/uploads/2026/05/vllm-high-performance-llm-inference-api-serving-guide-thumbnail.jpg)
![[AI] RAG 실전 구현 가이드: LLM 환각 현상 줄이고 최신 정보 활용하기](https://blog.pswq.net/wp-content/uploads/2026/04/rag-implementation-guide-llm-hallucination-reduction-thumbnail.jpg)