저는 최근 3개월간 이커머스 플랫폼의 AI 고객 서비스 시스템을 구축하면서 LangChain의 LCEL(LangChain Expression Language)에 깊이 빠져들었습니다. 일 평균 50,000건의 문의를 처리해야 하는 환경에서 LCEL의 모듈식 설계가 얼마나 강력한지 체감했어요. 이 튜토리얼에서는 제가 실제 프로젝트에서 겪은 문제들과 함께 LCEL의 핵심 개념부터 고급 활용법까지 다뤄보겠습니다.

LCEL이란 무엇인가?

LCEL은 LangChain 0.1.0부터 도입된 체인 구성 언어로, 프로미스 기반의 파이프라인 방식으로 LLM 체인을 선언적으로 정의할 수 있게 해줍니다. 전통적인 LangChain 체인은 복잡한 객체 생성과 메서드 체이닝이 필요했지만, LCEL은 Unix 파이프라인처럼 직관적인 문법으로 구성 요소를 연결합니다.

제가 구축한 이커머스 시스템에서는 상품 검색, 리뷰 분석, 추천 생성,客服 자동응답 등 8개의 서브 체인을 LCEL로 조합했습니다. 그 결과 코드 라인 수가 기존 대비 60% 감소하고, 유지보수성이 크게 향상되었습니다.

기초: Runnable 인터페이스와 체인 구성

LCEL의 핵심은 모든 구성 요소가 Runnable 인터페이스를 구현한다는 점입니다. 이를 통해 동일한 패턴으로 PromptTemplate, LLM, OutputParser, 도구 등을 연결할 수 있습니다.

기본 체인 구성

# LangChain LCEL 기본 체인 구성
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

HolySheep AI API 설정

