Study Archives/Deep Learning

LangChain, RAG, Agent

ns4A 2026. 3. 21. 01:05

LangChain, RAG, Agent란 무엇일까

처음 보는 사람도 이해할 수 있게 한 번에 정리해보기

LLM을 처음 접하면 비슷해 보이는 단어들이 한꺼번에 쏟아집니다.
LangChain, RAG, Agent, Memory, Retriever, Tool, Vector DB 같은 말들입니다.
이름만 보면 전부 비슷비슷해 보이는데, 실제로는 역할이 조금씩 다릅니다. LangChain은 LLM 애플리케이션을 조립하기 위한 프레임워크에 가깝고, 그 안에서 프롬프트, 대화 기록, 문서 검색, 외부 도구 호출 같은 구성 요소를 연결할 수 있습니다. 또 검색 기반 응답을 만드는 RAG, 도구를 선택해 행동을 수행하는 Agent 같은 구조도 함께 다룰 수 있습니다.

처음에는 이런 의문이 생깁니다.

“그냥 LLM API에 질문만 보내면 되는 것 아닌가?”
“RAG는 챗봇 이름인가?”
“Agent는 그냥 더 똑똑한 챗봇인가?”

그런데 조금만 더 들여다보면, 이 셋은 서로 경쟁하는 개념이 아니라 서로 다른 층위에서 역할을 나눠 가진 개념에 가깝습니다.
LLM이 답을 만들어내는 두뇌라면, LangChain은 그 두뇌를 여러 부품과 연결해 실제 서비스처럼 동작하게 만드는 틀이고, RAG는 외부 문서를 찾아와 답변에 반영하는 방식이며, Agent는 문제를 보고 어떤 도구를 어떤 순서로 쓸지 결정하는 실행 방식이라고 보는 편이 이해가 쉽습니다.

이 글에서는 이 셋을 따로따로 외우기보다, 왜 이런 구조가 필요해졌는지부터 차근차근 살펴볼겁니다.


먼저, 왜 LLM만으로는 부족할까

LLM은 정말 강력합니다. 질문을 던지면 설명을 해주고, 요약도 해주고, 코드도 짜줍니다.
그런데 실제 서비스를 만들기 시작하면 생각보다 빨리 한계가 드러납니다.

예를 들어 사용자가 이런 질문을 한다고 해보겠습니다.

“우리 팀 내부 위키를 참고해서 배포 절차를 알려줘.”
“방금 전에 내가 말한 프로젝트 이름 기억하고 이어서 답해줘.”
“필요하면 계산기도 쓰고, 문서도 검색해서 가장 그럴듯한 답을 내줘.”

이때 기본 LLM은 보통 세 가지를 스스로 해결하지 못합니다.
첫째, 사내 문서처럼 외부에 있는 최신 정보를 기본적으로 알지 못합니다. 둘째, 이전 대화를 길게 이어가려면 대화 기록을 별도로 관리해야 합니다. 셋째, 검색기나 계산기처럼 외부 도구를 호출하는 행동은 별도의 연결 구조가 필요합니다. 그래서 실제 애플리케이션에서는 입력 구성, 모델 호출, 출력 정리, 기록 관리, 검색, 도구 호출 같은 흐름을 체계적으로 묶어주는 계층이 필요해집니다.

바로 이 지점에서 LangChain이 등장합니다.



LangChain은 무엇을 해주는 프레임워크일까

LangChain을 아주 단순하게 말하면, LLM 주변의 부품들을 하나의 애플리케이션으로 엮어주는 연결 프레임워크입니다. 특정 모델 하나에만 묶여 있는 구조가 아니라, 비교적 공통된 방식으로 프롬프트, 모델, 출력 파서, 대화 이력, 문서, 임베딩, 벡터 저장소, 리트리버, 툴 등을 연결할 수 있게 설계되어 있습니다. 핵심 계층으로는 Core, 상위 LangChain 계층, Community, 파트너 패키지 구성이 제시되며, 이 안에서 체인, RAG, Agent 같은 구조를 조합할 수 있습니다.

초심자 관점에서는 이렇게 이해하면 편합니다.

LangChain은 “새로운 AI 모델”이 아닙니다.
오히려 모델을 잘 활용하기 위한 배선함에 가깝습니다.

