Conversation هي واجهة برمجة تطبيقات عالية المستوى تمثّل
محادثة واحدة ذات حالة مع النموذج اللغوي الكبير، وهي نقطة الدخول المقترَحة
لمعظم المستخدمين. وهي تدير داخليًا Session وتتعامل مع
مهام معالجة البيانات المعقّدة. تشمل هذه المهام الحفاظ على السياق الأوّلي وإدارة تعريفات الأدوات والمعالجة المسبقة للبيانات المتعددة الوسائط وتطبيق نماذج طلبات Jinja مع تنسيق الرسائل المستند إلى الأدوار.
سير عمل Conversation API
في ما يلي دورة الحياة النموذجية لاستخدام Conversation API:
- إنشاء
Engine: يمكنك تهيئةEngineواحد باستخدام مسار النموذج وإعداداته. وهو كائن كبير الحجم يحتوي على أوزان النموذج. - إنشاء
Conversation: يمكنك استخدامEngineلإنشاء كائنConversationواحد أو أكثر من الكائنات الخفيفة. - إرسال رسالة: يمكنك استخدام طرق كائن
Conversationلإرسال رسائل إلى النموذج اللغوي الكبير وتلقّي الردود، ما يتيح تفاعلاً يشبه المحادثة.
في ما يلي أبسط طريقة لإرسال رسالة والحصول على ردّ النموذج. يُنصح باستخدام هذه الطريقة في معظم حالات الاستخدام. وهي تعكس واجهات برمجة تطبيقات Gemini Chat.
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
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();
}
🔴 جديد: ميزة Multi-Token Prediction (MTP)
ميزة Multi-Token Prediction (MTP) هي تحسين للأداء يسرّع بشكل كبير سرعات فك الترميز. يُنصح باستخدام ميزة MTP عالميًا لجميع المهام على وحدات معالجة الرسومات.
لاستخدام ميزة MTP، عليك تفعيل فك الترميز التخميني في الإعدادات المتقدّمة لإعدادات المحرّك.
// 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::GPU);
CHECK_OK(engine_settings);
// 2. Enable MTP via speculative decoding in advanced settings.
litert::lm::AdvancedSettings advanced_settings;
advanced_settings.enable_speculative_decoding = true;
engine_settings->GetMutableMainExecutorSettings().SetAdvancedSettings(
advanced_settings);
// 3. Create the main Engine object.
absl::StatusOr<std::unique_ptr<Engine>> engine = Engine::CreateEngine(
*engine_settings);
CHECK_OK(engine);
// The same steps to create Conversation and send messages as above...
محتوى البيانات المتعددة الوسائط
// 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 ومعالجة البيانات المعقّدة قبل إرسال البيانات إلى Session.
أنواع الإدخال والإخراج
تنسيق الإدخال والإخراج الأساسي لـ Conversation API هو
Message. يتم حاليًا تنفيذ ذلك على أنّه
JsonMessage، وهو اسم نوع بديل لـ
ordered_json، وهو بنية بيانات مرنة للقيم الرئيسية المتداخلة.
تعمل Conversation API على أساس إرسال رسالة وتلقّي رسالة
، ما يحاكي تجربة المحادثة النموذجية. تسمح مرونة
Message للمستخدمين بتضمين حقول عشوائية حسب الحاجة من خلال
نماذج طلبات أو نماذج لغوية كبيرة معيّنة، ما يتيح لـ LiteRT-LM دعم مجموعة كبيرة من
النماذج.
على الرغم من عدم وجود معيار واحد صارم، تتوقّع معظم نماذج الطلبات والنماذج
أن تتّبع Message اصطلاحات مشابهة لتلك المستخدَمة في
محتوى Gemini API أو
بنية رسائل 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 الذي يستخدمه النموذج من خلال البيانات الوصفية لملف النموذج.
ملاحظة: يمكن أن يؤدي تغيير بسيط في الطلب بسبب التنسيق غير الصحيح إلى تدهور كبير في أداء النموذج. كما هو موضّح في Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design or: How I learned to start worrying about prompt formatting
تمهيد
Preface يضبط السياق الأوّلي للمحادثة. ويمكن أن يشمل الرسائل الأوّلية وتعريفات الأدوات وأي معلومات أساسية أخرى يحتاجها النموذج اللغوي الكبير لبدء التفاعل. يحقّق ذلك وظائف مشابهة لـ
the
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 يتطلّب عادةً سجلّ المحادثة بالكامل لإنشاء الطلب الصحيح للنموذج اللغوي الكبير.
ومع ذلك، فإنّ جلسة LiteRT-LM ذات حالة، ما يعني أنّها تعالج المدخلات بشكل تدريجي. لسدّ هذه الفجوة، تنشئ المحادثة الطلب التدريجي اللازم من خلال عرض نموذج الطلب مرّتَين: مرّة واحدة مع السجلّ حتى الدور السابق، ومرّة واحدة بما في ذلك الرسالة الحالية. من خلال مقارنة هذَين الطلبَين المعروضَين، يتم استخراج الجزء الجديد لإرساله إلى الجلسة.
ConversationConfig
ConversationConfig يُستخدَم لتهيئة مثيل
Conversation. يمكنك إنشاء هذا الإعداد بطريقتَين:
- من
Engine: تستخدم هذه الطريقةSessionConfigالتلقائي المرتبط بالمحرّك. - من
SessionConfigمعيّن: يتيح ذلك تحكّمًا أكثر دقة في إعدادات الجلسة.
بالإضافة إلى إعدادات الجلسة، يمكنك تخصيص السلوك بشكل أكبر ضمن
ConversationConfig.Conversation يشمل ذلك ما يلي:
- توفير
Preface. - الكتابة فوق
PromptTemplateالتلقائي - الكتابة فوق
DataProcessorConfigالتلقائي
تكون عمليات الكتابة فوق هذه مفيدة بشكل خاص للنماذج التي تم ضبطها بدقة، والتي قد تتطلب إعدادات أو نماذج طلبات مختلفة عن النموذج الأساسي الذي تم اشتقاقها منه.
MessageCallback
MessageCallback هي دالّة رد الاتصال التي يجب أن ينفّذها المستخدمون عند استخدام طريقة SendMessageAsync غير المتزامنة.
توقيع معاودة الاتصال هو absl::AnyInvocable<void(absl::StatusOr<Message>)>.
يتم تفعيل هذه الدالة في الحالات التالية:
- عند تلقّي جزء جديد من الـ
Messageمن النموذج - إذا حدث خطأ أثناء معالجة الرسالة في LiteRT-LM
- عند اكتمال استنتاج النموذج اللغوي الكبير، يتم تفعيل معاودة الاتصال باستخدام
فارغ
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، يُرجى الاطّلاع على مستندات فك الترميز المقيّد.
استخدام الأدوات
يسمح استدعاء الأدوات للنموذج اللغوي الكبير بطلب تنفيذ الدوال من جهة العميل. يمكنك تحديد الأدوات في Preface للمحادثة، مع وضع مفتاح لها حسب الاسم. عندما يعرض النموذج استدعاء أداة، يمكنك التقاطه وتنفيذ الدالة المقابلة في تطبيقك وإعادة النتيجة إلى النموذج.
خطوات التنفيذ العالية المستوى:
- التعريف بالأدوات: يمكنك تحديد الأدوات (الاسم والوصف والمعلّمات) في
PrefaceJSON. - رصد عمليات الاستدعاء: يمكنك التحقّق من
model_message["tool_calls"]في الردّ. - التنفيذ: يمكنك تشغيل منطق تطبيقك للأداة المطلوبة.
- الردّ: يمكنك إرسال رسالة تحتوي على
role: "tool"تتضمّن ناتج الأداة إلى النموذج.
للحصول على التفاصيل الكاملة ومثال على حلقة محادثة كاملة، يُرجى الاطّلاع على مستندات استخدام الأدوات.