LangChain

[LangChain] 랭체인의 LCEL 구성 컴파일

sian han 2026. 4. 27. 19:17

※ Runnable 인터페이스

 

이전 글에서 모델, 프롬프트 템플릿, 출력 파서의 형태에 대해 알아봤다. 이들은 서로 다른 구성 요소를 호출했지만, 유사한 인터페이스와 invoke() 메서드를 사용했다. Runnable 인터페이스는 랭체인의 모든 컴포넌트가 동일한 규격의 입출력 포트를 가지도록 강제하는 표준 프로토콜이다. 만약 각 컴포넌트마다 실행하는 메서드 이름이 제각각이라면, 이들을 하나로 묶기가 매우 까다로울 것이다. 

 

어떤 랭체인 객체이든 Runnable 인터페이스를 상속받았다면 다음 세가지 메서드를 기본적으로 지원한다. 

 

  • invoke : 하나의 입력을 하나의 출력으로 변환한다.
  • batch : 여러 입력을 여러 출력으로 변환한다.
  • stream : 하나의 입력이 생성하는 출력 결과를 실시간으로 전달한다. 

컴포넌트가 반복 출력을 지원하지 않아 모든 출력을 하나로 모아 구성하는 경우도 있는데, 방식은 두가지가 있다.

= 이 상황은 여러 번 호출해서 얻은 데이터를 하나의 Response 로 합쳐야 하는 상황을 의미한다. LLM 에게 질문을 던졌을 때, 한 번의 호출로 모든 답을 완벽히 얻기 어려울 수 있다.

 

  • 명령형 : 직접 model.invoke(...) 를 같은 메서드로 구성 요소를 호출한다.
  • 선언형 : 랭체인 표현 언어 (LCEL LangChain Expression Language) 를 사용한다. 

 

▶ 명령형 구성 (imperative composition)

명령형 구성은 단지 익숙하게 코드를 작성하는 방식의 다른 이름이다.

각 구성 요소를 함수와 클래스로 결합하는 행위가 명령형 구성이다. 

 

아래 예시는 프롬프트와 채팅 모델을 사용해 구성한 챗봇이다. 

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

# 1. 컴포넌트들을 각각 독립적으로 준비
model = ChatOpenAI(model="gpt-4o")
prompt_template = PromptTemplate.from_template("{language}의 주요 특징을 한 문장으로 알려줘.")
parser = StrOutputParser()

# 2. 처리할 데이터 리스트
languages = ["Java", "Python", "Golang"]

# 3. 명령형 방식: 개발자가 직접 흐름을 제어 (Iteration & Aggregation)
final_results = []

for lang in languages:
    # 단계 1: 프롬프트 조립 (PromptValue 생성)
    # 랭체인 내부의 f-string 문법이 여기서 동작합니다.
    prompt_value = prompt_template.invoke({"language": lang})
    
    # 단계 2: 모델 호출 (AIMessage 생성)
    ai_message = model.invoke(prompt_value)
    
    # 단계 3: 출력 파싱 (String 추출)
    parsed_result = parser.invoke(ai_message)
    
    # 단계 4: 결과 모으기
    final_results.append(f"[{lang}]: {parsed_result}")

# 4. 최종 출력
print("\n".join(final_results))

 

final_results.append() 에서 모든 출력을 하나로 모으고 있다. 컴포넌트 자체가 리스트를 한번에 다 처리해서 주는 기능을 지원하지 않더라도, 개발자가 외부 변수 (final_result) 를 사용해 출력을 하나하나 수집하여 최종 결과물을 구성하기 때문이다. 

 

예상출력 결과는 아래와 같다

[Java]: 정적 타이핑과 객체지향 설계를 바탕으로 대규모 엔터프라이즈 시스템 구축에 최적화된 견고한 언어입니다!!!
[Python]: 간결한 문법과 방대한 라이브러리를 통해 데이터 과학부터 백엔드 개발까지 빠른 생산성을 제공하는 언어입니다!!!
[Golang]: 강력한 동시성 제어와 컴파일 속도를 자랑하며 클라우드 네이티브 환경 및 마이크로서비스 구축에 특화된 언어입니다!!!

 

 

▶ 선언형 구성

