LiteRT-LM Cross-Platform C++ API

Conversation एक हाई-लेवल एपीआई है. यह एलएलएम के साथ एक, स्टेटफ़ुल बातचीत को दिखाता है. ज़्यादातर उपयोगकर्ताओं के लिए, इसे बातचीत शुरू करने का सुझाव दिया जाता है. यह इंटरनल तौर पर Session को मैनेज करता है और हैंडल करता है डेटा प्रोसेसिंग के मुश्किल टास्क को. इन टास्क में, शुरुआती कॉन्टेक्स्ट को बनाए रखना, टूल की परिभाषाओं को मैनेज करना, मल्टीमोडल डेटा को प्री-प्रोसेस करना, और भूमिका के हिसाब से मैसेज फ़ॉर्मैटिंग के साथ Jinja प्रॉम्प्ट टेंप्लेट लागू करना शामिल है.

Conversation API का वर्कफ़्लो

Conversation API का इस्तेमाल करने का सामान्य लाइफ़साइकल यह है:

  1. Engine बनाना: मॉडल पाथ और कॉन्फ़िगरेशन के साथ, एक Engine को शुरू करना. यह एक हैवीवेट ऑब्जेक्ट है, जिसमें मॉडल के वेट शामिल होते हैं.
  2. Conversation बनाना: एक या उससे ज़्यादा लाइटवेट Conversation ऑब्जेक्ट बनाने के लिए, Engine का इस्तेमाल करना.
  3. मैसेज भेजना: एलएलएम को मैसेज भेजने और जवाब पाने के लिए, Conversation ऑब्जेक्ट के तरीकों का इस्तेमाल करना. इससे चैट जैसा इंटरैक्शन हो पाता है.

मैसेज भेजने और मॉडल का जवाब पाने का सबसे आसान तरीका यहां दिया गया है. ज़्यादातर इस्तेमाल के मामलों में, इसका इस्तेमाल करने का सुझाव दिया जाता है. यह Gemini Chat API जैसा ही है.

  • 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 का उदाहरण

absl::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();
}

🔴 नया: मल्टी-टोकन प्रेडिक्शन (एमटीपी)

मल्टी-टोकन प्रेडिक्शन (एमटीपी) एक परफ़ॉर्मेंस ऑप्टिमाइज़ेशन है, जो डिकोड की स्पीड को काफ़ी तेज़ करता है. जीपीयू बैकएंड पर सभी टास्क के लिए, एमटीपी का इस्तेमाल करने का सुझाव दिया जाता है.

एमटीपी का इस्तेमाल करने के लिए, आपको इंजन कॉन्फ़िगरेशन की बेहतर सेटिंग में, स्पेकुलेटिव डिकोडिंग की सुविधा चालू करनी होगी.

// 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 को बनाए रख सकते हैं और सेशन को डेटा भेजने से पहले, डेटा प्रोसेसिंग के मुश्किल टास्क को पूरा कर सकते हैं.

I/O के टाइप

Conversation API के लिए, मुख्य इनपुट और आउटपुट फ़ॉर्मैट है Message. फ़िलहाल, इसे JsonMessage के तौर पर लागू किया गया है. यह ordered_json के लिए एक टाइप एलियास है. यह नेस्ट की गई की-वैल्यू डेटा स्ट्रक्चर है, जिसमें बदलाव किया जा सकता है.

Conversation API, मैसेज-इन-मैसेज-आउट के आधार पर काम करता है. यह चैट के सामान्य अनुभव जैसा होता है. Message में बदलाव करने की सुविधा होने की वजह से, उपयोगकर्ता अपनी ज़रूरत के हिसाब से फ़ील्ड शामिल कर सकते हैं. जैसे, खास प्रॉम्प्ट टेंप्लेट या एलएलएम मॉडल के लिए ज़रूरी फ़ील्ड. इससे LiteRT-LM को कई तरह के मॉडल के साथ काम करने में मदद मिलती है.

