본문 바로가기
공부방/Python & AI

[AI] MessagesPlaceholder

by 래채 2025. 10. 12.

MessagesPlaceholder는 LangChain에서 동적으로 메시지 리스트를 삽입할 수 있게 해주는 특수 플레이스홀더입니다.

대화 히스토리나 컨텍스트를 유연하게 관리할 수 있어서 챗봇, 에이전트, RAG 시스템에서 필수적으로 사용됩니다.

🎯 핵심 특징

  1. 동적 메시지 삽입: 실행 시점에 메시지 리스트를 주입
  2. 타입 안전성: BaseMessage 타입의 리스트만 허용
  3. Optional 지원: 메시지가 없어도 에러 없이 동작
  4. 다중 사용 가능: 여러 개의 placeholder를 템플릿에 배치 가능

🔍 일반 변수 vs MessagesPlaceholder

 
 
python
# 일반 변수: 단일 문자열
("human", "{user_input}")  # ❌ 리스트 불가

# MessagesPlaceholder: 메시지 리스트
MessagesPlaceholder(variable_name="history")  # ✅ 리스트 주입

상세한 예제 코드를 만들어드릴게요!

 

🎯 MessagesPlaceholder 핵심 개념

왜 필요한가?

일반 변수 {variable}는 문자열 하나만 받지만,
MessagesPlaceholder는 메시지 리스트 전체를 받아서 삽입합니다.

 
 
python
# ❌ 이건 안 됨
("human", "{history}")  # 리스트를 문자열로 변환해버림

# ✅ 이게 정답
MessagesPlaceholder(variable_name="history")  # 각 메시지를 올바르게 삽입

📌 주요 파라미터

파라미터설명기본값
variable_name 주입할 변수 이름 필수
optional 변수 누락 시 에러 발생 여부 False

💡 실전 활용 시나리오

1️⃣ 채팅봇 대화 기억

 
 
python
MessagesPlaceholder(variable_name="chat_history")

→ 이전 대화를 기억하는 챗봇

2️⃣ 에이전트 작업 기록

 
 
python
MessagesPlaceholder(variable_name="agent_scratchpad")

→ 도구 사용 내역을 추적

3️⃣ Few-shot 학습

 
 
python
MessagesPlaceholder(variable_name="examples")

→ 예제를 동적으로 주입

4️⃣ RAG + 대화

 
 
python
MessagesPlaceholder(variable_name="context_history", optional=True)

→ 문서 검색 + 대화 맥락 결합

⚠️ 주의사항

  1. 반드시 BaseMessage 타입: HumanMessage, AIMessage 등만 가능
  2. 빈 리스트 OK: []는 에러 없음 (optional=False여도)
  3. None은 에러: optional=False일 때 변수 누락 시 에러
  4. 순서 중요: 메시지 순서가 대화 흐름을 결정

🚀 실전 팁

 
 
python
# Tip 1: 최근 N개만 유지 (컨텍스트 길이 관리)
recent_history = chat_history[-10:]  # 최근 10개만

# Tip 2: 토큰 수 제한
def truncate_history(history, max_tokens=2000):
    # 토큰 수 계산 후 잘라내기
    pass

# Tip 3: 요약 기반 메모리
summary = SystemMessage(content="[이전 대화 요약] ...")
history = [summary] + recent_messages

 

 

- 샘플 코드!

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import (
    SystemMessage, 
    HumanMessage, 
    AIMessage,
    FunctionMessage,
    ToolMessage
)

print("=" * 70)
print("MessagesPlaceholder 완벽 가이드")
print("=" * 70)

# ============================================================
# 예제 1: 기본 사용법
# ============================================================
print("\n[예제 1] 기본 사용법")
print("-" * 70)

basic_template = ChatPromptTemplate.from_messages([
    ("system", "당신은 친절한 AI 어시스턴트입니다."),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{question}")
])

