LangChain 챗봇3장 · LangChain 기본 구조와 LCEL9 / 21← 이전목차다음 →MetaContents

3장. LangChain 기본 구조와 LCEL

파이프로 잇기와 데이터가 바뀌는 모습

공장의 컨베이어 벨트를 따라가 보면, 같은 벨트 위의 물건이 자리마다 모습을 바꿉니다. 처음에는 밀가루였다가, 반죽이 되고, 빵이 되고, 봉지에 담긴 상품이 됩니다. 체인의 | 도 같습니다. 같은 질문이 부품을 하나 지날 때마다 다른 꼴의 값으로 바뀝니다.

사전, ChatPromptValue, AIMessage, 글의 네 꼴로 바뀌며 체인을 지나가는 데이터의 흐름도

왼쪽에서 오른쪽으로, 체인에 넣은 사전이 프롬프트를 지나 ChatPromptValue 로, 모델을 지나 AIMessage 로, 파서를 지나 글(str)로 바뀝니다. 상자 사이의 화살표 하나가 | 하나이고, 화살표마다 부품 하나가 일합니다. 여기서 밀가루가 사전, 반죽이 프롬프트 값, 빵이 AI 메시지, 봉지에 담긴 상품이 글입니다. 비유와 다른 점은, 공장의 빵은 되돌릴 수 없지만 체인은 중간 단계를 따로 불러 언제든 다시 들여다볼 수 있다는 것입니다.

자리마다 흐르는 것

자리꼴안에 든 것확인하는 법
체인에 넣기 전dict{"question": "취소하면 환불되나요?"}그냥 찍는다
프롬프트 뒤ChatPromptValue시스템 메시지와 사용자 메시지.to_messages()
모델 뒤AIMessage답의 글, 토큰 수 같은 정보.content, .usage_metadata
파서 뒤str답의 글만그냥 찍는다

이 표를 외워 두면 오류 글을 읽기 쉬워집니다. "dict 를 받아야 하는데 str 이 왔다" 같은 오류는 두 부품 사이의 꼴이 맞지 않는다는 뜻입니다.

짧은 예시

체인을 쓰지 않고 부품의 invoke 를 하나씩 불러, 자리마다 흐르는 꼴을 찍어 봅니다.

Python
from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([("system", "너는 구름다리여행의 여행 비서다."), ("human", "{question}")])
model = FakeListChatModel(responses=["출발 10일 전까지는 전액 환불됩니다."])
parser = StrOutputParser()

# prompt | model | parser 가 안에서 하는 일을 한 걸음씩 손으로 한다
step0 = {"question": "취소하면 환불되나요?"}
step1 = prompt.invoke(step0)
step2 = model.invoke(step1)
step3 = parser.invoke(step2)

for name, value in [("넣은 것", step0), ("프롬프트 뒤", step1), ("모델 뒤", step2)]:
    print(f"{name}: {type(value).__name__}")
print("파서 뒤: 글자열인가?", isinstance(step3, str))
# 모델의 답 글과 파서가 꺼낸 글이 같은지 견준다
print(step2.content == step3)
출력
넣은 것: dict
프롬프트 뒤: ChatPromptValue
모델 뒤: AIMessage
파서 뒤: 글자열인가? True
True

네 자리에서 꼴이 dict → ChatPromptValue → AIMessage → 글자열로 바뀌었습니다. 마지막 줄의 True 는 파서가 AIMessage 의 .content 를 그대로 꺼냈다는 뜻입니다. 실습 2에서 체인 사이에 엿보는 부품을 끼워 같은 것을 체인 안에서 봅니다.

코드가 의미하는 것

이 코드는 컨베이어 벨트를 멈추고 자리마다 물건을 집어 들어 모습을 확인하는 일을 흉내 낸 것입니다. prompt | model | parser 가 안에서 하는 세 번의 넘기기를 손으로 하고, 자리마다 꼴을 찍습니다.

코드하는 일챗봇에서 뜻하는 것
step1 = prompt.invoke(step0)사전을 프롬프트 값으로 바꾼다손님 질문이 모델에게 보낼 메시지가 되는 자리입니다
step2 = model.invoke(step1)프롬프트 값을 받아 AIMessage 를 돌려준다모델이 답을 쓰는 자리입니다. 토큰 수 같은 정보도 여기에 붙어 옵니다
step3 = parser.invoke(step2)AIMessage 에서 글만 꺼낸다화면에 보여 줄 글이 되는 자리입니다
step2.content == step3모델의 답 글과 파서가 꺼낸 글을 견준다파서가 답을 바꾸지 않고 꺼내기만 했는지 확인합니다

결과에서 읽을 것은 두 가지입니다.

  • 자리마다 꼴이 바뀝니다. 앞의 네 줄이 dict, ChatPromptValue, AIMessage, 글자열 차례입니다. 앞의 "자리마다 흐르는 것" 표와 같습니다.
  • 파서는 글을 고치지 않습니다. 마지막 줄의 True 는 파서가 .content 를 그대로 꺼냈다는 뜻입니다. 체인이 하는 일은 이 세 번의 넘기기를 대신 해 주는 것뿐입니다.

주의할 점

  • 파서를 빼고 prompt | model 까지만 이으면 체인의 결과는 AIMessage 입니다. 답의 글이 필요한데 AIMessage(content='…') 가 찍히면 파서를 빠뜨린 것입니다.
  • 토큰 수(usage_metadata)는 모델 뒤의 AIMessage 에만 있고, 파서를 지나면 사라집니다. 토큰을 세야 하면 파서 앞에서 꺼냅니다.
← 이전 절3장 · OutputParser 와 StrOutputParser다음 절 →3장 · 다양한 프롬프트 1: 역할·형식·제약 지시