6. RAG 심화 - 실습 추가

이우철·2026년 3월 18일

예제 9: 대화형 지식베이스

# week6_11_interactive_kb.py
from langchain_ollama import OllamaEmbeddings, OllamaLLM
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

class InteractiveKnowledgeBase:
    """대화형 지식베이스"""
    
    def __init__(self):
        self.embeddings = OllamaEmbeddings(model="mxbai-embed-large")
        self.llm = OllamaLLM(model="qwen3:1.7b", temperature=0.5)
        self.vectorstore = None
        self.retriever = None
        self.rag_chain = None
    
    def initialize(self, vectorstore_path="./company_kb"):
        """초기화"""
        # 벡터 저장소 로드
        self.vectorstore = Chroma(
            collection_name="company_knowledge",
            embedding_function=self.embeddings,
            persist_directory=vectorstore_path
        )
        
        # Retriever 설정
        self.retriever = self.vectorstore.as_retriever(
            search_kwargs={"k": 3}
        )
        
        # RAG 체인 구성
        template = """당신은 회사의 AI 어시스턴트입니다.
다음 문서를 참고하여 직원의 질문에 답변하세요.

참고 문서:
{context}

직원 질문: {question}

답변 (친절하고 정확하게):"""
        
        prompt = ChatPromptTemplate.from_template(template)
        
        def format_docs(docs):
            return "\n\n".join(
                f"[{doc.metadata.get('title', '문서')}]\n{doc.page_content}"
                for doc in docs
            )
        
        self.rag_chain = (
            {"context": self.retriever | format_docs, 
             "question": RunnablePassthrough()}
            | prompt
            | self.llm
            | StrOutputParser()
        )
    
    def ask(self, question):
        """질문하기"""
        if not self.rag_chain:
            return "지식베이스가 초기화되지 않았습니다."
        
        # 관련 문서 찾기
        docs = self.retriever.invoke(question)
        
        # 답변 생성
        answer = self.rag_chain.invoke(question)
        
        return {
            "answer": answer,
            "sources": [
                {
                    "title": doc.metadata.get("title", "문서"),
                    "category": doc.metadata.get("category", "미분류")
                }
                for doc in docs
            ]
        }
    
    def chat(self):
        """대화형 인터페이스"""
        print("=== 회사 지식베이스 챗봇 ===")
        print("궁금한 것을 질문하세요. (종료: 'quit')\n")
        
        while True:
            question = input("직원: ")
            
            if question.lower() in ['quit', 'exit', '종료']:
                print("챗봇을 종료합니다.")
                break
            
            result = self.ask(question)
            
            print(f"\nAI: {result['answer']}")
            print(f"\n참고 문서: {', '.join([s['title'] for s in result['sources']])}")
            print("-" * 70 + "\n")

# 사용 예시 (비대화형 테스트)
kb = InteractiveKnowledgeBase()
kb.initialize()

test_questions = [
    "휴가 사용 방법을 알려주세요",
    "재택근무는 어떻게 하나요?",
    "회의실을 예약하고 싶어요"
]

print("=== 자동 테스트 모드 ===\n")
for question in test_questions:
    print(f"질문: {question}")
    result = kb.ask(question)
    print(f"답변: {result['answer']}")
    print(f"출처: {[s['title'] for s in result['sources']]}\n")

결과 :

``

질문: 휴가 사용 방법을 알려주세요
답변: 휴가 사용 방법:
1. 신청 방법:

  • 휴가 사용 전 최소 1일 전에 시스템을 통해 신청해야 합니다.
  • 휴가 종류(연차 휴가)에 따라 부서장 승인이 필요합니다.
  1. 사용 조건:

    • 휴가 사용 시 업무 시작/종료 시 메신저 체크인을 반드시 수행해야 합니다.
    • 휴가 사용 시 회의실 예약을 함께 확인하고, 사용 후 정리가 필요합니다.
  2. 연차 휴가 범위:

    • 입사 1년 미만: 월 1개 휴가
    • 입사 1년 이상: 연 15개 휴가
    • 3년 이상 근속: 2년마다 1개 추가 휴가 (총 2년마다 2개)
  3. 사용 제한:

    • 연말 미사용 시 휴가 소멸됩니다.
    • 휴가 사용 시 노쇼 3회로 인해 예약 제한이 가능합니다.

