LiteRT-LM Cross-Platform C++ API

Conversation është një API e nivelit të lartë, që përfaqëson një bisedë të vetme me LLM dhe është pika e hyrjes e rekomanduar për shumicën e përdoruesve. Ai menaxhon në mënyrë të brendshme një Session dhe trajton detyra komplekse të përpunimit të të dhënave. Këto detyra përfshijnë ruajtjen e kontekstit fillestar, menaxhimin e përkufizimeve të mjeteve, përpunimin paraprak të të dhënave multimodale dhe zbatimin e shablloneve të kërkesave Jinja me formatim mesazhesh të bazuara në role.

Fluksi i Punës së API-së së Bisedës

Cikli jetësor tipik për përdorimin e API-t të bisedës është:

  1. Krijo një Engine : Inicializoni një Engine të vetëm me shtegun dhe konfigurimin e modelit. Ky është një objekt i rëndë që mban peshat e modelit.
  2. Krijo një Conversation : Përdor Engine për të krijuar një ose më shumë objekte të lehta Conversation .
  3. Dërgo Mesazh : Përdorni metodat e objektit të Conversation për të dërguar mesazhe te LLM dhe për të marrë përgjigje, duke mundësuar në mënyrë efektive një bashkëveprim të ngjashëm me bisedën.

Më poshtë është mënyra më e thjeshtë për të dërguar mesazh dhe për të marrë përgjigje modeli. Rekomandohet për shumicën e rasteve të përdorimit. Pasqyron API-të e Gemini Chat .

  • SendMessage : Një thirrje bllokuese që merr të dhënat e përdoruesit dhe kthen përgjigjen e plotë të modelit.
  • SendMessageAsync : Një thirrje jo-bllokuese që transmeton përgjigjen e modelit mbrapsht token-pas-token-i përmes thirrjeve kthyese.

Ja një shembull i fragmenteve të kodit:

Përmbajtje vetëm me tekst

#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));

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

Përmbajtja e të dhënave multimodale

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

Përdorni bisedën me mjetet

Ju lutemi referojuni Përdorimit të Avancuar për Përdorimin e detajuar të Mjetit me API-në e Bisedës

Komponentët në bisedë

Conversation mund të konsiderohet si një delegim për përdoruesit për të mirëmbajtur Session dhe përpunimin e ndërlikuar të të dhënave përpara se të dërgohen të dhënat në Sesion.

Llojet e I/O-së

Formati bazë i hyrjes dhe daljes për API-në e bisedës është Message . Aktualisht, ky është implementuar si JsonMessage , i cili është një pseudonim tipi për ordered_json , një strukturë fleksibile e të dhënave çelës-vlerë e ndërthurur.

API-ja e Conversation funksionon në bazë të mesazhit brenda dhe jashtë, duke imituar një përvojë tipike të bisedës. Fleksibiliteti i Message u lejon përdoruesve të përfshijnë fusha arbitrare sipas nevojës nga shabllone specifike të kërkesave ose modele LLM, duke i mundësuar LiteRT-LM të mbështesë një gamë të gjerë modelesh.

Edhe pse nuk ka një standard të vetëm të ngurtë, shumica e shablloneve dhe modeleve të prompt presin që Message të ndjekë konventa të ngjashme me ato të përdorura në Gemini API Content ose në strukturën OpenAI Message .

Message duhet të përmbajë role , që përfaqëson se nga kush është dërguar mesazhi. content mund të jetë aq e thjeshtë sa një varg teksti.

{
  "role": "model", // Represent who the message is sent from.
  "content": "Hello World!" // Naive text only content.
}

Për futjen e të dhënave multimodale, content është një listë e part . Përsëri, part nuk është një strukturë e të dhënave e paracaktuar, por një lloj i të dhënave çift çelës-vlerë i renditur . Fushat specifike varen nga ajo që presin shablloni i kërkesës dhe modeli.

{
  "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"
    }
  ]
}

Për part multimodale, ne mbështesim formatin e mëposhtëm të trajtuar nga 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",
}