# 대화 히스토리 준비
chat_history = [
    HumanMessage(content="내 이름은 민수야"),
    AIMessage(content="안녕하세요, 민수님!"),
    HumanMessage(content="나는 서울에 살아"),
    AIMessage(content="서울에 사시는군요. 좋은 곳이죠!")
]

# 템플릿 실행
messages = basic_template.format_messages(
    chat_history=chat_history,
    question="내가 어디 산다고 했지?"
)

print("\n생성된 메시지:")
for i, msg in enumerate(messages, 1):
    print(f"{i}. [{msg.__class__.__name__}] {msg.content}")


# ============================================================
# 예제 2: Optional Parameter - 히스토리가 없을 때
# ============================================================
print("\n\n[예제 2] Optional Parameter")
print("-" * 70)

optional_template = ChatPromptTemplate.from_messages([
    ("system", "당신은 도움을 주는 AI입니다."),
    MessagesPlaceholder(variable_name="history", optional=True),  # ✅ optional=True
    ("human", "{input}")
])

# 케이스 1: 히스토리 있음
print("\n케이스 1: 히스토리 있음")
messages_with_history = optional_template.format_messages(
    history=[
        HumanMessage(content="안녕?"),
        AIMessage(content="안녕하세요!")
    ],
    input="오늘 날씨 어때?"
)
for msg in messages_with_history:
    print(f"  [{msg.__class__.__name__}] {msg.content}")

# 케이스 2: 히스토리 없음 (optional이므로 에러 없음)
print("\n케이스 2: 히스토리 없음")
messages_without_history = optional_template.format_messages(
    input="파이썬이 뭐야?"
)
for msg in messages_without_history:
    print(f"  [{msg.__class__.__name__}] {msg.content}")


# ============================================================
# 예제 3: 여러 개의 MessagesPlaceholder 사용
# ============================================================
print("\n\n[예제 3] 여러 개의 Placeholder")
print("-" * 70)

multi_placeholder_template = ChatPromptTemplate.from_messages([
    ("system", "당신은 {role}입니다."),
    MessagesPlaceholder(variable_name="examples"),  # Few-shot 예제
    MessagesPlaceholder(variable_name="conversation", optional=True),  # 대화
    ("human", "{current_query}")
])

# Few-shot 예제
few_shot_examples = [
    HumanMessage(content="감사합니다!"),
    AIMessage(content="긍정 - 감사"),
    HumanMessage(content="짜증나네..."),
    AIMessage(content="부정 - 짜증")
]

# 실제 대화
previous_conversation = [
    HumanMessage(content="이 시스템 어떻게 작동하나요?"),
    AIMessage(content="텍스트의 감정을 분석합니다.")
]

messages = multi_placeholder_template.format_messages(
    role="감정 분석 AI",
    examples=few_shot_examples,
    conversation=previous_conversation,
    current_query="오늘 정말 행복해요!"
)

print("\n생성된 프롬프트 구조:")
for i, msg in enumerate(messages, 1):
    print(f"{i}. [{msg.__class__.__name__}] {msg.content[:50]}...")


# ============================================================
# 예제 4: 실전 챗봇 구현 - 대화 히스토리 관리
# ============================================================
print("\n\n[예제 4] 실전 챗봇 시뮬레이션")
print("-" * 70)

chatbot_template = ChatPromptTemplate.from_messages([
    ("system", """당신은 고객 지원 챗봇입니다.
고객의 이전 대화 내용을 기억하고 맥락에 맞게 응답하세요.
고객 정보: {customer_info}"""),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{user_input}")
])

class SimpleChatbot:
    """간단한 챗봇 시뮬레이터"""
    
    def __init__(self, customer_info):
        self.customer_info = customer_info
        self.history = []
        
    def chat(self, user_input):
        """대화 처리"""
        # 프롬프트 생성
        messages = chatbot_template.format_messages(
            customer_info=self.customer_info,
            chat_history=self.history,
            user_input=user_input
        )
        
        # 히스토리에 추가 (실제로는 LLM 응답을 받아서 추가)
        self.history.append(HumanMessage(content=user_input))
        
        # 시뮬레이션: 실제로는 LLM 호출
        mock_ai_response = f"[AI 응답을 받는 프롬프트가 준비됨]"
        self.history.append(AIMessage(content=mock_ai_response))
        
        return messages
    
    def get_history_summary(self):
        """히스토리 요약"""
        return f"총 {len(self.history)}개의 메시지 (대화 턴: {len(self.history)//2})"

