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();
}
محتوى البيانات المتعددة الوسائط
// 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 ومعالجة البيانات المعقّدة قبل إرسال البيانات إلى "الجلسة".
أنواع I/O
إنّ تنسيق الإدخال والإخراج الأساسي لواجهة Conversation API هو
Message. في الوقت الحالي، يتم تنفيذ ذلك على النحو التالي: JsonMessage، وهو اسم مستعار للنوع ordered_json، وهو بنية بيانات مرنة ومتداخلة للمفتاح والقيمة.
تعمل واجهة برمجة التطبيقات Conversation على أساس رسالة واردة ورسالة صادرة، ما يحاكي تجربة محادثة نموذجية. تتيح مرونة
Message للمستخدمين تضمين حقول عشوائية حسب الحاجة
في نماذج الطلبات أو نماذج LLM المحدّدة، ما يتيح لـ 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 هو تنسيق شائع الاستخدام لإنشاء نماذج طلبات LLM. وفي ما يلي بعض الأمثلة على ذلك:
يجب أن يتطابق تنسيق محرك نماذج Jinja تمامًا مع البنية المتوقّعة من النموذج المضبوط حسب التعليمات. عادةً، تتضمّن إصدارات النماذج نموذج Jinja العادي لضمان الاستخدام السليم للنموذج.
سيتم توفير نموذج Jinja الذي يستخدمه النموذج من خلال بيانات النموذج الوصفية.
[!NOTE] يمكن أن يؤدي تغيير طفيف في الطلب بسبب التنسيق غير الصحيح إلى تدهور كبير في أداء النموذج. كما ورد في Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design or: How I learned to start worrying about prompt formatting
تمهيد
يحدّد Preface السياق الأوّلي للمحادثة. يمكن أن تتضمّن هذه الطلبات الرسائل الأولية وتعريفات الأدوات وأي معلومات أساسية أخرى يحتاجها النموذج اللغوي الكبير لبدء التفاعل. ويتيح ذلك وظائف مشابهة لتلك التي توفّرها 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معيّن: يتيح ذلك تحكّمًا أكثر دقة في إعدادات الجلسة.
بالإضافة إلى إعدادات الجلسة، يمكنك تخصيص سلوك Conversation بشكل أكبر ضمن ConversationConfig. يشمل ذلك ما يلي:
- تقديم
Preface - الكتابة فوق
PromptTemplateالتلقائي - الكتابة فوق
DataProcessorConfigالتلقائي
تكون عمليات الكتابة هذه مفيدة بشكل خاص للنماذج المضبوطة بدقة، والتي قد تتطلب إعدادات أو نماذج طلبات مختلفة عن النموذج الأساسي الذي تم اشتقاقها منه.
MessageCallback
MessageCallback هي دالة رد الاتصال التي يجب أن ينفّذها المستخدمون عند استخدام طريقة SendMessageAsync غير المتزامنة.
توقيع معاودة الاتصال هو absl::AnyInvocable<void(absl::StatusOr<Message>)>.
يتم تشغيل هذه الدالة في الحالات التالية:
- عند تلقّي جزء جديد من
Messageمن النموذج - في حال حدوث خطأ أثناء معالجة الرسالة في LiteRT-LM
- عند اكتمال استنتاج النموذج اللغوي الكبير، يتم تشغيل وظيفة رد الاتصال مع
Messageفارغ (مثلاً،JsonMessage()) للإشارة إلى نهاية الرد.
راجِع المكالمة غير المتزامنة في الخطوة 6 للحصول على مثال على التنفيذ.
[!IMPORTANT] لا تحتوي
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 بعد اكتمال المكالمة غير المتزامنة.
الاستخدام المتقدّم
Constrained Decoding
تتيح LiteRT-LM فك الترميز المقيد، ما يسمح لك بفرض بنى معيّنة على ناتج النموذج، مثل مخططات JSON أو أنماط Regex أو قواعد نحوية.
لتفعيلها، اضبط 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، يُرجى الاطّلاع على مستندات Constrained Decoding.
استخدام الأدوات
تتيح ميزة "استدعاء الأدوات" للنموذج اللغوي الكبير طلب تنفيذ وظائف من جهة العميل. يمكنك تحديد الأدوات في Preface من المحادثة، مع تحديدها حسب الاسم. عندما يُخرج النموذج طلبًا لاستخدام أداة، عليك تسجيله وتنفيذ الدالة المقابلة في تطبيقك وإعادة النتيجة إلى النموذج.
الخطوات الأساسية:
1. تعريف الأدوات: حدِّد الأدوات (الاسم والوصف والمعلَمات) في ملف Preface JSON.
2. رصد المكالمات: ابحث عن model_message["tool_calls"] في الردّ.
3- التنفيذ: شغِّل منطق تطبيقك للأداة المطلوبة.
4. الردّ: لإرسال رسالة تتضمّن role: "tool" تحتوي على ناتج الأداة إلى النموذج.
للاطّلاع على التفاصيل الكاملة ومثال كامل على حلقة المحادثة، يُرجى الرجوع إلى مستندات استخدام الأدوات.