딥러닝에서도 순수 텐서 연산만으로 모든 실험을 짜는 대신, 데이터 로딩, 모델 구성, 학습 루프, 로그 관리 같은 것을 프레임워크로 정리하곤 합니다. LangChain도 비슷합니다. LLM 애플리케이션에서 반복되는 구조를 컴포넌트로 정리하고, 그것들을 조립 가능한 형태로 제공하는 것입니다.

이 프레임워크를 이해할 때 가장 먼저 기억하면 좋은 흐름은 아주 단순합니다.

입력을 만든다 → 모델에 보낸다 → 결과를 다듬는다

문서에서는 이를 모델 입출력 관점의 단계로 제시하고 있고, 실제로도 LangChain 코드를 읽다 보면 대부분 이 흐름을 따릅니다. 프롬프트 템플릿으로 입력을 정리하고, chat model 또는 LLM에 보내고, output parser로 결과를 후처리합니다.


가장 작은 LangChain 예제부터 보자

처음부터 RAG나 Agent로 가면 조금 벅찹니다.
그래서 제일 작은 단위부터 보는 것이 좋습니다.

아래 코드는 “질문을 받아 설명을 생성하는 아주 작은 체인”입니다.

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

model = ChatOpenAI(model="gpt-4o-mini", api_key="YOUR_API_KEY")

prompt = ChatPromptTemplate.from_messages([
("system", "너는 초보 개발자에게 비유를 섞어 설명하는 튜터다."),
("human", "{topic}을 6문장 안에 쉽게 설명해줘.")
])

chain = prompt | model | StrOutputParser()

result = chain.invoke({"topic": "벡터 데이터베이스"})
print(result)
 

이 코드에서 핵심은 복잡한 문법이 아니라 역할 분리입니다.

prompt는 입력 문장을 만드는 역할을 합니다.
model은 실제 LLM을 호출합니다.
StrOutputParser()는 모델이 돌려준 결과를 우리가 쓰기 쉬운 문자열 형태로 정리합니다.

이 세 개를 | 로 이어붙인 것이 바로 작은 체인입니다. LangChain에서는 이런 식으로 컴포넌트를 연결해 하나의 실행 흐름을 만듭니다. 또한 Runnable 계층에서는 invoke, stream, batch, 비동기 호출 같은 방식으로 이 흐름을 실행할 수 있습니다.

딱 봐도 장점이 있습니다.
프롬프트를 바꾸고 싶으면 prompt만 바꾸면 되고, 모델을 바꾸고 싶으면 model만 교체하면 됩니다. 출력 형식을 JSON으로 바꾸고 싶다면 parser를 바꾸면 됩니다. 작은 예제인데도 이미 “조립형 구조”의 장점이 보이기 시작합니다.


프롬프트 엔지니어링은 왜 따로 중요한가

초심자 입장에서 프롬프트 엔지니어링은 조금 과장된 단어처럼 느껴질 수 있습니다.
그냥 질문 잘하는 법 아닌가 싶기도 합니다.

그런데 실제로는 꽤 중요합니다. 프롬프트는 모델의 입력 그 자체이고, 원하는 답을 얻기 위해 입력을 더 잘 설계하는 과정이 성능과 결과 형식에 큰 영향을 줍니다. 특히 원하는 스타일, 출력 형식, reasoning 유도, 예시 제공 같은 요소가 여기에 들어갑니다. LangChain은 이를 위해 문자열 기반 프롬프트, 메시지 기반 프롬프트, 메시지 placeholder, few-shot 예시 선택 같은 여러 도구를 제공합니다.

예를 들어 이런 차이가 있습니다.

  • “RAG 설명해줘”
  • “너는 초보자를 위한 기술 블로그 작성자다. RAG를 비유 1개와 함께 설명하고, 마지막에 3줄 요약을 붙여줘”

두 질문은 모델에게 완전히 다른 작업 지시를 줍니다.
둘 다 맞는 질문이지만, 두 번째가 보통 더 일관된 결과를 만듭니다.

