3장. LangChain 기본 구조와 LCEL
RunnablePassthrough·RunnableLambda·RunnableParallel 로 체인 넓히기
주방의 벨트가 한 줄만 있으면 한 번에 한 가지 요리만 나옵니다. 큰 식당은 주문서를 복사해 국 담당과 반찬 담당에게 동시에 보내고, 끝에 쟁반 하나에 모아 냅니다. 주문서 원본은 계산대에 그대로 넘깁니다. LangChain 에도 이렇게 벨트를 나누고, 그대로 넘기고, 내 손으로 한 가지 일을 끼워 넣는 부품이 있습니다.
세 상자가 체인을 넓히는 세 부품입니다. RunnableLambda 는 내 함수를 벨트에 세우고, RunnablePassthrough 는 받은 것을 그대로 넘기고, RunnableParallel 은 같은 입력을 여러 갈래에 동시에 넣어 결과를 사전 하나로 모읍니다. 여기서 주방에 새로 세운 직원이 RunnableLambda, 계산대로 가는 원본이 RunnablePassthrough, 국과 반찬을 동시에 만드는 두 갈래 벨트가 RunnableParallel 입니다. 비유와 다른 점은, 주방은 갈래마다 다른 주문서를 쓸 수 있지만 RunnableParallel 은 모든 갈래에 똑같은 입력을 넣는다는 것입니다.
세 부품
| 부품 | 하는 일 | 예 | 결과 |
|---|---|---|---|
RunnableLambda(함수) | 보통 함수를 부품으로 | RunnableLambda(len) | 함수의 결과 |
RunnablePassthrough() | 입력을 그대로 넘긴다 | 갈래 하나에 원래 질문을 남길 때 | 입력 그대로 |
RunnablePassthrough.assign(이름=부품) | 입력 사전에 열쇠를 하나 더한다 | 질문에 낱말 수를 붙일 때 | 입력 사전 + 새 열쇠 |
RunnableParallel(이름=부품, …) | 같은 입력을 여러 부품에 넣는다 | 요약과 키워드를 한 번에 | {이름: 결과, …} 사전 |
assign 은 "몫을 정해 주다"라는 뜻입니다. 체인 안에서는 RunnableParallel(a=…, b=…) 대신 {"a": …, "b": …} 처럼 사전을 그대로 적어도 같은 뜻으로 읽힙니다.
짧은 예시
from langchain_core.runnables import RunnableLambda, RunnableParallel, RunnablePassthrough
# 앞뒤 빈칸을 지우는 작은 함수를 부품으로 만든다. lambda 는 이름 없는 한 줄 함수
clean = RunnableLambda(lambda text: text.strip())
# 같은 입력을 세 갈래에 넣고 결과를 사전 하나로 모은다
both = RunnableParallel(original=RunnablePassthrough(), cleaned=clean, length=RunnableLambda(len))
print(both.invoke(" 환불되나요? "))
# assign: 입력 사전은 그대로 두고 words 열쇠를 더한다
add = RunnablePassthrough.assign(words=lambda d: len(d["question"].split()))
print(add.invoke({"question": "예약 날짜를 바꿀 수 있나요?"})){'original': ' 환불되나요? ', 'cleaned': '환불되나요?', 'length': 9}
{'question': '예약 날짜를 바꿀 수 있나요?', 'words': 5}세 갈래가 같은 입력 " 환불되나요? " 를 받았습니다. original 은 빈칸까지 그대로, cleaned 는 빈칸을 지운 글, length 는 빈칸을 포함한 글자 수 9 입니다. assign 은 원래 질문을 지우지 않고 words 를 옆에 붙였습니다. 실습 5에서 이 부품으로 손님 글의 요약과 키워드를 한 번에 뽑습니다.
코드가 의미하는 것
이 코드는 주문서를 세 갈래로 복사해 넘기고, 원본에 메모를 덧붙이는 일을 흉내 낸 것입니다. 내 함수를 부품으로 만들고, 같은 입력을 세 갈래에 넣고, 원래 사전에 열쇠를 하나 더합니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
RunnableLambda(lambda text: text.strip()) | 앞뒤 빈칸을 지우는 함수를 부품으로 만든다 | 손님 글을 모델에 넣기 전에 다듬는 일처럼, 모델이 아닌 작은 일을 체인에 끼웁니다 |
RunnableParallel(original=…, cleaned=…, length=…) | 같은 입력을 세 부품에 넣고 결과를 사전 하나로 모은다 | 한 질문에서 여러 정보를 한 번에 뽑을 때 씁니다. 이름표가 사전의 열쇠가 됩니다 |
RunnablePassthrough() | 입력을 그대로 넘긴다 | 다듬기 전의 원래 글을 결과에 남겨 둡니다 |
RunnablePassthrough.assign(words=…) | 입력 사전에 새 열쇠를 더한다 | 원래 질문은 지우지 않고 계산한 값을 옆에 붙입니다. 다음 부품이 둘을 함께 쓸 수 있습니다 |
결과에서 읽을 것은 두 가지입니다.
- 세 갈래가 같은 입력을 받았습니다. 첫 줄의
original,cleaned,length가 모두" 환불되나요? "에서 나왔습니다. - assign 은 더하기만 합니다. 둘째 줄에 원래의
question이 그대로 있고words5 가 옆에 붙었습니다.
주의할 점
RunnableParallel의 갈래들은 동시에 돌기 때문에, 진짜 모델이 갈래마다 있으면 요청이 한꺼번에 여러 개 나갑니다. 요금은 갈래 수만큼 듭니다.RunnableLambda에 넣는 함수는 입력을 하나만 받습니다. 값이 여러 개 필요하면 사전 하나로 받아 안에서 꺼냅니다.