Skip to content

feat(ai): Firebase AI Logic C++ SDK with LiteRT / LiteRT-LM Hybrid Inference - #1930

Draft
AustinBenoit wants to merge 2 commits into
mainfrom
feat/ai-litert-hybrid
Draft

AustinBenoit wants to merge 2 commits into
mainfrom
feat/ai-litert-hybrid

Conversation

@AustinBenoit

Copy link
Copy Markdown
Contributor

Summary

  • Adds the firebase::ai C++ SDK (FirebaseAI, GenerativeModel, Chat, TemplateGenerativeModel, TemplateChatSession, Schema, JsonSchema, Function Calling, Safety, and SSE Streaming) across Desktop, Android, and iOS/macOS.
  • Adds Hybrid On-Device + Cloud Inference (ONLY_ON_DEVICE, ONLY_IN_CLOUD, PREFER_ON_DEVICE, PREFER_IN_CLOUD) powered by Google AI Edge LiteRT (litert::CompiledModel for .tflite) and LiteRT-LM (CLiteRTLM for .litertlm Gemma models).
  • Auto-fetches the LiteRT C++ SDK headers (v2.2.0), libLiteRt, and CLiteRTLM (v0.18.0) via CMake (FIREBASE_AI_DOWNLOAD_LITERT=ON).
  • Adds firebase_ai_litert_bridge C ABI shared library for Unity P/Invoke hybrid integration.
  • Adds firebase_ai_hybrid_chat interactive multi-turn CLI sample with live mode toggling (/toggle, /mode), /compact, /clear, automatic context window detection & compaction, and repetition/4-gram loop prevention.
  • See ai/README.md for quick start and build instructions.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the firebase::ai C++ SDK for Firebase AI Logic, enabling hybrid cloud and local on-device inference powered by Google AI Edge LiteRT and LiteRT-LM. It includes platform-specific HTTP senders, a C ABI bridge for Unity integration, a multi-turn chat sample with automatic context compaction, and unit tests. The review feedback suggests several robustness improvements: using LoadLibraryW with UTF-8 to UTF-16 conversion on Windows to support non-ASCII paths, utilizing idx.is_numeric() during JSON parsing to safely handle varying numeric types for grounding chunk indices, and making CMake download failures fatal to prevent downstream compilation errors.

Comment on lines +53 to +60
void* LoadSharedLibrary(const std::string& path) {
if (path.empty()) return nullptr;
#if defined(_WIN32)
return reinterpret_cast<void*>(LoadLibraryA(path.c_str()));
#else
return dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
#endif
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

On Windows, LoadSharedLibrary uses LoadLibraryA which expects an ANSI string. If the installation path or runtime library path contains non-ASCII characters (e.g., in non-English locales or user directories), LoadLibraryA will fail to load the library. It is highly recommended to use LoadLibraryW with a UTF-8 to UTF-16 conversion helper to ensure robust cross-platform path support.

void* LoadSharedLibrary(const std::string& path) {
  if (path.empty()) return nullptr;
#if defined(_WIN32)
  int len = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, nullptr, 0);
  if (len <= 0) return nullptr;
  std::vector<wchar_t> wpath(len);
  MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, wpath.data(), len);
  return reinterpret_cast<void*>(LoadLibraryW(wpath.data()));
#else
  return dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
#endif
}

Comment on lines +391 to +394
if (idx.is_int64()) {
gs.grounding_chunk_indices.push_back(
static_cast<int>(idx.int64_value()));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In ParseGroundingMetadata, the code checks idx.is_int64() before extracting the grounding chunk index. However, depending on the JSON parser implementation and the platform, small integer values in JSON might be parsed as double or other numeric types. To prevent silently skipping valid indices, use idx.is_numeric() and convert it safely using idx.AsInt64().int64_value().

Suggested change
if (idx.is_int64()) {
gs.grounding_chunk_indices.push_back(
static_cast<int>(idx.int64_value()));
}
if (idx.is_numeric()) {
gs.grounding_chunk_indices.push_back(
static_cast<int>(idx.AsInt64().int64_value()));
}

Comment thread ai/CMakeLists.txt
Comment on lines +118 to +120
else()
message(WARNING "Firebase AI: Failed to download LiteRT C/C++ SDK (${LITERT_SDK_DL_STATUS})")
endif()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the download of the LiteRT C/C++ SDK fails, CMake currently prints a warning and continues configuration. This will lead to confusing compilation errors later in the build process when headers or libraries are missing. If FIREBASE_AI_DOWNLOAD_LITERT is enabled, a download failure should be treated as a fatal error so the user is notified immediately during the configuration step.

    else()
      message(FATAL_ERROR "Firebase AI: Failed to download LiteRT C/C++ SDK (${LITERT_SDK_DL_STATUS})")
    endif()

return false;
}
state_->cc_compiled_model.reset(
new litert::CompiledModel(std::move(*model_res)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Lint warning: Add #include <utility> for move

size_t probe_len =
(end_line == std::string::npos)
? std::min<size_t>(24, last_user_text.size() - first_non_ws)
: std::min<size_t>(24, end_line - first_non_ws);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Lint warning: Add #include <algorithm> for min

size_t total = size * nmemb;
if (total == 0 || !userdata) return 0;
CurlWriteContext* ctx = static_cast<CurlWriteContext*>(userdata);
long http_code = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Lint warning: Use int16/int64/etc, rather than the C type long


if (req.timeout_ms > 0) {
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS,
static_cast<long>(req.timeout_ms));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Lint warning: Use int16/int64/etc, rather than the C type long

curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.body.data());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
static_cast<long>(req.body.size()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Lint warning: Use int16/int64/etc, rather than the C type long

curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_ctx);

CURLcode res = curl_easy_perform(curl);
long http_status = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Lint warning: Use int16/int64/etc, rather than the C type long

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant