LiteRT-LM ক্রস-প্ল্যাটফর্ম C++ API

Conversation একটি উচ্চ-স্তরের API, যা LLM-এর সাথে একটি একক, স্টেটফুল কথোপকথনের প্রতিনিধিত্ব করে এবং বেশিরভাগ ব্যবহারকারীর জন্য এটি প্রস্তাবিত প্রবেশ বিন্দু। এটি অভ্যন্তরীণভাবে একটি Session পরিচালনা করে এবং জটিল ডেটা প্রক্রিয়াকরণের কাজগুলি পরিচালনা করে। এই কাজের মধ্যে রয়েছে প্রাথমিক প্রেক্ষাপট বজায় রাখা, টুলের সংজ্ঞা পরিচালনা করা, মাল্টিমোডাল ডেটা প্রিপ্রসেসিং এবং ভূমিকা-ভিত্তিক বার্তা বিন্যাস সহ জিনজা প্রম্পট টেমপ্লেট প্রয়োগ করা।

কথোপকথন API কর্মপ্রবাহ

কথোপকথন API ব্যবহারের জন্য সাধারণ জীবনচক্র হল:

  1. একটি Engine তৈরি করুন : মডেল পাথ এবং কনফিগারেশন সহ একটি একক Engine শুরু করুন। এটি একটি ভারী বস্তু যা মডেলের ওজন ধরে রাখে।
  2. একটি Conversation তৈরি করুন : এক বা একাধিক হালকা Conversation বস্তু তৈরি করতে Engine ব্যবহার করুন।
  3. বার্তা পাঠান : LLM-এ বার্তা পাঠাতে এবং প্রতিক্রিয়া গ্রহণ করতে Conversation বস্তুর পদ্ধতিগুলি ব্যবহার করুন, কার্যকরভাবে চ্যাটের মতো মিথস্ক্রিয়া সক্ষম করুন।

নিচে মেসেজ পাঠানোর এবং মডেল রেসপন্স পাওয়ার সবচেয়ে সহজ উপায় দেওয়া হল। বেশিরভাগ ব্যবহারের ক্ষেত্রে এটি সুপারিশ করা হয়। এটি জেমিনি চ্যাট 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();
}

মাল্টিমোডাল ডেটা কন্টেন্ট

// 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;

সরঞ্জামগুলির সাথে কথোপকথন ব্যবহার করুন

কথোপকথন API সহ বিস্তারিত টুল ব্যবহারের জন্য অনুগ্রহ করে উন্নত ব্যবহার দেখুন।

কথোপকথনের উপাদানসমূহ

Conversation ব্যবহারকারীদের জন্য একটি প্রতিনিধি হিসেবে বিবেচনা করা যেতে পারে যাতে সেশনে ডেটা পাঠানোর আগে Session এবং জটিল ডেটা প্রক্রিয়াকরণ বজায় রাখা যায়।

ইনপুট/আউটপুট প্রকারভেদ

Conversation API-এর মূল ইনপুট এবং আউটপুট ফর্ম্যাট হল Message । বর্তমানে, এটি JsonMessage হিসাবে প্রয়োগ করা হচ্ছে, যা ordered_json এর একটি টাইপ উপনাম, একটি নমনীয় নেস্টেড কী-মান ডেটা স্ট্রাকচার।

Conversation API একটি বার্তা-মধ্যে-বার্তা-আউট ভিত্তিতে কাজ করে, যা একটি সাধারণ চ্যাট অভিজ্ঞতার অনুকরণ করে। Message নমনীয়তা ব্যবহারকারীদের নির্দিষ্ট প্রম্পট টেমপ্লেট বা LLM মডেলের প্রয়োজন অনুসারে ইচ্ছামত ক্ষেত্র অন্তর্ভুক্ত করতে দেয়, যা LiteRT-LM কে বিভিন্ন ধরণের মডেল সমর্থন করতে সক্ষম করে।

যদিও কোনও একক কঠোর মান নেই, বেশিরভাগ প্রম্পট টেমপ্লেট এবং মডেল আশা করে Message Gemini API কন্টেন্ট বা 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 হল Jinja টেমপ্লেট ইঞ্জিনের একটি C++ বাস্তবায়ন, যা JSON ইনপুট প্রক্রিয়া করে ফর্ম্যাট করা প্রম্পট তৈরি করে।

জিনজা টেমপ্লেট ইঞ্জিন হল এলএলএম প্রম্পট টেমপ্লেটের জন্য একটি বহুল ব্যবহৃত ফর্ম্যাট। এখানে কয়েকটি উদাহরণ দেওয়া হল:

জিনজা টেমপ্লেট ইঞ্জিন ফর্ম্যাটটি নির্দেশ-সুরক্ষিত মডেল দ্বারা প্রত্যাশিত কাঠামোর সাথে কঠোরভাবে মিলিত হওয়া উচিত। সাধারণত, মডেল রিলিজে সঠিক মডেল ব্যবহার নিশ্চিত করার জন্য স্ট্যান্ডার্ড জিনজা টেমপ্লেট অন্তর্ভুক্ত থাকে।

মডেল দ্বারা ব্যবহৃত জিনজা টেমপ্লেটটি মডেল ফাইল মেটাডেটা দ্বারা সরবরাহ করা হবে।

[!বিঃদ্রঃ] ভুল ফর্ম্যাটিংয়ের কারণে প্রম্পটে একটি সূক্ষ্ম পরিবর্তন মডেলের উল্লেখযোগ্য অবক্ষয় ঘটাতে পারে। Quantifying Language Models' Sensitivity to Spurious Features in Prompt Design অথবা: How I learned to start worrying about prompt formatting

ভূমিকা

Preface কথোপকথনের প্রাথমিক প্রেক্ষাপট নির্ধারণ করে। এতে প্রাথমিক বার্তা, টুলের সংজ্ঞা এবং ইন্টারঅ্যাকশন শুরু করার জন্য LLM-এর প্রয়োজনীয় অন্যান্য পটভূমি তথ্য অন্তর্ভুক্ত থাকতে পারে। এটি Gemini API system instruction এবং Gemini API Tools মতো কার্যকারিতা অর্জন করে।

ভূমিকায় নিম্নলিখিত ক্ষেত্রগুলি রয়েছে

  • messages ভূমিকায় থাকা বার্তাগুলি। বার্তাগুলি কথোপকথনের প্রাথমিক পটভূমি প্রদান করে। উদাহরণস্বরূপ, বার্তাগুলি কথোপকথনের ইতিহাস, প্রম্পট ইঞ্জিনিয়ারিং সিস্টেমের নির্দেশাবলী, কয়েকটি শট উদাহরণ ইত্যাদি হতে পারে।

  • tools মডেলটি কথোপকথনে যে টুলগুলি ব্যবহার করতে পারে। টুলের ফর্ম্যাটটি আবার স্থির নয়, তবে বেশিরভাগ ক্ষেত্রেই Gemini API FunctionDeclaration অনুসরণ করে।

  • extra_context অতিরিক্ত প্রসঙ্গ যা মডেলগুলিকে কথোপকথন শুরু করার জন্য প্রয়োজনীয় প্রসঙ্গ তথ্য কাস্টমাইজ করার প্রসারণযোগ্যতা বজায় রাখে। উদাহরণস্বরূপ,

    • thinking মোড সহ মডেলগুলির জন্য 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}
  }
});

ইতিহাস

কথোপকথন সেশনের মধ্যে সমস্ত বার্তা বিনিময়ের একটি তালিকা বজায় রাখে। প্রম্পট টেমপ্লেট রেন্ডারিংয়ের জন্য এই ইতিহাস অত্যন্ত গুরুত্বপূর্ণ, কারণ জিনজা প্রম্পট টেমপ্লেটের জন্য সাধারণত LLM-এর জন্য সঠিক প্রম্পট তৈরি করতে সম্পূর্ণ কথোপকথনের ইতিহাসের প্রয়োজন হয়।

তবে, LiterRT-LM সেশনটি স্টেটফুল, অর্থাৎ এটি ইনপুটগুলিকে ক্রমবর্ধমানভাবে প্রক্রিয়া করে। এই ব্যবধান পূরণ করার জন্য, Conversation প্রম্পট টেমপ্লেটটি দুবার রেন্ডার করে প্রয়োজনীয় ক্রমবর্ধমান প্রম্পট তৈরি করে: একবার পূর্ববর্তী পালা পর্যন্ত ইতিহাস সহ, এবং একবার বর্তমান বার্তা অন্তর্ভুক্ত করে। এই দুটি রেন্ডার করা প্রম্পট তুলনা করে, এটি সেশনে পাঠানোর জন্য নতুন অংশটি বের করে।

কথোপকথন কনফিগারেশন