LangChain에서 프롬프트를 다루는 방식은 크게 두 가지로 생각하면 됩니다.
하나는 문자열 템플릿, 다른 하나는 메시지 템플릿입니다. 문자열 템플릿은 단순한 작업에 적합하고, 메시지 템플릿은 system / human / ai 역할을 분리해 대화형 구조를 설계하기 좋습니다. 여기에 MessagesPlaceholder를 넣으면 이전 대화나 예시 메시지를 중간에 자연스럽게 삽입할 수 있습니다. 또 few-shot prompting에서는 여러 예시 중 일부를 선택해 넣는 구조도 지원합니다.

예를 들어, 출력 형식을 강하게 통제하고 싶다면 이런 식으로 작성할 수 있습니다.

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o-mini", api_key="YOUR_API_KEY")

prompt = ChatPromptTemplate.from_messages([
("system",
"너는 문장 분석기다. 답변은 반드시 다음 형식을 지켜라.\n"
"1) 핵심 주제\n2) 한 줄 요약\n3) 쉬운 비유"),
("human", "{sentence}")
])

response = model.invoke(
prompt.format_messages(
sentence="RAG는 외부 문서를 찾아서 답변 품질을 높이는 방식이다."
)
)

print(response.content)

이 예제는 단순하지만 중요한 사실을 보여줍니다.
LLM은 단순히 “무엇을 묻느냐”뿐 아니라, 어떻게 시키느냐에 따라 결과가 꽤 달라집니다.


 

 

Memory는 “모델이 기억한다”는 착각을 정리해준다

처음 챗봇을 만들 때 많은 사람이 한 번쯤 착각합니다.

“방금 대화했으니 모델이 당연히 기억하겠지?”

실제로는 그렇지 않은 경우가 많습니다. LLM은 기본적으로 사람처럼 장기 기억을 가진 존재가 아니라, 현재 입력에 포함된 문맥을 바탕으로 답합니다. 그래서 이전 대화를 기억한 것처럼 보이려면, 필요한 기록을 다시 불러와 입력에 포함시키는 관리 방식이 필요합니다. 문서에서도 LLM은 기본적으로 기억 기능을 갖고 있지 않고, 기억 정보를 함께 넣어줘야 문맥을 파악할 수 있다고 설명합니다.

LangChain에서는 대화 이력을 단순히 메시지 리스트로 직접 넣을 수도 있고, ChatMessageHistory 같은 저장 클래스로 관리할 수도 있습니다. 더 나아가 RunnableWithMessageHistory를 쓰면 모델을 호출하기 전에 해당 세션의 기록을 불러오고, 응답 후에는 다시 저장하는 흐름을 자동화할 수 있습니다. 이때 session_id를 구분자로 써서 사용자별 또는 대화방별로 기록을 나누어 관리할 수 있습니다.

아래 코드는 공부용 챗봇이 사용자의 이전 질문을 이어받아 답하도록 만드는 예시입니다.

 
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

def get_history(session_id: str):
return SQLChatMessageHistory(
session_id=session_id,
connection_string="sqlite:///study_chat.db"
)

model = ChatOpenAI(model="gpt-4o-mini", api_key="YOUR_API_KEY")

prompt = ChatPromptTemplate.from_messages([
("system", "너는 파이썬을 처음 배우는 사람을 돕는 튜터다."),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{question}")
])

chain = prompt | model | StrOutputParser()

chatbot = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="question",
history_messages_key="chat_history",
)

print(
chatbot.invoke(
{"question": "내 이름은 현우야. 파이썬 공부를 시작했어."},
config={"configurable": {"session_id": "user-1"}}
)
)

print(
chatbot.invoke(
{"question": "방금 내가 말한 이름이 뭐였지?"},
config={"configurable": {"session_id": "user-1"}}
)
)

이 코드의 포인트는 어렵지 않습니다.
모델이 무언가를 “기억하는 척” 하는 것이 아니라, 이전 대화가 다시 들어가기 때문에 이어지는 것입니다.

이 차이를 이해하면, 왜 세션 분리와 기록 저장 구조가 중요한지 바로 보입니다. 사용자 A의 기록과 사용자 B의 기록이 섞이면 엉뚱한 답이 나올 수 있기 때문입니다. 실제 서비스에서는 이 부분이 단순 편의 기능이 아니라 꽤 중요한 품질 관리 포인트가 됩니다.


이제 RAG로 넘어가보자

왜 문서를 찾아와서 답해야 할까

