메시지
메시지 (Messages)
에이전트와 모델 사이에서 주고받는 것, 그리고 대화의 맥락을 구성하는 가장 기본 단위가 바로 **메시지(message)**예요. 메시지는 단순한 문자열이 아니라 역할(role), 내용(content), 메타데이터를 함께 담는 객체죠. 이번 페이지에서는 LangChain의 메시지 구조와 각 메시지 유형을, 그리고 멀티모달·스트리밍 같은 확장 개념까지 차근차근 살펴볼게요.
메시지가 뭔가요?
메시지는 LangChain에서 모델의 맥락을 나타내는 기본 단위예요. 모델의 입력과 출력을 나타내며, LLM과 대화할 때 대화 상태를 표현하는 데 필요한 내용과 메타데이터를 함께 담습니다. 메시지 객체가 포함하는 것들은 다음과 같아요.
- 역할 (Role) — 메시지 유형을 식별 (예:
system,user) - 내용 (Content) — 메시지의 실제 내용 (텍스트, 이미지, 오디오, 문서 등)
- 메타데이터 (Metadata) — 응답 정보, 메시지 ID, 토큰 사용량 같은 선택 필드
LangChain은 모든 모델 제공자에서 동작하는 표준 메시지 유형을 제공해서, 어떤 모델을 호출하든 일관된 동작을 보장해요.
기본 사용법 (Basic usage)
가장 간단한 사용법은 메시지 객체를 만들고 모델 호출 시 전달하는 거예요.
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
다회전(turn) 에이전트는 긴 메시지 히스토리를 축적해요. LangSmith는 각 턴, 도구 결과, 모델 응답을 기록하므로 전체 대화를 살펴볼 수 있어요. tracing quickstart로 추적을 활성화하는 걸 추천합니다. 에이전트가 쓴 메시지를 눈으로 확인하고 싶다면 LangSmith Engine을 함께 설정해서 트레이스를 모니터링하고 이슈를 감지·수정 제안을 받을 수도 있어요.
텍스트 프롬프트 (Text prompts)
텍스트 프롬프트는 문자열이에요. 대화 이력을 유지할 필요가 없는 단순 생성 작업에 적합하죠.
response = model.invoke("Write a haiku about spring")
텍스트 프롬프트를 쓸 만한 상황:
- 단일·독립적인 요청
- 대화 이력이 필요 없을 때
- 코드 복잡도를 최소화하고 싶을 때
메시지 프롬프트 (Message prompts)
반대로 메시지 객체의 리스트를 모델에 전달할 수도 있어요.
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)
메시지 프롬프트가 어울리는 상황:
- 다회전 대화 관리
- 멀티모달 콘텐츠 (이미지, 오디오, 파일)
- 시스템 지시 포함
딕셔너리 형식 (Dictionary format)
OpenAI 채팅 완성 형식 그대로 메시지를 지정할 수도 있어요.
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)
메시지 유형 (Message types)
- 시스템 메시지 — 모델이 어떻게 행동할지, 상호작용 맥락을 알려줘요
- 인간 메시지 — 사용자 입력과 모델과의 상호작용을 나타냄
- AI 메시지 — 모델이 생성한 응답 (텍스트, 도구 호출, 메타데이터 포함)
- 도구 메시지 — 도구 호출의 출력을 나타냄
시스템 메시지 (System message)
SystemMessage는 모델의 행동을 초기화하는 지시 집합이에요. 어조를 설정하거나, 모델의 역할을 정의하거나, 응답 지침을 세울 수 있어요.
system_msg = SystemMessage("You are a helpful coding assistant.")
messages = [
system_msg,
HumanMessage("How do I create a REST API?")
]
response = model.invoke(messages)
페르소나를 상세하게 지정할 수도 있어요.
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는 사용자 입력과 상호작용을 나타내요. 텍스트, 이미지, 오디오, 파일 같은 멀티모달 콘텐츠를 담을 수 있어요.
response = model.invoke([
HumanMessage("What is machine learning?")
])
# Using a string is a shortcut for a single HumanMessage
response = model.invoke("What is machine learning?")
메타데이터 추가하기
human_msg = HumanMessage(
content="Hello!",
name="alice", # Optional: identify different users
id="msg_123", # Optional: unique identifier for tracing
)
name 필드의 동작은 제공자마다 달라요. 어떤 제공자는 사용자 식별에 쓰고, 다른 제공자는 무시하기도 합니다. 자세한 내용은 해당 모델 제공자의 레퍼런스를 확인하세요.
AI 메시지 (AI message)
AIMessage는 모델 호출의 출력을 나타내요. 멀티모달 데이터, 도구 호출, 제공자별 메타데이터를 포함할 수 있어요.
response = model.invoke("Explain AI")
print(type(response)) # <class 'langchain.messages.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 | 메시지의 응답 메타데이터 |
도구 호출 (Tool calls)
모델이 도구 호출을 하면 그 호출이 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)이나 인용 같은 다른 구조적 데이터도 메시지 content에 나타날 수 있어요.
토큰 사용량 (Token usage)
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}}
스트리밍과 청크 (Streaming and chunks)
스트리밍 중에는 조각들을 합쳐 온전한 메시지 객체로 만들 수 있는 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,
)
메시지 콘텐츠 (Message content)
메시지의 content는 모델에 전송되는 데이터 페이로드라고 생각하면 돼요. 메시지의 content 속성은 느슨하게 타입이 지정되어 있어서 문자열과 타입 없는 객체 리스트(예: 딕셔너리)를 모두 지원해요. 덕분에 멀티모달 콘텐츠 같은 제공자 고유 구조를 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도 채워지지만, 타입이 안전한 인터페이스로 작업할 수 있어요.
표준 콘텐츠 블록 (Standard content blocks)
LangChain은 제공자 간에 동작하는 메시지 콘텐츠의 표준 표현을 제공해요. 메시지 객체는 content_blocks 속성을 구현하는데, content 속성을 지연(lazy) 파싱해 표준·타입 안전 표현으로 만듭니다. 예를 들어 ChatAnthropic이나 ChatOpenAI에서 생성된 메시지는 각 제공자 형식의 thinking/reasoning 블록을 담지만, 일관된 ReasoningContentBlock 표현으로 지연 파싱할 수 있어요.
예를 들면, Anthropic 메시지의 thinking 블록은 표준 reasoning 블록으로, OpenAI의 reasoning 블록도 동일한 reasoning 표현으로 정규화됩니다.
표준 콘텐츠 직렬화 — LangChain 밖의 애플리케이션이 표준 콘텐츠 블록 표현을 필요로 한다면, 메시지 콘텐츠에 콘텐츠 블록을 저장하는 것을 선택(opt-in)할 수 있어요. 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에 파일 이름을 요구합니다. 구체적인 내용은 선택한 모델의 제공자 페이지를 확인하세요.
이미지 입력 — URL, base64, 또는 제공자 관리 File ID 형태로 보낼 수 있어요.
# 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 문서 입력 — 파일은 URL, base64, File ID 형태로 보낼 수 있어요.
# 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"},
]
}
오디오/비디오 입력 — base64와 mime_type(예: audio/wav, video/mp4), 또는 File ID로 보낼 수 있어요.
모든 모델이 모든 파일 유형을 지원하는 건 아니에요. 지원 형식과 크기 제한은 모델 제공자의 레퍼런스를 확인하세요.
콘텐츠 블록 레퍼런스 (Content block reference)
콘텐츠 블록은 (메시지를 만들 때든 content_blocks 속성에 접근할 때든) 타입이 지정된 딕셔너리 리스트로 표현돼요. 주요 블록 유형은 다음과 같습니다.
핵심 (Core)
TextContentBlock— 표준 텍스트 출력.type은 항상"text",text(내용) 필수,annotations·extras선택.
{
"type": "text",
"text": "Hello world",
"annotations": []
}
ReasoningContentBlock— 모델 추론 단계.type은 항상"reasoning",reasoning(추론 내용),extras(예: Anthropic의signature)를 담아요.
도구 호출 (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", 부분 인자(불완전한 JSON일 수 있음)와 스트림 내 위치index를 담아요.InvalidToolCall— 잘못된 호출로, JSON 파싱 오류를 잡기 위한 것.error로 무엇이 잘못됐는지를 담아요.
서버측 도구 실행 (Server-Side Tool Execution)
ServerToolCall— 서버에서 실행되는 도구 호출.type은 항상"server_tool_call".ServerToolCallChunk— 서버측 도구 호출의 스트리밍 조각.ServerToolResult— 서버측 도구 실행 결과.status는"success"또는"error", 실행된 도구의output을 담아요.
제공자별 블록 (Provider-Specific Blocks)
NonStandardContentBlock— 제공자 고유의 이스케이프 해치.type은 항상"non_standard",value에 제공자 고유 데이터 구조를 담아요. 실험적이거나 제공자 고유 기능에 사용.
추가 제공자별 콘텐츠 유형은 각 모델 제공자의 레퍼런스 문서에서 찾을 수 있어요. 정규 타입 정의는 API 레퍼런스에서 확인하세요.
콘텐츠 블록은 LangChain v1에서 메시지의 새 속성으로 도입되어, 기존 코드와의 하위 호환성을 유지하면서 제공자 간 콘텐츠 형식을 표준화했어요. 콘텐츠 블록은 content 속성을 대체하는 게 아니라, 표준화된 형식으로 메시지 콘텐츠에 접근하는 새 속성이에요.
직렬화 (Serialization)
메시지를 일반 객체로 직렬화해 저장하고, 다시 메시지 인스턴스로 역직렬화할 수 있어요. 대화 이력을 영속화하거나 세션을 재개할 때 유용합니다.
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()를 호출하지 마세요.
챗 모델과 함께 쓰기 (Use with chat models)
챗 모델은 메시지 객체 시퀀스를 입력으로 받아 AIMessage를 출력으로 반환해요. 상호작용은 종종 비상태적(stateless)이라서, 단순 대화 루프는 커지는 메시지 리스트로 모델을 호출하는 방식입니다.
- 대화 이력을 영속화·관리하는 내장 기능
- 트리밍·요약을 포함한 컨텍스트 윈도우 관리 전략