친절한 안내:
휴가 신청 시 부서장과 미리 확인하고, 사용 전 반드시 메신저 체크인을 완료해 주세요! 😊
출처: ['재택근무 규정', '연차 휴가 정책', '회의실 예약 규정']

질문: 재택근무는 어떻게 하나요?
답변: 재택근무 관련 규정은 현재 제공된 문서에 명시되지 않았습니다. 그러나 일반적인 회사 운영 방침에 따르면 다음과 같은 절차를 따릅니다:

  1. 예약 및 신청

    • 재택근무는 사내 시스템을 통해 신청해야 하며, 최소 1일 전부터 신청이 필요합니다.
    • 부서장 또는 해당 부서의 관리자에게 승인을 받은 후 진행됩니다.
  2. 근무 시간 및 장소

    • 재택근무는 회사 규정에 따라 별도의 시간과 장소가 정해져 있으며, 사내 시스템에서 예약할 수 있습니다.
  3. 추가 사항

    • 재택근무 시 회사 규정에 따라 업무 효율성을 유지하며, 회의실 예약, 개발 환경 설정 등 모든 업무를 원활히 수행할 수 있도록 지원합니다.

문의 사항이면

  • 회사 인사부 또는 개발팀에 문의하여 구체적인 절차를 확인하시기 바랍니다.
  • 재택근무는 회사의 운영 정책에 따라 조정될 수 있으므로, 사전에 협의해주시는 것이 가장 좋습니다.

감사합니다! 🌟
출처: ['회의실 예약 규정', '개발 환경 설정', '연차 휴가 정책']

질문: 회의실을 예약하고 싶어요
답변: 회의실 예약 절차 및 주의사항

  1. 예약 방법:

    • 사내 시스템을 통해 예약합니다.
    • 최소 1시간 전 예약이 필요합니다.
  2. 회의실 종류 선택:

    • 소회의실 (4인): 3개
    • 중회의실 (8인): 2개
    • 대회의실 (20인): 1개
  3. 예약 확인:

    • 먼저 회의실 예약이 가능한지 확인해 주세요.
    • 예약이 없는 경우, 시스템을 통해 신청해야 합니다.
  4. 필요 사항:

    • 회의 시작/종료 시 메신저 체크인
    • 부서장 승인 필수 (예약 시 반드시 요청)
    • 사용 후 정리 필수 (노쇼 3회 시 예약 제한)
  5. 노쇼 정책:

    • 3회 이상 노쇼 시 회의실 예약이 제한됩니다.

참고:

  • 예약 시 전날 오후 5시까지 신청해 주세요.
  • 연말 미사용 시 회의실은 소멸됩니다.

계속적으로 회의실 예약을 원하시면, 부서장과 상담 후 시스템을 통해 예약해 주세요! 😊
출처: ['재택근무 규정', '연차 휴가 정책', '회의실 예약 규정']



예제 10: 문서 업데이트 시스템

# week6_12_update_system.py
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_core.documents import Document
from datetime import datetime

