LangChain 챗봇7장 · 웹 UI 연동과 배포7 / 22← 이전목차다음 →MetaContents

7장. 웹 UI 연동과 배포

챗봇 로직과 웹 화면 잇기

식당 홀 직원은 요리를 하지 않습니다. 손님의 주문을 받아 주문표에 적고, 주방에 주문을 넘기고, 나온 요리를 손님 앞에 놓을 뿐입니다. 주방장은 손님이 홀에 앉았는지, 포장 주문인지 몰라도 요리를 합니다. 일이 이렇게 나뉘어 있으면 주방장이 바뀌어도 홀은 그대로 돌아갑니다. 챗봇 앱도 이렇게 나눕니다. 화면 코드(홀)는 질문을 받고 말풍선을 그리기만 하고, 답을 만드는 일은 챗봇 로직(주방)의 함수에 맡깁니다.

질문 받기, 기록 꺼내기, 체인 부르기, 말풍선과 기록에 적기의 네 걸음을 이은 흐름도

질문 하나마다 네 상자를 지납니다. st.chat_input 으로 질문을 받고(1), st.session_state 에서 지금까지의 기록을 꺼내고(2), 기록과 질문을 챗봇 로직의 체인에 넘겨 답을 받고(3), 답을 말풍선으로 그리고 질문과 함께 기록에 적습니다(4). 4장 "대화 기억의 원리" 절의 네 걸음과 같은 차례이고, 기록철을 들고 다니는 자리에 st.session_state 가 들어갔습니다. 여기서 홀 직원이 화면 코드, 주방이 챗봇 로직의 함수, 주문표가 st.session_state 입니다. 비유와 다른 점은, 주방장은 주문표를 보지 않아도 요리하지만 챗봇 로직은 홀이 넘겨준 기록이 있어야 앞의 대화를 안다는 것입니다.

4차시의 부품이 들어갈 자리

4차시에서 쓴 것이 장에서 들어갈 자리까닭
store 사전과 세션 idst.session_state브라우저 탭마다 따로 있어 세션 id 를 따로 만들 필요가 없다
InMemoryChatMessageHistoryst.session_state.messages(사전 목록)화면에 다시 그리기 쉽고, 그대로 MessagesPlaceholder 에 넣을 수 있다
prompt | model | StrOutputParser()make_chain(model) 같은 함수화면과 떼어 두면 모델만 바꿔 끼우고, 화면 없이 시험할 수 있다
trim_messages 나 슬라이싱체인에 넘기기 전 messages[-10:]기록이 길어져도 보내는 양을 줄인다
6장의 RetrievalQA캐시한 함수가 만들어 돌려준다색인을 손님마다, 실행마다 다시 만들지 않는다

짧은 예시

4장의 대화 체인을 함수 두 개로 떼어 냅니다. Streamlit 을 부르지 않으므로 보통 파이썬으로 시험할 수 있습니다.

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


def make_chain(model):
    """4차시의 대화 체인을 함수로 떼어 낸다. 모델만 바꿔 끼울 수 있다"""
    prompt = ChatPromptTemplate.from_messages([
        ("system", "너는 구름다리여행의 여행 비서다."),
        MessagesPlaceholder("history"),
        ("human", "{question}"),
    ])
    return prompt | model | StrOutputParser()


def answer(chain, messages, question):
    """화면과 상관없는 챗봇 로직: 지금까지의 기록과 새 질문을 받아 답 글을 돌려준다"""
    return chain.invoke({"history": messages, "question": question})


chain = make_chain(FakeListChatModel(responses=["아이 둘과 제주도라면 동쪽 해안을 추천합니다."]))
# session_state 에 담을 꼴 그대로 {"role", "content"} 사전 목록을 넘긴다
messages = [{"role": "user", "content": "제주도 가요"}, {"role": "assistant", "content": "며칠 일정인가요?"}]

print("답:", answer(chain, messages, "아이 둘이랑 2박 3일이요"))
# chain.first 는 체인의 첫 부품(프롬프트)이다. 모델에 가는 메시지의 역할을 본다
sent = chain.first.invoke({"history": messages, "question": "아이 둘이랑 2박 3일이요"}).to_messages()
print("모델에 가는 메시지:", [m.type for m in sent])
출력
답: 아이 둘과 제주도라면 동쪽 해안을 추천합니다.
모델에 가는 메시지: ['system', 'human', 'ai', 'human']

{"role": "user"} 사전이 프롬프트 안에서 human 메시지로, {"role": "assistant"} 가 ai 메시지로 바뀌어 시스템 지시와 새 질문 사이에 끼워졌습니다. 화면 코드는 answer(chain, st.session_state.messages, question) 한 줄만 부르면 됩니다. 실습 2에서 이 두 함수를 채팅 화면에 붙입니다.

코드가 의미하는 것

이 코드는 주방을 홀과 떼어 따로 차린 것입니다. 체인을 만드는 함수와 답을 받는 함수 두 개를 두고, 홀이 넘길 주문표(사전 목록)를 넣어 봅니다.

코드하는 일챗봇에서 뜻하는 것
def make_chain(model):프롬프트, 모델, 파서를 이은 체인을 돌려준다모델을 인자로 받으므로 가짜 모델과 ChatOpenAI 를 바꿔 끼우기만 하면 됩니다
MessagesPlaceholder("history")기록이 들어갈 빈자리를 둔다4장에서 쓴 것과 같은 자리입니다. 화면의 st.session_state.messages 가 여기에 들어갑니다
def answer(chain, messages, question):기록과 질문을 받아 답 글을 돌려준다홀이 부르는 주방의 창구 하나입니다. 화면 코드는 이 함수만 알면 됩니다
chain.first.invoke(…).to_messages()체인의 첫 부품(프롬프트)만 돌려 모델에 갈 메시지를 본다모델을 부르지 않고 "무엇이 모델에 가는가"를 확인하는 방법입니다

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

  • 사전 꼴 기록이 메시지로 바뀌어 끼워졌습니다. 역할 목록 ['system', 'human', 'ai', 'human'] 에서 가운데 두 개가 넘긴 기록입니다.
  • 답은 화면 없이 나왔습니다. "아이 둘과 제주도라면 동쪽 해안을 추천합니다." 는 Streamlit 을 부르지 않고 얻은 답이라, 챗봇 로직만 따로 시험할 수 있습니다.

주의할 점

  • 기록은 체인을 부르기 전의 것을 넘기고, 새 질문과 답은 부른 뒤에 기록에 넣습니다. 질문을 먼저 기록에 넣고 넘기면 모델이 같은 질문을 두 번 받습니다.
  • 화면 코드 안에 프롬프트와 모델 설정을 모두 늘어놓으면 앱이 길어지고 시험하기 어렵습니다. 챗봇 로직을 함수로 떼어 두면, 8장에서 앱을 키울 때 로직 파일만 따로 고칠 수 있습니다.
← 이전 절7장 · 채팅 화면: chat_message 와 chat_input다음 절 →7장 · 캐시: cache_resource 와 cache_data