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"})
왜 이렇게 할까?
각 단계를 독립적으로 테스트하고, 필요하면 다른 모델이나 파서로 쉽게 교체할 수 있다.
장점과 단점
장점
- 코드가 간결해진다 (파이프라인으로 표현)
- 각 단계를 독립적으로 테스트 가능
- 동기/비동기, 배치, 스트리밍 모두 지원
- 재사용성이 높다
단점
- 처음 보면 개념이 생소하다
- 디버깅이 조금 어려울 수 있다 (어느 단계에서 문제인지)
- 간단한 작업에는 오버엔지니어링일 수 있다