runnable

topics 100-데이터분석 & AI 108 활용,도구
types 이론 레퍼런스
tags
references

Runnable

LangChain에서 Runnable이란?

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

왜 필요한가?

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

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

해결: Runnable로 표준화

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

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


주요 특징

1. 표준화된 인터페이스

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

# <span id="모두-같은-방식으로-실행"></span>모두 같은 방식으로 실행
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

# <span id="숫자에-1-더하고-2-곱하는-두-runnable-정의"></span>숫자에 1 더하고, 2 곱하는 두 Runnable 정의
r1 = RunnableLambda(lambda x: x + 1)
r2 = RunnableLambda(lambda x: x * 2)

# <span id="순차적으로-연결"></span>순차적으로 연결
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

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

# <span id="파이프라인으로-연결"></span>파이프라인으로 연결
chain = prompt | model | parser

# <span id="실행"></span>실행
result = chain.invoke({"topic": "cats"})

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


장점과 단점

장점

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

단점

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