हालांकि, कोई एक तय स्टैंडर्ड नहीं है. फिर भी, ज़्यादातर प्रॉम्प्ट टेंप्लेट और मॉडल expect 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",
}

Prompt Template

अलग-अलग मॉडल के लिए, PromptTemplate को Minja के रैपर के तौर पर लागू किया जाता है. इससे मॉडल में बदलाव करने की सुविधा मिलती है. Minja, Jinja टेंप्लेट इंजन का C++ लागू करने का तरीका है. यह फ़ॉर्मैट किए गए प्रॉम्प्ट जनरेट करने के लिए, JSON इनपुट को प्रोसेस करता है.

जिंजा टेंप्लेट इंजन, एलएलएम प्रॉम्प्ट टेंप्लेट के लिए एक लोकप्रिय फ़ॉर्मैट है. यहां कुछ उदाहरण दिए गए हैं:

Jinja टेंप्लेट इंजन का फ़ॉर्मैट, निर्देश के हिसाब से ट्यून किए गए मॉडल के लिए ज़रूरी स्ट्रक्चर से पूरी तरह मेल खाना चाहिए. आम तौर पर, मॉडल रिलीज़ में स्टैंडर्ड Jinja टेंप्लेट शामिल होता है. इससे मॉडल का सही तरीके से इस्तेमाल किया जा सकता है.

मॉडल के लिए इस्तेमाल किया गया Jinja टेंप्लेट, मॉडल फ़ाइल के मेटाडेटा में दिया जाएगा.

ध्यान दें: गलत फ़ॉर्मैटिंग की वजह से, प्रॉम्प्ट में मामूली बदलाव होने पर भी मॉडल की परफ़ॉर्मेंस में काफ़ी गिरावट आ सकती है. जैसा कि Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design या: How I learned to start worrying about prompt formatting में बताया गया है

प्रस्तावना

Preface , बातचीत के लिए शुरुआती कॉन्टेक्स्ट सेट करता है. इसमें शुरुआती मैसेज, टूल की परिभाषाएं, और एलएलएम को इंटरैक्शन शुरू करने के लिए ज़रूरी कोई भी बैकग्राउंड जानकारी शामिल हो सकती है. इससे जैसी सुविधाएं मिलती हैं Gemini API system instruction और Gemini API Tools

प्रस्तावना में ये फ़ील्ड शामिल होते हैं

  • messages प्रस्तावना में शामिल मैसेज. इन मैसेज से, बातचीत के लिए शुरुआती बैकग्राउंड मिलता है. उदाहरण के लिए, मैसेज, बातचीत का इतिहास, प्रॉम्प्ट इंजीनियरिंग सिस्टम के निर्देश, फ़्यू-शॉट उदाहरण वगैरह हो सकते हैं.

  • tools वे टूल जिनका इस्तेमाल मॉडल, बातचीत में कर सकता है. टूल का फ़ॉर्मैट भी तय नहीं होता, लेकिन ज़्यादातर यह Gemini API FunctionDeclarationके मुताबिक होता है.

  • extra_context यह अतिरिक्त कॉन्टेक्स्ट है. इससे मॉडल को बातचीत शुरू करने के लिए, ज़रूरी कॉन्टेक्स्ट की जानकारी को पसंद के मुताबिक बनाने की सुविधा मिलती है. उदाहरण के लिए,

    • थिंकिंग मोड वाले मॉडल के लिए, जैसे Qwen3 या SmolLM3-3B, enable_thinking.

शुरुआती सिस्टम निर्देश, टूल देने, और थिंकिंग मोड बंद करने के लिए, प्रस्तावना का उदाहरण.

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 इंस्टेंस को शुरू करने के लिए किया जाता है. इस कॉन्फ़िगरेशन को कुछ तरीकों से बनाया जा सकता है:

  1. किसी Engine से: इस तरीके में, इंजन से जुड़े डिफ़ॉल्ट SessionConfig का इस्तेमाल किया जाता है.
  2. किसी खास SessionConfig: इससे सेशन की सेटिंग पर ज़्यादा कंट्रोल मिलता है.

