AI가 이전 대화를 기억하고 맥락을 유지하는 방법
# week4_01_no_memory.py
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b")
# 첫 번째 질문
response1 = llm.invoke("내 이름은 이우철이야")
print("AI:", response1)
# 두 번째 질문
response2 = llm.invoke("내 이름이 뭐였지?")
print("AI:", response2)
# 결과: AI가 이름을 기억하지 못함!
응답 :
(llm_env) PS C:\dev\llm> & C:/dev/llm/llm_env/Scripts/python.exe c:/dev/llm/4_!.no_memory.py
AI: 안녕하세요! 이우철님의 이름은 매우 예민한 문화적 의미를 담고 있어요. 아래에 이름의 구성과 의미를 간단히 설명드리겠습니다.
---
### 1. **이우철의 구성**
- **이** (이름의 부모 이름): 한국의 고유한 고유명이거나 가족 이름입니다.
- **우철** (이름):
- **우**는 "우"라는 철학적 의미를 지닌다.
- **철**는 "철"이라는 단어로, 한국어에서는 "철"은 강철, 강철의 특성(강한, 견고한)을 상징합니다.
---
### 2. **명의의 의미**
- **"철"**은 강철을 상징하며, **강한 정신, 견고한 성격, 끈질긴 결단**을 의미합니다.
- 이 이름은 **강철의 끈질긴 정신과 견고한 성품**을 암시하고 있어, 이름 자체가 **정신력과 투명성**을 강조하는 뜻으로 해석할 수 있습니다.
---
### 3. **문화적 맥락**
- 한국의 이름은 **성, 이름, 별명**의 순서로 구성되며, "이"는 가족 이름, "우철"은 개인 이름입니다.
- "철"은 한국어에서 **강철**의 의미로, **강한 정신, 견고한 성품**을 상징합니다.
---
### 4. **기타 고려 사항**
- 이 이름은 **명확하고 강한 인상**을 줄 수 있어, **정신력이 뛰어난 사람**을 상징할 수 있습니다.
- 한국의 많은 이름은 **성격을 암시**하는 경우가 많아, "철"은 **강한 정신, 결단력**을 나타냅니다.
---
### 5. **추가 도움**
- 이름의 **기본 발음**은 "이우철"이며, **자기소개**를 할 때는 **정신력과 견고한 성품**을 강조하는 표현이 좋아요.
- **nickname**은 "철"을 중심으로 "철수", "철철" 등이 가능합니다.
---
### 6. **질문 사항**
- 이름이 **어떤 심리적 특성**을 암시하는지 궁금하시다면, 저는 **추가 설명**을 드릴 수 있어요.
- 예를 들어, "철"이 강한 정신을 상징한다고 생각하시면, **정신력이 뛰어난 사람**을 상징하는 이름이라 생각할 수 있습니다.
---
이름이 **강한 정신과 견고한 성품**을 암시하는 뜻이므로, **정신력이 뛰어난 사람**을 상징하는 데 유리한 이름일 수 있습니다. 궁금한 점이 있다면 언제든지 물어보세요! 😊
AI: 죄송합니다. 제가 당신의 이름을 알아보기 위해 더 많은 정보를 필요로 합니다. 당신의 이름을 알려주시면 도와드릴 수 있어요! 😊
# 좀전에 말해주었는데 모름.
아...수다쟁이...
LangChain 1.0의 기본 메모리 저장소
예제 1- 기본 사용법
# week4_02_chat_history_basic.py
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
# 메시지 히스토리 생성
history = ChatMessageHistory()
# 메시지 추가
history.add_user_message("내 이름은 이우철이야")
history.add_ai_message("안녕하세요, 이우철님!")
history.add_user_message("내가 좋아하는 색은 보라색이야")
history.add_ai_message("보라색을 좋아하시는군요!")
# 저장된 메시지 확인
print("=== 저장된 대화 ===")
for msg in history.messages:
if isinstance(msg, HumanMessage):
print(f"사용자: {msg.content}")
elif isinstance(msg, AIMessage):
print(f"AI: {msg.content}")
# 전체 메시지 수
print(f"\n총 메시지 수: {len(history.messages)}")
# 메시지 초기화
history.clear()
print(f"초기화 후: {len(history.messages)}개")

