LangChain의 이해 – RAG

나에게 맞는 도구 고르기: Deep Agents vs. LangChain vs. LangGraph

프로젝트의 복잡도에 따라 어떤 도구를 쓸지 선택해야 합니다. 대형 마트의 요리 코너에 비유할 수 있습니다.

[1] Deep Agents: “몸만 오면 되는 밀키트 패키지”

  • 설명: 자동 문맥 압축, 가상 파일 시스템, 부하 에이전트(조수 로봇) 생성 등 강력한 기능들이 이미 다 완성되어 들어있는 ‘풀패키지’ 에이전트입니다.
  • 추천: 처음부터 복잡한 기능을 직접 만들기 귀찮고, 이미 다 완성된 고성능 로봇이 필요할 때 사용합니다. (LangChain 기반으로 만들어졌습니다.)

[2] LangChain (create_agent): “내 입맛대로 만드는 커스텀 요리”

  • 설명: 나의 데이터와 상황에 딱 맞게 장비 수트(Harness)를 직접 조립하는 방식입니다.
  • 추천: AI가 내 데이터(사내 문서 등)를 읽고 내가 원하는 도구만 쏙쏙 골라 쓰게 만들고 싶을 때 사용합니다. 자유도가 높습니다.

[3] LangGraph: “복잡한 공장의 자동화 컨베이어 벨트”

  • 설명: 정해진 순서대로 움직이는 작업(순서도)과 AI가 스스로 판단해서 움직이는 작업(에이전트)을 복잡하게 엮어주는 정밀 제어 프레임워크입니다.
  • 추천: AI가 일하다가 막히면 “뒤로 돌아가서 다시 해!”라고 명령하거나, 여러 명의 AI가 협업하는 복잡한 미로 같은 시스템을 설계할 때 사용합니다.

[4] 랭스미스(LangSmith): “AI 전용 CCTV 모니터링 룸”

AI 로봇이 일을 시작하면 내부에서 무슨 생각을 하고 어떤 도구를 쓰는지 눈에 보이지 않습니다. 이때 LangSmith를 사용합니다.

  • 추천: 에이전트가 작동하는 과정을 추적(Trace)하고, 에러를 잡고(Debug), 얼마나 잘하는지 평가(Evaluate)하는 도구입니다.
  • 엔진(Engine) 기능: CCTV처럼 지켜보기만 하는 것을 넘어, 문제가 생기면 이를 감지하고 해결책까지 제안해 줍니다.

VS 코드 단축키

alt + shift + 방향키 아래 : 복사

ctrl +. => 임포트 자동

Tool 맛보기 — LLM이 직접 호출하는 함수 (20분)

LLM은 학습 데이터 안에서만 답할 수 있는 닫힌 상자입니다. Tool은 LLM이 외부 세계와 상호작용하게 해주는 함수입니다.

오늘은 “모델이 도구 호출을 결정하는 것”까지만 봅니다. 실제로 도구를 실행하고 결과를 다시 모델에 돌려주는 루프는 LangGraph로 일반적으로 합니다.

 RAG 4단계 흐름

단계무엇을어떤 도구
1. 준비내 문서들을 잘게 쪼개서 → 숫자(벡터)로 바꿔 → 창고에 넣기TextSplitter / Embeddings / VectorStore
2. 질문사용자 질문도 같은 방식으로 숫자화같은 Embeddings
3. 검색창고에서 질문 벡터와 가장 비슷한 문서 N 개 찾기Retriever (= VectorStore + 검색 함수)
4. 답변“이 문서를 참고해서 답해줘” 하고 LLM 에 넣기LCEL 체인

  • Document : 문서 1개 (str + metadata)
  • Chunk : 문서를 잘게 쪼갠 조각 (LLM context 에 한 번에 들어가야 하니까 작게)
  • Embedding : 텍스트를 의미 벡터로 변환한 것 (예: 1536차원 float)
  • Vector Store : 청크 + 임베딩을 저장·검색하는 DB (Chroma, FAISS, Pinecone 등)
  • Retriever : 질문 → 관련 청크 N 개 반환하는 인터페이스

4-3. 청킹 (Chunking) — 왜 잘게 쪼개는가?

LLM 의 context window 는 무한이 아닙니다. 그리고 너무 긴 문서를 통째로 넣으면 관련 없는 부분까지 끼어서 노이즈 가 됩니다. 그래서 문서를 잘게 쪼갭니다.