सेशन की सेटिंग के अलावा, Conversation के व्यवहार को भी ConversationConfig में जाकर पसंद के मुताबिक बनाया जा सकता है. इसमें ये शामिल हैं:

ये बदलाव, फ़ाइन-ट्यून किए गए मॉडल के लिए खास तौर पर काम के होते हैं. ऐसा इसलिए, क्योंकि इन्हें अलग-अलग कॉन्फ़िगरेशन या प्रॉम्प्ट टेंप्लेट की ज़रूरत पड़ सकती है. ये प्रॉम्प्ट टेंप्लेट, उस बेस मॉडल से अलग हो सकते हैं जिनसे ये मॉडल लिए गए हैं.

MessageCallback

MessageCallback, कॉलबैक फ़ंक्शन है. उपयोगकर्ताओं को एसिंक्रोनस SendMessageAsync तरीके का इस्तेमाल करते समय, इसे लागू करना चाहिए.

कॉलबैक सिग्नेचर absl::AnyInvocable<void(absl::StatusOr<Message>)> है. यह फ़ंक्शन इन स्थितियों में ट्रिगर होता है:

  • जब मॉडल से Message का कोई नया हिस्सा मिलता है.
  • अगर LiteRT-LM के मैसेज प्रोसेसिंग के दौरान कोई गड़बड़ी होती है.
  • एलएलएम के इन्फ़रंस पूरा होने पर, कॉलबैक को खाली Message (जैसे, JsonMessage()) के साथ ट्रिगर किया जाता है. इससे जवाब खत्म होने का सिग्नल मिलता है.

लागू करने के उदाहरण के लिए, एसिंक्रोनस कॉल का छठा चरण देखें.

ध्यान दें: कॉलबैक से मिले 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 स्कीमा, Regex पैटर्न या व्याकरण के नियम.

इसे चालू करने के लिए, ConversationConfig में EnableConstrainedDecoding(true) सेट करें और ConstraintProviderConfig दें. जैसे, regex/JSON/व्याकरण के लिए LlGuidanceConfig. इसके बाद, SendMessage में OptionalArgs के ज़रिए कॉन्स्ट्रेंट पास करें.

उदाहरण: 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}
);

JSON स्कीमा और Lark Grammar के साथ काम करने की सुविधा के बारे में पूरी जानकारी पाने के लिए, कॉन्स्ट्रेंट डिकोडिंग का दस्तावेज़ देखें.

टूल का इस्तेमाल

टूल कॉलिंग की मदद से, एलएलएम क्लाइंट-साइड फ़ंक्शन के एक्ज़ीक्यूशन का अनुरोध कर सकता है. बातचीत के Preface में टूल तय किए जाते हैं. इन्हें नाम के हिसाब से की किया जाता है. जब मॉडल, टूल कॉल का आउटपुट देता है, तो उसे कैप्चर किया जाता है. इसके बाद, अपने ऐप्लिकेशन में उससे जुड़ा फ़ंक्शन एक्ज़ीक्यूट किया जाता है और मॉडल को नतीजा वापस भेजा जाता है.

हाई-लेवल फ़्लो:

  1. टूल की जानकारी देना: Preface JSON में टूल (नाम, ब्यौरा, पैरामीटर) तय करें.
  2. कॉल का पता लगाना: जवाब में model_message["tool_calls"] देखें.
  3. एक्ज़ीक्यूट करना: अनुरोध किए गए टूल के लिए, अपने ऐप्लिकेशन लॉजिक को रन करें.
  4. जवाब देना: role: "tool" के साथ एक मैसेज भेजें. इसमें टूल का आउटपुट शामिल हो वापस मॉडल को.

पूरी जानकारी और चैट लूप के उदाहरण के लिए, टूल के इस्तेमाल का दस्तावेज़ देखें.