Conversation è un'API di alto livello che rappresenta una singola conversazione stateful con l'LLM ed è il punto di accesso consigliato per la maggior parte degli utenti. Gestisce internamente un Session e gestisce attività complesse di elaborazione dei dati. Queste attività includono il mantenimento del contesto iniziale, la gestione
delle definizioni degli strumenti, l'elaborazione preliminare dei dati multimodali e l'applicazione di modelli
di prompt Jinja con formattazione dei messaggi basata sui ruoli.
Workflow API Conversation
Il ciclo di vita tipico per l'utilizzo dell'API Conversation è:
- Crea un
Engine: inizializza un singoloEnginecon il percorso e la configurazione del modello. Si tratta di un oggetto pesante che contiene i pesi del modello. - Crea un
Conversation: utilizzaEngineper creare uno o più oggettiConversationleggeri. - Send Message (Invia messaggio): utilizza i metodi dell'oggetto
Conversationper inviare messaggi all'LLM e ricevere risposte, consentendo di fatto un'interazione simile a una chat.
Di seguito è riportato il modo più semplice per inviare un messaggio e ottenere una risposta del modello. È consigliato per la maggior parte dei casi d'uso. Rispecchia le API Gemini Chat.
SendMessage: una chiamata bloccante che accetta l'input utente e restituisce la risposta completa del modello.SendMessageAsync: una chiamata non bloccante che trasmette in streaming la risposta del modello token per token tramite i callback.
Ecco un esempio di snippet di codice:
Contenuti solo testo
#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));
Esempio 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();
}
Contenuti dei dati multimodali
// 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;
Utilizzare Conversazione con Strumenti
Per informazioni dettagliate sull'utilizzo dello strumento con l'API Conversation, consulta Utilizzo avanzato.
Componenti in Conversazione
Conversation potrebbe essere considerato un delegato per gli utenti per
mantenere Session ed eseguire un trattamento dei dati complesso prima di inviarli
a Session.
Tipi di I/O
Il formato di input e output principale per l'API Conversation è
Message. Attualmente, questa operazione viene implementata come
JsonMessage, che è un alias di tipo per
ordered_json, una struttura di dati flessibile di coppie chiave-valore nidificate.
L'API Conversation funziona in base al principio di input/output dei messaggi, simulando una tipica esperienza di chat. La flessibilità di
Message consente agli utenti di includere campi arbitrari in base alle esigenze di
modelli di prompt o modelli LLM specifici, consentendo a LiteRT-LM di supportare un'ampia
varietà di modelli.
Sebbene non esista un unico standard rigido, la maggior parte dei modelli e dei modelli di prompt
si aspetta che Message segua convenzioni simili a quelle utilizzate nella
Gemini API Content o nella
struttura dei messaggi di OpenAI.
Message deve contenere role, che rappresenta il mittente del messaggio. content può essere semplice come una stringa di testo.
{
"role": "model", // Represent who the message is sent from.
"content": "Hello World!" // Naive text only content.
}
Per l'input di dati multimodali, content è un elenco di part. Anche in questo caso part non è una
struttura di dati predefinita, ma un
tipo di dati di coppia chiave-valore ordinata. I campi specifici dipendono da
ciò che si aspettano il modello e il modello di prompt.
{
"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"
}
]
}
Per part multimodale, supportiamo il seguente formato gestito da
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",
}
Prompt Template
Per mantenere la flessibilità dei modelli di varianti, PromptTemplate viene implementato come wrapper leggero intorno a Minja. Minja è un'implementazione C++ del motore di modelli Jinja, che elabora l'input JSON per generare prompt formattati.
Il motore di modelli Jinja è un formato ampiamente adottato per i modelli di prompt LLM. Ecco alcuni esempi:
Il formato del motore di modelli Jinja deve corrispondere rigorosamente alla struttura prevista dal modello ottimizzato per le istruzioni. In genere, le release del modello includono il modello Jinja standard per garantire un utilizzo corretto del modello.
Il modello Jinja utilizzato dal modello verrà fornito dai metadati del file del modello.
[!NOTE] Una leggera modifica del prompt dovuta a una formattazione errata può comportare un significativo degrado del modello. Come riportato in Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design or: How I learned to start worrying about prompt formatting
Prefazione
Preface imposta il contesto iniziale della conversazione. Possono
includere messaggi iniziali, definizioni di strumenti e qualsiasi altra informazione di base
di cui l'LLM ha bisogno per avviare l'interazione. In questo modo si ottiene una funzionalità simile a
Gemini API system instruction
e Gemini API Tools
Prefazione contiene i seguenti campi
messagesI messaggi nella prefazione. I messaggi hanno fornito il contesto iniziale della conversazione. Ad esempio, i messaggi possono essere la cronologia della conversazione, le istruzioni del sistema di prompt engineering, esempi few-shot e così via.toolsGli strumenti che il modello può utilizzare nella conversazione. Il formato degli strumenti non è di nuovo fisso, ma segue per lo piùGemini API FunctionDeclaration.extra_contextIl contesto aggiuntivo che mantiene l'estensibilità dei modelli per personalizzare le informazioni di contesto richieste per iniziare una conversazione. Ad esempio:enable_thinkingper i modelli con modalità di ragionamento, ad es. Qwen3 o SmolLM3-3B.
Prefazione di esempio per fornire istruzioni di sistema iniziali, strumenti e disattivare la modalità di pensiero.
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}
}
});
Cronologia
Conversazione mantiene un elenco di tutti gli scambi di messaggi all'interno della sessione. Questa cronologia è fondamentale per il rendering del modello di prompt, in quanto il modello di prompt Jinja in genere richiede l'intera cronologia della conversazione per generare il prompt corretto per l'LLM.
Tuttavia, la sessione LiteRT-LM è stateful, il che significa che elabora gli input in modo incrementale. Per colmare questa lacuna, Conversation genera il prompt incrementale necessario eseguendo il rendering del modello di prompt due volte: una volta con la cronologia fino al turno precedente e una volta includendo il messaggio attuale. Confrontando questi due prompt sottoposti a rendering, estrae la nuova parte da inviare alla sessione.
ConversationConfig
ConversationConfig viene utilizzato per inizializzare un'istanza di Conversation. Puoi creare questa configurazione in
due modi:
- Da un
Engine: questo metodo utilizza ilSessionConfigpredefinito associato al motore. - Da un
SessionConfigspecifico:consente un controllo più preciso delle impostazioni della sessione.
Oltre alle impostazioni della sessione, puoi personalizzare ulteriormente il comportamento di
Conversation all'interno di
ConversationConfig. È incluso quanto segue:
- Fornendo un
Preface. - Sovrascrittura del valore predefinito
PromptTemplate. - Sovrascrittura del valore predefinito
DataProcessorConfig.
Queste sovrascritture sono particolarmente utili per i modelli ottimizzati, che potrebbero richiedere configurazioni o modelli di prompt diversi rispetto al modello di base da cui sono derivati.
MessageCallback
MessageCallback è la funzione di callback che gli utenti devono
implementare quando utilizzano il metodo asincrono SendMessageAsync.
La firma del callback è absl::AnyInvocable<void(absl::StatusOr<Message>)>.
Questa funzione viene attivata nelle seguenti condizioni:
- Quando viene ricevuto un nuovo blocco di
Messagedal modello. - Se si verifica un errore durante l'elaborazione dei messaggi di LiteRT-LM.
- Al termine dell'inferenza del LLM, viene attivato il callback con un
Messagevuoto (ad es.JsonMessage()) per segnalare la fine della risposta.
Per un esempio di implementazione, consulta la chiamata asincrona del passaggio 6.
[!IMPORTANT] Il
Messagericevuto dal callback contiene solo l'ultimo blocco dell'output del modello, non l'intera cronologia dei messaggi.
Ad esempio, se la risposta completa del modello prevista da una chiamata di blocco
SendMessage sarebbe:
{
"role": "model",
"content": [
"type": "text",
"text": "Hello World!"
]
}
Il callback in SendMessageAsync potrebbe essere richiamato più
volte, ogni volta con una parte successiva del testo:
// 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'implementatore è responsabile dell'accumulo di questi blocchi se è necessaria la risposta completa durante lo stream asincrono. In alternativa, la risposta completa
sarà disponibile come ultima voce nel History una volta
completata la chiamata asincrona.
Utilizzo avanzato
Decodifica vincolata
LiteRT-LM supporta la decodifica vincolata, che consente di applicare strutture specifiche all'output del modello, come schemi JSON, pattern Regex o regole grammaticali.
Per abilitarlo, imposta EnableConstrainedDecoding(true) in ConversationConfig e fornisci un ConstraintProviderConfig (ad es. LlGuidanceConfig per il supporto di espressioni regolari/JSON/grammatica). Poi, passa i vincoli tramite OptionalArgs in SendMessage.
Esempio: vincolo regex
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}
);
Per tutti i dettagli, inclusi il supporto di JSON Schema e Lark Grammar, consulta la documentazione sul decodifica vincolata.
Utilizzo degli strumenti
La chiamata di strumenti consente all'LLM di richiedere l'esecuzione di funzioni lato client. Definisci gli strumenti nel Preface della conversazione, digitandoli per nome. Quando il modello restituisce una chiamata allo strumento, la acquisisci, esegui la funzione corrispondente nella tua applicazione e restituisci il risultato al modello.
Flusso di alto livello:
1. Dichiarare gli strumenti:definisci gli strumenti (nome, descrizione, parametri) nel file JSON Preface.
2. Detect Calls: controlla model_message["tool_calls"] nella risposta.
3. Esegui:esegui la logica dell'applicazione per lo strumento richiesto.
4. Rispondi:invia un messaggio con role: "tool" contenente l'output dello strumento al modello.
Per tutti i dettagli e un esempio completo di ciclo di chat, consulta la documentazione sull'utilizzo degli strumenti.