예제 -2. 확장
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_ollama import OllamaLLM
# 1. 모델 설정
llm = OllamaLLM(model="qwen3:1.7b", temperature=0.7)
# 2. 대화 기록 저장소 생성 (ConversationBufferMemory의 최신 대안)
history = ChatMessageHistory()
# 3. 프롬프트 템플릿 설정
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 친절한 AI 어시스턴트입니다."),
MessagesPlaceholder(variable_name="chat_history"), # 대화 기록이 들어갈 자리
("human", "{input}")
])
# 4. 대화 함수 정의
def chat(user_input):
# 체인 생성 (LCEL 방식)
chain = prompt | llm
# 실행 시 현재까지 저장된 history.messages를 전달
response = chain.invoke({
"chat_history": history.messages,
"input": user_input
})
# 대화 내용을 기록에 추가
history.add_user_message(user_input)
history.add_ai_message(response)
return response
# --- 대화 테스트 ---
print("=== 대화 시작 ===")
print("AI:", chat("내 이름은 이우철이야"))
print("AI:", chat("내가 좋아하는 색은 보라색이야"))
print("AI:", chat("내 이름과 좋아하는 색을 기억하니?"))
# 저장된 전체 기록 확인
print("\n=== 전체 기록 ===")
print(history.messages)
응담 :
(llm_env) PS C:\dev\llm> & C:/dev/llm/llm_env/Scripts/python.exe c:/dev/llm/4_2.buffer_memory_basic.py
=== 대화 시작 ===
AI: 안녕하세요! 저는 이우철님을 소개합니다. 도움을 요청하거나 질문이 있으면 언제든지问我吧! 😊
원하시는 도움이 있으시면 말씀해 주세요!
AI: 안녕하세요! 보라색을 좋아하시네요~ 이색적인 색감이 매력스러워요! 평소에 보라색을 어떻게 사용하시나요? 예를 들어 일상에서의 활용법이나 특별한 이유가 있나요? 도움이 필요하면 언제든지 말해주세요! 😊
=== 전체 기록 ===
[HumanMessage(content='내 이름은 이우철야', additional_kwargs={}, response_metadata={}), AIMessage(content='안녕하세요! 저는 이우철님을 소개합니다. 도움을 요청하거나 질문이 있으 면 언제든지问我吧! 😊 \n원하시는 도움이 있으시면 말씀해 주세요!', additional_kwargs={}, response_metadata={}, tool_calls=[], invalid_tool_calls=[]), HumanMessage(content='내가 좋아하는 색은 보라색이야', additional_kwargs={}, response_metadata={}), AIMessage(content='안녕하세요! 보라색을 좋아하시네요~ 이색적인 색감이 매력스러워요! 평소에 보라색을 어떻게 사용하시나요? 예를 들어 일상에서의 활용법이나 특별한 이유가 있나요? 도움이 필요하면 언제든지 말해주세요! 😊', additional_kwargs={}, response_metadata={}, tool_calls=[], invalid_tool_calls=[]), HumanMessage(content='내 이름과 좋아하는 색을 기억하니?', additional_kwargs={}, response_metadata={}), AIMessage(content='안녕하세요! 이름은 이우철이구요, 보라색 을 좋아하시네요~ 이색적인 색감이 매력스럽습니다! 평소에 보라색을 어떻게 사용하시나요? 예를 들어 일상에서의 활용법이나 특별한 이유가 있나요? 도움이 필요하면 언제든지 말해주세요! 😊', additional_kwargs={}, response_metadata={}, tool_calls=[], invalid_tool_calls=[])]
예제3- 다양한 메세지 타입
# week4_03_message_types.py
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
history = ChatMessageHistory()
# System 메시지 (AI의 행동 지침)
history.add_message(SystemMessage(content="당신은 친절한 선생님입니다."))
# 대화 추가
history.add_user_message("Python이 뭐예요?")
history.add_ai_message("Python은 배우기 쉬운 프로그래밍 언어예요.")
history.add_user_message("어디에 쓰이나요?")
history.add_ai_message("웹 개발, 데이터 분석, AI 등 다양한 곳에 쓰입니다.")
# 메시지 타입별 확인
print("=== 메시지 타입별 출력 ===")
for msg in history.messages:
msg_type = msg.__class__.__name__
print(f"[{msg_type}] {msg.content}")

LCEL 체인과 메시지 히스토리를 연결하는 핵심 클래스
예제 4- 기본 메모리 통합
# week4_04_runnable_with_history.py
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b", temperature=0.7)
# 프롬프트 (메시지 히스토리 placeholder 포함)
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 친절한 AI 어시스턴트입니다."),
MessagesPlaceholder(variable_name="chat_history"), # 대화 기록
("human", "{input}")
])
# LCEL 체인
chain = prompt | llm
# 메모리 저장소 (인메모리)
memory = ChatMessageHistory()
def get_session_history():
return memory
# 메모리가 통합된 체인
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history"
)
# 대화 시작
print("=== 대화 1 ===")
response1 = chain_with_history.invoke(
{"input": "내 이름은 이우철이야"},
config={"configurable": {"session_id": "default"}}
)
print(f"AI: {response1}\n")
print("=== 대화 2 ===")
response2 = chain_with_history.invoke(
{"input": "내가 좋아하는 음식은 자장면이야"},
config={"configurable": {"session_id": "default"}}
)
print(f"AI: {response2}\n")
print("=== 대화 3 (기억 테스트) ===")
response3 = chain_with_history.invoke(
{"input": "내 이름과 좋아하는 음식이 뭐였지?"},
config={"configurable": {"session_id": "default"}}
)
print(f"AI: {response3}\n")
# 저장된 메시지 확인
print("=== 저장된 대화 기록 ===")
for msg in memory.messages:
print(f"{msg.__class__.__name__}: {msg.content}")

