Conversation est une API de haut niveau qui représente une conversation unique et avec état avec le LLM. Il s'agit du point d'entrée recommandé pour la plupart des utilisateurs. Elle gère en interne une Session et traite
des tâches complexes de traitement des données. Ces tâches incluent la gestion du contexte initial, la gestion des définitions d'outils, le prétraitement des données multimodales et l'application de modèles de prompt Jinja avec une mise en forme des messages basée sur les rôles.
Workflow de l'API Conversation
Voici le cycle de vie typique de l'utilisation de l'API Conversation :
- Créer un
Engine: initialisez un seulEngineavec le chemin et la configuration du modèle. Il s'agit d'un objet lourd qui contient les poids du modèle. - Créer un
Conversation: utilisezEnginepour créer un ou plusieurs objetsConversationlégers. - Envoyer un message : utilisez les méthodes de l'objet
Conversationpour envoyer des messages au LLM et recevoir des réponses, ce qui permet une interaction de type chat.
Vous trouverez ci-dessous la méthode la plus simple pour envoyer un message et obtenir une réponse du modèle. Elle est recommandée pour la plupart des cas d'utilisation. Elle reflète les API Gemini Chat.
SendMessage: appel bloquant qui prend l'entrée utilisateur et renvoie la réponse complète du modèle.SendMessageAsync: appel non bloquant qui diffuse la réponse du modèle jeton par jeton via des rappels.
Voici un exemple d'extrait de code :
Contenu texte uniquement
#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));
Exemple CreatePrintMessageCallback
CreatePrintMessageCallbackabsl::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();
}
Contenu de données multimodales
// 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;
Utiliser Conversation avec des outils
Pour en savoir plus sur l'utilisation des outils avec l'API Conversation, consultez Utilisation avancée.
Composants de Conversation
Conversation peut être considéré comme un délégué permettant aux utilisateurs de
gérer Session et le traitement complexe des données avant d'envoyer les
données à Session.
Types d'E/S
Le format d'entrée et de sortie principal de l'API Conversation est
Message. Actuellement, il est implémenté en tant que
JsonMessage, qui est un alias de type pour
ordered_json, une structure de données clé/valeur imbriquée flexible.
L'API Conversation fonctionne sur la base d'un message entrant et d'un message sortant, ce qui imite une expérience de chat typique. La flexibilité de
Message permet aux utilisateurs d'inclure des champs arbitraires selon les besoins de
modèles de prompt ou de modèles LLM spécifiques, ce qui permet à LiteRT-LM de prendre en charge une grande
variété de modèles.
Bien qu'il n'existe pas de norme unique et rigide, la plupart des modèles de prompt et des modèles
s'attendent à ce que Message suive des conventions semblables à celles utilisées dans le
contenu de l'API Gemini ou la
structure de message OpenAI.
Message doit contenir role, qui représente l'expéditeur du message. content peut être aussi simple qu'une chaîne de texte.
{
"role": "model", // Represent who the message is sent from.
"content": "Hello World!" // Naive text only content.
}
Pour l'entrée de données multimodales, content est une liste de part. Là encore, part n'est pas une
structure de données prédéfinie, mais un
type de données de paire clé/valeur ordonné. Les champs spécifiques dépendent de ce que le modèle de prompt et le modèle attendent.
{
"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"
}
]
}
Pour le multimodale part, nous acceptons le format suivant géré par
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",
}
Modèle d'invite.
Pour maintenir la flexibilité des modèles de variantes, PromptTemplate est implémenté en tant que wrapper fin autour de Minja. Minja est une implémentation C++ du moteur de modèle Jinja, qui traite l'entrée JSON pour générer des prompts mis en forme.
Le moteur de modèle Jinja est un format largement adopté pour les modèles de prompt LLM. Voici quelques exemples :
Le format du moteur de modèle Jinja doit correspondre exactement à la structure attendue par le modèle ajusté par instruction. En règle générale, les versions de modèle incluent le modèle Jinja standard pour garantir une utilisation appropriée du modèle.
Le modèle Jinja utilisé par le modèle sera fourni par les métadonnées du fichier de modèle.
Remarque : Une modification subtile du prompt en raison d'une mise en forme incorrecte peut entraîner une dégradation importante du modèle. Comme indiqué dans Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design or: How I learned to start worrying about prompt formatting (Quantification de la sensibilité des modèles de langage aux caractéristiques parasites dans la conception des prompts ou : comment j'ai appris à m'inquiéter de la mise en forme des prompts).
Préface
Preface définit le contexte initial de la conversation. Il peut inclure des messages initiaux, des définitions d'outils et toute autre information de base dont le LLM a besoin pour démarrer l'interaction. Cela permet d'obtenir des fonctionnalités semblables à celles de
the
Gemini API system instruction
and Gemini API Tools
La préface contient les champs suivants :
messages: messages de la préface. Les messages ont fourni le contexte initial de la conversation. Par exemple, les messages peuvent être l'historique des conversations, les instructions système d'ingénierie des prompts, des exemples de quelques prises de vue, etc.tools: outils que le modèle peut utiliser dans la conversation. Le format des outils n'est pas fixe, mais suit principalementGemini API FunctionDeclaration.extra_context: contexte supplémentaire qui permet aux modèles de personnaliser les informations de contexte requises pour démarrer une conversation. Par exemple,enable_thinkingpour les modèles avec mode de réflexion, par exemple Qwen3 ou SmolLM3-3B.
Exemple de préface pour fournir des instructions système initiales, des outils et désactiver le mode de réflexion.
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}
}
});
Historique
Conversation conserve une liste de tous les échanges de messages au sein de la session. Cet historique est essentiel pour le rendu du modèle de prompt, car le modèle de prompt Jinja nécessite généralement l'intégralité de l'historique des conversations pour générer le prompt correct pour le LLM.
Toutefois, la session LiteRT-LM est avec état, ce qui signifie qu'elle traite les entrées de manière incrémentielle. Pour combler cette lacune, Conversation génère le prompt incrémentiel nécessaire en effectuant le rendu du modèle de prompt deux fois : une fois avec l'historique jusqu'au tour précédent et une fois avec le message actuel. En comparant ces deux prompts rendus, il extrait la nouvelle partie à envoyer à la session.
ConversationConfig
ConversationConfig permet d'initialiser une instance
Conversation. Vous pouvez créer cette configuration de plusieurs manières :
- À partir d'un
Engine: cette méthode utilise leSessionConfigpar défaut associé au moteur. - À partir d'un
SessionConfig: cela permet un contrôle plus précis des paramètres de session.
Au-delà des paramètres de session, vous pouvez personnaliser davantage le
Conversation comportement dans le
ConversationConfig. Par exemple :
- Fournir un
Preface. - Remplacer le
PromptTemplatepar défaut. - Remplacer le
DataProcessorConfigpar défaut.
Ces remplacements sont particulièrement utiles pour les modèles ajustés, qui peuvent nécessiter des configurations ou des modèles de prompt différents de ceux du modèle de base à partir duquel ils ont été dérivés.
MessageCallback
MessageCallback est la fonction de rappel que les utilisateurs doivent
implémenter lorsqu'ils utilisent la méthode SendMessageAsync
asynchrone.
La signature de rappel est absl::AnyInvocable<void(absl::StatusOr<Message>)>.
Cette fonction est déclenchée dans les conditions suivantes :
- Lorsqu'un nouveau bloc du
Messageest reçu du modèle. - Si une erreur se produit lors du traitement des messages de LiteRT-LM.
- Une fois l'inférence du LLM terminée, le rappel est déclenché avec un
vide
Message(par exemple,JsonMessage()) pour signaler la fin de la réponse.
Pour obtenir un exemple d'implémentation, consultez l'appel asynchrone de l'étape 6.
Remarque : Le
Message
reçu par le rappel ne contient que le dernier bloc de la sortie du modèle,
et non l’intégralité de l’historique des messages.
Par exemple, si la réponse complète du modèle attendue d'un appel bloquant
SendMessage est la suivante :
{
"role": "model",
"content": [
"type": "text",
"text": "Hello World!"
]
}
Le rappel dans SendMessageAsync peut être appelé plusieurs
fois, chaque fois avec un élément de texte suivant :
// 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!"
]
}
L'implémenteur est responsable de l'accumulation de ces blocs si la réponse complète est nécessaire pendant le flux asynchrone. Sinon, la réponse complète
sera disponible en tant que dernière entrée dans le History une fois
l'appel asynchrone terminé.
Utilisation avancée
Décodage contraint
LiteRT-LM est compatible avec le décodage contraint, ce qui vous permet d'appliquer des structures spécifiques à la sortie du modèle, telles que des schémas JSON, des modèles d'expressions régulières ou des règles de grammaire.
Pour l'activer, définissez EnableConstrainedDecoding(true) dans ConversationConfig et fournissez un ConstraintProviderConfig (par exemple, LlGuidanceConfig pour la prise en charge des expressions régulières/JSON/grammaire). Ensuite, transmettez les contraintes via OptionalArgs dans SendMessage.
Exemple : contrainte d'expression régulière
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}
);
Pour en savoir plus, y compris sur la prise en charge du schéma JSON et de la grammaire Lark, consultez la documentation sur le décodage contraint.
Utilisation de l'outil
L'appel d'outil permet au LLM de demander l'exécution de fonctions côté client. Vous définissez des outils dans la Preface de la conversation, en les associant par nom. Lorsque le modèle génère un appel d'outil, vous le capturez, exécutez la fonction correspondante dans votre application et renvoyez le résultat au modèle.
Flux de haut niveau :
- Déclarer des outils : définissez des outils (nom, description, paramètres) dans le JSON
Preface. - Détecter les appels : vérifiez
model_message["tool_calls"]dans la réponse. - Exécuter : exécutez la logique de votre application pour l'outil demandé.
- Répondre : renvoyez un message avec
role: "tool"contenant la sortie de l'outil au modèle.