Типы сообщений LangChain
В LangChain все разговоры передаются через объекты Message. Понимание назначения различных типов сообщений — основа для написания агентов.
Четыре основных типа сообщений
LangChain определяет четыре основных типа сообщений, которые соответствуют различным ролям в разговоре:
| Type | Роль | Описание | Типичное содержимое |
|---|---|---|---|
| HumanMessage | Пользователь | Сообщение, отправленное пользователем | "Какая сегодня погода?" |
| AIMessage | ИИ-ассистент | Ответ модели, может включать tool_calls | "Сегодня в Ханчжоу солнечно, 25°C" |
| SystemMessage | Система | Системная инструкция, определяющая роль и правила поведения ИИ | "Вы — профессиональный ассистент по погоде" |
| ToolMessage | Инструмент | Возврат результата после выполнения инструмента | "Солнечно, 25°C, влажность 60%" |
HumanMessage — сообщение пользователя
HumanMessage представляет собой сообщение, отправленное пользователем ИИ. Это наиболее распространённый тип сообщения и начальная точка диалога.
Пример
from langchain.chat_models import init_chat_model
# Создать сообщение пользователя
msg = HumanMessage(content="Что такое Python?")
print(f"Тип: {msg.type}") # human
print(f"Содержимое: {msg.content}") # Что такое Python?
print(f"Роль: {msg.role}") # user
# Создание списка сообщений (представляющего историю многоходового диалога)
messages = [
HumanMessage(content="Привет"),
HumanMessage(content="Какие курсы предлагает Example?"),
HumanMessage(content="Подходит ли курс Python для начинающих с нуля?"),
]
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
response = model.invoke(messages)
print(f"\n"Ответ модели: {response.content}")
Вывод:
Type: human Content: What is Python? Role: user Model reply: Python is a high-level, interpreted programming language known for its clear and readable syntax, widely used across many fields. Its rich ecosystem and beginner-friendly design make it an excellent choice for those starting from zero.
Быстрые способы создания HumanMessage
При создании списков сообщений можно использовать кортежи или словари в качестве сокращений:
Пример
# Способ 1: Стандартное построение
msg1 = HumanMessage(content="Hello")
# Метод 2: Сокращение через кортеж (role, content)
msg2 = ("user", "Hello")
msg3 = ("human", "Hello")
# Метод 3: Сокращение через словарь
msg4 = {"role": "user", "content": "Hello"}
# Все четыре метода эквивалентны и будут преобразованы в HumanMessage внутри Agent.
print(type(msg1)) # <class 'langchain_core.messages.human.HumanMessage'>
AIMessage — ответ ИИ
AIMessage представляет собой ответ модели. В отличие от обычного текста, AIMessage может включатьtool_calls(запросы на вызов инструментов).
Пример
# Обычный ответ ИИ (без вызова инструмента)
ai_msg = AIMessage(content="Example is a programming learning platform")
# Ответ AI, содержащий вызов инструмента
ai_with_tools = AIMessage(
content="", # При вызове инструмента content обычно пуст
tool_calls=[
{
"name": "get_weather",
"args": {"city": "Hangzhou"},
"id": "call_abc123",
"type": "tool_call",
}
]
)
print("=== Обычное сообщение AI ===")
print(f"содержимое: {ai_msg.content}")
print(f"tool_calls: {ai_msg.tool_calls}") # []
print("\n"=== Сообщение AI с вызовом инструмента ===")
print(f"содержимое: {ai_with_tools.content}")
print(f"tool_calls: {ai_with_tools.tool_calls}")
# [{'name': 'get_weather', 'args': {'city': 'Hangzhou'}, ...}]
Дополнительная информация для AIMessage
Пример
model = init_chat_model("deepseek:deepseek-v4-flash")
response = model.invoke("Расскажи о Python")
# AIMessage содержит расширенные метаданные
print(f"Содержимое: {response.content}")
print(f"ID сообщения: {response.id}")
print(f"Имя модели: {response.response_metadata.get('model_name')}")
print(f"Причина завершения: {response.response_metadata.get('finish_reason')}")
# usage_metadata содержит информацию об использовании токенов
if response.usage_metadata:
print(f"Входные токены: {response.usage_metadata.get('input_tokens')}")
print(f"Выходные токены: {response.usage_metadata.get('output_tokens')}")
print(f"Всего токенов: {response.usage_metadata.get('total_tokens')}")
Вывод:
Content: Example is a free programming learning platform for beginners... Message ID: msg_abc123def456 Model name: deepseek-v4-flash Finish reason: stop Input tokens: 19 Output tokens: 47 Total tokens: 66
SystemMessage — системная инструкция
SystemMessage используется для задания поведения, роли и ограничений ИИ. Он размещается в начале списка сообщений и определяет, как модель отвечает.
Пример
from langchain.chat_models import init_chat_model
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0.7)
# Ответ без системной инструкции
messages_no_system = [HumanMessage(content="Расскажи о Python")]
response = model.invoke(messages_no_system)
print(f"Без системной инструкции: {response.content[:80]}...")
# Ответ с системной инструкцией
messages_with_system = [
SystemMessage(content="Ты блогер в стиле Xiaohongshu; ответы должны быть живыми, используй эмодзи и добавляй тематические теги."),
HumanMessage(content="Расскажи о Python")
]
response = model.invoke(messages_with_system)
print(f"\n"С системной инструкцией: {response.content}")
Вывод:
Without system prompt: Example is an online programming learning platform for beginners, offering rich tutorials and examples... With system prompt: Amazing site! Recommending Example to every beginner who wants to learn programming! Comprehensive free tutorials covering everything from HTML to Python! Plenty of examples and quick to get started - a perfect entry point for programming novices! #ProgrammingLearning #FreeTutorials #ZeroBasics #Example
ToolMessage — Результат возврата инструмента
ToolMessage содержит результат, возвращённый после выполнения инструмента. Он должен быть связан с соответствующим tool_call.
Пример
from langchain.chat_models import init_chat_model
# Имитировать полный раунд диалога с вызовом инструментов
messages = [
HumanMessage(content="Какая погода в Ханчжоу?"),
# Модель запрашивает вызов инструмента
AIMessage(
content="",
tool_calls=[
{"name": "get_weather", "args": {"city": "Ханчжоу"},
"id": "call_abc", "type": "tool_call"}
]
),
# Результат возврата инструмента (должен включать tool_call_id, соответствующий id выше)
ToolMessage(
content="Солнечно, 25°C, влажность 60%",
tool_call_id="call_abc", # Соответствует id из tool_call
name="get_weather", # Имя инструмента
),
]
model = init_chat_model("deepseek:deepseek-v4-flash")
response = model.invoke(messages)
print(f"Ответ модели на основе результата инструмента: {response.content}")
Вывод:
Model reply based on tool result: Hangzhou is sunny today, 25 C, humidity 60%, great for outdoor activities.
tool_call_id в ToolMessage должен точно совпадать с id tool_call в AIMessage. Если они не совпадают, модель может проигнорировать результат инструмента или выдавать сбивающее с толку поведение.
AIMessageChunk — фрагмент сообщения потокового вывода
Когда вы используете stream() для потокового вывода, каждый приходящий фрагмент — это AIMessageChunk, а не полный AIMessage:
Пример
model = init_chat_model("deepseek:deepseek-v4-flash")
print("Процесс потокового вывода:")
# stream() возвращает итератор AIMessageChunk
for chunk in model.stream("Опиши Python в одном предложении"):
# Каждый фрагмент — небольшой кусочек текста
print(chunk.content, end="", flush=True)
print() # Перевод строки
Вывод:
Streaming output: Example is a free online learning platform for programming beginners, offering rich tutorials and examples.
Краткая справочная таблица типов сообщений
| Тип сообщения | атрибут type | атрибут role | Ключевые поля | Когда использовать |
|---|---|---|---|---|
| HumanMessage | human | user | content | Ввод пользователя |
| AIMessage | ai | assistant | content, tool_calls, usage_metadata | Ответ модели |
| AIMessageChunk | ai | assistant | content (delta) | Фрагмент потокового вывода |
| SystemMessage | system | system | content | Задать роль ИИ |
| ToolMessage | tool | tool | content, tool_call_id, name | Результат выполнения инструмента |
ContentBlock — структурированное содержимое сообщения
До сих пор содержимое сообщений, которое мы использовали, было обычными строками. Но на самом деле содержимое каждого сообщения может состоять из несколькихContentBlock(блоков содержимого), образующих список.
Три наиболее распространённых типа блоков содержимого:
| Type | Описание | Назначение |
|---|---|---|
| PlainTextContentBlock | Содержимое в виде обычного текста | Обычное текстовое сообщение |
| ImageContentBlock | Содержимое изображения (base64 или URL) | Ввод изображения для мультимодальных моделей |
| ToolCall | Запрос на вызов инструмента | ИИ запрашивает вызов инструмента |
Пример
from langchain.messages import PlainTextContentBlock, ImageContentBlock
# Создание сообщений с использованием блоков содержимого
# содержимое может быть обычной строкой (простой сценарий)
simple_msg = HumanMessage(content=Привет)
# содержимое также может быть списком ContentBlock (сложный сценарий)
complex_msg = HumanMessage(content=[
PlainTextContentBlock(text="Что на этой картинке?"),
# Изображение может быть URL-адресом или в кодировке base64
ImageContentBlock(
url="https://example.com/photo.jpg"
),
])
print(f"Тип содержимого простого сообщения: {type(simple_msg.content)}")
# Вывод: <class 'str'>
print(f"Тип содержимого сложного сообщения: {type(complex_msg.content)}")
# Вывод: <class 'list'>
print(f"Количество блоков содержимого: {len(complex_msg.content)}")
# Вывод: 2
Когда вам нужно отправить только обычный текст, вы можете просто передать строку, и LangChain обработает её автоматически. Только когда вам нужно смешать текст и изображения в одном сообщении, вам нужно вручную создать список ContentBlock.
Мультимодальные сообщения — позволяем модели "видеть" изображения
Если ваша модель поддерживает мультимодальный ввод (например, GPT-4o, Claude 3+), вы можете позволить ей анализировать содержимое изображения:
Пример
from pathlib import Path
from langchain.messages import HumanMessage
from langchain.chat_models import init_chat_model
# Закодировать локальное изображение в base64
def encode_image(image_path: str) -> str:
Прочитать файл изображения и преобразовать в кодировку base64
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
# Создание мультимодальной модели (должна поддерживать ввод изображений)
model = init_chat_model("deepseek:deepseek-v4-flash")
# Формирование сообщения, содержащего изображение
# Примечание: content использует формат списка, содержащий текстовые блоки и блоки изображений
image_data = encode_image("screenshot.png")
messages = [
HumanMessage(content=[
{"type": "text", "text": Опишите содержимое этого скриншота официального сайта Python.},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{image_data}",
"detail": "auto" # Необязательно: low, high, auto
}
}
])
]
response = model.invoke(messages)
print(fРезультат анализа изображения: {response.content})
Если ваше сообщение содержит только изображение и не содержит текста, можно напрямую использовать:
Пример
model = init_chat_model("deepseek:deepseek-v4-flash")
# Использование URL для прямой ссылки на изображение из интернета
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": Что описывает это изображение?},
{
"type": "image_url",
"image_url": {"url": "https://www.example.com/wp-content/uploads/2024/01/logo.png"}
}
]
}
]
response = model.invoke(messages)
print(response.content)
Не все модели поддерживают мультимодальный ввод. Серия GPT-4o от OpenAI, Claude 3+ от Anthropic и серия Gemini от Google поддерживают его. Если вы отправите изображение с использованием неподдерживаемой модели, вы получите ошибку.
ToolCall — сообщение вызова инструмента
Поле tool_calls в AIMessage — это список объектов ToolCall, где каждый ToolCall представляет запрос модели на вызов инструмента:
Пример
from langchain.messages.tool import ToolCall
# Создать ToolCall вручную
tool_call = ToolCall(
name="get_weather", # Имя инструмента
args={"city": Hangzhou}, # Аргументы вызова
id="call_abc123", # Уникальный идентификатор
type="tool_call", # Фиксированное значение
)
# Создать AIMessage, содержащий tool_calls
ai_message = AIMessage(
content="", # Когда tool_calls присутствует, content обычно пустой
tool_calls=[tool_call],
)
print(fИмя инструмента: {ai_message.tool_calls)
print(fПараметры вызова: {ai_message.tool_calls)
print(fID вызова: {ai_message.tool_calls)
Проверка, содержит ли AIMessage вызовы инструментов
Пример
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# После привязки инструментов модель может вернуть tool_calls
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": Запрос погоды,
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": Название города}
},
"required": ["city"]
}
}
}]
model_with_tools = model.bind_tools(tools)
# Вопрос, инициирующий вызов инструмента
response = model_with_tools.invoke(Какая погода в Ханчжоу?)
# Два способа определить, есть ли вызов инструмента
# Способ 1: Проверить, что список tool_calls не пуст
if response.tool_calls:
print(Модель запрашивает вызов инструмента)
# Способ 2: Проверить, пусто ли content
# (Для большинства моделей content пуст, когда присутствует tool_calls)
if not response.content:
print(Содержимое модели пусто, что указывает на то, что она хочет вызвать инструмент, а не ответить напрямую.)
trim_messages() — Обрезка истории сообщений
Когда разговор становится длиннее, список сообщений может превысить контекстное окно модели.trim_messages()Функция помогает интеллектуально обрезать историю сообщений.
Пример
HumanMessage, AIMessage, SystemMessage, trim_messages
)
from langchain.chat_models import init_chat_model
# Имитировать длинную историю разговора
messages = [
SystemMessage(content=Вы — ИИ-ассистент учебника EXAMPLE.),
HumanMessage(content=Как начать работу с Python?),
AIMessage(content=Начало работы с Python может начинаться с базовых знаний...),
HumanMessage(content=Есть ли рекомендуемые IDE?),
AIMessage(content=Рекомендую VS Code или PyCharm...),
HumanMessage(content=Как установить сторонние библиотеки?),
AIMessage(content=Используйте команду pip install...),
HumanMessage(content=Что такое NumPy?),
AIMessage(content=NumPy — это библиотека для научных вычислений...),
HumanMessage(content=В чём разница между pandas и NumPy?),
]
model = init_chat_model("deepseek:deepseek-v4-flash")
# Обрезать сообщения, чтобы уместить их в контекстное окно модели (не более 1000 токенов)
# strategy="last" сохраняет последнее системное сообщение и самый недавний диалог
trimmed = trim_messages(
messages,
max_tokens=1000, # Хранить не более 1000 токенов
strategy="last", # Сохранять последнее системное сообщение + самые последние сообщения диалога
token_counter=model, # Использовать метод подсчёта токенов модели
include_system=True, # Всегда сохранять SystemMessage
start_on="human", # После обрезки начинать с сообщения человека
)
print(fПеред обрезкой: {len(messages)} сообщений)
print(fПосле обрезки: {len(trimmed)} сообщений)
for msg in trimmed:
snippet = msg.content[:50] if isinstance(msg.content, str) else str(msg.content)[:50]
print(f" [{msg.type}] {snippet}...")
Результат выполнения:
Before trimming: 10 messages After trimming: 5 messages [system] You are the AI assistant of Example... [human] How do I install third-party libraries?... [ai] Use the pip install command... [human] What is NumPy?... [ai] NumPy is a scientific computing library...
| Стратегия | Описание | Применимые сценарии |
|---|---|---|
| strategy="last" | Сохранить системное сообщение + последнюю часть диалога | В длинных диалогах важен только последний контекст |
| strategy="first" | Сохранить системное сообщение + начало диалога | Убедиться, что ключевой контекст не обрезан |
start_on="human" гарантирует, что обрезанный список сообщений начинается с сообщения пользователя (а не с сообщения ИИ), не позволяя модели получить список, который начинается с отдельного ответа ИИ.
RemoveMessage — Удаление конкретных сообщений
В некоторых продвинутых сценариях может потребоваться удалить конкретные сообщения из истории сообщений (например, для очистки конфиденциального содержимого, повторной генерации ответов и т. д.):
Пример
# Предположим, есть диалог
messages = [
HumanMessage(content=Привет, id="msg_1"),
AIMessage(content=Здравствуйте! Чем я могу вам помочь?, id="msg_2"),
HumanMessage(content=Проверь погоду для меня, id="msg_3"),
]
# Используйте RemoveMessage для удаления конкретного сообщения (по ID)
# RemoveMessage используется с редьюсером add_messages
# При обновлении состояния Agent RemoveMessage удаляет сообщение с соответствующим ID из списка
removal = RemoveMessage(id="msg_3")
print(fID сообщения для удаления: {removal.id})
print(fТип: {removal.type}) # удалить
RemoveMessage обычно используется вместе с редьюсером add_messages из AgentState. Возврат RemoveMessage в middleware или хуках after_model позволяет динамически очищать историю сообщений.
Общие методы для свойств сообщения
Все типы сообщений наследуются от BaseMessage и имеют некоторые общие методы:
Пример
msg = HumanMessage(content=Привет, Example)
# Базовые атрибуты
print(f"content: {msg.content}") # Содержимое сообщения
print(f"type: {msg.type}") # Тип сообщения (human/ai/system/tool)
print(f"id: {msg.id}") # Автоматически сгенерированный уникальный ID
# Атрибут text: если это текстовое содержимое, вернуть текст; иначе вернуть ""
print(f"text: {msg.text}")
# pretty_repr(): форматированный вывод, подходящий для отладки
print(f"Форматированный вывод:\n{msg.pretty_repr()}")
Результат выполнения:
content: Hello, world type: human id: 00000000-0000-4000-8000-000000000000 text: Hello, world Pretty output: ================================ Human Message ================================= Hello, worldДругие расширения