LangChain이란?
LangChain은 LLM(대규모 언어 모델)을 활용한 애플리케이션을 구축하기 위한 프레임워크입니다. 레고 블록처럼 프롬프트, 모델, 도구, 메모리 등의 컴포넌트를 조합하여 복잡한 AI 워크플로우를 만들 수 있습니다.
단순히 LLM API를 호출하는 것과 LangChain을 사용하는 것의 차이는, 재료만 있는 것과 레시피가 있는 것의 차이와 같습니다. LangChain은 검증된 패턴과 추상화를 제공하여 반복적인 코드를 줄여줍니다.
핵심 개념 정리
| 개념 | 설명 | 비유 |
|---|---|---|
| Chain | 여러 단계를 순차 실행하는 파이프라인 | 공장 조립 라인 |
| Agent | LLM이 도구를 선택하고 실행하는 자율 시스템 | 만능 비서 |
| Memory | 대화 이력을 저장하고 참조하는 모듈 | 대화 노트 |
| Tool | 에이전트가 사용할 수 있는 외부 기능 | 비서의 도구함 |
| Prompt Template | 동적으로 생성되는 프롬프트 틀 | 빈칸 채우기 양식 |
설치 및 기본 설정
# LangChain 및 OpenAI 패키지 설치
pip install langchain langchain-openai langchain-community
# 환경변수 설정 (.env 파일 또는 export)
export OPENAI_API_KEY="sk-your-api-key"
Chain: 단계별 파이프라인 구성
Chain은 여러 처리 단계를 연결하는 핵심 개념입니다. LangChain Expression Language(LCEL)를 사용하면 | 연산자로 직관적으로 체인을 구성할 수 있습니다.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 모델 초기화
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
# 프롬프트 템플릿 정의
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 {role} 전문가입니다. 한국어로 답변하세요."),
("human", "{question}")
])
# LCEL로 체인 구성 (프롬프트 → 모델 → 출력 파서)
chain = prompt | llm | StrOutputParser()
# 체인 실행
result = chain.invoke({
"role": "Python",
"question": "데코레이터의 동작 원리를 설명해주세요"
})
print(result)
# 출력: 데코레이터는 함수를 인자로 받아 새로운 함수를 반환하는 고차 함수입니다...
여러 체인 연결하기
하나의 체인 출력을 다른 체인의 입력으로 연결할 수 있습니다.
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
# 1단계: 주제에 대한 요약 생성
summary_prompt = ChatPromptTemplate.from_template(
"{topic}에 대해 3문장으로 요약해주세요."
)
summary_chain = summary_prompt | llm | StrOutputParser()
# 2단계: 요약을 기반으로 퀴즈 생성
quiz_prompt = ChatPromptTemplate.from_template(
"다음 내용을 바탕으로 객관식 퀴즈 2문제를 만들어주세요:\n{summary}"
)
quiz_chain = quiz_prompt | llm | StrOutputParser()
# 두 체인을 연결하여 실행
full_chain = (
summary_chain
| (lambda summary: {"summary": summary})
| quiz_chain
)
result = full_chain.invoke({"topic": "Python 비동기 프로그래밍"})
print(result)
# 출력: 1. Python에서 비동기 함수를 정의할 때 사용하는 키워드는?
# a) async b) await c) yield d) thread
# ...
Agent: LLM이 도구를 선택하고 실행
에이전트는 LLM이 스스로 판단하여 적절한 도구를 선택하고 실행하는 시스템입니다. 계산기, 검색, API 호출 등 다양한 도구를 연결할 수 있습니다.
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
# 커스텀 도구 정의
@tool
def calculate(expression: str) -> str:
"""수학 표현식을 계산합니다. 예: '2 + 3 * 4'"""
try:
# 안전한 수식 평가
result = eval(expression, {"__builtins__": {}})
return f"계산 결과: {result}"
except Exception as e:
return f"계산 오류: {e}"
@tool
def get_current_weather(city: str) -> str:
"""도시의 현재 날씨를 조회합니다."""
# 실제로는 외부 API 호출
weather_data = {
"서울": "맑음, 15°C",
"부산": "흐림, 18°C",
"제주": "비, 16°C"
}
return weather_data.get(city, f"{city}의 날씨 정보를 찾을 수 없습니다")
# 에이전트 구성
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [calculate, get_current_weather]
prompt = ChatPromptTemplate.from_messages([
("system", "도구를 활용하여 사용자의 질문에 답변하세요."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}")
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 에이전트 실행
response = executor.invoke({
"input": "서울 날씨 알려주고, 15의 제곱근을 계산해줘"
})
print(response["output"])
# 출력:
# > Entering new AgentExecutor chain...
# > Tool: get_current_weather("서울") → 맑음, 15°C
# > Tool: calculate("15 ** 0.5") → 계산 결과: 3.872983...
# > 서울의 현재 날씨는 맑음, 15°C이고, 15의 제곱근은 약 3.87입니다.
Memory: 대화 맥락 유지
LLM은 기본적으로 이전 대화를 기억하지 못합니다. Memory 모듈을 사용하면 대화 이력을 저장하고, 이전 맥락을 참조하여 자연스러운 대화가 가능합니다.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 세션별 대화 이력 저장소
store = {}
def get_session_history(session_id: str):
"""세션 ID별 대화 이력을 반환합니다."""
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 프롬프트에 대화 이력 포함
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 친절한 AI 어시스턴트입니다. 한국어로 답변하세요."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
llm = ChatOpenAI(model="gpt-4o")
chain = prompt | llm
# 메모리가 포함된 체인 생성
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
# 대화 실행 — 같은 세션 ID로 맥락 유지
config = {"configurable": {"session_id": "user-001"}}
# 첫 번째 질문
response1 = chain_with_history.invoke(
{"input": "제 이름은 김철수입니다"},
config=config
)
print(response1.content)
# 출력: 안녕하세요, 김철수님! 무엇을 도와드릴까요?
# 두 번째 질문 — 이전 맥락을 기억
response2 = chain_with_history.invoke(
{"input": "제 이름이 뭐라고 했죠?"},
config=config
)
print(response2.content)
# 출력: 김철수님이라고 하셨습니다!
RAG와 LangChain 연동
LangChain은 RAG(검색 증강 생성) 파이프라인도 쉽게 구성할 수 있습니다.
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 문서 준비 및 분할
documents = [
"LangChain은 LLM 애플리케이션 개발 프레임워크입니다.",
"LCEL은 LangChain Expression Language의 약자입니다.",
"에이전트는 LLM이 도구를 선택하여 실행하는 시스템입니다.",
]
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, # 청크 최대 길이
chunk_overlap=50 # 청크 간 겹침
)
# 2. 벡터 스토어 생성
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_texts(documents, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 3. RAG 체인 구성
rag_prompt = ChatPromptTemplate.from_template("""
다음 컨텍스트를 기반으로 질문에 답변하세요.
컨텍스트: {context}
질문: {question}
""")
def format_docs(docs):
"""검색된 문서를 하나의 문자열로 결합합니다."""
return "\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| rag_prompt
| ChatOpenAI(model="gpt-4o")
| StrOutputParser()
)
# 4. RAG 체인 실행
answer = rag_chain.invoke("LCEL이 뭔가요?")
print(answer)
# 출력: LCEL은 LangChain Expression Language의 약자로,
# LangChain에서 체인을 구성하는 표현 언어입니다.
LangChain 프로젝트 구조 권장
실제 프로젝트에서는 다음과 같은 구조를 권장합니다.
my-langchain-app/
├── chains/ # 체인 정의
│ ├── summary.py
│ └── rag.py
├── agents/ # 에이전트 정의
│ └── assistant.py
├── tools/ # 커스텀 도구
│ ├── calculator.py
│ └── search.py
├── prompts/ # 프롬프트 템플릿
│ └── templates.py
├── config.py # 설정 (모델, 파라미터)
├── main.py # 진입점
└── .env # API 키 (gitignore 필수)
실전 팁
- LCEL을 사용하세요:
LLMChain등 레거시 API 대신|연산자 기반 LCEL이 현재 권장 방식입니다. - Streaming을 활용하세요:
chain.stream()으로 토큰 단위 스트리밍이 가능하여 UX가 크게 개선됩니다. - LangSmith로 디버깅하세요: LangSmith를 연동하면 체인의 각 단계별 입출력을 추적할 수 있어 디버깅이 쉬워집니다.
- Temperature를 용도에 맞게 조절하세요: 사실 기반 답변은 0, 창의적 생성은 0.7~1.0이 적합합니다.
- 에이전트의 도구 설명을 명확하게 작성하세요:
@tool데코레이터의 docstring이 곧 LLM이 도구를 선택하는 기준이 됩니다. - 비용 관리:
callbacks를 사용하여 토큰 사용량을 추적하고, 캐싱(SQLiteCache)으로 동일 쿼리의 중복 호출을 줄일 수 있습니다.