Отображение сообщений в чате с агентом
Диалоговый ин терфейс GigaLab умеет отображать сложные элементы (карточки, ход рассуждений модели, подсказки) на основе их описания, которое передается в соответствующем сообщении.
Для этого в сообщение нужно добавить дополнительные поля additional_kwargs с описанием элементов.
В этом разделе вы найдете примеры структур различных сообщений и элементов, и того, как они отображаются в чате с агентом.
Функциональность работает в чате с агентом, после публикации в любом окружении: DEV, IFT или ПРОМ.
Поддерживается отображение сообщений заданных типов:
- пользователя — тип
human; - агента — тип
ai; - сообщение, сгенерированное в процессе работы функции GigaChat — тип
tool.
Также возможно отображение собственных типов, например, для отображения логов.
Диалоговый интерфейс агента можно также мен ять с помощью специальных функций SDK.
Сообщения пользователя
Сообщение пользователя передается с типом human.
Пример сообщения в чате с агентом:
Пример кода для формирования сообщения:
import uuid
from langchain_core.messages import HumanMessage
def make_human_message(content: str) -> HumanMessage:
return HumanMessage(
id=str(uuid.uuid4()),
content=content,
additional_kwargs={},
)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "human",
"content": "Вопрос пользователя"
}
Приложенные файлы
Если агент может работать с файлами, которые передал пользователь, они также будут отображаться в чате. Пример отображения сообщения пользователя с приложенными файлами:
Пример кода для формирования сообщения:
import uuid
from langchain_core.messages import HumanMessage
def make_human_with_files(content: str, files: list[dict]) -> HumanMessage:
# files: [{"id": "...", "filename": "...", "url": "..."}]
return HumanMessage(
id=str(uuid.uuid4()),
content=content,
additional_kwargs={"attached_files": files},
)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "human",
"content": "Вопрос пользователя",
"additional_kwargs": {
"attached_files": [
{
"id": "<UUIDv4>",
"filename": "example.md",
"url": "<fileUploadUrl>"
}
]
}
}
Сообщения агента
GigaLab автоматически отрисовывает MDX-контент сгенерированный агентом и переданный в сообщении с типом ai.
Поддерживаются таблицы, картинки, ссылки, математические формулы и другое форматирование.
Пример кода для формирования сообщения:
from langchain_core.messages import AIMessage
def make_ai_message(content: str) -> AIMessage:
return AIMessage(content=content)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "ai",
"content": "MDX контент"
}
Вы также можете отображать дополнительные элементы, например, процесс размышления агента или подсказки.
Отображение размышлений
Если агент поддерживает размышления (thinking), то вы сможете отобразить шаги его рассуждений.
Для этого в сообщении с типом ai нужно передать специальный тег <thinking>.
В интерфейсе это буд ет выглядеть так:
Пример кода для формирования сообщения:
from langchain_core.messages import AIMessage
def make_ai_thinking_message(text: str) -> AIMessage:
mdx_content = f"<thinking>\n{text}\n</thinking>"
return AIMessage(content=mdx_content)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "ai",
"content": "<thinking>\nДля вывода текущего времени в Москве мне потребуется воспользоваться библиотекой datetime и pytz в Python. Я создам соответствующий скрипт и выполню его.\n</thinking>"
}
Отображение источников
В ответах агента можно отображать источники, списки ссылок и карточки.
Для этого нужно использовать соответствующие структуры, переданные в объекте additional_kwargs.
Отображение источников интерфейсе:
Для отображения источников используйте структуру additional_kwarg.shelves:
Пример кода для формирования сообщения:
from langchain_core.messages import AIMessage
from pydantic import BaseModel
from typing import List, Optional
class SearchDocument(BaseModel):
title: Optional[str] = None
url: Optional[str] = None
snippet: Optional[str] = None
source: Optional[str] = None # "tg", "facts", "news" и т.п.
class Shelf(BaseModel):
shelfName: str
documents: List[SearchDocument]
def make_ai_with_shelves(answer: str, shelves: List[Shelf]) -> AIMessage:
shelves_json = [shelf.model_dump() for shelf in shelves]
return AIMessage(
content=answer,
additional_kwargs={"shelves": shelves_json},
)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "ai",
"content": "<Контент сообщения в формате MDX>",
"additional_kwargs": {
"shelves": [
{
"shelfName": "Telegram",
"documents": [
{
"title": "Бизнес.com",
"url": "<sourceUrl>",
"snippet": "...",
"source": "tg"
}
]
}
]
}
}
Отображение карточек
Карточки в интерфейсе:
Пример таких карточек можно найти в чате с агентом помощником туриста.
Для отображения карточек используйте структуру additional_kwargs.carousels.
Пример кода для формирования сообщен ия:
from langchain_core.messages import AIMessage
def make_ai_with_carousel(cards: list[dict]) -> AIMessage:
# cards: [{"title": "...", "image_urls": ["..."], "additional_data": {...}}, ...]
carousels = [
{
"type": "CARD",
"sub_type": "EXCURSION",
"priority": 2,
"data": cards,
}
]
return AIMessage(
content="Описание и рекомендации по найденным вариантам.",
additional_kwargs={"frontend_data": {"carousels": carousels, "sources": []}},
)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "ai",
"content": "<Контент сообщения в формате MDX>",
"additional_kwargs": {
"frontend_data": {
"carousels": [
{
"type": "CARD",
"sub_type": "EXCURSION",
"priority": 2,
"data": [
{
"title": "Обзорная экскурсия г. Южно=Сахалинск",
"image_urls": ["<imageUrl>"],
"additional_data": {
"additional_attributes": [
"Городская экскурсия",
"3 часа"
],
"address": "Муниципальное образование городской округ «Город Южно-Сахалинск»",
"price": 7000,
"number_of_people": 1,
"url": "<cardUrl>",
"urls": [
{
"url": "<externalUrl>",
"type": "EXTERNAL_WEBSITE"
}
],
"priority": 0
}
}
]
}
],
"sources": []
}
}
}
Отображение ссылок
Ссылки в интерфейсе:
Для отображения ссылок используйте структуру additional_kwargs.artifacts
Пример кода для формирования сообщения:
from langchain_core.messages import AIMessage
def make_ai_with_artifacts(layout: str, artifact_links: list[dict]) -> AIMessage:
# artifact_links: [{"type": "pdf" | "docx" | "markdown", "link": "<url>"}]
return AIMessage(
content=layout,
additional_kwargs={"artifacts": artifact_links},
)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "ai",
"content": "<Контент сообщения в формате MDX>",
"additional_kwargs": {
"artifacts": [
{
"type": "pdf",
"link": "<artifactDownloadUrl>"
},
{
"type": "docx",
"link": "<artifactDownloadUrl>"
},
{
"type": "markdown",
"link": "<artifactDownloadUrl>"
}
]
}
}
Сообщения функций
Сообщения, сгенерированные в результате работы функций можно отобразить с помощью типа tool:
Обычно, сообщения функций формируются средой исполнения при ответе инструмента.
Пример кода для формирования сообщения:
from langchain_core.messages import ToolMessage
import json
import uuid
def make_tool_message(result: dict, tool_call_id: str) -> ToolMessage:
return ToolMessage(
id=str(uuid.uuid4()),
tool_call_id=tool_call_id,
content=json.dumps(result, ensure_ascii=False),
)
JSON-схема данных, которые поступают на фронтенд:
{
"id": "<UUIDv4>",
"type": "tool",
"content": "{\"message\": \"Результат выполнения: ...\", \"is_exception\": false}",
"tool_call_id": "<UUIDv4>"
}
Добавление подсказок
Подсказки (suggests) — кнопки с вариантами продолжения диалога, которые появляются после ответа агента.
Пример JSON-схемы данных подсказок, которые поступают на фронтенд:
{
"json_schema_extra": {
"examples": [
{
"should_suggest": True,
"suggests": [
{
"label": "Уточнить свежие новости по теме",
"prompt": "Используй DuckDuckGo и найди последние новости",
"position": 0,
}
],
},
{
"should_suggest": False,
"suggests": [],
},
]
}
}
В образцах агентов sample и sample-ts подсказки добавляются в отдельном узле suggests:
def suggest_node(state: RickState) -> dict:
try:
ctx = _now_context()
mem_req = MEMORY_REQUIREMENTS.format(tz=ctx["tz"])
suggests = generate_suggests(
state.messages,
system_prompt=SYSTEM_TEMPLATE.format(
tool_inventory=AGENT_CAPABILITIES,
memory_requirements=mem_req,
now_iso=ctx["now_iso"],
today_iso=ctx["today_iso"],
tz=ctx["tz"],
),
agent_capabilities=AGENT_CAPABILITIES,
)
except Exception as error: # fail open to avoid breaking the dialogue
logger.warning("Failed to generate suggests: %s", error)
return {}
attach_suggests_to_last_message(state.messages, suggests)
logger.info("Suggests attached to last message. additional_kwargs=%s", state.messages[-1].additional_kwargs)
result_messages = [state.messages[-1]]
return {"messages": result_messages}
function loadSuggestsFromText(raw: string): SuggestsOutput | null {
const cleaned = raw.replace(/\{\{/g, "{").replace(/\}\}/g, "}").trim();
try {
return SuggestsOutputSchema.parse(JSON.parse(cleaned));
} catch {
// Извлечь JSON из текста
const start = cleaned.indexOf("{");
const end = cleaned.lastIndexOf("}");
if (start === -1 || end === -1) return null;
return SuggestsOutputSchema.parse(JSON.parse(cleaned.slice(start, end + 1)));
}
}
Генерация подсказок
Модель может самостоятельно генерировать подсказки в зависимости от контекста.
def generate_suggests(
messages: Sequence[BaseMessage],
*,
system_prompt: str,
agent_capabilities: str,
max_suggests: int = 3,
) -> List[Dict]:
"""Генерация подсказок для последнего сообщения агента."""
# Формирование истории диалога
conversation = format_conversation(messages)
# Сборка промпта для генерации подсказок
instruction = SUGGEST_INSTRUCTION_TEMPLATE.format(
MAX_SUGGESTS=max_suggests,
CONVERSATION=conversation,
CAPABILITIES=agent_capabilities
)
# Настройка LLM (отключение потоковой передачи для получения коректного JSON-объекта)
llm = get_model(type="chat", throttled=True)
llm.disable_streaming = True
# Генерация подсказок со структурированным выводом
structured_llm = llm.with_structured_output(SuggestsOutput, method="json_mode")
result = structured_llm.invoke([
SystemMessage(content=system_prompt),
HumanMessage(content=instruction)
])
# Возврат пустого списка, если подсказки не нужны
if not result.should_suggest:
return []
# Форматирование и возврат подсказок
suggestions = []
for idx, item in enumerate(result.suggests):
if not item.prompt:
continue
suggestions.append({
"prompt": item.prompt,
"label": item.label or item.prompt,
"position": item.position or idx
})
return suggestions[:max_suggests]
При генерации подсказок в llm.invoke() обязательно нужно передавать массив callbacks: [].
В противном случае LangGraph-компонент StreamMessagesHandler будет передавать подсказки в потоке токенов как обычное сообщение в формате SSE (Server Sent Events).
При этом в веб-интерфейсе появится JSON-текст, который потом пропадет.
Кроме этого, для корректного получения JSON-объекта с подсказками, нужно отключить потоковую передачу streaming: false при инициализации GigaChat.
Пример генерации подсказок:
export async function generateSuggests(
messages: BaseMessage[],
systemPrompt: string,
agentCapabilities: string,
maxSuggests = 3,
): Promise<Array<Record<string, any>>> {
const conversation = formatConversation(messages);
const instruction = SUGGEST_INSTRUCTION_TEMPLATE
.replace("{MAX_SUGGESTS}", String(maxSuggests))
.replace("{CONVERSATION}", conversation)
.replace("{CAPABILITIES}", agentCapabilities);
const llm = new GigaChat({
model: process.env.GIGACHAT_MODEL || "GigaChat-Max",
baseUrl: process.env.GIGACHAT_BASE_URL,
temperature: 0.3,
streaming: false, // Отключение потоковой передачи для корректной обработки JSON-вывода
});
await ensureFreshToken(llm, tokenOpts);
// callbacks: [] нужно передавать обязательно
const rawMessage = await llm.invoke(promptMessages, { callbacks: [] });
// ... парсинг JSON ...
}
Прикрепление к сообщению агента
Подсказки прикрепляются к последнему сообщению агента (тип ai).
def attach_suggests_to_last_message(
messages: Sequence[BaseMessage],
suggests: Sequence[Dict],
) -> None:
"""Mutate the last AI message by placing suggests into its additional kwargs."""
if not suggests:
logger.debug("attach_suggests_to_last_message: no suggests to attach")
return
if not messages:
logger.warning("attach_suggests_to_last_message called with empty messages list")
return
last_message = messages[-1]
if not isinstance(last_message, AIMessage):
logger.warning(
"attach_suggests_to_last_message: last message is not AIMessage (type=%s)",
type(last_message),
)
return
additional = dict(getattr(last_message, "additional_kwargs", {}) or {})
additional["suggests"] = list(suggests)
last_message.additional_kwargs = additional
logger.debug("attach_suggests_to_last_message: updated additional_kwargs=%s", additional)
Для определения типа используется функция _getType(), которая корректно определяет тип сообщения.
Это связанно с тем, что метод instanceof AIMessage вернет false для экземпляра AIMessageChunk.
Так как в LangChain JS-классы AIMessageChunk и AIMessage наследуются от BaseMessage.
Пример прикрепления подсказки к сообщению:
export function attachSuggestsToLastMessage(
messages: BaseMessage[],
suggests: Array<Record<string, any>>,
): void {
if (!suggests.length || !messages.length) return;
const last = messages[messages.length - 1];
// Вместо `instanceof AIMessage` использется метод `_getType()`
if (last._getType() !== "ai") {
console.warn(`Last message is not AI type (type=${last._getType()})`);
return;
}
const additional = { ...(last.additional_kwargs || {}) };
additional.suggests = [...suggests];
last.additional_kwargs = additional;
}
Разбор JSON-ответа
Модель может вернуть JSON-объект с лишним текстом или внутри markdown-разметки.
Пример разбора JSON-ответа:
def _load_suggests_from_text(raw_text: str) -> Optional[SuggestsOutput]:
if not raw_text:
return None
cleaned = _normalise_json_braces(raw_text.strip())
try:
data = json.loads(cleaned)
except json.JSONDecodeError:
logger.debug("Failed to json.loads cleaned text, trying to locate JSON block")
start = cleaned.find("{")
end = cleaned.rfind("}")
if start == -1 or end == -1 or end <= start:
return None
try:
data = json.loads(cleaned[start : end + 1])
except json.JSONDecodeError as error:
logger.debug("JSON extraction still failed: %s", error)
return None
if isinstance(data, SuggestsOutput):
return data
if isinstance(data, dict):
try:
return SuggestsOutput(**data)
except Exception as error: # noqa: BLE001
logger.debug("Pydantic validation failed for fallback JSON: %s", error)
return None
return None
function loadSuggestsFromText(raw: string): SuggestsOutput | null {
const cleaned = raw.replace(/\{\{/g, "{").replace(/\}\}/g, "}").trim();
try {
return SuggestsOutputSchema.parse(JSON.parse(cleaned));
} catch {
// Извлечь JSON из текста
const start = cleaned.indexOf("{");
const end = cleaned.lastIndexOf("}");
if (start === -1 || end === -1) return null;
return SuggestsOutputSchema.parse(JSON.parse(cleaned.slice(start, end + 1)));
}
}
Примеры
| Пример | Описание |
|---|---|
suggest_utils.py | Подробный пример добавления подсказок на Python. Пример демонстрирует работу со структурированными данными. Чтобы получить ответ единым блоком и корректно обработать его, перед вызовом модели отключается потоковая передача токенов llm.disable_streaming = True . По умолчанию потоковая передача токенов включена |
suggest_utils.ts | Подробный пример добавления подсказок на TypeScript |
Собственные сообщения
Интерфейс чата также может отображать произвольные сообщения event=custom.
Например, лог работы агента:
Пример кода для формирования сообщения:
from langgraph.types import StreamWriter
from agent_lab_sdk.schema import LogMessage
async def run_step(state, writer: StreamWriter):
writer(LogMessage("🔍 Запуск исследования для 'Привет'..."))
# ... полезная работа ...
writer(LogMessage("✅ Обработка завершена"))
return state
JSON-схема данных, которые поступают на фронтенд:
{
"type":"log",
"content":"🔍 Запуск исследования для 'Привет'..."
}