메시지
메시지 (Messages · LangChain Python)
메시지(Messages)는 LangChain에서 모델의 문맥을 구성하는 가장 기본적인 단위예요. 모델의 입력과 출력을 나타내며, LLM과 상호작용할 때 대화 상태를 표현하는 데 필요한 내용(content)과 메타데이터를 함께 담아요.
메시지는 다음을 담는 객체예요.
- 역할(Role) — 메시지 유형을 구분해 줘요 (예:
system,user) - 내용(Content) — 메시지의 실제 내용을 나타내요 (텍스트, 이미지, 오디오, 문서 등)
- 메타데이터(Metadata) — 응답 정보, 메시지 ID, 토큰 사용량 같은 선택적 필드예요
LangChain은 모든 모델 프로바이더에서 동작하는 표준 메시지 유형을 제공해서, 어느 모델을 호출하든 일관된 동작을 보장해요.
출처: 공식문서
기본 사용법
메시지를 쓰는 가장 간단한 방법은 메시지 객체를 만들고, 모델을 호출할 때 넘겨주는 거예요.
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage, AIMessage, SystemMessage
model = init_chat_model("gpt-5-nano")
system_msg = SystemMessage("You are a helpful assistant.")
human_msg = HumanMessage("Hello, how are you?")
# Use with chat models
messages = [system_msg, human_msg]
response = model.invoke(messages) # Returns AIMessage
💡 다중 턴 에이전트는 긴 메시지 기록을 쌓아요. LangSmith는 매 턴마다 도구 결과와 모델 응답을 기록하므로 전체 대화를 살펴볼 수 있어요. tracing quickstart를 따라 추적을 켜 보세요. 추적을 모니터링하고 문제를 감지해 해결책을 제안하는 LangSmith Engine도 함께 구성하는 걸 권장해요.
텍스트 프롬프트
텍스트 프롬프트는 문자열이에요. 대화 기록을 유지할 필요가 없는 단순한 생성 작업에 딱 맞죠.
response = model.invoke("Write a haiku about spring")
텍스트 프롬프트는 이런 때 써요:
- 단발성, 독립적인 요청일 때
- 대화 기록이 필요 없을 때
- 코드를 최대한 단순하게 유지하고 싶을 때
메시지 프롬프트
다른 방법으로는 메시지 객체의 리스트를 만들어 모델에 넘겨줄 수 있어요.
from langchain.messages import SystemMessage, HumanMessage, AIMessage
messages = [
SystemMessage("You are a poetry expert"),
HumanMessage("Write a haiku about spring"),
AIMessage("Cherry blossoms bloom...")
]
response = model.invoke(messages)
메시지 프롬프트는 이런 때 써요:
- 다중 턴 대화를 다룰 때
- 멀티모달 콘텐츠(이미지, 오디오, 파일)를 다룰 때
- 시스템 지침을 포함할 때
딕셔너리 형식
메시지를 OpenAI chat completions 형식으로 직접 지정할 수도 있어요.
messages = [
{"role": "system", "content": "You are a poetry expert"},
{"role": "user", "content": "Write a haiku about spring"},
{"role": "assistant", "content": "Cherry blossoms bloom..."}
]
response = model.invoke(messages)
메시지 유형
- System message — 모델이 어떻게 행동하고 상호작용할지 맥락을 알려 줘요
- Human message — 사용자 입력과 모델과의 상호작용을 나타내요
- AI message — 모델이 생성한 응답 (텍스트, 도구 호출, 메타데이터 포함)
- Tool message — 도구 호출의 결과를 나타내요
System message
SystemMessage는 모델의 행동을 초기화하는 일련의 지침이에요. 톤을 정하거나, 모델의 역할을 정의하거나, 응답 기준을 세우는 데 쓸 수 있어요.
# Basic instructions
system_msg = SystemMessage("You are a helpful coding assistant.")
messages = [
system_msg,
HumanMessage("How do I create a REST API?")
]
response = model.invoke(messages)
# Detailed persona
from langchain.messages import SystemMessage, HumanMessage
system_msg = SystemMessage("""
You are a senior Python developer with expertise in web frameworks.
Always provide code examples and explain your reasoning.
Be concise but thorough in your explanations.
""")
messages = [
system_msg,
HumanMessage("How do I create a REST API?")
]
response = model.invoke(messages)
Human message
HumanMessage는 사용자 입력과 상호작용을 나타내요. 텍스트, 이미지, 오디오, 파일, 그 밖의 어떤 멀티모달 콘텐츠도 담을 수 있어요.
텍스트 콘텐츠
# Message object
response = model.invoke([
HumanMessage("What is machine learning?")
])
# String shortcut
# Using a string is a shortcut for a single HumanMessage
response = model.invoke("What is machine learning?")
메시지 메타데이터
# Add metadata
human_msg = HumanMessage(
content="Hello!",
name="alice", # Optional: identify different users
id="msg_123", # Optional: unique identifier for tracing
)
📝
name필드의 동작은 프로바이더마다 달라요 — 어떤 곳은 사용자 식별에 쓰고, 어떤 곳은 무시해요. 확인하려면 모델 프로바이더의 레퍼런스를 보세요.
AI message
AIMessage는 모델 호출의 출력을 나타내요. 멀티모달 데이터, 도구 호출, 그리고 나중에 접근할 수 있는 프로바이더별 메타데이터를 담을 수 있어요.
response = model.invoke("Explain AI")
print(type(response)) # <class 'langchain.messages.AIMessage'>
AIMessage 객체는 모델 호출 시 반환되며, 응답에 담긴 모든 관련 메타데이터를 포함해요.
프로바이더마다 메시지 유형에 가중치를 두거나 맥락화하는 방식이 달라서, 새 AIMessage 객체를 수동으로 만들어 마치 모델이 만든 것처럼 메시지 기록에 넣어 줘야 할 때가 있어요.
from langchain.messages import AIMessage, SystemMessage, HumanMessage
# Create an AI message manually (e.g., for conversation history)
ai_msg = AIMessage("I'd be happy to help you with that question!")
# Add to conversation history
messages = [
SystemMessage("You are a helpful assistant"),
HumanMessage("Can you help me?"),
ai_msg, # Insert as if it came from the model
HumanMessage("Great! What's 2+2?")
]
response = model.invoke(messages)
AIMessage의 주요 속성:
text(string) — 메시지의 텍스트 내용content(string | dict[]) — 메시지의 원시 내용content_blocks(ContentBlock[]) — 메시지의 표준화된 콘텐츠 블록tool_calls(dict[] | None) — 모델이 만든 도구 호출. 호출된 도구가 없으면 비어 있음id(string) — 메시지의 고유 식별자 (LangChain이 자동 생성하거나 프로바이더 응답에서 반환)usage_metadata(dict | None) — 토큰 수 등 사용량 메타데이터 (가능할 때)response_metadata(ResponseMetadata | None) — 메시지의 응답 메타데이터
도구 호출
모델이 도구 호출을 하면, 그 호출이 AIMessage에 포함돼요.
from langchain.chat_models import init_chat_model
model = init_chat_model("gpt-5-nano")
def get_weather(location: str) -> str:
"""Get the weather at a location."""
...
model_with_tools = model.bind_tools([get_weather])
response = model_with_tools.invoke("What's the weather in Paris?")
for tool_call in response.tool_calls:
print(f"Tool: {tool_call['name']}")
print(f"Args: {tool_call['args']}")
print(f"ID: {tool_call['id']}")
추론(reasoning)이나 인용(citations) 같은 다른 구조화된 데이터도 메시지 콘텐츠에 나타날 수 있어요.
토큰 사용량
AIMessage는 usage_metadata 필드에 토큰 수와 기타 사용량 메타데이터를 담을 수 있어요.
from langchain.chat_models import init_chat_model
model = init_chat_model("gpt-5-nano")
response = model.invoke("Hello!")
response.usage_metadata
{'input_tokens': 8,
'output_tokens': 304,
'total_tokens': 312,
'input_token_details': {'audio': 0, 'cache_read': 0},
'output_token_details': {'audio': 0, 'reasoning': 256}}
자세한 내용은 UsageMetadata를 참고하세요.
스트리밍과 청크
스트리밍 중에는 AIMessageChunk 객체를 받게 되는데, 이 청크들을 합쳐 완전한 메시지 객체로 만들 수 있어요.
chunks = []
full_message = None
for chunk in model.stream("Hi"):
chunks.append(chunk)
print(chunk.text)
full_message = chunk if full_message is None else full_message + chunk
📝 더 알아보기: 채팅 모델에서 토큰 스트리밍, 에이전트에서 토큰·스텝 스트리밍
Tool message
도구 호출을 지원하는 모델에서는 AI 메시지가 도구 호출을 담을 수 있어요. Tool message는 단일 도구 실행의 결과를 모델에 다시 전달하는 데 써요.
도구는 ToolMessage 객체를 직접 만들 수 있어요. 아래는 간단한 예시예요. 더 자세한 내용은 도구 가이드를 보세요.
from langchain.messages import AIMessage
from langchain.messages import ToolMessage
# After a model makes a tool call
# (Here, we demonstrate manually creating the messages for brevity)
ai_message = AIMessage(
content=[],
tool_calls=[{
"name": "get_weather",
"args": {"location": "San Francisco"},
"id": "call_123"
}]
)
# Execute tool and create result message
weather_result = "Sunny, 72°F"
tool_message = ToolMessage(
content=weather_result,
tool_call_id="call_123" # Must match the call ID
)
# Continue conversation
messages = [
HumanMessage("What's the weather in San Francisco?"),
ai_message, # Model's tool call
tool_message, # Tool execution result
]
response = model.invoke(messages) # Model processes the result
ToolMessage의 주요 속성:
content(string, 필수) — 도구 호출의 문자열화된 출력tool_call_id(string, 필수) — 이 메시지가 응답하는 도구 호출의 ID.AIMessage안의 도구 호출 ID와 일치해야 해요name(string, 필수) — 호출된 도구의 이름artifact(dict) — 모델에 보내지 않지만 프로그램적으로 접근할 수 있는 추가 데이터
📝
artifact필드에는 모델에 보내지 않지만 프로그램적으로 접근할 수 있는 보충 데이터를 넣어요. 원시 결과, 디버깅 정보, 다운스트림 처리를 위한 데이터를 모델의 문맥을 어지럽히지 않고 저장하기 좋아요.예를 들어 검색(retrieval) 도구가 문서에서 구절을 찾아 모델이 참조하도록 반환한다고 해 봐요. 메시지
content에는 모델이 참조할 텍스트를,artifact에는 애플리케이션이 쓸 문서 식별자나 메타데이터(예: 페이지 렌더링용)를 담을 수 있어요.from langchain.messages import ToolMessage # Sent to model message_content = "It was the best of times, it was the worst of times." # Artifact available downstream artifact = {"document_id": "doc_123", "page": 0} tool_message = ToolMessage( content=message_content, tool_call_id="call_123", name="search_books", artifact=artifact, )
메시지 콘텐츠
메시지의 콘텐츠는 모델로 보내지는 데이터 페이로드라고 생각하면 돼요. 메시지에는 느슨하게 타입된 content 속성이 있는데, 문자열과 타입 없는 객체(딕셔너리 등)의 리스트를 모두 지원해요. 덕분에 프로바이더 고유 구조(예: 멀티모달 콘텐츠나 기타 데이터)를 LangChain 채팅 모델에 그대로 쓸 수 있어요.
이와 별도로 LangChain은 텍스트, 추론, 인용, 멀티모달 데이터, 서버 측 도구 호출 등 메시지 콘텐츠에 대한 전용 콘텐츠 타입도 제공해요. 아래 콘텐츠 블록을 보세요.
LangChain 채팅 모델은 content 속성으로 메시지 콘텐츠를 받아요. 여기에는 다음 중 하나가 담길 수 있어요.
- 문자열
- 프로바이더 고유 형식의 콘텐츠 블록 리스트
- LangChain 표준 콘텐츠 블록 리스트
멀티모달 입력을 쓰는 예시를 보면:
from langchain.messages import HumanMessage
# String content
human_message = HumanMessage("Hello, how are you?")
# Provider-native format (e.g., OpenAI)
human_message = HumanMessage(content=[
{"type": "text", "text": "Hello, how are you?"},
{"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
])
# List of standard content blocks
human_message = HumanMessage(content_blocks=[
{"type": "text", "text": "Hello, how are you?"},
{"type": "image", "url": "https://example.com/image.jpg"},
])
💡 메시지를 초기화할 때
content_blocks를 지정하면 메시지content도 채워지지만, 타입 안전한 인터페이스로 채울 수 있어요.
표준 콘텐츠 블록
LangChain은 프로바이더 간에 동작하는 메시지 콘텐츠의 표준 표현을 제공해요.
메시지 객체는 content_blocks 속성을 구현하는데, content 속성을 지연(lazily) 파싱해 표준적이고 타입 안전한 표현으로 바꿔 줘요. 예를 들어 ChatAnthropic이나 ChatOpenAI에서 생성된 메시지는 각 프로바이더 형식의 thinking이나 reasoning 블록을 담지만, 지연 파싱을 거치면 일관된 ReasoningContentBlock 표현으로 바뀌어요.
# Anthropic
from langchain.messages import AIMessage
message = AIMessage(
content=[
{"type": "thinking", "thinking": "...", "signature": "WaUjzkyp..."},
{"type": "text", "text": "..."},
],
response_metadata={"model_provider": "anthropic"}
)
message.content_blocks
[{'type': 'reasoning',
'reasoning': '...',
'extras': {'signature': 'WaUjzkyp...'}},
{'type': 'text', 'text': '...'}]
# OpenAI
from langchain.messages import AIMessage
message = AIMessage(
content=[
{
"type": "reasoning",
"id": "rs_abc123",
"summary": [
{"type": "summary_text", "text": "summary 1"},
{"type": "summary_text", "text": "summary 2"},
],
},
{"type": "text", "text": "...", "id": "msg_abc123"},
],
response_metadata={"model_provider": "openai"}
)
message.content_blocks
[{'type': 'reasoning', 'id': 'rs_abc123', 'reasoning': 'summary 1'},
{'type': 'reasoning', 'id': 'rs_abc123', 'reasoning': 'summary 2'},
{'type': 'text', 'text': '...', 'id': 'msg_abc123'}]
원하는 추론 프로바이더로 시작하려면 통합 가이드를 보세요.
📝 표준 콘텐츠 직렬화. LangChain 밖의 애플리케이션이 표준 콘텐츠 블록 표현에 접근해야 한다면, 메시지 콘텐츠에 콘텐츠 블록을 저장하도록 선택할 수 있어요. 이렇게 하려면
LC_OUTPUT_VERSION환경변수를v1로 설정하거나, 어떤 채팅 모델이든output_version="v1"로 초기화하면 돼요.from langchain.chat_models import init_chat_model model = init_chat_model("gpt-5-nano", output_version="v1")
멀티모달 (Multimodal)
멀티모달(multimodality) 은 텍스트, 오디오, 이미지, 비디오처럼 서로 다른 형태의 데이터를 다루는 능력을 말해요. LangChain은 이런 데이터에 대해 프로바이더 간에 쓸 수 있는 표준 타입을 포함하고 있어요.
채팅 모델은 멀티모달 데이터를 입력으로 받고 출력으로도 생성할 수 있어요. 아래에 멀티모달 데이터를 넣은 입력 메시지의 짧은 예시를 보여 드릴게요.
📝 콘텐츠 블록의 최상위에 추가 키를 넣거나
"extras": {"key": value}안에 중첩할 수 있어요. 예를 들어 OpenAI는 PDF에 파일 이름을 요구해요. 선택한 모델의 프로바이더 페이지에서 구체적인 요구 사항을 확인하세요.
# Image input
# From URL
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this image."},
{"type": "image", "url": "https://example.com/path/to/image.jpg"},
]
}
# From base64 data
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this image."},
{
"type": "image",
"base64": "AAAAIGZ0eXBtcDQyAAAAAGlzb21tcDQyAAACAGlzb2...",
"mime_type": "image/jpeg",
},
]
}
# From provider-managed File ID
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this image."},
{"type": "image", "file_id": "file-abc123"},
]
}
# PDF document input
# From URL
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this document."},
{"type": "file", "url": "https://example.com/path/to/document.pdf"},
]
}
# From base64 data
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this document."},
{
"type": "file",
"base64": "AAAAIGZ0eXBtcDQyAAAAAGlzb21tcDQyAAACAGlzb2...",
"mime_type": "application/pdf",
},
]
}
# From provider-managed File ID
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this document."},
{"type": "file", "file_id": "file-abc123"},
]
}
# Audio input
# From base64 data
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this audio."},
{
"type": "audio",
"base64": "AAAAIGZ0eXBtcDQyAAAAAGlzb21tcDQyAAACAGlzb2...",
"mime_type": "audio/wav",
},
]
}
# From provider-managed File ID
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this audio."},
{"type": "audio", "file_id": "file-abc123"},
]
}
# Video input
# From base64 data
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this video."},
{
"type": "video",
"base64": "AAAAIGZ0eXBtcDQyAAAAAGlzb21tcDQyAAACAGlzb2...",
"mime_type": "video/mp4",
},
]
}
# From provider-managed File ID
message = {
"role": "user",
"content": [
{"type": "text", "text": "Describe the content of this video."},
{"type": "video", "file_id": "file-abc123"},
]
}
⚠️ 모든 모델이 모든 파일 타입을 지원하는 건 아니에요. 지원 형식과 크기 제한은 모델 프로바이더의 레퍼런스를 확인하세요.
콘텐츠 블록 레퍼런스
콘텐츠 블록은 (메시지를 만들 때든 content_blocks 속성에 접근할 때든) 타입이 있는 딕셔너리의 리스트로 표현돼요. 리스트의 각 항목은 다음 블록 타입 중 하나를 따라야 해요.
핵심 (Core):
- TextContentBlock — 목적: 표준 텍스트 출력.
type은 항상"text". 필드:text(필수),annotations,extras.{ "type": "text", "text": "Hello world", "annotations": [] } - ReasoningContentBlock — 목적: 모델 추론 단계.
type은 항상"reasoning". 필드:reasoning,extras.{ "type": "reasoning", "reasoning": "The user is asking about...", "extras": {"signature": "abc123"}, }
멀티모달 (Multimodal):
- ImageContentBlock — 목적: 이미지 데이터.
type은 항상"image". 필드:url,base64,id,mime_type(base64 데이터에 필수, 예:image/jpeg,image/png). - AudioContentBlock — 목적: 오디오 데이터.
type은 항상"audio". 필드:url,base64,id,mime_type(예:audio/mpeg,audio/wav). - VideoContentBlock — 목적: 비디오 데이터.
type은 항상"video". 필드:url,base64,id,mime_type(예:video/mp4,video/webm). - FileContentBlock — 목적: 일반 파일(PDF 등).
type은 항상"file". 필드:url,base64,id,mime_type(예:application/pdf). - PlainTextContentBlock — 목적: 문서 텍스트(
.txt,.md).type은 항상"text-plain". 필드:text,mime_type(예:text/plain,text/markdown).
도구 호출 (Tool Calling):
- ToolCall — 목적: 함수 호출.
type은 항상"tool_call". 필드:name(필수),args(필수),id(필수).{ "type": "tool_call", "name": "search", "args": {"query": "weather"}, "id": "call_123" } - ToolCallChunk — 목적: 스트리밍 도구 호출 조각.
type은 항상"tool_call_chunk". 필드:name,args(불완전한 JSON일 수 있음),id,index. - InvalidToolCall — 목적: 잘못된 형식의 호출. JSON 파싱 오류를 잡기 위한 것.
type은 항상"invalid_tool_call". 필드:name,args,error.
서버 측 도구 실행 (Server-Side Tool Execution):
- ServerToolCall — 목적: 서버 측에서 실행되는 도구 호출.
type은 항상"server_tool_call". 필드:id(필수),name(필수),args(필수). - ServerToolCallChunk — 목적: 스트리밍 서버 측 도구 호출 조각.
type은 항상"server_tool_call_chunk". 필드:id,name,args,index. - ServerToolResult — 목적: 검색 결과.
type은 항상"server_tool_result". 필드:tool_call_id(필수),id,status(필수,"success"또는"error"),output.
프로바이더 전용 블록 (Provider-Specific Blocks):
- NonStandardContentBlock — 목적: 프로바이더 전용 이스케이프 해치.
type은 항상"non_standard". 필드:value(필수). 실험이나 프로바이더 고유 기능에 사용해요.
추가 프로바이더 전용 콘텐츠 타입은 각 모델 프로바이더의 레퍼런스 문서에서 찾을 수 있어요.
💡 정식 타입 정의는 API 레퍼런스에서 확인할 수 있어요.
ℹ️ 콘텐츠 블록은 LangChain v1에서 메시지의 새 속성으로 도입됐어요. 프로바이더 간 콘텐츠 형식을 표준화하면서도 기존 코드와의 하위 호환성을 유지하기 위해서예요. 콘텐츠 블록은
content속성을 대체하는 게 아니라, 메시지 콘텐츠를 표준화된 형식으로 접근할 수 있는 새 속성이에요.
직렬화
메시지를 일반 객체로 직렬화해 저장하고, 다시 메시지 인스턴스로 역직렬화할 수 있어요. 대화 기록을 영속화하고 세션을 재개할 때 유용해요.
from langchain.messages import HumanMessage
from langchain_core.load import dumpd, load
message = HumanMessage("What is the capital of France?")
# Serialize to a plain dict
serialized = dumpd(message)
# Deserialize back to a message object
restored = load(serialized)
⚠️
load()는 Python 객체를 인스턴스화하며 역직렬화 중 부작용을 유발할 수 있어요. 신뢰할 수 없거나 인증되지 않은 출처의 데이터에는load()를 절대 호출하지 마세요.
채팅 모델과 함께 쓰기
채팅 모델은 메시지 객체의 시퀀스를 입력으로 받아 AIMessage를 출력으로 반환해요. 상호작용은 대개 무상태(stateless)라서, 단순한 대화 루프는 점점 커지는 메시지 리스트로 모델을 호출하는 방식이에요.
더 알아보려면 아래 가이드를 참고하세요.
- 대화 기록 영속화·관리를 위한 내장 기능
- 트리밍과 요약 등 컨텍스트 윈도우 관리 전략