runnable

topics
types
tags
#langchain #runnable #llm #lcel
references

Runnable

LangChain에서 Runnable이란?

Runnable은 LangChain에서 실행 가능한 컴포넌트를 의미한다. 즉, 입력을 받아서 어떤 처리를 수행한 뒤, 결과를 반환하거나 다음 컴포넌트로 넘기는 역할을 하는 객체다.

왜 필요한가?

문제: LLM 워크플로우가 복잡해진다

  • 프롬프트 → 모델 → 파서 → 다른 모델 → ... 이렇게 체인이 길어진다
  • 각 단계를 어떻게 연결하고 관리할까?
  • 비동기, 배치, 스트리밍 등 다양한 실행 방식이 필요하다

해결: Runnable로 표준화

  • 모든 컴포넌트를 같은 방식(invoke)으로 실행
  • 파이프라인처럼 연결 (| 연산자)
  • 재사용 가능한 독립적인 블록

왜 이렇게 만들었을까?
LangChain 초기에는 체인을 만들 때마다 다른 방식으로 연결해야 했다. Runnable이 등장하면서 모든 컴포넌트를 일관되게 다룰 수 있게 됐다.


주요 특징

1. 표준화된 인터페이스

모든 Runnable 객체는 invoke 메서드로 실행된다. 프롬프트든 모델이든 파서든 동일한 방식이다.

# 모두 같은 방식으로 실행
result = prompt.invoke(input)
result = model.invoke(input)
result = parser.invoke(input)

2. 조합 가능성 (파이프라인)

| 연산자로 여러 Runnable을 연결할 수 있다. Unix 파이프와 비슷한 개념이다.

chain = prompt | model | parser
result = chain.invoke(input)

3. 다양한 실행 방식

  • invoke(input): 단일 입력, 동기 실행
  • batch(inputs): 여러 입력 한번에 처리
  • stream(input): 결과를 스트리밍으로 받기
  • ainvoke, abatch, astream: 각각의 비동기 버전

4. 재사용성

Runnable 객체는 독립적으로 정의하고, 여러 곳에서 재사용할 수 있다.


Runnable의 주요 종류

종류 설명 사용 예시
RunnablePassthrough 입력을 그대로 넘기거나 추가 필드만 더해 전달 RunnablePassthrough()
RunnableLambda 사용자 정의 함수를 Runnable로 변환 RunnableLambda(lambda x: x*2)
RunnableSequence 여러 Runnable을 순차적으로 연결 r1 | r2 | r3
RunnableParallel 여러 Runnable을 병렬로 실행 { "a": r1, "b": r2 }
RunnableBranch 조건에 따라 다음 Runnable을 선택 조건 분기 처리

실전 예제

간단한 체인

from langchain_core.runnables import RunnableLambda

# 숫자에 1 더하고, 2 곱하는 두 Runnable 정의
r1 = RunnableLambda(lambda x: x + 1)
r2 = RunnableLambda(lambda x: x * 2)

# 순차적으로 연결
sequence = r1 | r2
print(sequence.invoke(1))  # 결과: 4  (1+1=2, 2*2=4)

LLM 체인

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

# 각 단계를 Runnable로 정의
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
model = ChatOpenAI()
parser = StrOutputParser()

# 파이프라인으로 연결
chain = prompt | model | parser

# 실행
result = chain.invoke({"topic": "cats"})

왜 이렇게 할까?
각 단계를 독립적으로 테스트하고, 필요하면 다른 모델이나 파서로 쉽게 교체할 수 있다.


장점과 단점

장점

  • 코드가 간결해진다 (파이프라인으로 표현)
  • 각 단계를 독립적으로 테스트 가능
  • 동기/비동기, 배치, 스트리밍 모두 지원
  • 재사용성이 높다

단점

  • 처음 보면 개념이 생소하다
  • 디버깅이 조금 어려울 수 있다 (어느 단계에서 문제인지)
  • 간단한 작업에는 오버엔지니어링일 수 있다