# 챗봇 초기화
bot = SimpleChatbot(customer_info="VIP 고객, 가입일: 2023-01-15")

# 대화 1
print("\n대화 1:")
messages = bot.chat("주문한 상품이 언제 도착하나요?")
print(f"프롬프트 메시지 수: {len(messages)}")
print(f"히스토리 상태: {bot.get_history_summary()}")

# 대화 2
print("\n대화 2:")
messages = bot.chat("배송 추적은 어떻게 하나요?")
print(f"프롬프트 메시지 수: {len(messages)}")
print(f"히스토리 상태: {bot.get_history_summary()}")

# 대화 3
print("\n대화 3:")
messages = bot.chat("제 첫 질문이 뭐였죠?")
print(f"프롬프트 메시지 수: {len(messages)}")
print(f"히스토리 상태: {bot.get_history_summary()}")

print("\n최종 프롬프트 구조:")
for i, msg in enumerate(messages, 1):
    print(f"{i}. [{msg.__class__.__name__}] {msg.content[:60]}...")


# ============================================================
# 예제 5: 조건부 히스토리 (최근 N개만 포함)
# ============================================================
print("\n\n[예제 5] 조건부 히스토리 - 최근 N개만")
print("-" * 70)

def get_recent_messages(history, n=4):
    """최근 N개의 메시지만 반환"""
    return history[-n:] if len(history) > n else history

# 긴 대화 히스토리
long_history = [
    HumanMessage(content="1번 질문"),
    AIMessage(content="1번 답변"),
    HumanMessage(content="2번 질문"),
    AIMessage(content="2번 답변"),
    HumanMessage(content="3번 질문"),
    AIMessage(content="3번 답변"),
    HumanMessage(content="4번 질문"),
    AIMessage(content="4번 답변"),
    HumanMessage(content="5번 질문"),
    AIMessage(content="5번 답변"),
]

recent_template = ChatPromptTemplate.from_messages([
    ("system", "당신은 AI 어시스턴트입니다."),
    MessagesPlaceholder(variable_name="recent_history"),
    ("human", "{question}")
])

# 최근 4개만 사용
recent_only = get_recent_messages(long_history, n=4)
messages = recent_template.format_messages(
    recent_history=recent_only,
    question="새로운 질문입니다"
)

print(f"\n전체 히스토리: {len(long_history)}개 메시지")
print(f"사용된 히스토리: {len(recent_only)}개 메시지")
print("\n포함된 메시지:")
for msg in recent_only:
    print(f"  - {msg.content}")


# ============================================================
# 예제 6: Agent 패턴 - Tool 사용 히스토리
# ============================================================
print("\n\n[예제 6] Agent 패턴 - Tool 사용")
print("-" * 70)

agent_template = ChatPromptTemplate.from_messages([
    ("system", """당신은 도구를 사용할 수 있는 에이전트입니다.
사용 가능한 도구: {tools}
작업 목표: {objective}"""),
    MessagesPlaceholder(variable_name="agent_scratchpad"),  # 에이전트 작업 기록
    ("human", "{input}")
])

# 에이전트의 작업 기록 (tool 사용 내역)
agent_scratchpad = [
    AIMessage(content="먼저 날씨 정보를 확인하겠습니다."),
    ToolMessage(
        content='{"temperature": 25, "condition": "맑음"}',
        tool_call_id="call_1"
    ),
    AIMessage(content="날씨 정보를 받았습니다. 이제 일정을 확인하겠습니다."),
    ToolMessage(
        content='{"events": ["회의 10시", "점심 12시"]}',
        tool_call_id="call_2"
    ),
]