RAG는 Retrieval Augmented Generation의 줄임말입니다. 이름이 조금 길어서 어렵게 느껴지지만, 뜻은 생각보다 직관적입니다. 필요한 정보를 먼저 찾아온 다음, 그 정보를 바탕으로 답을 생성하는 방식입니다. Retrieval은 이미 존재하는 정보나 기억을 찾아오는 과정이고, RAG는 외부 데이터를 검색해 LLM에 전달한 뒤 그 내용을 바탕으로 응답을 만들게 하는 구조입니다.

왜 이런 방식이 필요할까요.

가장 흔한 이유는 세 가지입니다.
하나는 모델이 학습한 시점 이후의 최신 정보가 필요할 때입니다.
다른 하나는 모델 내부에 넣어둘 수 없는 민감한 내부 문서를 다뤄야 할 때입니다.
마지막은 웹 문서나 사내 파일처럼 외부에 있는 자료를 지금 이 질문에 맞게 끌어와야 할 때입니다. 이런 이유 때문에 Retrieval이 LLM 시스템에서 중요한 역할을 합니다.

조금 쉽게 비유해보겠습니다.

LLM만 단독으로 쓰는 것은 기억력은 좋지만 참고서를 펼쳐보지 않는 학생에 가깝습니다.
반면 RAG는 문제를 받으면 먼저 책갈피를 찾아 관련 페이지를 펼쳐본 뒤 답하는 학생에 가깝습니다.

그래서 RAG의 핵심은 생성 자체보다, 어떤 문서를 어떻게 준비하고 어떻게 꺼내오느냐에 있습니다.


RAG는 보통 두 단계로 이해하면 편하다

RAG는 크게 보면 두 단계입니다.

첫 번째는 인덱싱 단계입니다.
문서를 가져오고, 적당한 크기로 자르고, 임베딩 벡터로 바꾸고, 벡터 저장소에 넣습니다. 문서에서는 이 과정을 load, split, embed, store 흐름으로 설명합니다. 큰 텍스트를 chunk로 나누는 이유는 인덱싱에도 유리하고, 모델 입력 길이 관점에서도 유리하기 때문입니다. 문서 로더, 텍스트 스플리터, 임베딩 모델, 벡터 스토어, 리트리버가 이 단계와 직접 연결됩니다.

두 번째는 질문-응답 단계입니다.
사용자 질문이 들어오면 관련성이 높은 chunk를 찾아오고, 그 내용을 질문과 함께 모델에 넣어 답을 생성합니다. 문서에서는 retrieval과 generation을 분리해 설명하고, 검색 결과를 필터링하고 랭킹한 뒤 응답 생성에 반영하는 흐름을 제시합니다.

이걸 실제 업무 상황으로 바꾸면 대략 이런 그림입니다.

  • 사내 규정 PDF를 미리 잘게 나눠 저장해둔다.
  • 사용자가 “재택근무 신청 절차 알려줘”라고 묻는다.
  • 관련 문단 몇 개를 검색해 가져온다.
  • 모델은 그 문단을 참고해 답한다.

이 구조가 중요한 이유는, 모델이 막연한 상식으로 대답하는 것이 아니라 검색된 근거를 중심으로 말하게 만들 수 있기 때문입니다. 물론 RAG가 자동으로 항상 정답을 보장하는 것은 아니지만, 적어도 “무엇을 근거로 답하고 있는가”를 훨씬 잘 통제할 수 있습니다.



RAG에서 자주 나오는 용어를 헷갈리지 말자

처음 보면 retriever, vector store, embedding이 전부 섞여 보입니다.
그런데 역할을 나눠 보면 생각보다 단순합니다.

Embedding은 텍스트를 숫자 벡터로 바꾸는 과정입니다.
Vector store는 그 벡터들을 저장하고 유사도 검색을 도와주는 저장소입니다.
Retriever는 사용자의 질문을 받아 관련 문서를 찾아 반환하는 인터페이스입니다. 문서에서도 retriever는 쿼리를 받아 document를 반환하는 더 일반적인 개념으로 설명되고, vector store는 유사도 기반 검색과 저장 기능을 담당하는 요소로 소개됩니다. 또 리트리버는 vector DB 기반 방식뿐 아니라 multi-query 같은 검색 전략도 가질 수 있습니다.

