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 |