messages = agent_template.format_messages(
    tools="날씨 조회, 일정 확인, 메일 전송",
    objective="사용자의 하루 일정 계획",
    agent_scratchpad=agent_scratchpad,
    input="오늘 야외 활동이 가능할까요?"
)

print("\n생성된 에이전트 프롬프트:")
for i, msg in enumerate(messages, 1):
    content_preview = msg.content[:80] if msg.content else "[도구 실행 결과]"
    print(f"{i}. [{msg.__class__.__name__}] {content_preview}...")


# ============================================================
# 예제 7: RAG with History - 문서 기반 대화
# ============================================================
print("\n\n[예제 7] RAG with History")
print("-" * 70)

rag_with_history_template = ChatPromptTemplate.from_messages([
    ("system", """당신은 문서 기반 질의응답 시스템입니다.
    
참조 문서:
{context}

이전 대화를 참고하여 일관된 답변을 제공하세요."""),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{question}")
])

context = """
[문서 1] 파이썬은 1991년 귀도 반 로섬이 개발했습니다.
[문서 2] 파이썬은 간결하고 읽기 쉬운 문법이 특징입니다.
[문서 3] 데이터 과학, 웹 개발, AI 분야에서 널리 사용됩니다.
"""

# 시나리오: 연속된 질문
history = [
    HumanMessage(content="파이썬을 누가 만들었나요?"),
    AIMessage(content="파이썬은 귀도 반 로섬이 1991년에 개발했습니다."),
]

messages = rag_with_history_template.format_messages(
    context=context,
    chat_history=history,
    question="그 사람이 만든 언어의 특징은 뭔가요?"  # 맥락 의존적 질문
)

print("\n생성된 RAG 프롬프트:")
for i, msg in enumerate(messages, 1):
    print(f"{i}. [{msg.__class__.__name__}]")
    print(f"   {msg.content[:100]}...")


# ============================================================
# 예제 8: 메모리 타입별 사용
# ============================================================
print("\n\n[예제 8] 다양한 메모리 타입")
print("-" * 70)

memory_template = ChatPromptTemplate.from_messages([
    ("system", "당신은 {persona}입니다."),
    MessagesPlaceholder(variable_name="episodic_memory", optional=True),  # 일화 기억
    MessagesPlaceholder(variable_name="working_memory"),  # 작업 기억 (현재 대화)
    ("human", "{input}")
])

# 일화 기억 (장기 기억, 요약된 과거 대화)
episodic = [
    SystemMessage(content="[요약] 사용자는 머신러닝 엔지니어이며 PyTorch를 주로 사용합니다."),
]

# 작업 기억 (현재 세션의 대화)
working = [
    HumanMessage(content="GPU 메모리 에러가 발생했어요"),
    AIMessage(content="배치 크기를 줄여보세요"),
]

messages = memory_template.format_messages(
    persona="AI 기술 지원 전문가",
    episodic_memory=episodic,
    working_memory=working,
    input="여전히 에러가 나네요"
)

print("\n메모리 구조:")
print("1. 일화 기억 (장기):", len(episodic), "개 메시지")
print("2. 작업 기억 (단기):", len(working), "개 메시지")
print("\n전체 프롬프트:")
for msg in messages:
    print(f"  [{msg.__class__.__name__}] {msg.content[:60]}...")


# ============================================================
# 예제 9: 빈 히스토리 처리 패턴
# ============================================================
print("\n\n[예제 9] 빈 히스토리 처리")
print("-" * 70)

# optional=False인 경우 (기본값)
strict_template = ChatPromptTemplate.from_messages([
    ("system", "시스템 메시지"),
    MessagesPlaceholder(variable_name="history"),  # optional=False
    ("human", "{input}")
])

print("\n케이스 1: optional=False, 빈 리스트 전달")
try:
    messages = strict_template.format_messages(
        history=[],  # 빈 리스트는 OK
        input="안녕?"
    )
    print(f"  ✅ 성공! 메시지 수: {len(messages)}")
