Conversation adalah API tingkat tinggi, yang merepresentasikan satu percakapan
dengan LLM yang memiliki status dan merupakan titik entri yang direkomendasikan untuk sebagian besar
pengguna. Secara internal, class ini mengelola Session dan menangani tugas pemrosesan data yang kompleks. Tugas ini mencakup mempertahankan konteks awal, mengelola
definisi alat, memproses data multimodal, dan menerapkan template perintah Jinja
dengan format pesan berbasis peran.
Alur Kerja Conversation API
Siklus proses umum untuk menggunakan Conversation API adalah:
- Buat
Engine: Lakukan inisialisasi satuEnginedengan jalur dan konfigurasi model. Ini adalah objek berat yang memuat bobot model. - Buat
Conversation: GunakanEngineuntuk membuat satu atau beberapa objekConversationringan. - Kirim Pesan: Gunakan metode objek
Conversationuntuk mengirim pesan ke LLM dan menerima respons, sehingga memungkinkan interaksi seperti chat.
Berikut adalah cara paling sederhana untuk mengirim pesan dan mendapatkan respons model. Direkomendasikan untuk sebagian besar kasus penggunaan. API ini mencerminkan Gemini Chat API.
SendMessage: Panggilan pemblokiran yang mengambil input pengguna dan menampilkan respons model lengkap.SendMessageAsync: Panggilan non-blocking yang mengalirkan respons model kembali token demi token melalui callback.
Berikut contoh cuplikan kode:
Konten hanya teks
#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));
Contoh 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();
}
Konten data multimodal
// 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;
Menggunakan Percakapan dengan Alat
Lihat Penggunaan Lanjutan untuk Penggunaan Alat mendetail dengan Conversation API
Komponen dalam Percakapan
Conversation dapat dianggap sebagai penerima tugas bagi pengguna untuk
mempertahankan Session dan pemrosesan data yang rumit sebelum mengirimkan
data ke Sesi.
Jenis I/O
Format input dan output inti untuk Conversation API adalah
Message. Saat ini, hal ini diimplementasikan sebagai
JsonMessage, yang merupakan alias jenis untuk
ordered_json, struktur data key-value bertingkat yang fleksibel.
API Conversation beroperasi berdasarkan pesan masuk-pesan keluar, meniru pengalaman chat biasa. Fleksibilitas
Message memungkinkan pengguna menyertakan kolom arbitrer sesuai kebutuhan
template perintah atau model LLM tertentu, sehingga LiteRT-LM dapat mendukung berbagai
model.
Meskipun tidak ada satu standar yang ketat, sebagian besar template dan model perintah
mengharapkan Message mengikuti konvensi yang serupa dengan yang digunakan dalam
Konten Gemini API atau
Struktur Pesan OpenAI.
Message harus berisi role, yang menunjukkan siapa pengirim pesan. content bisa sesederhana string teks.
{
"role": "model", // Represent who the message is sent from.
"content": "Hello World!" // Naive text only content.
}
Untuk input data multimodal, content adalah daftar part. Sekali lagi, part bukanlah
struktur data yang telah ditentukan sebelumnya, melainkan
jenis data key-value pair yang diurutkan. Kolom tertentu bergantung pada
apa yang diharapkan oleh template perintah dan model.
{
"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"
}
]
}
Untuk part multimodal, kami mendukung format berikut yang ditangani oleh
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",
}
Template Perintah
Untuk mempertahankan fleksibilitas model varian, PromptTemplate diimplementasikan sebagai wrapper tipis di sekitar Minja. Minja adalah penerapan C++ dari mesin template Jinja, yang memproses input JSON untuk menghasilkan perintah yang diformat.
Mesin template Jinja adalah format yang banyak digunakan untuk template perintah LLM. Berikut adalah beberapa contohnya:
Format mesin template Jinja harus sesuai dengan struktur yang diharapkan oleh model yang dioptimalkan untuk mengikuti perintah. Biasanya, rilis model mencakup template Jinja standar untuk memastikan penggunaan model yang tepat.
Template Jinja yang digunakan oleh model akan disediakan oleh metadata file model.
[!CATATAN] Perubahan kecil pada perintah karena pemformatan yang salah dapat menyebabkan penurunan kualitas model yang signifikan. Seperti yang dilaporkan dalam Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design or: How I learned to start worrying about prompt formatting
Pengantar
Preface menetapkan konteks awal untuk percakapan. Hal ini dapat
mencakup pesan awal, definisi alat, dan informasi latar belakang lainnya
yang dibutuhkan LLM untuk memulai interaksi. Hal ini mencapai fungsi yang serupa dengan
Gemini API system instruction
dan Gemini API Tools
Preface berisi kolom berikut
messagesPesan dalam kata pengantar. Pesan tersebut memberikan latar belakang awal percakapan. Misalnya, pesan dapat berupa histori percakapan, petunjuk sistem teknik perintah, contoh beberapa tembakan, dll.toolsAlat yang dapat digunakan model dalam percakapan. Format alat tidak tetap, tetapi sebagian besar mengikutiGemini API FunctionDeclaration.extra_contextKonteks tambahan yang mempertahankan ekstensibilitas model untuk menyesuaikan informasi konteks yang diperlukan untuk memulai percakapan. Misalnya:enable_thinkinguntuk model dengan mode berpikir, misalnya Qwen3 atau SmolLM3-3B.
Contoh kata pengantar untuk memberikan petunjuk sistem awal, alat, dan menonaktifkan mode berpikir.
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}
}
});
Histori
Conversation menyimpan daftar semua pertukaran Message dalam sesi. Histori ini sangat penting untuk rendering template perintah, karena template perintah jinja biasanya memerlukan seluruh histori percakapan untuk menghasilkan perintah yang benar bagi LLM.
Namun, Sesi LiteRT-LM bersifat stateful, yang berarti sesi memproses input secara inkremental. Untuk menjembatani kesenjangan ini, Conversation membuat perintah inkremental yang diperlukan dengan merender template perintah dua kali: sekali dengan histori hingga giliran sebelumnya, dan sekali dengan menyertakan pesan saat ini. Dengan membandingkan kedua perintah yang dirender ini, bagian baru akan diekstrak untuk dikirim ke Session.
ConversationConfig
ConversationConfig digunakan untuk menginisialisasi instance
Conversation. Anda dapat membuat konfigurasi ini dengan
beberapa cara:
- Dari
Engine: Metode ini menggunakanSessionConfigdefault yang terkait dengan mesin. - Dari
SessionConfigtertentu: Hal ini memungkinkan kontrol yang lebih terperinci atas setelan sesi.
Selain setelan sesi, Anda dapat menyesuaikan lebih lanjut perilaku
Conversation dalam
ConversationConfig. Hal ini mencakup:
- Menyediakan
Preface. - Menimpa
PromptTemplatedefault. - Menimpa
DataProcessorConfigdefault.
Penggantian ini sangat berguna untuk model yang di-fine-tune, yang mungkin memerlukan konfigurasi atau template perintah yang berbeda dengan model dasar yang menjadi dasarnya.
MessageCallback
MessageCallback adalah fungsi callback yang harus
diterapkan pengguna saat menggunakan metode
SendMessageAsync asinkron.
Tanda tangan callback adalah absl::AnyInvocable<void(absl::StatusOr<Message>)>.
Fungsi ini dipicu dalam kondisi berikut:
- Saat potongan
Messagebaru diterima dari Model. - Jika terjadi error selama pemrosesan pesan LiteRT-LM.
- Setelah inferensi LLM selesai, callback dipicu dengan
Messagekosong (misalnya,JsonMessage()) untuk menandakan akhir respons.
Lihat Panggilan asinkron Langkah 6 untuk contoh penerapannya.
[!PENTING]
Messageyang diterima oleh callback hanya berisi potongan output model terbaru, bukan seluruh histori pesan.
Misalnya, jika respons model lengkap yang diharapkan dari panggilan
SendMessage yang memblokir adalah:
{
"role": "model",
"content": [
"type": "text",
"text": "Hello World!"
]
}
Callback di SendMessageAsync dapat dipanggil beberapa kali, setiap kali dengan bagian teks berikutnya:
// 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!"
]
}
Pelaksana bertanggung jawab untuk mengumpulkan bagian-bagian ini jika respons lengkap
diperlukan selama streaming asinkron. Atau, respons
lengkap akan tersedia sebagai entri terakhir di History setelah
panggilan asinkron selesai.
Penggunaan Lanjutan
Decoding Terkendala
LiteRT-LM mendukung decoding terbatas, sehingga Anda dapat menerapkan struktur tertentu pada output model, seperti skema JSON, pola Regex, atau aturan tata bahasa.
Untuk mengaktifkannya, tetapkan EnableConstrainedDecoding(true) di ConversationConfig dan berikan ConstraintProviderConfig (misalnya, LlGuidanceConfig untuk dukungan regex/JSON/tata bahasa). Kemudian, teruskan batasan melalui OptionalArgs di SendMessage.
Contoh: Batasan Ekspresi Reguler
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}
);
Untuk mengetahui detail selengkapnya, termasuk dukungan Skema JSON dan Tata Bahasa Lark, lihat dokumentasi Dekode Terbatas.
Penggunaan Alat
Panggilan alat memungkinkan LLM meminta eksekusi fungsi sisi klien. Anda menentukan alat dalam Preface percakapan, dengan menentukannya berdasarkan nama. Saat model menghasilkan panggilan alat, Anda akan mengambilnya, menjalankan fungsi yang sesuai di aplikasi, dan menampilkan hasilnya ke model.
Alur tingkat tinggi:
1. Mendeklarasikan Alat: Tentukan alat (nama, deskripsi, parameter) dalam JSON Preface.
2. Mendeteksi Panggilan: Periksa model_message["tool_calls"] dalam respons.
3. Jalankan: Jalankan logika aplikasi Anda untuk alat yang diminta.
4. Respons: Kirim pesan dengan role: "tool" yang berisi output alat kembali ke model.
Untuk mengetahui detail selengkapnya dan contoh loop percakapan lengkap, lihat dokumentasi Penggunaan Alat.