예제5- 세션별 메모리 관리
# week4_05_session_management.py
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b", temperature=0.7)
prompt = ChatPromptTemplate.from_messages([
("system", "당신은 친절한 AI입니다."),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}")
])
chain = prompt | llm
# 세션별 메모리 저장소
session_store = {}
def get_session_history(session_id: str):
"""세션 ID별로 독립적인 메모리 반환"""
if session_id not in session_store:
session_store[session_id] = ChatMessageHistory()
return session_store[session_id]
# 메모리가 통합된 체인
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history"
)
# 사용자 A (세션 1)
print("=== 사용자 A ===")
config_a = {"configurable": {"session_id": "user_a"}}
response_a1 = chain_with_history.invoke(
{"input": "내 이름은 이우철이야"},
config=config_a
)
print(f"AI: {response_a1}")
# 사용자 B (세션 2)
print("\n=== 사용자 B ===")
config_b = {"configurable": {"session_id": "user_b"}}
response_b1 = chain_with_history.invoke(
{"input": "내 이름은 찰스야"},
config=config_b
)
print(f"AI: {response_b1}")
# 다시 사용자 A
print("\n=== 사용자 A (다시) ===")
response_a2 = chain_with_history.invoke(
{"input": "내 이름이 뭐였지?"},
config=config_a
)
print(f"AI: {response_a2}")
# 다시 사용자 B
print("\n=== 사용자 B (다시) ===")
response_b2 = chain_with_history.invoke(
{"input": "내 이름이 뭐였지?"},
config=config_b
)
print(f"AI: {response_b2}")
# 세션별 메시지 수 확인
print(f"\n사용자 A 메시지 수: {len(session_store['user_a'].messages)}")
print(f"사용자 B 메시지 수: {len(session_store['user_b'].messages)}")

4-5 실전예제 - 컨텍스트 유지 챗봇
# week4_06_personal_assistant.py
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b", temperature=0.7)
# 비서 전용 프롬프트
prompt = ChatPromptTemplate.from_messages([
("system", """당신은 이웇러님의 개인 비서인 자비스입니다.
역할:
- 일정, 할 일, 메모를 기억합니다
- 사용자가 물어보면 저장된 정보를 정확히 전달합니다
- 정중하고 간결하게 답변합니다"""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}")
])
chain = prompt | llm
# 메모리
memory_store = {}
def get_memory(session_id: str):
if session_id not in memory_store:
memory_store[session_id] = ChatMessageHistory()
return memory_store[session_id]
chain_with_memory = RunnableWithMessageHistory(
chain,
get_memory,
input_messages_key="input",
history_messages_key="chat_history"
)
# 비서 함수
def assistant(user_input, session_id="default"):
config = {"configurable": {"session_id": session_id}}
response = chain_with_memory.invoke({"input": user_input}, config=config)
return response
# 테스트 시나리오
print("=== 개인 비서 챗봇 ===\n")
scenarios = [
"내일 오전 10시에 팀 회의가 있어",
"회의 주제는 1분기 실적 리뷰야",
"참석자는 김팀장, 백대리, 박사원이야",
"오후 3시에 고객사 미팅이 외부에 있어",
"내일 일정을 정리해줘"
]
for i, user_msg in enumerate(scenarios, 1):
print(f"[{i}] 사용자: {user_msg}")
response = assistant(user_msg)
print(f" 비서: {response}\n")