class VersionedKnowledgeBase:
    """버전 관리 지식베이스"""
    
    def __init__(self, persist_directory="./versioned_kb"):
        self.embeddings = OllamaEmbeddings(model="mxbai-embed-large")
        self.persist_directory = persist_directory
        self.vectorstore = None
    
    def initialize(self):
        """초기화"""
        try:
            self.vectorstore = Chroma(
                collection_name="versioned_docs",
                embedding_function=self.embeddings,
                persist_directory=self.persist_directory
            )
        except:
            self.vectorstore = None
    
    def add_document(self, content, metadata):
        """문서 추가 (버전 관리)"""
        # 버전 정보 추가
        metadata["version"] = metadata.get("version", 1)
        metadata["updated_at"] = datetime.now().isoformat()
        
        doc = Document(page_content=content, metadata=metadata)
        
        if self.vectorstore is None:
            # 새로 생성
            self.vectorstore = Chroma.from_documents(
                documents=[doc],
                embedding=self.embeddings,
                collection_name="versioned_docs",
                persist_directory=self.persist_directory
            )
        else:
            # 기존에 추가
            self.vectorstore.add_documents([doc])
        
        return metadata["version"]
    
    def update_document(self, doc_id, new_content, old_version):
        """문서 업데이트"""
        # 새 버전 생성
        new_version = old_version + 1
        
        metadata = {
            "doc_id": doc_id,
            "version": new_version,
            "updated_at": datetime.now().isoformat()
        }
        
        return self.add_document(new_content, metadata)
    
    def get_latest_version(self, doc_id):
        """최신 버전 조회"""
        # 모든 버전 검색
        results = self.vectorstore.similarity_search(
            "", 
            k=100,  # 충분히 큰 수
            filter={"doc_id": doc_id}
        )
        
        if not results:
            return None
        
        # 버전 번호로 정렬
        latest = max(results, key=lambda x: x.metadata.get("version", 0))
        return latest

# 테스트
print("=== 버전 관리 시스템 ===\n")

kb = VersionedKnowledgeBase()
kb.initialize()

# 문서 추가
print("1. 초기 문서 추가")
v1 = kb.add_document(
    "연차는 입사 1년 후 15일 제공됩니다.",
    {"doc_id": "vacation_policy", "version": 1}
)
print(f"   버전 {v1} 생성 완료\n")

# 문서 업데이트
print("2. 문서 업데이트")
v2 = kb.update_document(
    "vacation_policy",
    "연차는 입사 1년 후 15일 제공됩니다. 3년 이상 근속 시 추가 제공됩니다.",
    v1
)
print(f"   버전 {v2} 생성 완료\n")

# 최신 버전 조회
print("3. 최신 버전 조회")
latest = kb.get_latest_version("vacation_policy")
if latest:
    print(f"   버전: {latest.metadata['version']}")
    print(f"   내용: {latest.page_content}")
    print(f"   수정일: {latest.metadata['updated_at']}")

결과 :

  • 핵심 개념 정리
개념설명사용 시점
Embedding텍스트 → 벡터 변환모든 RAG 시스템에서 필수
Chroma DB벡터 저장소문서 저장 및 검색
Similarity Search유사도 기반 검색기본 검색 방식
MMR다양성 고려 검색다양한 결과가 필요할 때
Metadata Filter조건부 검색특정 카테고리만 검색할 때
RAG Chain검색 + 생성 통합QA 시스템 구현 시 활용

성능 최적화 팁
1. 적절한 청크 크기

python
# 문서 길이에 따라 조절
short_splitter = RecursiveCharacterTextSplitter(chunk_size=200)  # 짧은 문서
long_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)  # 긴 문서
  1. 검색 결과 수 조정
python
# k값 조정: 너무 많으면 노이즈, 너무 적으면 정보 부족
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})  # 보통 3-5개
  1. 메타데이터 활용
python
# 카테고리, 날짜, 부서 등으로 사전 필터링
results = vectorstore.similarity_search(
    query, 
    filter={"department": "개발", "year": 2024}
)

FAQ
Q: Embedding 모델을 바꿀 수 있나요?
A: 네, 다른 Ollama 모델이나 OpenAI Embeddings로 변경 가능합니다. 단, 벡터 차원이 달라지면 재구축이 필요합니다.
Q: Chroma DB 대신 다른 벡터 DB를 사용할 수 있나요?
A: 네, FAISS, Pinecone, Weaviate 등 다양한 옵션이 있습니다.
Q: 검색 정확도를 높이려면?
A:

  • 청크 크기 최적화
  • 메타데이터 활용
  • 쿼리 재작성 (질문을 더 구체적으로)
  • Reranking 추가

Q: 대용량 문서는 어떻게 처리하나요?
A: 배치로 나눠서 추가하고, persist_directory로 영속화하세요.

profile
개발 정리 공간 - 업무일때도 있고, 공부일때도 있고...

0개의 댓글