이 셋을 요리 비유로 바꾸면 더 쉽습니다.

  • 임베딩은 재료를 같은 기준으로 분류 가능한 형태로 바꾸는 것
  • 벡터 저장소는 정리된 재료 창고
  • 리트리버는 주문에 맞는 재료를 꺼내오는 담당자

결국 RAG 품질은 “모델이 얼마나 똑똑하냐”만으로 정해지지 않습니다.
문서를 어떻게 쪼갰는지, 어떤 임베딩을 썼는지, 몇 개를 검색하는지, 검색 결과를 어떻게 묶어 프롬프트에 넣는지에 따라서도 많이 달라집니다. 문서에서도 텍스트 스플리터의 종류와 chunk 처리, retriever의 다양한 방식이 따로 다뤄집니다.


아주 단순한 RAG 예제

이번에는 로컬 텍스트 문서를 기반으로 RAG를 만드는 예시를 보겠습니다.
아래 코드는 연구실 안내 문서를 읽어, 질문이 들어오면 관련 조각을 찾아 답하게 만드는 흐름입니다.

 
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnablePassthrough

# 1) 문서 불러오기
docs = TextLoader("lab_guide.txt", encoding="utf-8").load()

# 2) 문서를 적당한 길이로 분할
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=80
)
chunks = splitter.split_documents(docs)

# 3) 벡터 저장소 만들기
embeddings = OpenAIEmbeddings(api_key="YOUR_API_KEY")
vector_db = FAISS.from_documents(chunks, embeddings)

# 4) 검색기 만들기
retriever = vector_db.as_retriever(search_kwargs={"k": 3})

# 5) 검색 결과를 문자열로 합치기
def join_context(documents):
return "\n\n".join(doc.page_content for doc in documents)

# 6) 답변용 프롬프트
prompt = ChatPromptTemplate.from_template(
"""
너는 연구실 안내 도우미다.
아래 참고 문맥을 바탕으로만 답해라.
문맥에 없는 내용은 추측하지 말고 모른다고 말해라.

[참고 문맥]
{context}

[질문]
{question}
"""
)

model = ChatOpenAI(model="gpt-4o-mini", api_key="YOUR_API_KEY")

rag_chain = (
{
"context": retriever | RunnableLambda(join_context),
"question": RunnablePassthrough(),
}
| prompt
| model
| StrOutputParser()
)

answer = rag_chain.invoke("야간 출입은 몇 시까지 가능한가요?")
print(answer)

이 코드는 구조를 보여주기 위한 예시이기 때문에, 실제 서비스에서는 예외 처리나 문서 전처리, 검색 품질 튜닝이 더 필요합니다.
다만 초심자 입장에서는 이 흐름만 이해해도 RAG의 본질을 꽤 잘 잡은 것입니다.

핵심은 이것입니다.
RAG는 “LLM에게 더 많이 공부시키는 방식”이라기보다, 질문이 들어올 때마다 필요한 문맥을 찾아 붙이는 방식입니다.


Agent는 RAG와 무엇이 다를까

이제 가장 많이 헷갈리는 Agent로 넘어가보겠습니다.

처음에는 RAG도 외부 정보를 가져오고, Agent도 뭔가 외부를 사용하니 비슷해 보입니다.
하지만 두 개는 생각보다 성격이 다릅니다.

Agent는 기본적으로 무엇을 해야 할지 스스로 정하는 실행 구조입니다. LLM 자체는 입력에 대한 응답을 생성할 뿐, 스스로 행동을 취하는 시스템은 아닙니다. 반면 Agent는 LLM을 추론 엔진처럼 사용해 어떤 행동을 선택할지, 그리고 그 행동에 어떤 입력을 넣을지 결정하는 시스템으로 설명됩니다. 또한 문제에 대한 해결방안을 고르고, 필요한 행동을 선택하고, 결과를 평가해 다음 행동을 이어갈 수 있다는 점이 강조됩니다.

조금 쉽게 말하면 이렇습니다.

  • RAG는 “찾아온 문서를 참고해서 답하는 구조”
  • Agent는 “상황을 보고 어떤 도구를 쓸지 정하고 실행하는 구조”

예를 들어 사용자가 이런 질문을 한다고 해보겠습니다.

“오늘 회의실 예약 상황을 확인하고, 비어 있으면 3시에 1시간 예약해줘.”