# week4_07_customer_support.py
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b", temperature=0.6)
# 고객 상담 프롬프트
prompt = ChatPromptTemplate.from_messages([
("system", """당신은 KT알파 쇼핑몰의 고객 상담사입니다.
상담 원칙:
1. 고객의 이전 문의를 기억하고 연결하여 답변
2. 정중하고 친절한 말투
3. 구체적이고 실용적인 해결책 제시
4. 고객의 선호도와 요구사항을 파악하여 맞춤 추천"""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}")
])
chain = prompt | llm
# 고객별 메모리
customer_sessions = {}
def get_customer_history(customer_id: str):
if customer_id not in customer_sessions:
customer_sessions[customer_id] = ChatMessageHistory()
return customer_sessions[customer_id]
support_chain = RunnableWithMessageHistory(
chain,
get_customer_history,
input_messages_key="input",
history_messages_key="chat_history"
)
# 고객 상담 시뮬레이션
def chat_with_customer(customer_id, message):
config = {"configurable": {"session_id": customer_id}}
return support_chain.invoke({"input": message}, config=config)
# 시나리오
print("=== 고객 상담 시나리오 ===\n")
customer_id = "customer_001"
conversations = [
"노트북을 구매하려고 합니다",
"주로 영상 편집 작업을 할 예정이에요",
"예산은 200만원 정도입니다",
"추천해주신 제품의 배송 기간은 얼마나 되나요?",
"좋아요, 그럼 제가 말한 용도와 예산에 맞는 제품으로 주문하겠습니다"
]
for i, msg in enumerate(conversations, 1):
print(f"[대화 {i}]")
print(f"고객: {msg}")
response = chat_with_customer(customer_id, msg)
print(f"상담사: {response}\n")
print("-" * 70)
응답 :
(llm_env) PS C:\dev\llm> & C:/dev/llm/llm_env/Scripts/python.exe c:/dev/llm/4_07_customer_support.py
=== 고객 상담 시나리오 ===
[대화 1]
고객: 노트북을 구매하려고 합니다
상담사: 안녕하세요! KT알파 쇼핑몰의 고객 상담사입니다.
제가 고객님의 요청을 기억해 두고, 더 맞춤형한 도움을 드릴 수 있도록 아래에 문의 내용을 정리해 드리겠습니다.
**문의 내용 정리:**
- **구매 목적:** 노트북 구매
- **현재 상태:** 처음 문의
- **요청사항:** 구체적인 정보 수집 후 맞춤 추천
**추가 정보를 알려주시면 더 나은 서비스를 제공해 드리겠습니다.**
1. **예산 범위:** 100만 ~ 500만 원 범위로 구매하시나요?
2. **관심 기능/특징:** 풀HD 레이트, 게임용 GPU, 높은 터치 성능, 태블릿 기능 등?
3. **사용 목적:** 업무용, 게임용, 창작용 등?
4. **관심 브랜드:** 삼성, 애플, 레노버, 디스플레이 기업 등?
5. **기타 요구사항:** 키보드 타이핑 속도, 배터리 수명, 무결성 등?
이해 부족한 점이 있으면 언제든지 말씀해 주세요! 📞
----------------------------------------------------------------------
[대화 2]
고객: 주로 영상 편집 작업을 할 예정이에요
상담사: 안녕하세요! 영상 편집 작업을 위해 노트북을 구매하시려는 점을 잘 알고 있습니다. 아래에 영상 편집에 유리한 특징을 참고해 드릴 수 있는 정보를 추가해 드리겠습니다.
**추가 정보를 알려주시면 더 맞춤형 추천을 드릴 수 있습니다.**
1. **예산 범위:** 100만 ~ 500만 원 범위로 구매하시나요?
2. **관심 기능/특징:**
- 풀HD 레이트 또는 4K 해상도
- 고성능 GPU(그래픽 처리가 필요한 작업을 위해)
- 터치 스크린 또는 높은 키보드 타이핑 속도
- 배터리 수명(장시간 작업에 유리)
- 무결성(편집 작업 중 키보드 터치나 터치 스크린 사용 시 유용)
3. **사용 목적:**
- 업무용(프로젝트 편집, 편집 소프트웨어 사용)
- 게임용(게임 편집, 편집 소프트웨어 사용)
- 창작용(영상 편집, 편집 소프트웨어 사용)
4. **관심 브랜드:**
- 삼성, 애플, 레노버, 디스플레이 기업 등
5. **기타 요구사항:**
- 무결성, 키보드 타이핑 속도, 배터리 수명 등
추가적으로 어떤 특정 기능이나 브랜드를 선호하시면 더 구체적인 추천을 드릴 수 있습니다. 필요하시면 언제든지 말씀해 주세요! 📞
----------------------------------------------------------------------
[대화 3]
고객: 예산은 200만원 정도입니다
상담사: 안녕하세요! 200만 원 수준의 예산으로 영상 편집 작업을 위해 고성능 노트북을 구매하시고자 합니다. 아래에 추천하는 모델과 관련된 정보를 정리해 드릴게요.
---
### **추천 노트북 모델 및 특징**
1. **삼성전자**
- **모델**: 14인치 4K 2000 (예: 2023년 라이트 프로)
- **특징**:
- 4K 해상도, 고성능 GPU, 터치 스크린, 무결성 기능
- 빠른 키보드 타이핑 속도, 장시간 사용에도 긴 배터리 수명
- 가벼운 무게(1.5kg 미만)로 편리하게 이동 가능
- **추천 이유**: 영상 편집에 최적화된 성능과 가벼운 무게, 터치 스크린 활용성
- **추가 고려사항**: 키보드 타이핑 속도, 배터리 수명, 무결성
2. **레노버**
- **모델**: 15.6인치 4K 2000 (예: 2023년 라이트 프로)
- **특징**:
- 4K 해상도, 고성능 GPU, 무결성 기능, 빠른 키보드 타이핑
- 흔들림 방지 설계, 빠른 성능
- **추천 이유**: 키보드 타이핑 속도와 무결성, 편리한 사용성
3. **애플**
- **모델**: MacBook Pro 14인치 4K (예: 2023년 모델)
- **특징**:
- 고성능 GPU, 터치 스크린, 무결성 기능
- 빠른 성능, 편리한 디자인
- **추천 이유**: 영상 편집에 최적화된 성능, 뛰어난 무결성, 편리한 디자인
---
### **추가 고려 사항**
- **사용 목적**: 업무용(프로젝트 편집, 편집 소프트웨어 사용) 또는 창작용(영상 편집, 편집 소프트웨어 사용)에 따라 모델 선택
- **배터리 수명**: 장시간 작업에 유리한 모델(예: 12시간 이상)
- **무결성**: 키보드 타이핑 속도와 터치 스크린 사용 시 유용
---
### **추천 모델 예시**
1. **삼성전자 14인치 4K 2000**
- **특징**: 4K 해상도, 고성능 GPU, 무결성 기능
- **장점**: 편리한 무게, 빠른 키보드 타이핑
- **단점**: 높은 가격
2. **레노버 15.6인치 4K 2000**
- **특징**: 터치 스크린, 빠른 성능
- **장점**: 무결성, 편리한 디자인
- **단점**: 키보드 타이핑 속도
3. **애플 MacBook Pro 14인치 4K**
- **특징**: 고성능 GPU, 무결성 기능
- **장점**: 뛰어난 성능, 편리한 디자인
- **단점**: 가격 상승
---
### **추가 문의사항**
- **관심 브랜드**: 삼성, 레노버, 애플 중 어떤 브랜드를 선호하시나요?
- **특정 기능**: 터치 스크린, 무결성, 배터리 수명 등 구체적으로 원하시면 알려주세요!
더 나은 추천을 위해 추가 정보를 알려주시면 감사하겠습니다! 📞
----------------------------------------------------------------------
[대화 4]
고객: 추천해주신 제품의 배송 기간은 얼마나 되나요?
상담사: 안녕하세요! 추천한 노트북 모델의 배송 기간은 다음과 같이 일반적인 시간을 알려드릴 수 있습니다:
- **삼성전자**: 3~7일(정품 배송, 주문 후 3~7일 소요)
- **레노버**: 3~7일(정품 배송, 주문 후 3~7일 소요)
- **애플**: 3~7일(정품 배송, 주문 후 3~7일 소요)
이 외에도 구체적인 배송 일정은 제품의 상세 정보와 주문 플랫폼에 따라 달라질 수 있습니다.
추천 모델을 더 구체적으로 선택하시면, 해당 모델의 배송 일정과 추가 정보를 알려드리겠습니다!
**추천 모델을 선택하시면 더욱 정확한 정보를 제공해 드릴 수 있습니다.**
- **관심 브랜드**: 삼성, 레노버, 애플 중 어떤 브랜드를 선호하시나요?
- **특정 기능**: 터치 스크린, 무결성, 배터리 수명 등 구체적으로 원하시면 알려주세요!
원하시면 언제든지 추가 정보를 알려주시면 감사드리겠습니다! 📞
----------------------------------------------------------------------
[대화 5]
고객: 좋아요, 그럼 제가 말한 용도와 예산에 맞는 제품으로 주문하겠습니다
상담사: 안녕하세요! 최적의 노트북 모델을 선택해 주셔서 감사합니다! 아래에 추천 모델 중 **200만 원 범위에 최적화된 선택**을 드릴 수 있습니다.
---
### **추천 모델 및 특징**
1. **삼성전자 14인치 4K 2000 (예: 2023년 라이트 프로)**
- **장점**:
- 4K 해상도, 고성능 GPU, 무결성 기능
- 빠른 키보드 타이핑 속도, 12시간 이상 배터리 수명
- 가벼운 무게(1.5kg 미만)로 편리한 이동
- **단점**: 고성능 GPU가 포함되어 비용이 상승
2. **레노버 15.6인치 4K 2000 (예: 2023년 라이트 프로)**
- **장점**:
- 터치 스크린, 빠른 성능, 무결성 기능
- 흔들림 방지 설계, 빠른 키보드 타이핑
- **단점**: 배터리 수명이 비교적 짧음
3. **애플 MacBook Pro 14인치 4K (2023년 모델)**
- **장점**:
- 고성능 GPU, 무결성 기능, 뛰어난 성능
- 편리한 디자인, 높은 무결성
- **단점**: 고가 제품으로 200만 원 범위에 맞는 제품이 아님
---
### **추천 이유**
- **200만 원 범위**: 레노버의 15.6인치 모델은 터치 스크린과 무결성 기능, 빠른 키보드 타이핑 속도가 조화로워 실제 작업에 유리합니다.
- **배터리 수명**: 12시간 이상 사용 가능하며, 영상 편집 작업에 최적화된 설계입니다.
---
### **추가 문의**
- **관심 브랜드**: 레노버가 가장 적합한 모델이 드물 수 있으니, 선택해 주세요!
- **특정 기능**: 무결성, 터치 스크린, 배터리 수명 등 추가 원하시면 알려주세요!
원하시면 **레노버 15.6인치 4K 2000**을 선택해 주세요! 📞
이후에 해당 모델의 배송 일정과 상세 정보를 알려드리겠습니다!
# week4_08_study_assistant.py
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b", temperature=0.7)
prompt = ChatPromptTemplate.from_messages([
("system", """당신은 학습 도우미 AI입니다.
역할:
- 사용자가 공부한 내용을 기억합니다
- 학습 진도를 추적합니다
- 복습이 필요한 내용을 상기시킵니다
- 격려와 피드백을 제공합니다"""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}")
])
chain = prompt | llm
student_sessions = {}
def get_student_history(student_id: str):
if student_id not in student_sessions:
student_sessions[student_id] = ChatMessageHistory()
return student_sessions[student_id]
study_chain = RunnableWithMessageHistory(
chain,
get_student_history,
input_messages_key="input",
history_messages_key="chat_history"
)
def study_with_ai(student_id, message):
config = {"configurable": {"session_id": student_id}}
return study_chain.invoke({"input": message}, config=config)
# 학습 시나리오
print("=== 학습 도우미 챗봇 ===\n")
student = "student_jun"
study_log = [
"오늘 Python 리스트에 대해 공부했어",
"append, extend, insert 메소드를 배웠어",
"내일은 딕셔너리를 공부할 예정이야",
"오늘 뭐 공부했었지?",
"내일 공부할 주제도 알려줘"
]
for i, msg in enumerate(study_log, 1):
print(f"[{i}] 학생: {msg}")
response = study_with_ai(student, msg)
print(f" AI: {response}\n")
응답 :
(llm_env) PS C:\dev\llm> & C:/dev/llm/llm_env/Scripts/python.exe c:/dev/llm/4_08_study_assistant.py
=== 학습 도우미 챗봇 ===
[1] 학생: 오늘 Python 리스트에 대해 공부했어
AI: 안녕하세요! 오늘 Python의 리스트에 대해 공부한 내용을 기억하고 있습니다. 아래는 학습 내용의 요약과 추가적인 도움이 필요할 경우 제공할 수 있는 정보입니다:
---
### 📌 학습 내용 요약
1. **리스트의 특징**
- **순서가 있는 자료형** (순서가 유지됨)
- **변형 가능** (값을 추가/삭제/수정 가능)
- **다양한 데이터 타입 지원** (정수, 문자열, boolean, 객체 등)
- **리스트는 괄호로 묶어짐** (예: `[1, 2, 3]`)
2. **핵심 기능**
- **인덱싱** (리스트의 특정 위치에 위치한 요소를 가져오기)
- 예: `my_list[0]` → 리스트의 첫 요소 가져오기
- **이ндекс링** (리스트의 특정 위치에 요소를 삽입/삭제)
- `my_list.insert(index, value)`
- **추가/삭제**
- `my_list.append(value)` → 마지막 요소 추가
- `my_list.pop(index)` → 특정 위치의 요소 제거
- `my_list.remove(value)` → 값 대체 (첫 번째 발생되는 요소 제거)
- **리스트의 길이**
- `len(my_list)` → 요소 수 확인
3. **기본적인 연산**
- **합치기**
- `my_list1 + my_list2` → 두 리스트 합치기
- `my_list * 2` → 리스트의 요소를 두 번 반복
- **리스트의 요소 접근**
- `my_list[0:3]` → 0부터 3-1 번째 요소까지 (포함)
- `my_list[::2]` → 0, 2, 4... 순서로 요소 추출
4. **실용적인 예제**
- `numbers = [1, 2, 3, 4, 5]`
- `numbers[2]` → 3
- `numbers.append(6)` → [1, 2, 3, 4, 5, 6]
- `numbers.pop(0)` → 1 제거 → [2, 3, 4, 5]
---
### 🧠 도움 요청
- **복습이 필요한 부분**:
- 리스트의 순서와 인덱싱
- `append` vs `insert`의 차이
- 리스트의 연산 (합치기, 반복)
- **문제 풀이**:
- 리스트의 요소를 특정 위치에 삽입하는 코드 작성
- 리스트의 길이를 확인하는 코드 작성
---
### 💡 피드백
- **이해도 못한 부분**:
- `list comprehensions` (예: `[x**2 for x in range(5)]`)
- `sorted()` 함수의 사용 방법
- **성장 포인트**:
- 리스트는 다른 자료형(튜플, 딕셔너리)과의 혼합 사용 가능
- 리스트의 요소를 다른 구조로 변환(예: `tuple()`로 변환)
---
### 📝 주의 사항
- **리스트는 순서에 따라 데이터가 변형됨** (예: `my_list = [3, 1, 2]` → `my_list[0] = 1` → `[1, 1, 2]`)
- **리스트의 요소는 변경 가능** (예: `my_list[0] = 10` → 요소 변경)
---
학습 도우미로 도와드릴 수 있어요! 추가로 궁금한 점이 있으면 언제든지 물어보세요. 😊
[2] 학생: append, extend, insert 메소드를 배웠어
AI: 안녕하세요! `append`, `extend`, `insert` 메소드를 잘 이해했어요! 아래는 실제 활용 예제와 주의 사항을 정리해 드리겠습니다.
---
### 🧩 주요 메소드 요약
| 메소드 | 기능 | 설명 |
|--------|------|------|
| **`append(value)`** | | 마지막 요소에 `value`를 추가합니다. |
| **`extend(iterable)`** | | `iterable`에 있는 요소들을 리스트에 차례로 추가합니다. |
| **`insert(index, value)`** | | `index` 위치에 `value`를 삽입합니다. |
| **`pop(index)`** | | 특정 위치의 요소를 제거하고 반환합니다. |
| **`remove(value)`** | | `value`를 찾아 제거합니다. |
---
### 📌 실무 활용 예제
appendnumbers = [1, 2, 3]
numbers.append(4) # [1, 2, 3, 4]
extendnumbers = [1, 2, 3]
numbers.extend([4, 5]) # [1, 2, 3, 4, 5]
insertnumbers = [1, 2, 4]
numbers.insert(2, 3) # [1, 2, 3, 4]
### 🚩 주의 사항
- **`append`**은 리스트의 끝에 요소를 추가하므로, `insert`보다 더 간단합니다.
- **`extend`**은 다른 리스트의 요소들을 차례로 추가하므로, `append`보다 다수 요소를 추가하는 데 적합합니다.
- **`insert`**은 인덱스를 전달해야 하며, 인덱스가 범위를 벗어나면 오류가 발생할 수 있습니다.
---
### 🧠 도움 요청
- **실전 활용**:
- `extend([1, 2, 3])`을 사용해 여러 요소를 한 번에 추가하려면?
- `insert(2, 5)`을 사용해 특정 위치에 5를 삽입하려면?
- **문제 풀이**:
- `numbers = [1, 3, 5]` → `numbers.insert(1, 2)` → `[1, 2, 3, 5]`
- `numbers = [1, 2, 3]` → `numbers.extend([4, 5])` → `[1, 2, 3, 4, 5]`
---
학습 도우미로 도와드릴 수 있어요! 추가로 궁금한 점이 있으면 언제든지 물어보세요. 😊
[3] 학생: 내일은 딕셔너리를 공부할 예정이야
AI: 안녕하세요! 딕셔너리를 공부해요! 아래는 학습 내용의 요약과 주의 사항입니다:
---
### 📌 학습 내용 요약
1. **딕셔너리의 특징**
- **순서가 없는 자료형** (키에 따라 순서가 결정됨)
- **변형 가능** (키와 값 변경 가능)
- **키와 값의 짝이 필요** (예: `{ 'name': 'Alice', 'age': 25 }`)
- **키는 고유 값** (두 키는 같은 값이면 오류 발생)
2. **핵심 기능**
- **키 접근** (값을 가져오기)
- `dictionary[key]`
- **키 추가/변경/삭제**
- `dictionary[key] = value` (값 바꾸기)
- `dictionary.pop(key)` (키 제거)
- `dictionary.get(key, default)` (키 없을 때 기본값 반환)
- **키의 수**
- `len(dictionary)` → 키 수 확인
3. **기본적인 연산**
- **합치기**
- `dict1 | dict2` → 두 딕셔너리 합치기
- **리스트 변환**
- `dict.keys()`, `dict.values()`, `dict.items()` → 키/값/키-값 리스트 반환
4. **실용적인 예제**
```python
person = {'name': 'Alice', 'age': 25, 'city': 'NY'}
print(person['name']) # Alice
person['job'] = 'Engineer' # 추가
person.pop('city') # 'NY' 삭제
### 🧠 도움 요청
- **실전 활용**:
- `{'name': 'Bob', 'age': 30}.get('age')` → 30
- `{'a': 1, 'b': 2}.update({'b': 3})` → {'a': 1, 'b': 3}
- **문제 풀이**:
- `{'fruit': 'apple', 'color': 'red'}` → `{'color': 'red', 'fruit': 'apple'}`
- `{'x': 10, 'y': 20}` → `{'x': 10, 'y': 20}`
---
### 💡 주의 사항
- **딕셔너리는 순서가 없음** (키의 순서가 변할 수 있음)
- **키는 중복 불가** (두 키가 같은 값이라면 오류 발생)
- **get()**은 키가 없을 때 기본값 반환, **pop()**은 키가 없을 때 오류 발생
---
학습 도우미로 도와드릴 수 있어요! 추가로 궁금한 점이 있으면 언제든지 물어보세요. 😊
[4] 학생: 오늘 뭐 공부했었지?
AI: 안녕하세요! 오늘은 Python의 **리스트**와 **딕셔너리** 두 주제를 공부했어요! 아래는 학습 내용의 요약과 주요 포인트입니다:
---
### 📌 학습 내용 요약
1. **리스트**
- **순서가 있음** (인덱싱 가능)
- **변형 가능** (append, insert, pop 등 기능)
- **예: [1, 2, 3]**
- **메소드**: `append()`, `extend()`, `insert()`
- **주의 사항**: 순서에 따라 데이터 변형 가능
2. **딕셔너리**
- **순서가 없음** (키에 따라 순서 결정)
- **변형 가능** (키와 값 변경 가능)
- **예: {'name': 'Alice', 'age': 25}'
- **메소드**: `get()`, `pop()`, `update()`
- **주의 사항**: 키 중복 불가, 순서 없음
---
### 🧠 핵심 포인트
- **리스트**는 순서가 유지되지만, 딕셔너리는 키의 순서가 변할 수 있음.
- **딕셔너리**는 키와 값의 짝이 필요하며, `get()`은 키 없을 때 기본값 반환.
- **실전 활용**:
- 리스트는 단일 요소 추가/삭제, 딕셔너리는 여러 요소 추가/삭제 가능.
- `update()`로 딕셔너리 값 갱신, `pop()`으로 특정 키 삭제.
---
### 📝 주의 사항
- **리스트**는 `append()`으로 끝에 요소 추가, `extend()`으로 여러 요소 추가.
- **딕셔너리**는 `get()`으로 키 없을 때 기본값 반환, `pop()`은 키 없을 때 오류 발생.
---
학습 도우미로 도와드릴 수 있어요! 오늘은 리스트와 딕셔너리의 차이점과 활용법을 잘 이해했어요! 추가로 궁금한 점이 있으면 언제든지 물어보세요. 😊
예제 10 : 메모리관리
# week4_10_message_limit.py
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b")
# 최근 N개 메시지만 유지하는 함수
def trim_messages(messages, max_messages=10):
"""최근 max_messages개만 유지"""
if len(messages) > max_messages:
return messages[-max_messages:]
return messages
# 커스텀 히스토리 클래스
class LimitedChatHistory(ChatMessageHistory):
# Pydantic 필드로 미리 선언합니다.
max_messages: int = 10
def __init__(self, max_messages=10):
super().__init__()
self.max_messages = max_messages
def add_message(self, message):
super().add_message(message)
# 메시지 수 제한
if len(self.messages) > self.max_messages:
self.messages = self.messages[-self.max_messages:]
# 사용
limited_history = LimitedChatHistory(max_messages=6) # 최근 6개만
# 많은 메시지 추가
for i in range(10):
limited_history.add_user_message(f"메시지 {i}")
limited_history.add_ai_message(f"응답 {i}")
print(f"총 메시지 수: {len(limited_history.messages)}")
print("저장된 메시지:")
for msg in limited_history.messages:
print(f" - {msg.content}")

예제11 - 대화요약 기능
# week4_11_conversation_summary.py
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_ollama import OllamaLLM
llm = OllamaLLM(model="qwen3:1.7b", temperature=0.3)
# 대화 요약 프롬프트
summary_prompt = ChatPromptTemplate.from_messages([
("system", "다음 대화를 3문장 이내로 요약해주세요."),
("human", "{conversation}")
])
# 대화 히스토리
history = ChatMessageHistory()
# 대화 추가
conversations = [
("사용자", "나는 서울에 살아"),
("AI", "서울에 사시는군요!"),
("사용자", "직업은 개발자야"),
("AI", "개발자시군요."),
("사용자", "Python을 주로 써"),
("AI", "Python 좋죠!"),
("사용자", "요즘 LangChain 공부 중이야"),
("AI", "좋은 선택입니다!"),
]
for role, msg in conversations:
if role == "사용자":
history.add_user_message(msg)
else:
history.add_ai_message(msg)
# 대화 내용을 문자열로 변환
conversation_text = "\n".join([
f"{msg.__class__.__name__}: {msg.content}"
for msg in history.messages
])
# 요약 생성
summary_chain = summary_prompt | llm
summary = summary_chain.invoke({"conversation": conversation_text})
print("=== 원본 대화 ===")
print(conversation_text)
print(f"\n=== 요약 ({len(summary)} 문자) ===")
print(summary)
