Подсказки
Подсказки (suggests) — кнопки с вариантами продолжения диалога, которые появляются после ответа агента.
Подсказки добавляются с помощью узла графа — suggests:
- Узел
suggestsвызывает LLM с историей диалога и описанием инструментов. - LLM возвращает JSON-объект с вариантами продолжения диалога.
- Подсказки прикрепляются к последнему AI-сообщению с помощью
additional_kwargs.suggests. - Веб-интерфейс GigaLab отображает подсказки в виде интерактивных кнопок.
Генерация подсказок
При генерации подсказок в 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).
Для определения типа используется функция _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;
}
Узел suggests в графе
Пример создания узла suggests в графе агента:
async function suggestNode(
state: typeof MessagesAnnotation.State,
) {
try {
const suggests = await generateSuggests(
state.messages,
buildSystemPrompt(),
AGENT_CAPABILITIES,
);
attachSuggestsToLastMessage(state.messages, suggests);
return { messages: [state.messages[state.messages.length - 1]] };
} catch (e: any) {
console.warn(`[suggests] Failed: ${e.message}`);
return {}; // Не ломаем диалог при ошибке
}
}
Формат suggests
JSON-схема массива подсказок:
{
"additional_kwargs": {
"suggests": [
{
"label": "Текст кнопки для пользователя",
"prompt": "Полный текст команды при нажатии",
"position": 0
}
]
}
}
| Поле | Тип | Описание |
|---|---|---|
label | string | Короткий текст кнопки. Необязательное поле |
prompt | string | Команда, которую выполнит агент |
position | number | Порядок отображения. Необязательное поле. Первый элемент — 0 |
Разбор JSON-ответа
Модель может вернуть JSON-объект с лишним текстом или внутри markdown-разметки.
Пример разбора JSON-ответа:
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)));
}
}