ConversationConfig একটি Conversation ইনস্ট্যান্স শুরু করতে ব্যবহৃত হয়। আপনি এই কনফিগারেশনটি কয়েকটি উপায়ে তৈরি করতে পারেন:

  1. একটি Engine থেকে: এই পদ্ধতিটি ইঞ্জিনের সাথে সম্পর্কিত ডিফল্ট SessionConfig ব্যবহার করে।
  2. একটি নির্দিষ্ট SessionConfig থেকে: এটি সেশন সেটিংসের উপর আরও সূক্ষ্ম নিয়ন্ত্রণের অনুমতি দেয়।

সেশন সেটিংসের বাইরে, আপনি ConversationConfig এর মধ্যে Conversation আচরণ আরও কাস্টমাইজ করতে পারেন। এর মধ্যে রয়েছে:

  • একটি Preface প্রদান।
  • ডিফল্ট PromptTemplate ওভাররাইট করা হচ্ছে।
  • ডিফল্ট DataProcessorConfig ওভাররাইট করা হচ্ছে।

এই ওভাররাইটগুলি বিশেষভাবে সূক্ষ্ম-সুরযুক্ত মডেলগুলির জন্য কার্যকর, যার জন্য বেস মডেলের চেয়ে ভিন্ন কনফিগারেশন বা প্রম্পট টেমপ্লেটের প্রয়োজন হতে পারে।

বার্তা কলব্যাক

MessageCallback হল কলব্যাক ফাংশন যা ব্যবহারকারীদের অ্যাসিঙ্ক্রোনাস SendMessageAsync পদ্ধতি ব্যবহার করার সময় প্রয়োগ করা উচিত।

কলব্যাক স্বাক্ষর হল absl::AnyInvocable<void(absl::StatusOr<Message>)> । এই ফাংশনটি নিম্নলিখিত শর্তাবলীর অধীনে ট্রিগার করা হয়:

  • যখন মডেল থেকে Message একটি নতুন অংশ পাওয়া যায়।
  • LiteRT-LM এর বার্তা প্রক্রিয়াকরণের সময় যদি কোনও ত্রুটি দেখা দেয়।
  • LLM এর অনুমান সম্পন্ন হওয়ার পর, প্রতিক্রিয়ার সমাপ্তির সংকেত দেওয়ার জন্য একটি খালি 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 স্কিমা, রেজেক্স প্যাটার্ন, বা ব্যাকরণের নিয়ম প্রয়োগ করতে দেয়।

এটি সক্রিয় করতে, ConversationConfigEnableConstrainedDecoding(true) সেট করুন এবং একটি ConstraintProviderConfig প্রদান করুন (যেমন, regex/JSON/grammar সাপোর্টের জন্য LlGuidanceConfig )। তারপর, SendMessageOptionalArgs মাধ্যমে সীমাবদ্ধতাগুলি পাস করুন।

উদাহরণ: রেজেক্স সীমাবদ্ধতা

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 স্কিমা এবং লার্ক গ্রামার সাপোর্ট সহ সম্পূর্ণ বিবরণের জন্য, সীমাবদ্ধ ডিকোডিং ডকুমেন্টেশন দেখুন।

টুল ব্যবহার

টুল কলিং LLM কে ক্লায়েন্ট-সাইড ফাংশনগুলির কার্যকরীকরণের অনুরোধ করতে দেয়। আপনি কথোপকথনের Preface টুলগুলিকে সংজ্ঞায়িত করেন, নাম দিয়ে কী করে। যখন মডেলটি একটি টুল কল আউটপুট করে, তখন আপনি এটি ক্যাপচার করেন, আপনার অ্যাপ্লিকেশনে সংশ্লিষ্ট ফাংশনটি কার্যকর করেন এবং ফলাফলটি মডেলে ফেরত দেন।

উচ্চ-স্তরের প্রবাহ: ১. টুলস ঘোষণা করুন: Preface JSON-এ টুলস (নাম, বর্ণনা, প্যারামিটার) সংজ্ঞায়িত করুন। ২. কল সনাক্ত করুন: প্রতিক্রিয়ায় model_message["tool_calls"] পরীক্ষা করুন। ৩. কার্যকর করুন: অনুরোধ করা টুলের জন্য আপনার অ্যাপ্লিকেশন লজিক চালান। ৪. প্রতিক্রিয়া: role: "tool" সহ একটি বার্তা পাঠান যাতে টুলের আউটপুট মডেলে ফিরে আসে।

সম্পূর্ণ বিবরণ এবং একটি সম্পূর্ণ চ্যাট লুপের উদাহরণের জন্য, টুল ব্যবহারের ডকুমেন্টেশন দেখুন।