LangChain 개요
LangChain은 대형 언어 모델(LLM)을 활용한 애플리케이션 개발을 돕는 파이썬 프레임워크입니다. 프롬프트 작성, LLM 호출, 응답 파싱, 메모리 관리, 외부 도구 연동 등 LLM 기반 응용에 필요한 구성 요소들을 체계적으로 제공합니다. 이를 통해 개발자는 저수준의 API 호출보다는 고수준의 컴포넌트를 조합하여 체인(chain) 형태로 자연어 처리 흐름을 설계할 수 있습니다. LangChain을 사용하면 사용자 입력으로 프롬프트를 만들고, LLM으로부터 응답을 얻고, 응답을 원하는 형식으로 변환하는 일련의 과정을 간결한 코드로 구현할 수 있습니다.
특히 2025년 기준 LangChain v0.3.x 버전에서는 **LangChain Expression Language (LCEL)**라는 새로운 개념을 도입하여, 파이프라인을 구축하듯이 여러 컴포넌트를 연결해 LLM 워크플로우를 만들 수 있습니다. 이번 튜토리얼에서는 LangChain v0.3.x의 주요 변경 사항인 모듈화된 아키텍처와 LCEL을 활용한 체인 구성을 살펴보고, OpenAI의 GPT-4o-mini 모델을 예시로 LangChain을 사용하는 방법을 실습합니다.
LangChain 아키텍처 및 모듈화 구조
LangChain v0.3에서는 기능별로 패키지가 세분화되어 모듈화되었습니다. 아래 그림은 LangChain 생태계의 주요 패키지와 그 관계를 보여줍니다.
LangChain v0.3 모듈 구조 (각 패키지 간 의존 관계). LangChain의 핵심 기능은 langchain-core 패키지로 분리되었으며, 여기에 다양한 기본 추상 클래스와 LCEL 실행 기능이 포함되어 있습니다. 메인 패키지인 langchain은 langchain-core를 포함하지만, 구체적인 모델 제공자나 외부 연동 기능은 기본 설치에 포함되지 않습니다. 대신 OpenAI, Anthropic 등의 LLM API 연동은 langchain-openai, langchain-anthropic과 같은 별도 패키지로 제공되며, 필요에 따라 개별적으로 설치해 사용합니다. 또한 아직 개별 패키지로 분리되지 않은 통합들은 langchain-community 패키지에 모여 있고, 실험적 기능은 langchain-experimental에 포함되어 있습니다.
이러한 구조 덕분에, 사용자는 필요한 부분만 선택적으로 설치하여 경량으로 사용할 수 있고, LangChain 자체의 핵심 업그레이드와 외부 서비스 연동 업데이트를 독립적으로 관리할 수 있습니다. 정리하면, 주요 LangChain 패키지는 다음과 같습니다:
- langchain-core: LangChain의 핵심 로직과 추상 클래스들을 담은 패키지입니다. 프롬프트 템플릿, LLM 래퍼, 체인 실행(LCEL) 등의 기본 기능을 제공합니다.
- langchain (메인 패키지): 사용자 입장에서 LangChain을 시작하는 기본 패키지로, langchain-core에 의존하며 일반적으로 함께 설치됩니다. 다만 특별한 통합 의존성은 포함하지 않으므로, 기본 설치만으로는 OpenAI 같은 특정 모델 API는 사용할 수 없습니다.
- langchain-openai, langchain-anthropic, langchain-postgres 등: 모델 제공자나 데이터소스별로 분리된 통합 패키지들입니다. 예를 들어 OpenAI API를 사용하려면 langchain-openai를 설치해야 하며, Anthropic을 쓰려면 langchain-anthropic을 설치하는 식입니다. 이러한 패키지들은 모두 langchain-core에 의존하여 동작합니다.
- langchain-community: 아직 개별 패키지로 쪼개지 않은 다양한 통합 기능들이 모여있는 패키지입니다. 특정 통합이 별도 패키지로 이관되기 전까지 이 공간에 포함되어 관리됩니다.
- langchain-experimental: 실험적이거나 연구 단계의 기능을 제공하는 패키지입니다. 안정성이 충분히 검증되지 않은 모듈들이며, 필요할 경우 별도로 설치하여 사용합니다.
요약하면, LangChain v0.3에서는 코어 기능과 외부 연동을 분리하여 관리하므로, 메인 패키지와 함께 사용하고자 하는 LLM 제공자용 패키지를 추가로 설치해야 합니다. 예를 들어 OpenAI의 GPT-4나 GPT-4o-mini 모델을 쓰기 위해서는 langchain과 함께
langchain-openai 패키지를 설치해야 합니다.
LangChain 설치 및 환경 구성
먼저 Jupyter 노트북 환경에 LangChain과 관련 패키지들을 설치하겠습니다. 아래 셀을 실행하여 LangChain 메인 패키지와 OpenAI 연동 패키지(langchain-openai), 그리고 환경 변수 관리를 위한 python-dotenv를 설치합니다:
!pip install langchain langchain-openai python-dotenv
설치가 완료되었으면, OpenAI API 키를 설정해야 합니다. OpenAI의 GPT-4o-mini 모델을 호출하려면 유효한 OpenAI API 키가 필요합니다. API 키는 OpenAI 계정에서 발급받을 수 있으며, 노트북이나 코드에 직접 노출하지 않고 안전하게 사용하기 위해 환경 변수로 설정하는 것이 좋습니다. 이를 위해 프로젝트 디렉토리에 .env 파일을 만들고 다음과 같이 API 키를 저장합니다 (따옴표 없이 입력):
OPENAI_API_KEY=sk-********************
이 .env 파일을 통해 환경 변수를 불러오면 코드 내에서 API 키 문자열을 직접 다루지 않아도 됩니다. 이제 파이썬 코드로 .env 파일을 로드해보겠습니다 (python-dotenv 라이브러리를 사용):
from dotenv import load_dotenv
load_dotenv() # .env 파일의 내용 환경 변수로 로드
import os
if os.getenv("OPENAI_API_KEY"):
print("API key loaded successfully!")
else:
print("Error: OpenAI API key not found. Please check .env file.")
위 코드가 API key loaded successfully!를 출력하면 환경 설정은 완료된 것입니다. OpenAI API 키는 이제 OPENAI_API_KEY 환경 변수로 등록되었고, LangChain의 OpenAI LLM 래퍼는 별도로 키를 명시하지 않아도 이 환경 변수를 자동으로 참조합니다. 즉, ChatOpenAI와 같은 클래스 사용 시 api_key를 전달하지 않으면 OPENAI_API_KEY 값을 이용해 OpenAI에 연결합니다. (물론 필요에 따라 인스턴스 생성 시 api_key 파라미터로 다른 키를 지정할 수도 있습니다.)
NOTE: 실제 OpenAI API를 호출하게 되므로, 실행 시 과금이나 토큰 소모가 발생할 수 있습니다. GPT-4o-mini 모델은 GPT-4의 소형 버전으로 비교적 저렴하지만, 학습 목적으로 호출 시에도 비용이 들 수 있으니 주의하세요.
LangChain 핵심 컴포넌트 소개
이제 LangChain의 주요 컴포넌트들을 살펴보겠습니다. LangChain은 모델 호출 단계를 구성하는 여러 추상화 요소를 제공하는데, 그 중 이번 튜토리얼에서 다룰 핵심 컴포넌트는 다음과 같습니다:
- PromptTemplate: 프롬프트 템플릿 – LLM에게 보낼 입력 프롬프트를 구성하는 템플릿 객체
- ChatOpenAI: OpenAI 챗 모델 래퍼 – OpenAI의 GPT-4, GPT-3.5, GPT-4o-mini 등의 챗 모델을 호출하는 LangChain 클래스
- Runnable: 실행 가능한 객체에 대한 공통 인터페이스 – LangChain 컴포넌트들이 구현하는 프로토콜로, invoke() 메서드를 통해 입력->출력 실행을 지원
- StrOutputParser: 문자열 출력 파서 – LLM의 응답 결과를 파싱하여 순수 텍스트(string)로 반환하는 출력 파서 클래스
각 컴포넌트의 역할과 사용법을 이론과 코드 예제로 알아보겠습니다.
PromptTemplate (프롬프트 템플릿)
PromptTemplate은 프롬프트 문자열에 자리표시자(variable)를 넣어 두고, 런타임에 실제 값으로 치환하여 완성된 프롬프트를 만들어주는 클래스입니다. 예를 들어 사용자가 입력한 정보를 프롬프트에 삽입하거나, 여러 곳에서 재사용할 공통 템플릿을 정의할 때 유용합니다. PromptTemplate을 사용하면 {...} 형태의 플레이스홀더를 지정해 두고, .format(...) 또는 .invoke(...) 메서드로 구체적인 값을 채워 완전한 문자열을 얻을 수 있습니다.
다음 코드 셀에서 PromptTemplate을 생성하고 동작을 확인해보겠습니다. 예시로, 어떤 **제품(product)**을 생산하는 회사 이름을 묻는 프롬프트를 템플릿으로 만들어보겠습니다.
from langchain_core.prompts import PromptTemplate
# 프롬프트 템플릿 정의: {product} 부분이 이후에 채워질 플레이스홀더입니다.
template = "What is a good name for a company that makes {product}?"
prompt = PromptTemplate.from_template(template)
# 템플릿에 값을 채워 실제 프롬프트 생성
formatted_prompt = prompt.format(product="coffee")
print(formatted_prompt)
실행 결과 (formatted_prompt 출력):
What is a good name for a company that makes coffee?
위와 같이 {product} 플레이스홀더가 "coffee"로 치환되어 최종 프롬프트 문자열이 완성되었습니다. PromptTemplate.from_template(...) 메서드는 간단히 문자열을 템플릿으로 등록하고, 내부적으로 필요한 input_variables를 자동 추론해주는 편의 함수입니다. (from_template을 쓰지 않고 PromptTemplate(template="...", input_variables=[...]) 형태로 직접 생성할 수도 있습니다.)
PromptTemplate은 LangChain의 Runnable 인터페이스를 구현하고 있어서, 나중에 체인(chain) 구성 시 첫 번째 단계로 사용할 수 있습니다. 이 객체를 체인의 시작에 두면 .invoke({"product": "coffee"}) 형태로 호출하여 곧바로 포맷된 프롬프트 문자열을 얻어낼 수 있습니다. (Runnable 인터페이스에 대해서는 뒤에서 추가 설명합니다.)
ChatOpenAI (OpenAI 챗 모델 래퍼)
ChatOpenAI 클래스는 OpenAI의 GPT 계열 챗 모델을 간편하게 사용할 수 있도록 래핑한 LangChain 컴포넌트입니다. OpenAI에서 제공하는 openai 파이썬 패키지 및 Chat Completion API를 내부적으로 사용하여, 프롬프트 메시지를 보내고 모델의 응답을 받아오는 역할을 합니다. ChatOpenAI를 이용하면 모델명, 온도(temperature), 토큰 제한 등의 파라미터를 객체 초기화 시 지정할 수 있고, 이후 .invoke(...) 등을 통해 대화형 LLM 호출을 할 수 있습니다.
GPT-4o-mini는 OpenAI에서 제공하는 GPT-4의 경량화 버전 모델로, 모델 이름은 "gpt-4o-mini"입니다. ChatOpenAI 객체를 만들 때 model="gpt-4o-mini"로 지정하면 이 모델을 사용하게 됩니다 (기본값은 "gpt-3.5-turbo"이므로 명시적으로 설정 필요).
다음 코드에서 ChatOpenAI 인스턴스를 생성하고, 간단한 메시지를 모델에 보내 응답을 받아보겠습니다.
from langchain_openai import ChatOpenAI
# OpenAI 챗 모델 래퍼 생성 (GPT-4o-mini 모델 사용, 온도=0으로 설정하여 결정론적 응답)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 예시 메시지를 모델에 보내보기 (역할: human, 내용: 인사말)
response = llm.invoke([("human", "Hello, how are you?")])
print(response)
실행 결과 (response 출력의 예시):
AIMessage(content="I'm doing well, thank you! How can I assist you today?", additional_kwargs={}, example=False)
위 결과에서 볼 수 있듯이, ChatOpenAI의 .invoke(...) 메서드는 OpenAI 모델이 응답한 메시지 객체를 반환합니다. response는 LangChain의 AIMessage 형식으로 표시되었는데, 여기에 content 속성으로 실제 답변 텍스트가 담겨 있고 그 외에 토큰 사용량 등 부가 정보가 포함될 수 있습니다. 이처럼 ChatOpenAI 단독 호출 결과는 객체 형태로 제공되는데, 이는 대화 맥락을 유지하거나 추가 메타데이터를 활용할 때 유용합니다. 그러나 최종적으로 우리가 필요한 것은 모델의 본문 답변 문자열 자체인 경우가 많습니다. 다음으로 소개할 StrOutputParser는 이러한 응답 객체를 사람이 읽기 좋은 문자열로 변환하는 역할을 합니다.
참고: ChatOpenAI에서 .invoke([("human", "...\")])처럼 튜플의 리스트를 인자로 전달하는 것은, 대화의 **역할(role)**과 **내용(content)**을 명시하는 방식입니다. ("human", "...")는 사용자 메시지, ("system", "...")은 시스템(시스템 프롬프트), ("ai", "...") 또는 ("assistant", "...")는 모델의 응답을 나타냅니다. OpenAI Chat API의 messages 구조를 반영한 것이며, LangChain에서는 "human"/"ai" 등의 키워드를 내부적으로 처리합니다. 단순히 하나의 사용자 질문만 보낼 경우 위 예시처럼 리스트에 한 개의 튜플을 넣어 호출하면 됩니다.
Runnable 인터페이스 (실행 가능한 구성 요소)
LangChain의 주요 구성 요소들은 공통적으로 Runnable 인터페이스를 구현하고 있습니다. Runnable은 한 마디로 "입력을 받아 출력을 생성할 수 있는 실행 가능한 객체"를 뜻하며, 표준화된 메서드 (invoke, batch, stream 등)를 제공합니다. 앞서 본 PromptTemplate, ChatOpenAI, StrOutputParser 모두 Runnable의 구현체로서, 동일한 방식으로 .invoke(input)를 호출할 수 있고 체인 내에서 순차 실행 단계로 파이프 연결될 수 있습니다.
Runnable 인터페이스 덕분에 이질적인 컴포넌트들도 호환되는 형태로 결합될 수 있습니다. 예를 들어 PromptTemplate의 출력은 문자열이지만, 이를 ChatOpenAI의 입력으로 바로 넘길 수 있었던 것은 두 객체가 모두 Runnable 프로토콜을 따르기 때문입니다. 또한 Runnable은 .invoke 외에도 .bind나 .with_config 등의 메서드를 제공하여 구성 설정 주입이나 체인 병렬 실행 (RunnableParallel) 같은 고급 기능도 지원합니다. 이번 튜토리얼에서는 단일 순차 체인을 다루므로 고급 내용은 다루지 않지만, 내부적으로 이러한 공통 인터페이스가 작동하고 있다는 것을 염두에 두시면 좋습니다.
정리하면, Runnable = 실행 가능한 한 조각의 논리로 생각할 수 있고, LangChain v0.3에서는 개별 컴포넌트들이 모두 Runnable이므로 마치 함수를 합성하듯 연결하여 사용할 수 있습니다.
StrOutputParser (문자열 출력 파서)
StrOutputParser는 LangChain에서 가장 기본적인 **출력 파서(Output Parser)**로, LLM의 응답 결과로부터 순수한 텍스트 문자열을 추출해 줍니다. 특별한 구조화나 후처리 없이 그대로 문자열로 반환하기 때문에, LLM 응답을 그대로 보여주거나 다음 단계로 넘길 때 유용합니다. 예를 들어 ChatOpenAI의 응답이 AIMessage 객체로 반환되었을 때, StrOutputParser를 사용하면 그 안의 content 문자열을 쉽게 꺼낼 수 있습니다.
StrOutputParser 역시 Runnable로 구현되어 있어 체인에 추가할 수 있고, 단독으로 .invoke(...)하여 사용할 수도 있습니다. 앞서 ChatOpenAI 예시에서 받은 response 객체를 StrOutputParser로 처리해보겠습니다:
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
parsed_text = parser.invoke(response) # ChatOpenAI의 응답 객체를 파싱
print(parsed_text)
실행 결과 (parsed_text 출력):
I'm doing well, thank you! How can I assist you today?
원래 response가 AIMessage 객체였는데, StrOutputParser를 거치면서 그 본문 내용만 추출된 문자열을 얻었습니다. 즉, 모델의 순수 답변 텍스트만 남게 된 것입니다. StrOutputParser는 내부적으로 LLM 결과의 가장 가능성 높은 출력을 선택하여 문자열로 반환합니다. 추가 가공 없이 그대로 반환하기 때문에, 보다 구조적인 출력이 필요할 경우에는 JSONOutputParser나 PydanticOutputParser 등의 다른 파서를 사용할 수도 있습니다. 하지만 일반적인 사용 사례에서는 StrOutputParser로 충분하며, 체인의 마지막 단계에 이 파서를 넣어주면 최종 결과를 다루기 편한 형태로 얻을 수 있습니다.
참고: 출력 파서는 LLM 응답에 일관된 구조를 부여하는 데 중요한 역할을 합니다. StrOutputParser는 가장 단순한 형태로 아무런 구조를 강요하지 않고 문자열로 내보내지만, 특정 키-값 형식이나 리스트 형식이 필요하면 해당 형태에 맞는 OutputParser 클래스를 사용해 응답을 파싱할 수 있습니다. (이번 예제에서는 단순 문자열만 필요하므로 StrOutputParser로 충분합니다.)
LangChain Expression Language (LCEL)을 활용한 단일 체인 실행
앞에서 소개한 PromptTemplate, ChatOpenAI, StrOutputParser를 개별적으로 사용해보았으니, 이제 이들을 하나의 **체인(chain)**으로 묶어 실행해보겠습니다. LangChain v0.3에서 도입된 **LCEL (LangChain Expression Language)**은 파이프(|) 구문을 사용하여 여러 Runnable 컴포넌트를 손쉽게 연결할 수 있게 해줍니다. 마치 함수들을 파이프라인으로 이어주는 것처럼, A | B | C 식으로 작성하면 A의 출력이 B로, B의 출력이 C로 전달되는 새로운 Runnable 체인 객체가 생성됩니다. 이러한 표현 덕분에 체인 구성을 간결하게 작성할 수 있고, LangChain이 실행 단계를 최적화하여 병렬화하거나 스트리밍하는 등의 이점도 얻을 수 있습니다.
실습 예제: 회사 이름 생성 체인 – PromptTemplate에 제품명을 넣으면 GPT-4o-mini 모델을 통해 해당 제품을 만드는 회사의 이름 아이디어를 얻고, 그 결과를 문자열로 반환하는 체인을 만들어보겠습니다. 앞서 정의한 prompt, llm, parser 객체를 모두 활용합니다.
# 앞서 생성한 prompt, llm, parser를 파이프로 연결하여 체인 구성
chain = prompt | llm | parser # PromptTemplate -> ChatOpenAI -> StrOutputParser
# 체인 실행: 입력 딕셔너리로 prompt에 필요한 변수 전달
result = chain.invoke({"product": "coffee"})
print("최종 답변:", result)
실행 결과 (예시):
최종 답변: Brew Bliss Coffee Co.
위 결과는 예시로 **커피(coffee)**를 만드는 회사를 위한 이름을 묻자 모델이 "Brew Bliss Coffee Co."라는 창의적인 답변을 생성한 것입니다. 체인 실행은 매우 간단하게 chain.invoke({...}) 한 줄로 이루어졌지만, 내부적으로는 다음과 같은 단계가 순차적으로 수행되었습니다:
- PromptTemplate 단계 – {"product": "coffee"} 입력을 받아 템플릿 "What is a good name for a company that makes {product}?"에 값을 채워 완전한 프롬프트 문자열 "What is a good name for a company that makes coffee?"를 생성합니다.
- ChatOpenAI 단계 – 위에서 생성된 프롬프트를 OpenAI GPT-4o-mini 모델에 전달합니다. 모델은 최적의 답을 생성하며, 예시에서는 "Brew Bliss Coffee Co."라는 텍스트를 응답했습니다. ChatOpenAI는 이 응답을 AIMessage 객체로 반환합니다 (대화 형태를 유지하기 위해).
- StrOutputParser 단계 – AIMessage 객체에서 실제 콘텐츠 문자열만 추출합니다. 그 결과 "Brew Bliss Coffee Co."라는 순수 문자열만 남게 되며, 이것이 chain.invoke()의 최종 반환값이 됩니다.
LCEL을 활용한 이러한 단일 체인 구성은 직관적이고 간결합니다. prompt | llm | parser 형태로 작성하여 하나의 큰 Runnable로 취급할 수 있고, 필요하면 chain.invoke() 외에도 chain.batch()로 여러 입력을 병렬 처리하거나 chain.stream()으로 스트림 출력받는 것도 가능합니다. 간단한 직렬 흐름에서는 LCEL 체인이 기존 방식에 비해 구현을 크게 단순화해주며, LangChain이 내부 실행을 최적화해주기 때문에 효율성도 높아집니다.
마지막으로, 입력 값을 바꿔가며 체인을 실험해보세요. 예를 들어 {"product": "smartwatch"}를 넣으면 스마트워치를 만드는 회사 이름 아이디어를 얻을 수 있고, {"product": "챗봇 서비스"}처럼 한글을 넣어도 한국어에 맞는 회사 이름을 생성해줄 것입니다. LangChain을 통해 프롬프트 처리와 모델 호출, 응답 파싱이 하나의 일관된 흐름으로 연결되어 개발 생산성을 높여줌을 체감할 수 있을 것입니다.
마무리 및 정리
이상으로 LangChain v0.3.x의 핵심 개념과 OpenAI GPT-4o-mini 모델을 사용하는 방법을 살펴보았습니다. 요약하면:
- LangChain v0.3의 모듈화: langchain-core를 중심으로 기능이 분리되었고, 사용 시 관련 패키지를 선택적으로 설치해야 합니다.
- 환경 설정: OpenAI API 키를 .env로 관리하고 python-dotenv로 불러와 안전하게 활용했습니다.
- 핵심 컴포넌트: PromptTemplate으로 프롬프트 템플릿을 만들고, ChatOpenAI로 OpenAI 챗 모델을 불러오며, StrOutputParser로 응답을 문자열로 변환했습니다. 이 모두가 Runnable 인터페이스를 공유하여 유연하게 조합 가능합니다.
- LCEL 기반 체인 실행: 파이프 | 연산자를 사용해 컴포넌트들을 연결함으로써 하나의 체인으로 묶었고, .invoke()로 단일 입력에 대한 LLM 워크플로우 실행 결과를 손쉽게 얻었습니다.
LangChain을 활용하면 이처럼 복잡한 LLM 처리 과정을 모듈화하고 간소화할 수 있습니다. 더 나아가 다중 단계 체인, 에이전트, 메모리 등 풍부한 기능들이 제공되지만, 우선 이번 튜토리얼에서 다룬 개념들을 충분히 익힌 후에 하나씩 탐구해보길 권장합니다. 앞으로도 LangChain과 같은 LLM 프레임워크를 통해 생산적인 AI 개발을 해나가시길 바랍니다.
'HRDI_AI > [인공지능] LangChain과 RAG를 활용한 AI 서비스 개발 Ⅰ' 카테고리의 다른 글
| 6. LangChain LCEL (0) | 2025.05.30 |
|---|---|
| 5. LangChain 메모리 (0) | 2025.05.30 |
| 4. LangChain 출력 파서(OutputParser) (2) | 2025.05.26 |
| 3. LangChain 프롬프트 템플릿 (0) | 2025.05.26 |
| 2. LangChain을 활용한 모델 사용, 비용 모니터링 및 캐싱 전략 (0) | 2025.05.26 |