Conversation — это высокоуровневый API, представляющий собой единый диалог с LLM, сохраняющий состояние, и является рекомендуемой точкой входа для большинства пользователей. Он внутренне управляет Session и обрабатывает сложные задачи обработки данных. Эти задачи включают в себя поддержание исходного контекста, управление определениями инструментов, предварительную обработку мультимодальных данных и применение шаблонов подсказок Jinja с форматированием сообщений на основе ролей.
Рабочий процесс API диалога
Типичный жизненный цикл использования API для диалогов выглядит следующим образом:
- Создание
Engine: инициализируйте одинEngineуказав путь к модели и ее конфигурацию. Это ресурсоемкий объект, содержащий веса модели. - Создание
Conversation: ИспользуйтеEngineдля создания одного или нескольких легковесных объектовConversation. - Отправка сообщения : Используйте методы объекта
Conversationдля отправки сообщений в LLM и получения ответов, что позволяет эффективно организовать взаимодействие, подобное чату.
Ниже представлен самый простой способ отправить сообщение и получить ответ от модели. Он рекомендуется для большинства случаев использования. Он аналогичен API чата Gemini .
-
SendMessage: Блокирующий вызов, который принимает ввод пользователя и возвращает полный ответ модели. -
SendMessageAsync: неблокирующий вызов, который передает ответ модели по одному токену через функции обратного вызова.
Вот пример фрагмента кода:
Содержит только текст.
#include "runtime/engine/engine.h"
// ...
// 1. Define model assets and engine settings.
auto model_assets = ModelAssets::Create(model_path);
CHECK_OK(model_assets);
auto engine_settings = EngineSettings::CreateDefault(
model_assets,
/*backend=*/litert::lm::Backend::CPU);
// 2. Create the main Engine object.
absl::StatusOr<std::unique_ptr<Engine>> engine = Engine::CreateEngine(engine_settings);
CHECK_OK(engine);
// 3. Create a Conversation
auto conversation_config = ConversationConfig::CreateDefault(**engine);
CHECK_OK(conversation_config)
absl::StatusOr<std::unique_ptr<Conversation>> conversation = Conversation::Create(**engine, *conversation_config);
CHECK_OK(conversation);
// 4. Send message to the LLM with blocking call.
absl::StatusOr<Message> model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", "What is the tallest building in the world?"}
});
CHECK_OK(model_message);
// 5. Print the model message.
std::cout << *model_message << std::endl;
// 6. Send message to the LLM with asynchronous call
// where CreatePrintMessageCallback is a users implemented callback that would
// process the message once a chunk of message output is received.
std::stringstream captured_output;
(*conversation)->SendMessageAsync(
JsonMessage{
{"role", "user"},
{"content", "What is the tallest building in the world?"}
},
CreatePrintMessageCallback(std::stringstream& captured_output)
);
// Wait until asynchronous finish or timeout.
*engine->WaitUntilDone(absl::Seconds(10));
Пример CreatePrintMessageCallback
absl::AnyInvocable<void(absl::StatusOr<Message>)> CreatePrintMessageCallback(
std::stringstream& captured_output) {
return [&captured_output](absl::StatusOr<Message> message) {
if (!message.ok()) {
std::cout << message.status().message() << std::endl;
return;
}
if (auto json_message = std::get_if<JsonMessage>(&(*message))) {
if (json_message->is_null()) {
std::cout << std::endl << std::flush;
return;
}
ABSL_CHECK_OK(PrintJsonMessage(*json_message, captured_output,
/*streaming=*/true));
}
};
}
absl::Status PrintJsonMessage(const JsonMessage& message,
std::stringstream& captured_output,
bool streaming = false) {
if (message["content"].is_array()) {
for (const auto& content : message["content"]) {
if (content["type"] == "text") {
captured_output << content["text"].get<std::string>();
std::cout << content["text"].get<std::string>();
}
}
if (!streaming) {
captured_output << std::endl << std::flush;
std::cout << std::endl << std::flush;
} else {
captured_output << std::flush;
std::cout << std::flush;
}
} else if (message["content"]["text"].is_string()) {
if (!streaming) {
captured_output << message["content"]["text"].get<std::string>()
<< std::endl
<< std::flush;
std::cout << message["content"]["text"].get<std::string>() << std::endl
<< std::flush;
} else {
captured_output << message["content"]["text"].get<std::string>()
<< std::flush;
std::cout << message["content"]["text"].get<std::string>() << std::flush;
}
} else {
return absl::InvalidArgumentError("Invalid message: " + message.dump());
}
return absl::OkStatus();
}
Мультимодальное содержание данных
// To use multimodality, the engine must be created with vision and audio
// backend depending on the multimodality to be used
auto engine_settings = EngineSettings::CreateDefault(
model_assets,
/*backend=*/litert::lm::Backend::CPU,
/*vision_backend*/litert::lm::Backend::GPU,
/*audio_backend*/litert::lm::Backend::CPU,
);
// The same steps to create Engine and Conversation as above...
// Send message to the LLM with image data.
absl::StatusOr<Message> model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", { // Now content must be an array.
{
{"type", "text"}, {"text", "Describe the following image: "}
},
{
{"type", "image"}, {"path", "/file/path/to/image.jpg"}
}
}},
});
CHECK_OK(model_message);
// Print the model message.
std::cout << *model_message << std::endl;
// Send message to the LLM with audio data.
model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", { // Now content must be an array.
{
{"type", "text"}, {"text", "Transcribe the audio: "}
},
{
{"type", "audio"}, {"path", "/file/path/to/audio.wav"}
}
}},
});
CHECK_OK(model_message);
// Print the model message.
std::cout << *model_message << std::endl;
// The content can include multiple image or audio data.
model_message = (*conversation)->SendMessage(
JsonMessage{
{"role", "user"},
{"content", { // Now content must be an array.
{
{"type", "text"}, {"text", "First briefly describe the two images "}
},
{
{"type", "image"}, {"path", "/file/path/to/image1.jpg"}
},
{
{"type", "text"}, {"text", "and "}
},
{
{"type", "image"}, {"path", "/file/path/to/image2.jpg"}
},
{
{"type", "text"}, {"text", " then transcribe the content in the audio"}
},
{
{"type", "audio"}, {"path", "/file/path/to/audio.wav"}
}
}},
});
CHECK_OK(model_message);
// Print the model message.
std::cout << *model_message << std::endl;
Используйте диалог с помощью инструментов.
Для получения подробной информации об использовании инструмента Conversation API см. раздел «Расширенное использование» .
Компоненты в разговоре
Conversation можно рассматривать как делегата, передающего пользователям полномочия по поддержанию Session и сложной обработке данных перед отправкой данных в сессию.
Типы ввода/вывода
Основной формат ввода и вывода для Conversation API — Message . В настоящее время он реализован как JsonMessage , который является псевдонимом типа ordered_json , гибкой вложенной структуры данных типа «ключ-значение».
API Conversation работает по принципу "сообщение входящее - сообщение исходящее", имитируя типичный чат. Гибкость Message позволяет пользователям включать произвольные поля по мере необходимости в соответствии с конкретными шаблонами запросов или моделями LLM, что позволяет LiteRT-LM поддерживать широкий спектр моделей.
Хотя единого жесткого стандарта не существует, большинство шаблонов и моделей подсказок ожидают, Message будет следовать соглашениям, аналогичным тем, которые используются в Gemini API Content или структуре Message OpenAI .
Message должно содержать role , указывающий, от кого оно отправлено. content может быть простым текстовым полем.
{
"role": "model", // Represent who the message is sent from.
"content": "Hello World!" // Naive text only content.
}
При многомодальном вводе данных content представляет собой список part . Опять же, part не является предопределенной структурой данных, а представляет собой упорядоченный тип данных «ключ-значение» . Конкретные поля зависят от того, что ожидают шаблон запроса и модель.
{
"role": "user",
"content": [ // Multimodal content.
// Now the content is composed of parts
{
"type": "text",
"text": "Describe the image in details: "
},
{
"type": "image",
"path": "/path/to/image.jpg"
}
]
}
Для многомодальной part мы поддерживаем следующий формат, обрабатываемый файлом data_utils.h
{
"type": "text",
"text": "this is a text"
}
{
"type": "image",
"path": "/path/to/image.jpg"
}
{
"type": "image",
"blob": "base64 encoded image bytes as string",
}
{
"type": "audio",
"path": "/path/to/audio.wav"
}
{
"type": "audio",
"blob": "base64 encoded audio bytes as string",
}
Шаблон подсказки
Для обеспечения гибкости при работе с вариантами моделей, PromptTemplate реализован как тонкая обертка над Minja . Minja — это реализация на C++ шаблонизатора Jinja , который обрабатывает входные данные в формате JSON для генерации отформатированных подсказок.
Шаблонный движок Jinja — это широко распространенный формат для шаблонов заданий к магистерским диссертациям. Вот несколько примеров:
Формат шаблонизатора Jinja должен строго соответствовать структуре, ожидаемой от модели, оптимизированной для конкретных инструкций. Как правило, в релизы моделей включается стандартный шаблон Jinja для обеспечения корректного использования модели.
Шаблон Jinja, используемый моделью, будет предоставлен метаданными файла модели.
[!NOTE] Незначительное изменение в подсказке из-за неправильного форматирования может привести к существенному ухудшению качества модели. Как сообщается в статье «Количественная оценка чувствительности языковых моделей к ложным признакам в дизайне подсказок, или: Как я научился беспокоиться о форматировании подсказок».
Предисловие
Preface задает первоначальный контекст для разговора. Оно может включать в себя начальные сообщения, определения инструментов и любую другую справочную информацию, необходимую LLM для начала взаимодействия. Это обеспечивает функциональность, аналогичную Gemini API system instruction и Gemini API Tools
Предисловие содержит следующие поля
messagesСообщения в предисловии. Сообщения послужили исходным фоном для разговора. Например, сообщениями могут быть история разговора, инструкции по работе с системой, примеры из нескольких случаев и т. д.toolsИнструменты, которые модель может использовать в диалоге. Формат инструментов снова не фиксирован, но в основном соответствуетGemini API FunctionDeclaration.extra_contextдополнительный контекст, обеспечивающий расширяемость моделей и позволяющий настраивать необходимую контекстную информацию для начала разговора. Например,-
enable_thinkingдля моделей с режимом мышления, например, Qwen3 или SmolLM3-3B .
-
Пример предисловия, содержащего первоначальные инструкции по использованию системы, инструменты и информацию о режиме отключения мышления.
Preface preface = JsonPreface({
.messages = {
{"role", "system"},
{"content", {"You are a model that can do function calling."}}
},
.tools = {
{
{"name", "get_weather"},
{"description", "Returns the weather for a given location."},
{"parameters", {
{"type", "object"},
{"properties", {
{"location", {
{"type", "string"},
{"description", "The location to get the weather for."}
}}
}},
{"required", {"location"}}
}}
},
{
{"name", "get_stock_price"},
{"description", "Returns the stock price for a given stock symbol."},
{"parameters", {
{"type", "object"},
{"properties", {
{"stock_symbol", {
{"type", "string"},
{"description", "The stock symbol to get the price for."}
}}
}},
{"required", {"stock_symbol"}}
}}
}
},
.extra_context = {
{"enable_thinking": false}
}
});
История
В разделе «Разговор» хранится список всех обменов сообщениями в рамках сессии. Эта история имеет решающее значение для генерации шаблона подсказки, поскольку шаблон подсказки Jinja обычно требует наличия всей истории разговора для создания правильной подсказки для LLM.
Однако сессия LiteRT-LM является состоятельной, то есть обрабатывает входные данные инкрементально. Чтобы устранить этот пробел, Conversation генерирует необходимую инкрементальную подсказку, дважды отображая шаблон подсказки: один раз с историей до предыдущего хода, и один раз включая текущее сообщение. Сравнивая эти две отрендеренные подсказки, он извлекает новую часть, которая будет отправлена в сессию .
ConversationConfig
ConversationConfig используется для инициализации экземпляра Conversation . Создать эту конфигурацию можно несколькими способами:
- Из
Engine: Этот метод используетSessionConfigпо умолчанию, связанную с движком. - В рамках конкретного параметра
SessionConfig: это позволяет более точно контролировать настройки сессии.
Помимо настроек сессии, вы можете дополнительно настроить поведение Conversation в файле ConversationConfig . Это включает в себя:
-
Preface. - Переопределение
PromptTemplateпо умолчанию. - Переопределение конфигурации
DataProcessorConfigпо умолчанию.
Эти перезаписи особенно полезны для точно настроенных моделей, которые могут требовать иных конфигураций или шаблонов подсказок, чем базовая модель, на основе которой они были созданы.
MessageCallback
MessageCallback — это функция обратного вызова, которую пользователи должны реализовать при использовании асинхронного метода SendMessageAsync .
Сигнатура функции обратного вызова absl::AnyInvocable<void(absl::StatusOr<Message>)> . Эта функция запускается при следующих условиях:
- Когда от модели поступает новый фрагмент
Message. - Если во время обработки сообщений LiteRT-LM возникает ошибка.
- После завершения вывода LLM, вызывается функция обратного вызова с пустым
Message(например,JsonMessage()), сигнализирующая об окончании ответа.
Пример реализации приведен в разделе асинхронного вызова на шаге 6 .
[!ВАЖНО]
Message, полученное функцией обратного вызова, содержит только последний фрагмент выходных данных модели, а не всю историю сообщений.
Например, если полный ответ модели, ожидаемый от блокирующего вызова SendMessage будет следующим:
{
"role": "model",
"content": [
"type": "text",
"text": "Hello World!"
]
}
Функция обратного вызова в SendMessageAsync может вызываться несколько раз, каждый раз с последующим фрагментом текста:
// 1st Message
{
"role": "model",
"content": [
"type": "text",
"text": "He"
]
}
// 2nd Message
{
"role": "model",
"content": [
"type": "text",
"text": "llo"
]
}
// 3rd Message
{
"role": "model",
"content": [
"type": "text",
"text": " Wo"
]
}
// 4th Message
{
"role": "model",
"content": [
"type": "text",
"text": "rl"
]
}
// 5th Message
{
"role": "model",
"content": [
"type": "text",
"text": "d!"
]
}
Разработчик отвечает за накопление этих фрагментов, если полный ответ необходим во время асинхронного потока. В качестве альтернативы, полный ответ будет доступен в качестве последней записи в History после завершения асинхронного вызова.
Расширенные возможности использования
Декодирование с ограничениями
LiteRT-LM поддерживает декодирование с ограничениями, позволяя применять к выходным данным модели определенные структуры, такие как JSON-схемы, шаблоны регулярных выражений или правила грамматики.
Чтобы включить эту функцию, установите EnableConstrainedDecoding(true) в ConversationConfig и укажите ConstraintProviderConfig (например, LlGuidanceConfig для поддержки регулярных выражений/JSON/грамматики). Затем передайте ограничения через OptionalArgs в SendMessage .
Пример: Ограничение регулярного выражения
LlGuidanceConstraintArg constraint_arg;
constraint_arg.constraint_type = LlgConstraintType::kRegex;
constraint_arg.constraint_string = "a+b+"; // Force output to match this regex
auto response = conversation->SendMessage(
user_message,
{.decoding_constraint = constraint_arg}
);
Подробную информацию, включая поддержку JSON Schema и Lark Grammar, см. в документации по декодированию с ограничениями .
Использование инструментов
Вызов инструментов позволяет LLM запрашивать выполнение функций на стороне клиента. Вы определяете инструменты в Preface » диалога, указывая их по имени. Когда модель выдает вызов инструмента, вы перехватываете его, выполняете соответствующую функцию в своем приложении и возвращаете результат модели.
Общая схема работы: 1. Объявление инструментов: Определите инструменты (имя, описание, параметры) в JSON- Preface . 2. Обнаружение вызовов: Проверьте model_message["tool_calls"] в ответе. 3. Выполнение: Запустите логику вашего приложения для запрошенного инструмента. 4. Ответ: Отправьте сообщение с role: "tool" , содержащее выходные данные инструмента, обратно в модель.
Подробную информацию и полный пример цикла чата см. в документации по использованию инструмента .