Shablloni i kërkesës

Për të ruajtur fleksibilitetin për modelet variante, PromptTemplate zbatohet si një mbështjellës i hollë rreth Minja-s . Minja është një zbatim C++ i motorit të shablloneve Jinja , i cili përpunon të dhënat hyrëse JSON për të gjeneruar kërkesa të formatuara.

Motori i shablloneve Jinja është një format i përdorur gjerësisht për shabllonet e prompteve LLM. Ja disa shembuj:

Formati i motorit të shabllonit Jinja duhet të përputhet në mënyrë strikte me strukturën e pritur nga modeli i akorduar sipas udhëzimeve. Zakonisht, versionet e modelit përfshijnë shabllonin standard Jinja për të siguruar përdorimin e duhur të modelit.

Shablloni Jinja i përdorur nga modeli do të ofrohet nga meta të dhënat e skedarit të modelit.

[!SHËNIM] Një ndryshim i vogël në kërkesë për shkak të formatimit të pasaktë mund të çojë në degradim të konsiderueshëm të modelit. Siç raportohet në Kuantifikimin e Ndjeshmërisë së Modeleve të Gjuhës ndaj Karakteristikave të Rreme në Dizajnin e Kërkesës ose: Si mësova të filloj të shqetësohem për formatimin e kërkesës

Parathënie

Preface përcakton kontekstin fillestar për bisedën. Mund të përfshijë mesazhe fillestare, përkufizime mjetesh dhe çdo informacion tjetër në sfond që i nevojitet LLM-së për të filluar bashkëveprimin. Kjo arrin funksionalitet të ngjashëm me Gemini API system instruction dhe Gemini API Tools

Parathënia përmban fushat e mëposhtme

  • messages Mesazhet në parathënie. Mesazhet siguruan sfondin fillestar për bisedën. Për shembull, mesazhet mund të jenë historiku i bisedës, udhëzime të menjëhershme të sistemit inxhinierik, shembuj të shkurtër, etj.

  • tools Mjetet që modeli mund të përdorë në bisedë. Formati i mjeteve përsëri nuk është i fiksuar, por kryesisht ndjek Gemini API FunctionDeclaration .

  • extra_context Konteksti shtesë që ruan zgjerueshmërinë për modelet për të personalizuar informacionin e kërkuar të kontekstit për të filluar një bisedë. Për shembuj,

    • enable_thinking për modelet me modalitetin e të menduarit, p.sh. Qwen3 ose SmolLM3-3B .

Shembull parathënieje për të ofruar udhëzime fillestare të sistemit, mjete dhe për të çaktivizuar mënyrën e të menduarit.

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}
  }
});

Histori

Biseda mban një listë të të gjitha shkëmbimeve të Mesazheve brenda seancës. Ky historik është thelbësor për paraqitjen e shpejtë të shabllonit, pasi shablloni i kërkesës jinja zakonisht kërkon të gjithë historikun e bisedës për të gjeneruar kërkesën e saktë për LLM-në.

Megjithatë, LiteRT-LM Session është gjendje-formë, që do të thotë se përpunon të dhënat hyrëse në mënyrë graduale. Për të kapërcyer këtë boshllëk, Conversation gjeneron kërkesën e nevojshme graduale duke e paraqitur shabllonin e kërkesës dy herë: një herë me historikun deri në kthesën e mëparshme dhe një herë duke përfshirë mesazhin aktual. Duke krahasuar këto dy kërkesa të paraqitura, ajo nxjerr pjesën e re që do t'i dërgohet Session .

Konfigurimi i bisedës

ConversationConfig përdoret për të inicializuar një instancë Conversation . Ju mund ta krijoni këtë konfigurim në disa mënyra:

  1. Nga një Engine : Kjo metodë përdor SessionConfig e parazgjedhur të lidhur me motorin.
  2. Nga një SessionConfig specifik: Kjo lejon kontroll më të detajuar mbi cilësimet e sesionit.