except Exception as e:
    print(f"  ❌ 에러: {e}")

print("\n케이스 2: optional=False, 변수 누락")
try:
    messages = strict_template.format_messages(
        input="안녕?"  # history 누락
    )
    print(f"  ✅ 성공!")
except Exception as e:
    print(f"  ❌ 에러: {type(e).__name__}")

print("\n케이스 3: optional=True, 변수 누락")
flexible_template = ChatPromptTemplate.from_messages([
    ("system", "시스템 메시지"),
    MessagesPlaceholder(variable_name="history", optional=True),
    ("human", "{input}")
])
try:
    messages = flexible_template.format_messages(
        input="안녕?"  # history 누락해도 OK
    )
    print(f"  ✅ 성공! 메시지 수: {len(messages)}")
except Exception as e:
    print(f"  ❌ 에러: {e}")


# ============================================================
# 예제 10: 실전 활용 - 스트리밍 챗봇
# ============================================================
print("\n\n[예제 10] 스트리밍 챗봇 구조")
print("-" * 70)

streaming_template = ChatPromptTemplate.from_messages([
    ("system", """당신은 실시간 스트리밍 챗봇입니다.
응답 스타일: {style}
최대 토큰: {max_tokens}"""),
    MessagesPlaceholder(variable_name="message_history"),
    ("human", "{user_message}")
])

print("""
실전 사용 예시:

```python
from langchain_openai import ChatOpenAI
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# LLM 초기화
llm = ChatOpenAI(model="gpt-4", streaming=True)

# 메모리 저장소
store = {}

def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# 체인 생성
chain = streaming_template | llm

# 히스토리와 함께 실행
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="user_message",
    history_messages_key="message_history",
)

# 스트리밍 실행
for chunk in chain_with_history.stream(
    {
        "style": "친근하고 전문적인",
        "max_tokens": 150,
        "user_message": "AI에 대해 설명해줘"
    },
    config={"configurable": {"session_id": "user_123"}}
):
    print(chunk.content, end="", flush=True)
```
""")


# ============================================================
# 보너스: MessagesPlaceholder 디버깅 팁
# ============================================================
print("\n\n[보너스] 디버깅 팁")
print("-" * 70)

debug_template = ChatPromptTemplate.from_messages([
    ("system", "System message"),
    MessagesPlaceholder(variable_name="debug_history", optional=True),
    ("human", "{query}")
])

print("\n1. 템플릿 구조 확인:")
print(f"   input_variables: {debug_template.input_variables}")
print(f"   optional_variables: {debug_template.optional_variables}")

print("\n2. 메시지 타입 검증:")
valid_messages = [
    HumanMessage(content="올바른 메시지"),
    AIMessage(content="올바른 메시지")
]
print(f"   ✅ 모든 메시지가 BaseMessage 타입: {all(hasattr(m, 'content') for m in valid_messages)}")

print("\n3. 히스토리 길이 모니터링:")
test_history = [HumanMessage(content="test")] * 10
print(f"   현재 히스토리 길이: {len(test_history)}")
print(f"   토큰 수 추정: ~{len(test_history) * 50} tokens")

print("\n" + "=" * 70)
print("MessagesPlaceholder 가이드 완료!")
print("=" * 70)

print("""
핵심 정리:
✅ MessagesPlaceholder는 메시지 리스트를 동적 삽입
✅ optional=True로 선택적 히스토리 관리
✅ 챗봇, 에이전트, RAG 시스템의 핵심 컴포넌트
✅ 대화 맥락 유지와 메모리 관리에 필수
""")

 

messages_placeholder_guide.py
0.02MB

 

with Claude

'공부방 > Python & AI' 카테고리의 다른 글

[AI] ChatPromptTemplate.from_messages  (0) 2025.10.13
[Python] Sequence  (0) 2025.10.12
[AI] add_messages  (0) 2025.10.12
[Python] Annotated  (0) 2025.10.12
[AI] ChatPromptTemplate  (0) 2025.10.12