3장. LangChain 기본 구조와 LCEL
Runnable 과 invoke·batch·stream
커피 전문점의 기계는 회사마다 생김새가 달라도 버튼은 비슷합니다. "한 잔", "여러 잔 연속", 그리고 우유 거품처럼 조금씩 나오는 것을 지켜보는 기능이 있습니다. 새 기계로 바꿔도 직원은 같은 버튼을 누르면 됩니다. LangChain 의 부품도 모두 같은 버튼, 곧 같은 손잡이를 가집니다.
세 상자가 모든 러너블에 달린 세 손잡이입니다. invoke 는 입력 하나로 결과 하나를, batch 는 입력 목록으로 결과 목록을, stream 은 결과를 조각조각 받습니다. 여기서 커피 기계가 러너블이고, 버튼이 손잡이입니다. 비유와 다른 점은, 커피 기계는 버튼마다 만드는 것이 다르지만 러너블의 세 손잡이는 결과가 같고 받는 방식만 다르다는 것입니다.
세 손잡이
| 손잡이 | 넣는 것 | 받는 것 | 어울리는 때 |
|---|---|---|---|
invoke(입력) | 입력 하나 | 결과 하나 | 질문 하나에 답 하나 |
batch([입력, …]) | 입력 목록 | 같은 차례의 결과 목록 | 손님 질문 여러 개를 한꺼번에 시험할 때 |
stream(입력) | 입력 하나 | 결과의 조각을 차례로 | 챗봇 화면에 답이 타자 치듯 나오게 할 때 |
프롬프트 템플릿, 모델, 파서, 체인이 모두 이 셋을 가집니다. 그래서 가짜 모델로 시험하던 코드에 진짜 모델을 끼워도 부르는 줄은 바꿀 필요가 없습니다.
짧은 예시
글자 수를 세는 파이썬 함수 len 을 러너블로 바꿔 세 손잡이를 눌러 봅니다. 모델의 stream 은 가짜 모델로 봅니다.
from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.runnables import RunnableLambda
# RunnableLambda: 보통 함수를 러너블로 바꾼다. 이제 invoke, batch, stream 을 쓸 수 있다
count = RunnableLambda(len)
print(count.invoke("제주도"))
print(count.batch(["제주도", "강릉", "부산 해운대"]))
# 가짜 모델의 stream 은 답을 한 글자씩 조각(chunk)으로 내놓는다
model = FakeListChatModel(responses=["동쪽 해안을 추천합니다."])
for chunk in model.stream("어디로 갈까요?"):
print(repr(chunk.content), end=" ")
print()
print(type(chunk).__name__)3
[3, 2, 6]
'동' '쪽' ' ' '해' '안' '을' ' ' '추' '천' '합' '니' '다' '.'
AIMessageChunkbatch 는 넣은 차례 그대로 [3, 2, 6] 을 돌려줍니다. stream 의 조각은 AIMessageChunk(chunk 는 "덩어리, 조각")라는 꼴이고, 조각마다 .content 에 글이 조금씩 들어 있습니다. 가짜 모델은 한 글자씩 자르지만, 진짜 모델은 토큰 몇 개씩 묶어 보냅니다.
코드가 의미하는 것
이 코드는 커피 기계의 세 버튼을 차례로 눌러 보는 일을 흉내 낸 것입니다. 보통 함수 len 에 세 버튼을 달아 invoke 와 batch 를 누르고, 가짜 모델에서 stream 을 누릅니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
RunnableLambda(len) | 보통 함수를 러너블로 바꾼다 | 내가 쓴 함수도 세 손잡이를 가진 부품이 됩니다. 체인 사이에 끼워 넣을 수 있습니다 |
count.invoke("제주도") | 입력 하나로 결과 하나를 받는다 | 질문 하나에 답 하나를 받는 가장 흔한 부르기입니다 |
count.batch([...]) | 입력 목록으로 결과 목록을 받는다 | 손님 질문 여러 개를 한꺼번에 시험할 때 누르는 버튼입니다 |
model.stream(...) | 결과를 조각으로 차례차례 내놓는다 | 챗봇 화면에 답이 타자 치듯 나오게 하는 버튼입니다. 7장의 웹 화면에서 씁니다 |
chunk.content | 조각 하나에 든 글을 꺼낸다 | 조각마다 글이 조금씩 들어 있어, 화면에 이어 붙여 보여 줍니다 |
결과에서 읽을 것은 세 가지입니다.
- 같은 부품에 버튼만 다릅니다.
invoke는 3 하나를,batch는[3, 2, 6]목록을 돌려줬습니다. 계산은 같고 받는 방식만 다릅니다. - stream 은 빈칸까지 조각으로 옵니다. 셋째 줄에서
' '도 조각 하나로 왔습니다. 화면에 보여 줄 때는 조각을 고치지 않고 그대로 이어 붙입니다. - 조각의 꼴은 AIMessageChunk 입니다. 마지막 줄처럼 모델의 stream 은
AIMessage의 조각을 내놓습니다. 파서를 이으면 글 조각으로 바뀌는데, 실습 5에서 봅니다.
주의할 점
batch는 여러 입력을 동시에(나란히) 처리합니다. 진짜 모델이라면 요청이 한꺼번에 여러 개 나가므로, 계정의 분당 요청 한도를 넘지 않게config={"max_concurrency": 2}처럼 동시에 보낼 수를 줄일 수 있습니다. concurrency 는 "동시에 일어남"이라는 뜻입니다.stream은 조각을 내놓는 "발전기(generator)"를 돌려줍니다.for로 돌려야 조각이 나오고, 한 번 다 돌면 다시 쓸 수 없습니다.