Përtej cilësimeve të sesionit, mund ta personalizoni më tej sjelljen e Conversation brenda ConversationConfig . Kjo përfshin:

Këto mbishkrime janë veçanërisht të dobishme për modelet e përmirësuara, të cilat mund të kërkojnë konfigurime ose shabllone të ndryshme të kërkesave sesa modeli bazë nga i cili janë nxjerrë.

MesazhCallback

MessageCallback është funksioni i rikthimit të thirrjes që përdoruesit duhet të implementojnë kur përdorin metodën asinkrone SendMessageAsync .

Nënshkrimi i rikthimit të thirrjes është absl::AnyInvocable<void(absl::StatusOr<Message>)> . Ky funksion aktivizohet në kushtet e mëposhtme:

  • Kur një pjesë e re e Message merret nga Modeli.
  • Nëse ndodh një gabim gjatë përpunimit të mesazhit të LiteRT-LM.
  • Pas përfundimit të nxjerrjes së përfundimit të LLM-së, thirrja mbrapsht aktivizohet me një Message bosh (p.sh., JsonMessage() ) për të sinjalizuar fundin e përgjigjes.

Referojuni thirrjes asinkrone të Hapi 6 për një shembull zbatimi.

[!E RËNDËSISHME] Message i marrë nga rikthimi i thirrjes përmban vetëm pjesën më të fundit të rezultatit të modelit, jo të gjithë historikun e mesazheve.

Për shembull, nëse përgjigja e plotë e modelit që pritet nga një thirrje bllokuese SendMessage do të ishte:

{
  "role": "model",
  "content": [
    "type": "text",
    "text": "Hello World!"
  ]
}

Thirrja mbrapsht në SendMessageAsync mund të thirret disa herë, çdo herë me një pjesë pasuese të tekstit:

// 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!"
  ]
}

Implementuesi është përgjegjës për grumbullimin e këtyre pjesëve nëse nevojitet përgjigja e plotë gjatë rrjedhës asinkrone. Nga ana tjetër, përgjigja e plotë do të jetë e disponueshme si hyrja e fundit në History pasi të përfundojë thirrja asinkrone.

Përdorimi i Avancuar

Dekodimi i Kufizuar

LiteRT-LM mbështet dekodimin e kufizuar, duke ju lejuar të zbatoni struktura specifike në daljen e modelit, siç janë skemat JSON, modelet Regex ose rregullat gramatikore.

Për ta aktivizuar, caktoni EnableConstrainedDecoding(true)ConversationConfig dhe jepni një ConstraintProviderConfig (p.sh., LlGuidanceConfig për mbështetje regex/JSON/gramatike). Pastaj, kaloni kufizimet nëpërmjet OptionalArgsSendMessage .

Shembull: Kufizim 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}
);

Për detaje të plota, duke përfshirë mbështetjen për JSON Schema dhe Lark Grammar, shihni dokumentacionin e Dekodimit të Kufizuar .

Përdorimi i mjetit

Thirrja e mjeteve i lejon LLM-së të kërkojë ekzekutimin e funksioneve në anën e klientit. Ju përcaktoni mjetet në Preface e bisedës, duke i shkruar ato me emër. Kur modeli nxjerr një thirrje mjeti, ju e kapni atë, ekzekutoni funksionin përkatës në aplikacionin tuaj dhe ia ktheni rezultatin modelit.

Rrjedha e nivelit të lartë: 1. Deklarimi i mjeteve: Përcaktimi i mjeteve (emri, përshkrimi, parametrat) në Preface JSON. 2. Zbulimi i thirrjeve: Kontrollimi i model_message["tool_calls"] në përgjigje. 3. Ekzekutimi: Ekzekutimi i logjikës së aplikacionit tuaj për mjetin e kërkuar. 4. Përgjigju: Dërgoni një mesazh me role: "tool" që përmban rezultatin e mjetit përsëri te modeli.

Për detaje të plota dhe një shembull të plotë të ciklit të bisedës, shihni dokumentacionin e Përdorimit të Mjetit .