LCEL (LangChain Expression Language) 는 선언형 언어를 이용해 랭체인 요소를 구성한다.

랭체인은 LCEL 구성을 최적화된 실행 계획으로 컴파일하며, 자동병렬화, 스트리밍, 추적 및 비동기 지원을 수행한다. 

 

최적화된 실행계획으로 컴파일이란,

마치 고수준의 SQL 쿼리를 작성하면, DB 엔진이 가장 빠른 실행계획을 세워 결과를 가져오는 과정같다. 

LCEL은 단순히 코드를 짧게 만들어주는 것이 아니라, 프레임워크 수준의 도구이다. 

 

  • 명령형 코드 : 절차 중심으로 작성한다
    • ex ) 1단계로 프롬프트를 만들고, 변수를 할당하고, 모델을 부르고, 에러가 나면 재시도하고...
  • 선언형 코드 (LCEL): chain = prompt | model | parser (관계 중심)

이와 같이 파이프로 연결하는 순간, 랭체인은 내부적으로 이 연결 정보를 그래프(Graph) 구조로 저장한다. 

개발자는 파이프라인의 입출력 관계만 선언했을 뿐이지만, 랭체인은 이미 전체 지도를 갖게 된다. 

 

▷ 랭체인의 LCEL 구성 컴파일

 작성된 LCEL 체인을 실행하는 순간, 랭체인 엔진은 이 그래프를 분석하여 가장 효율적인 실행 경로를 계산한다. 이것이 랭체인의 컴파일 과정이다. 

 

아래는 "최적화된 실행 계획으로 컴파일" 한다는 것의 상세 예시이다

 

1. 병렬 실행 자동화

만약 체인 안에 여러개의 독립적인 작업이 있다면, 랭체인은 사용자가 멀티스레딩 코드를 짜지 않아도 알아서 동시에 실행한다. 

 

2. 스트리밍 및 비동기 최적화

LCEL 로 구성된 체인은 내부의 모든 컴포넌트가 동일한 인터페이스(Runnable)을 따른다. 랭체인의 모든 컴포넌트(프롬프트, 모델, 파서)가 Runnable 이라는 규격을 따르고 있다는 것은, 모든 부품들이 실시간 스트리밍 모드(stream 메서드)를 공통으로 지원한다는 뜻이다. 

  • 모델 : 텍스트를 한꺼번에 주지 않고 토큰 단위로 뱉어냄
  • 파서 : 들어온 텍스트 조각을 즉시 해석해서 다음 단계로 넘길 수 있도록 설계됨

일반적인 명령형 코드에서는 데이터를 다 받을 때까지 기다렸다가 다음 단계로 넘기지만, LCEL은 데이터를 조각 단위로 쪼개서 실시간으로 흘려보낸다. 랭체인은 이를 분석하여, 첫번째 단어가 생성하자마자 파서를 통과시켜 사용자에게 전달하는 최단 거리의 데이터 파이프라인을 구축한다. 

 

  • 예제 : "안녕하세요, 반가워요"
    • 1. 모델이 "안" 이라는 첫 토큰을 뱉음
    • 2. "안" 은 Runnable 인터페이스를 타고 즉시 파서로 흐름
    • 3. 파서는 "안"을 받자마다 가공해서 사용자에게 바로 전달함
    • 4. 사용자는 모델이 전체 문장을 다 만들기도 전에 화면에서 첫 글자가 나타나는 것을 보게 됨

 

3. 지연 시간 최소화

데이터가 모델 내부에서 파서로, 파서에서 사용자에게로 이동할때 완성될 때까지 기다리는 정체 구간이 없음. 

 


 

랭체인을 사용해 LLM 애플리케이션을 구축하는 필요한 구성요소는 아래와 같다. 

 

  • LLM 모델: 예측을 수행함
  • 프롬프트 지시사항 : 바람직한 결과물을 도출하도록 유도함
  • 출력 파서 : 모델의 출력 형식을 변환함

LLM 애플리케이션은 위와 같은 구성요소로 체인을 형성한다. 

 

모든 랭체인 구성 요소는 다양한 입력과 출력을 처리하는 동일한 인터페이스를 공유한다. 

다음은 AI 챗봇에 외부 데이터를 컨텍스트로 제공하는 방법을 익혀보자.