llm = ChatOpenAI( model="gpt-4.1", base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY", temperature=0.7, max_tokens=2048 )

프롬프트 템플릿 정의

prompt = ChatPromptTemplate.from_messages([ ("system", "당신은 {product_category} 전문가입니다. 사용자의 질문에 정확하고 친절하게 답변해주세요."), ("user", "{user_question}") ])

출력 파서 정의

output_parser = StrOutputParser()

LCEL 체인 구성: | 연산자로 연결

chain = prompt | llm | output_parser

체인 실행

result = chain.invoke({ "product_category": "전자제품", "user_question": "최신 노트북 구매 가이드를 알려주세요" }) print(result)

출력: 전자제품 전문가로서 최신 노트북 구매 시 고려사항을 안내드립니다...

실전 활용: 이커머스 AI 고객 서비스 시스템

제가 구축한 이커머스 AI 고객 서비스 시스템은 주문 조회, 배송 추적, 교환/반품, 상품 추천 등 복합적인 기능을 LCEL 체인으로 구현했습니다. 특히 HolySheep AI의 다중 모델 통합 기능을 활용하여 작업 특성에 따라 최적의 모델을 선택적으로 사용합니다.

# 이커머스 AI 고객 서비스: 다중 모델 LCEL 체인
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.runnables import RunnableParallel, RunnableBranch

HolySheep AI: 다양한 모델 설정

fast_llm = ChatOpenAI( model="gpt-4.1", base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY", temperature=0.3, max_tokens=512 ) # 빠른 응답용: $8/MTok quality_llm = ChatAnthropic( model="claude-sonnet-4-20250514", base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY", max_tokens=2048 ) # 고품질 응답용: $15/MTok

목적 분류 체인

category_prompt = ChatPromptTemplate.from_messages([ ("system", "고객 문의를 다음 카테고리로 분류해주세요: order, shipping, return, product_recommendation, general"), ("user", "{inquiry}") ]) category_chain = category_prompt | fast_llm | JsonOutputParser()

카테고리별 응답 체인 정의

order_response = ChatPromptTemplate.from_messages([ ("system", "주문 관련 전문가로서 정확한 주문 정보를 제공해주세요."), ("user", "{inquiry}") ]) | quality_llm shipping_response = ChatPromptTemplate.from_messages([ ("system", "배송 전문가로서 실시간 배송 현황을 안내해주세요."), ("user", "{inquiry}") ]) | quality_llm return_response = ChatPromptTemplate.from_messages([ ("system", "교환/반품 전문가로서 정확한 절차를 안내해주세요."), ("user", "{inquiry}") ]) | quality_llm recommend_response = ChatPromptTemplate.from_messages([ ("system", "상품 추천 전문가로서 고객 취향에 맞는 상품을 추천해주세요."), ("user", "{inquiry}") ]) | quality_llm general_response = ChatPromptTemplate.from_messages([ ("system", "친절한客服로서 일반 문의에 답변해주세요."), ("user", "{inquiry}") ]) | fast_llm

RunnableBranch로 카테고리별 분기 처리

branch_chain = RunnableBranch( ("order", order_response), ("shipping", shipping_response), ("return", return_response), ("product_recommendation", recommend_response), general_response )

통합客服 체인: 분류 -> 분기 -> 응답

customer_service_chain = category_chain | branch_chain

실제 실행

response = customer_service_chain.invoke({ "inquiry": "지난주에 주문한 신발이 아직 도착하지 않았어요. 배송 상황을 알려주세요." }) print(f"카테고리: {response.get('category', 'N/A')}") print(f"응답: {response.get('response', response)}")

고급 패턴: RAG 체인과 문서 처리

기업 내부 지식 베이스 RAG 시스템에서는 문서 임베딩, 벡터 검색, 재순위화, hallucination 방지 등 복잡한 파이프라인이 필요합니다. LCEL의 RunnableParallel과 RunnableSequence를 활용하면 이 모든 것을 선언적으로 구성할 수 있습니다.

# 기업 RAG 시스템: 문서 검색 및 응답 생성
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough

HolySheep AI 임베딩 및 LLM 설정

embeddings = OpenAIEmbeddings( model="text-embedding-3-large", base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY" ) llm = ChatOpenAI( model="gpt-4.1", base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY", temperature=0.2, max_tokens=2048 )

벡터 스토어 로드 (기존 인덱스)

vectorstore = FAISS.load_local( "company_knowledge_index", embeddings, allow_dangerous_deserialization=True ) retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

컨텍스트 포맷팅

def format_docs(docs): return "\n\n".join([f"[문서 {i+1}] {doc.page_content}" for i, doc in enumerate(docs)])

RAG 체인 구성

rag_prompt = ChatPromptTemplate.from_messages([ ("system", """당신은 기업 내부 지식 베이스 기반 AI 어시스턴트입니다. 아래 제공된 문서를 기반으로 정확하고 상세한 답변을 제공해주세요. 문서에 관련 정보가 없는 경우 '문서에 해당 정보가 없습니다'라고 명시적으로 답변해주세요. [문서] {context}"""), ("user", "{question}") ])

RunnableParallel로 검색과 프롬프트 준비 동시 수행

setup = RunnableParallel( {"context": retriever | format_docs, "question": RunnablePassthrough()} )

최종 RAG 체인

rag_chain = setup | rag_prompt | llm

쿼리 실행 예시

result = rag_chain.invoke("2024년 마케팅 예산 배분 계획은 무엇인가요?") print(result.content)

성능 최적화: 배치 처리와 캐싱

대규모 문서 처리나 실시간 트래픽 환경에서는 배치 처리와 응답 캐싱이 핵심입니다. LCEL은 native하게 배치 처리를 지원하며, 캐싱 레이어를 쉽게 추가할 수 있습니다.

# 배치 처리 및 캐싱이 적용된 제품 리뷰 분석 체인
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.runnables import RunnableParallel
from langchain.cache import InMemoryCache
from langchain.globals import set_llm_cache

HolySheep AI LLM 설정

llm = ChatOpenAI( model="gpt-4.1", base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY", temperature=0.1, max_tokens=512 )

LLM 캐싱 활성화

set_llm_cache(InMemoryCache())

리뷰 분석 프롬프트

review_prompt = ChatPromptTemplate.from_messages([ ("system", "제품 리뷰를 분석하여 감정(positive/negative/neutral), 핵심 이슈, 평점을 추출해주세요."), ("user", "리뷰: {review_text}\n제품명: {product_name}") ])

출력 파서

parser = JsonOutputParser()

LCEL 체인 구성

review_analysis_chain = review_prompt | llm | parser

배치 처리 예시: 100개 리뷰 동시 분석

batch_reviews = [ {"product_name": "무선 헤드폰", "review_text": "음질이 훌륭하고 착용감이 편안합니다. 배터리가 오래 갑니다."}, {"product_name": "무선 헤드폰", "review_text": "소리가 잘 안 들리고 连接이 자주 끊겨요."}, # ... 98개 더 ]

배치 실행 (순차 처리)

results = [] for review in batch_reviews: result = review_analysis_chain.invoke(review) results.append(result)

동시 배치 실행 (성능 최적화)

from langchain_core.runnables import RunnableBatch batch_results = list(review_analysis_chain.batch(batch_reviews)) print(f"처리 완료: {len(batch_results)}개 리뷰 분석")

모듈식 아키텍처: 서브 체인 조합

복잡한 AI 시스템에서는 기능을 모듈화하여 재사용 가능한 서브 체인을 만드는 것이 중요합니다. 저는 각 기능을 독립적인 체인으로 분리한 뒤, 필요에 따라 조합하는 방식으로 아키텍처를 설계했습니다.

# 모듈식 서브 체인 구성 예시
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda, RunnableBranch

HolySheep AI 설정

base_url = "https://api.holysheep.ai/v1" api_key = "YOUR_HOLYSHEEP_API_KEY"

서브 체인 1: 의도 분류

intent_classifier = ( ChatPromptTemplate.from_template( "사용자 메시지: {message}\n의도를 분류: inquiry, complaint, compliment, order_request" ) | ChatOpenAI(model="gpt-4.1", base_url=base_url, api_key=api_key, temperature=0.1) )

서브 체인 2: 감정 분석

sentiment_analyzer = ( ChatPromptTemplate.from_template( "리뷰/메시지: {text}\n감정 점수(-1~1)와 이유를 분석해주세요." ) | ChatOpenAI(model="gpt-4.1", base_url=base_url, api_key=api_key, temperature=0.1) )

서브 체인 3: 응답 생성

response_generator = ( ChatPromptTemplate.from_template( "Intent: {intent}, Sentiment: {sentiment}\n최적의 고객 응대 응답을 생성해주세요." ) | ChatOpenAI(model="gpt-4.1", base_url=base_url, api_key=api_key, temperature=0.7) )

서브 체인 4: 긴급도 판별

urgency_detector = ( ChatPromptTemplate.from_template( "고객 메시지: {message}\n긴급도 수준을 판별: critical, high, normal, low" ) | ChatOpenAI(model="gpt-4.1", base_url=base_url, api_key=api_key, temperature=0.1) )

메인 체인: 서브 체인 조합

def combine_analysis(input_dict): return { "intent": input_dict["intent"], "sentiment": input_dict["sentiment"], "message": input_dict["message"] }

병렬 분석 후 조합

parallel_analysis = RunnableParallel( intent=intent_classifier, sentiment=sentiment_analyzer, urgency=urgency_detector, message=lambda x: x["message"] )

최종 응답 생성 체인

main_chain = parallel_analysis | combine_analysis | response_generator

실행

result = main_chain.invoke({ "message": "배송이 2주나 늦어졌는데 아무런 안내도 없어요. 정말 실망스럽습니다." }) print(f"의도: {result.get('intent', 'N/A')}") print(f"감정: {result.get('sentiment', 'N/A')}") print(f"응답: {result.get('content', result)}")

HolySheep AI 통합: 비용 최적화 전략

실제 운영 환경에서는 응답 품질과 비용 사이의 균형이 핵심입니다. HolySheep AI의 다중 모델 통합 기능을 활용하면 작업 특성에 따라 최적의 모델을 선택하여 비용을 최적화할 수 있습니다.

제가 적용한 전략은 이렇습니다: 빠른 분류나 임시 응답에는 Gemini 2.5 Flash($2.50/MTok)를, 정확한 분석이나 고객-facing 응답에는 GPT-4.1($8/MTok) 또는 Claude Sonnet($15/MTok)을 사용합니다. 배치 처리에는 DeepSeek V3.2($0.42/MTok)를 활용하면 비용을 대폭 절감할 수 있어요.

# HolySheep AI: 비용 최적화 모델 선택 로직
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic

class CostOptimizedLLMRouter:
    """작업 유형에 따라 최적의 모델을 선택하는 라우터"""
    
    MODEL_CONFIG = {
        "fast_classification": {
            "provider": "openai",
            "model": "gpt-4.1",
            "cost_per_1k": 0.008,  # $8/MTok
            "use_case": "분류, 태깅, 짧은 응답"
        },
        "quality_response": {
            "provider": "anthropic", 
            "model": "claude-sonnet-4-20250514",
            "cost_per_1k": 0.015,  # $15/MTok
            "use_case": "긴 응답, 분석, 창작"
        },
        "batch_processing": {
            "provider": "openai",
            "model": "deepseek-chat",
            "cost_per_1k": 0.00042,  # $0.42/MTok
            "use_case": "대량 배치 처리"
        },
        "ultra_fast": {
            "provider": "openai",
            "model": "gpt-4.1",
            "cost_per_1k": 0.0025,  # $2.50/MTok
            "use_case": "간단한 변환, 포맷팅"
        }
    }
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        self.clients = {}
        self._init_clients()
    
    def _init_clients(self):
        self.clients["openai"] = ChatOpenAI(
            base_url=self.base_url,
            api_key=self.api_key
        )
        self.clients["anthropic"] = ChatAnthropic(
            base_url=self.base_url,
            api_key=self.api_key
        )
    
    def get_llm(self, task_type: str) -> any:
        config = self.MODEL_CONFIG.get(task_type, self.MODEL_CONFIG["fast_classification"])
        return self.clients[config["provider"]], config
    
    def estimate_cost(self, task_type: str, input_tokens: int, output_tokens: int) -> float:
        config = self.MODEL_CONFIG.get(task_type)
        if not config:
            return 0.0
        input_cost = (input_tokens / 1000) * config["cost_per_1k"]
        output_cost = (output_tokens / 1000) * config["cost_per_1k"] * 2  # 출력은 2배
        return input_cost + output_cost

사용 예시

router = CostOptimizedLLMRouter(api_key="YOUR_HOLYSHEEP_API_KEY")

태스크별 모델 선택

llm, config = router.get_llm("fast_classification") print(f"선택된 모델: {config['model']} ({config['use_case']})")

비용 추정

cost = router.estimate_cost("batch_processing", input_tokens=100000, output_tokens=50000) print(f"예상 비용: ${cost:.4f}")

자주 발생하는 오류와 해결책

오류 1: LCEL 체인에서 None 값 전달

# ❌ 잘못된 예: 중간 단계에서 None 반환
chain = prompt | llm | (lambda x: None if x == "error" else x) | output_parser

✅ 올바른 예: RunnablePassthrough 또는 조건부 분기 사용

from langchain_core.runnables import RunnablePassthrough, RunnableBranch chain = RunnableBranch( (lambda x: x.get("input") == "error", RunnablePassthrough().assign(output="에러 처리됨")), prompt | llm | output_parser ).with_fallbacks([RunnablePassthrough().assign(output="폴백 응답")])

또는 if_else 사용

from langchain_core.runnables import RunnableIf chain = ( RunnablePassthrough.assign(is_error=lambda x: x.get("input") == "error") | RunnableIf( lambda x: x["is_error"], then_router=RunnablePassthrough().assign(output="에러 처리"), else_router=prompt | llm | output_parser ) )

오류 2: 비동기 체인과 동기 코드 혼용

# ❌ 잘못된 예: async chain.invoke() 혼용
async def process():
    result = await chain.ainvoke({"question": "..."})  # 이것만 async
    sync_result = chain.invoke({"question": "..."})  # 이것은 sync - 충돌 가능
    return result, sync_result

✅ 올바른 예: 일관된 비동기 처리

from langchain_core.runnables import RunnableParallel async def process_batch(questions: list): # 배치 동시 실행 tasks = [chain.ainvoke({"question": q}) for q in questions] results = await RunnableParallel(*tasks).ainvoke({}) return results

또는 전체를 async로 처리

async def process_sequential(questions: list): results = [] for q in questions: result = await chain.ainvoke({"question": q}) results.append(result) return results

동시+순차 혼합: 중요도 순으로 처리

async def process_mixed(questions: list, priorities: list): results = {} # 高우선순위 동시 처리 high_priority = [q for q, p in zip(questions, priorities) if p == "high"] if high_priority: tasks = [chain.ainvoke({"question": q}) for q in high_priority]