Conversation هي واجهة برمجة تطبيقات عالية المستوى تمثّل
محادثة واحدة ذات حالة مع النموذج اللغوي الكبير، وهي نقطة الدخول المقترَحة
لمعظم المستخدمين. وهي تدير داخليًا Session وتعالج
مهام معالجة البيانات المعقّدة. تشمل هذه المهام الحفاظ على السياق الأوّلي وإدارة تعريفات الأدوات والمعالجة المسبقة للبيانات المتعددة الوسائط وتطبيق نماذج الطلبات المستندة إلى Jinja مع تنسيق الرسائل المستند إلى الأدوار.
سير عمل Conversation API
في ما يلي دورة الحياة النموذجية لاستخدام Conversation API:
- إنشاء
Engine: يمكنك إعدادEngineواحد باستخدام مسار النموذج وإعداداته. وهو كائن كبير الحجم يحتوي على أوزان النموذج. - إنشاء
Conversation: استخدِمEngineلإنشاء كائنConversationواحد أو أكثر من الكائنات الخفيفة. - إرسال رسالة: استخدِم طرق كائن
Conversationلإرسال رسائل إلى النموذج اللغوي الكبير وتلقّي الردود، ما يتيح تفاعلاً مشابهًا للمحادثة.
في ما يلي أبسط طريقة لإرسال رسالة والحصول على ردّ النموذج. يُنصح باستخدام هذه الطريقة في معظم حالات الاستخدام. وهي تعكس Gemini Chat APIs.
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 ومعالجة البيانات المعقّدة قبل إرسال
البيانات إلى Session.
أنواع الإدخال والإخراج
تنسيق الإدخال والإخراج الأساسي لـ Conversation API هو
Message. يتم حاليًا تنفيذ ذلك على أنّه
JsonMessage، وهو اسم مستعار لنوع
ordered_json، وهو بنية بيانات مرنة للقيم الرئيسية المتداخلة.
تعمل Conversation API على أساس إرسال رسالة وتلقّي رسالة
، ما يحاكي تجربة المحادثة النموذجية. تسمح مرونة
Message للمستخدمين بتضمين حقول عشوائية حسب الحاجة من خلال
نماذج الطلبات أو نماذج النموذج اللغوي الكبير، ما يتيح لـ LiteRT-LM دعم مجموعة كبيرة
من النماذج.
على الرغم من عدم وجود معيار واحد صارم، تتوقّع معظم نماذج الطلبات والنماذج
أن تتّبع Messageاصطلاحات مشابهة لتلك المستخدَمة في
Gemini API Content أو بنية
OpenAI Message.
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"تتضمّن ناتج الأداة إلى النموذج.
للحصول على التفاصيل الكاملة ومثال على حلقة محادثة كاملة، يُرجى الاطّلاع على مستندات استخدام الأدوات.