왜 Recursive? — 문장·단락 경계를 우선해서 자르고, 그 이상 못 자를 때만 글자 단위로. 의미가 덜 깨집니다.

chunk_overlap 의 역할 — 청크 끝부분이 다음 청크 앞부분과 겹쳐서, 경계에서 잘릴 만한 정보를 보존.

import math
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings

load_dotenv()

# OpenAI의 표준 임베딩 모델 사용
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

def cosine_similarity(a, b):
    """두 벡터의 코사인 유사도를 계산하는 함수"""
    dot_product = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(x * x for x in b))
    if norm_a == 0 or norm_b == 0:
        return 0.0
    return dot_product / (norm_a * norm_b)

def demo_embedding_v2():
    """문장 기반 임베딩을 통한 유사도 비교 검증"""
    
    # 평가 대상 문장 쌍 정의 (문맥이 포함된 문장으로 구성)
    pairs = [
        # 1. 표현은 다르지만 의미가 유사한 한글 문장 (높은 유사도 기대)
        ("오늘 연차 휴가 신청하려고 합니다.", "나 오늘 회사 쉬어도 되나요?"),
        
        # 2. 동일한 맥락을 가진 한영 번역 문장 (높은 유사도 기대)
        ("저는 파이썬으로 인공지능을 개발합니다.", "I develop artificial intelligence using Python."),
        
        # 3. 사용된 단어는 일부 겹치나 주제가 전혀 다른 문장 (낮은 유사도 기대)
        ("오늘 연차 휴가 신청하려고 합니다.", "사내 보안 시스템 비밀번호 변경 정책 안내"),
    ]
    
    print("문장별 코사인 유사도 분석 결과:")
    print("-" * 60)
    
    for a, b in pairs:
        # 텍스트를 고차원 벡터로 변환
        vector_a = embeddings.embed_query(a)
        vector_b = embeddings.embed_query(b)
        
        # 유사도 계산
        score = cosine_similarity(vector_a, vector_b)
        print(f"문장 A: {a}")
        print(f"문장 B: {b}")
        print(f"-> 유사도 점수: {score:.3f}")
        print("-" * 60)

if __name__ == "__main__":
    demo_embedding_v2()

from langchain_chroma import Chroma
from langchain_core.documents import Document


def demo_vectorstore():
    """청크 + 임베딩을 Chroma 에 저장 → 검색까지."""
    docs = [
        Document(page_content="연차 휴가는 연 15일 부여됩니다.", metadata={"source": "hr"}),
        Document(page_content="비밀번호는 12자 이상이며 90일마다 변경합니다.", metadata={"source": "it"}),
        Document(page_content="출장비는 영수증 첨부 후 7일 내 정산합니다.", metadata={"source": "expense"}),
        Document(page_content="신입사원은 4주 온보딩을 거칩니다.", metadata={"source": "onboarding"}),
        Document(page_content="프로젝트 예산 변경은 매니저 승인이 필요합니다.", metadata={"source": "project"}),
    ]

    db = Chroma.from_documents(
        documents=docs,
        embedding=embeddings,
        collection_name="day1_demo",
    )

    # 의미 검색 — 키워드가 정확히 안 맞아도 찾아짐!
    for q in ["월차 며칠?", "비밀번호 룰", "예산 변경하려면?"]:
        hits = db.similarity_search(q, k=1)
        print(f"\n질문: {q}")
        for h in hits:
            print(f"  [{h.metadata['source']}] {h.page_content[:50]}")

demo_vectorstore()

 코드 해설:

  • Chroma.from_documents() — Document 리스트를 받아 임베딩하고 인메모리 DB에 저장합니다.
  • similarity_search(q, k=2) — 질문과 가장 가까운 청크 2개를 반환합니다.
  • "월차 며칠?" 로 검색해도 "연차 휴가는 연 15일..." 이 나옵니다 — 의미 검색의 위력.
  • persist_directory 옵션을 주면 디스크에 저장되어, 다음 실행시 다시 임베딩 안 해도 됩니다.
상황우리 체인 동작이상적 동작
검색 결과가 질문과 무관그냥 답변 (할루시네이션 가능)다시 검색 / 거부
답변이 문서에 근거 안 함그래도 답변 출력환각 체크 후 재시도
모호한 질문 (“그거 알려줘”)검색 결과 안 좋아도 답함질문 재작성 후 재검색
외부 정보 필요 (사내 문서에 없음)그냥 “모릅니다”웹 검색 fallback

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다