이건 단순히 문서를 읽는 일만으로 끝나지 않습니다.
캘린더 조회 도구를 써야 할 수도 있고, 예약 도구를 다시 호출해야 할 수도 있습니다.
즉, 질문을 받고 행동 계획이 필요합니다. 이럴 때 Agent 개념이 더 잘 맞습니다.



Agent에서 Tool은 왜 중요한가

Agent가 실제로 움직이려면 손발이 필요합니다.
그 손발 역할을 하는 것이 Tool입니다.

LangChain에서 Tool은 chat model 또는 LLM이 외부와 상호작용할 수 있게 해주는 인터페이스입니다. 도구에는 이름, 설명, 입력 스키마, 실제 실행 함수 같은 정보가 연결되고, 모델은 이 정보를 바탕으로 적절한 인자를 넣어 도구를 호출할 수 있습니다. 도구 호출 결과는 다시 메시지 형태로 모델에 전달할 수도 있습니다.

아래는 아주 단순한 예시입니다.
학생용 도우미가 일정 조회 함수와 점수 계산 함수를 쓸 수 있다고 가정해보겠습니다.

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

@tool
def lookup_deadline(course_name: str) -> str:
"""과목 이름을 받아 과제 마감일을 알려준다."""
fake_db = {
"딥러닝": "다음 주 화요일 밤 11시 59분",
"컴퓨터비전": "이번 주 금요일 오후 6시",
}
return fake_db.get(course_name, "등록된 과목 정보가 없습니다.")

@tool
def score_to_percent(correct: int, total: int) -> str:
"""맞은 개수와 전체 문항 수를 받아 백분율 점수를 계산한다."""
if total == 0:
return "전체 문항 수는 0이 될 수 없습니다."
return f"{(correct / total) * 100:.1f}%"

model = ChatOpenAI(model="gpt-4o-mini", api_key="YOUR_API_KEY")
model_with_tools = model.bind_tools([lookup_deadline, score_to_percent])

response = model_with_tools.invoke(
"딥러닝 과제 마감일도 알려주고, 20문제 중 17개 맞았을 때 점수도 계산해줘."
)

print(response.tool_calls)

이 코드는 최종 에이전트를 완성한 것은 아니지만, 아주 중요한 개념을 보여줍니다.
모델은 단순히 문장을 생성하는 것에서 그치지 않고, **“이 문제를 풀려면 어떤 함수가 필요할까?”**를 판단하게 됩니다.

그 다음 단계에서는 이 tool call을 실제로 실행하고, 결과를 다시 모델에 넣어 최종 자연어 답변을 만들 수 있습니다. 문서에서도 tool call 결과를 ToolMessage로 전달해 후속 응답을 완성하는 흐름이 소개됩니다.


Chain, RAG, Agent의 차이를 한 번에 정리해보자

이쯤 오면 세 개가 조금 정리됩니다.

Chain은 고정된 파이프라인입니다.
입력 → 프롬프트 → 모델 → 파서처럼 흐름이 비교적 정해져 있습니다.

RAG는 고정된 파이프라인 안에 검색 단계가 들어간 구조입니다.
질문이 들어오면 관련 문서를 찾아 붙이고, 그 문맥을 바탕으로 답합니다.

Agent는 고정된 경로만 따르지 않습니다.
문제를 보고, 어떤 도구를 쓸지 고르고, 결과를 보고 다음 행동을 정할 수 있습니다. 도구, 메모리, 계획, 행동이 함께 연결되는 구조로 확장될 수 있습니다.

그래서 실제 프로젝트에서는 이런 식으로 나뉘는 경우가 많습니다.

간단한 문장 변환기나 요약기라면 Chain이면 충분합니다.
사내 문서 기반 Q&A라면 RAG가 잘 맞습니다.
여러 도구를 오가며 작업을 수행해야 하면 Agent가 필요해집니다.

물론 현실에서는 이 셋이 섞이기도 합니다.
예를 들어 Agent가 필요할 때, 그 Agent가 사용할 도구 중 하나로 RAG 검색기를 둘 수도 있습니다. 즉, Agent가 “먼저 문서를 검색해라”라고 판단하고, 그 검색 도구가 다시 RAG 파이프라인을 사용할 수도 있는 것입니다. 이런 조합 가능성도 LangChain 계층과 구성 요소들이 조립식으로 설계되어 있기 때문에 가능합니다.


