RAG를 구현할 때, 사용자와 LLM 간의 대화가 지속적으로 이루어지기 위해서는 "멀티턴"이 필요하다.
이에 대해서 알아보자.
전체 대화의 맥락을 읽고 대화를 주고 받는 것을 멀티턴(Multi-turn)이라 한다.
↔ 이와 다르게 바로 직전의 질문에만 답하는 것은 싱글턴(single-turn)이라 한다.
chat = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant. Answer all questions to the best of your ability. YOU MUST ANSWER IN KOREAN.",
),
("placeholder", "{messages}"),
]
)
멀티턴을 구현한다는 것은 기본적인 답변 기능에 이전 대화 기록 저장 기능이 추가되는 것이다.
chain = prompt | chat
위에서 만든 채팅모델(chat)과 프롬프트(prompt)를 버티컬바(|)로 연결해주어, 각 단계가 이전 단계의 결과를 이어받아 처리하도록 한다.
A key feature of chatbots is their ability to use content of previous conversation turns as context.
This state management can take several forms, including:
🔊 We can see that by passing the previous conversation into a chain, it can use it as context to answer questions.
This is the basic concept underpinning chatbot memory - the rest of the guide will demonstrate convenient techniques for passing or reformatting messages.
# we want to trim the number of messages passed to the chain to only the 2 most recent ones.
# count each message as 1 "token" (token_counter=len) and keep only the last two messages
trimmer = trim_messages(strategy="last", max_tokens=2, token_counter=len)
LLMs and chat models have limited context windows, and even if you're not directly hitting limits, you may want to limit the amount of distraction the model has to deal with. One solution is trim the historic messages before passing them to the model.
LLM과의 다단계 대화 (multi-turn conversation) 를 구현하려면 문맥 유지 (context preservation) 와 대화 이력 관리가 핵심이다!
멀티턴은 단순히 질문을 여러 번 던지는 것이 아니라, 과거의 질문과 답변을 기억하고 반영해서 대화를 이어가는 걸 의미한다.
OpenAI API, LangChain 등에서는 아래처럼 role을 사용한 대화 이력 리스트를 유지함:
[
{"role": "system", "content": "당신은 친절한 AI 비서입니다."},
{"role": "user", "content": "오늘 날씨 어때?"},
{"role": "assistant", "content": "서울은 맑고 28도입니다."},
{"role": "user", "content": "그럼 반팔 입어도 될까?"}
]
→ LLM은 이 전체 문맥을 보고 마지막 질문에 답변을 생성함
messages = [
{"role": "system", "content": "당신은 친절한 AI 비서입니다."}
]
user_input = "오늘 점심 뭐 먹을까?"
messages.append({"role": "user", "content": user_input})
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
response = client.chat.completions.create(
model="gpt-4",
messages=messages
)
assistant_reply = response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_reply})
이후 대화에서도 위의 messages 리스트를 계속 업데이트하면서 API를 호출하면 멀티턴이 완성된다!
| 항목 | 설명 |
|---|---|
| 🔁 메시지 길이 제한 | GPT-4의 토큰 제한(예: 128k context) 안에 모든 대화 이력을 유지해야 함 |
| 🧹 이력 정리 | 오래된 대화는 삭제하거나 요약해서 문맥 압축 |
| 🧠 기억 기능 | 중요한 정보(예: 유저 이름, 선호도 등)를 따로 저장해서 context에 추가 가능 |
| 🤖 LangChain 사용 시 | ConversationBufferMemory, ConversationSummaryMemory 등으로 상태 관리 자동화 가능 |
| 구성 요소 | 역할 |
|---|---|
messages 리스트 | 모든 대화 이력 저장 (user, assistant role 포함) |
| 매 요청 시 context 포함 | LLM은 과거 대화를 기반으로 다음 응답 생성 |
| 토큰 제한 관리 | 오래된 메시지를 정리하거나 요약 필요 |
| 고급 구현 | LangChain, LangGraph 등으로 memory 자동화 가능 |
사용자와 LLM의 대화가 한 턴씩 이루어질 때마다, 그 내용은 다음처럼 리스트에 계속 추가되고, 그 전체를 LLM에게 다시 보내야 멀티턴 대화가 성립된다!
messages = [
{"role": "system", "content": "당신은 친절한 AI 비서입니다."}, # 시스템 프롬프트 (선택적)
{"role": "user", "content": "안녕?"}, # 1턴
{"role": "assistant", "content": "안녕하세요! 무엇을 도와드릴까요?"},
{"role": "user", "content": "오늘 날씨 어때?"}, # 2턴
{"role": "assistant", "content": "서울은 맑고 28도입니다."},
{"role": "user", "content": "그럼 반팔 입어도 될까?"} # 3턴
]
이걸 OpenAI API에 보내면, LLM은 지금까지의 흐름을 이해한 상태로 "반팔 입어도 될까?" 에 대한 답변을 생성함.
response = client.chat.completions.create(
model="gpt-4",
messages=messages
)
💡 왜 이렇게 하냐면...
- LLM은 기본적으로 "기억 상실증" 😅
→ 이전 턴을 따로 저장하거나 기억하지 않기 때문에,
→ 문맥을 유지하려면 사람이 직접 모든 이력을 매번 넘겨줘야 한다!
messages = [
{"role": "system", "content": "당신은 친절한 AI 비서입니다."}
]
# User 질문 1
user_input = "안녕"
messages.append({"role": "user", "content": user_input})
# → GPT 응답
assistant_reply = "안녕하세요! 무엇을 도와드릴까요?"
messages.append({"role": "assistant", "content": assistant_reply})
# User 질문 2
user_input = "날씨 어때?"
messages.append({"role": "user", "content": user_input})
# → GPT 응답
assistant_reply = "서울은 맑고 28도입니다."
messages.append({"role": "assistant", "content": assistant_reply})
# → messages 전체가 다음 질문에도 계속 쓰임
| 항목 | 설명 |
|---|---|
| ✔️ 리스트에 추가되는 항목 | {"role": "user", "content": "..."} / {"role": "assistant", "content": "..."} |
| ✔️ 다음 요청 시 사용되는 메시지 | 전체 messages (이전 대화 포함) |
| ✔️ 대화 문맥 유지 방식 | 이전 대화 기록을 계속 누적해서 LLM에 전달 |
| ⚠️ 주의할 점 | 토큰 한계가 있으므로 너무 긴 대화는 정리/요약 필요 |