LangChain을 공부할 때 추천하는 순서

처음부터 Agent를 파고들면 생각보다 어렵습니다.
도구 호출, 상태 관리, 반복 실행, 실패 처리까지 한꺼번에 이해해야 하기 때문입니다.

그래서 저는 다음 순서를 추천합니다.

먼저 프롬프트와 가장 작은 체인부터 익히는 것이 좋습니다.
입력 템플릿, 모델 호출, 출력 파서를 연결해보면 LangChain의 기본 감각이 잡힙니다. prompt template, message 기반 프롬프트, output parser, runnable 실행 방식이 여기서 핵심이 됩니다.

그 다음에는 메모리와 대화 기록을 붙여보는 편이 좋습니다.
이 단계에서 “기억한다”는 표현의 실제 의미를 이해하게 됩니다. MessagesPlaceholder, ChatMessageHistory, RunnableWithMessageHistory, session_id 개념이 자연스럽게 들어옵니다.

그다음으로 RAG를 해보면 좋습니다.
이 단계에서 document loader, text splitter, embeddings, vector store, retriever가 어떤 역할을 하는지 손에 잡히기 시작합니다. 특히 문서를 어떻게 자르느냐가 품질에 꽤 큰 영향을 준다는 것도 느끼게 됩니다.

마지막으로 Tool과 Agent로 가는 것이 흐름상 자연스럽습니다.
이 시점이 되면 이미 “모델에 입력을 만들고, 기록을 관리하고, 문서를 검색하는 것”에 익숙해져 있으니, 거기에 행동 결정 레이어만 얹으면 되기 때문입니다. Tool 인터페이스와 호출 결과를 다시 모델에 전달하는 구조를 이해하면 Agent 쪽도 훨씬 덜 낯설어집니다.


한편, 멀티모달까지 생각하면 LangChain의 쓰임새는 더 넓어진다

LangChain은 텍스트만 다루는 틀로 끝나지 않습니다. chat model 계층은 멀티모달 입력도 다룰 수 있고, 이미지 같은 입력을 메시지 형태로 전달하는 구조도 가능합니다. 또한 이런 멀티모달 입력과 Tool을 함께 사용하는 흐름도 확장할 수 있습니다.

이 말은 결국, 앞으로 애플리케이션이 텍스트를 넘어 이미지나 다른 입력까지 함께 다룰 가능성을 생각하면, LangChain을 단순 프롬프트 라이브러리로만 보는 것은 조금 좁은 해석일 수 있다는 뜻입니다.
실제로는 여러 입력과 여러 동작을 하나의 실행 그래프로 엮는 방향으로 이해하는 편이 더 맞아 보입니다.


마지막으로, 초심자가 꼭 기억하면 좋은 한 문장들

LangChain은 모델이 아니라 연결 프레임워크입니다.
RAG는 모델을 더 똑똑하게 “만드는” 것이 아니라, 필요한 문서를 가져와 붙이는 방식입니다.
Agent는 정답을 바로 쓰는 구조가 아니라, 무슨 행동을 해야 할지 고르는 구조입니다.

처음에는 용어가 많아서 복잡해 보이지만, 결국 하나의 질문으로 정리됩니다.

“이 답을 만들기 위해, 모델 앞뒤에 무엇을 붙여야 하는가?”

프롬프트를 잘 만들고 싶다면 Prompt Engineering으로 가면 됩니다.
이전 대화를 이어가고 싶다면 Memory가 필요합니다.
외부 문서를 근거로 답하고 싶다면 RAG가 필요합니다.
도구를 선택해 행동하게 만들고 싶다면 Agent가 필요합니다.
LangChain은 이 모든 조각을 하나의 애플리케이션으로 엮는 틀입니다.

결국 중요한 것은 유행어를 많이 아는 것이 아니라, 내가 만들고 싶은 시스템이 어떤 문제를 해결해야 하는지를 먼저 보는 일 같습니다.
단순한 요약기라면 거창한 Agent는 필요 없을 수도 있습니다.
반대로 여러 시스템을 넘나드는 업무 자동화라면 RAG 하나만으로는 부족할 수도 있습니다.

이 차이를 구분하기 시작하면, LangChain도 훨씬 덜 막막하게 느껴질 겁니다.