Skip to content

[Feature] Add Microsoft.Maui.Essentials.AI - Android / Gemini Nano#33609

Draft
mattleibow wants to merge 20 commits into
mainfrom
dev/ai-android
Draft

[Feature] Add Microsoft.Maui.Essentials.AI - Android / Gemini Nano#33609
mattleibow wants to merge 20 commits into
mainfrom
dev/ai-android

Conversation

@mattleibow

@mattleibow mattleibow commented Jan 19, 2026

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Important

This PR is huge because it is based on 2 PRs, one of which is the main part of the code.
The true diff is dev/alt-main...dev/ai-android

Description

This PR adds Android Gemini Nano (MLKit GenAI) support to Microsoft.Maui.Essentials.AI, enabling on-device AI chat capabilities through the Microsoft.Extensions.AI abstractions on Android.

What's Added

Android Gemini Nano Integration

GeminiNanoChatClient - Full IChatClient implementation for Google MLKit GenAI:

Feature Description
Synchronous Responses GetResponseAsync() for complete responses
Streaming Responses GetStreamingResponseAsync() returns IAsyncEnumerable<ChatResponseUpdate> for real-time UI updates
Singleton Model Default constructor uses Generation.Instance.Client singleton
Custom Model Support Constructor overload accepts existing IGenerativeModel
Cancellation Support Full CancellationToken support via Android CancellationSignal
Middleware Compatible Works with IChatClient pipeline builders (.AsBuilder().UseLogging().Build())

Extension Methods:

  • IGenerativeModel.AsIChatClient() - Wrap existing IGenerativeModel as IChatClient

Internal C# Extensions:

  • GenerativeModelExtensions - Async wrappers converting Kotlin coroutines to C# Task-based patterns
    • GenerateContentAsync() - Non-streaming content generation
    • GenerateContentStreamAsync() - Streaming content generation as IAsyncEnumerable
    • CheckStatusAsync() - Model availability check
    • WarmupAsync() - Model warmup for better first-response latency

Capabilities

Feature Status Notes
Chat Completion ✅ Supported Full non-streaming via GetResponseAsync()
Streaming Responses ✅ Supported IAsyncEnumerable<ChatResponseUpdate> via Kotlin Flow bridge
Text Embeddings ❌ Not Available MLKit GenAI doesn't expose embedding API; use cloud services
Function/Tool Calling ❌ Not Supported Not available in Gemini Nano
Structured JSON Output ❌ Not Supported Not available in Gemini Nano
System Prompts ✅ Supported Via PromptPrefix in GenerateContentRequest
Multi-turn Conversation ⚠️ Flattened Messages concatenated into single TextPart
Image Input ✅ Supported Single image via DataContentImagePart (one per request)
Temperature ✅ Supported Via GenerateContentRequest.Builder.Temperature
TopK ✅ Supported Via GenerateContentRequest.Builder.TopK
TopP ❌ Not Supported Not exposed by MLKit GenAI
Seed ✅ Supported Via GenerateContentRequest.Builder.Seed
Max Output Tokens ❌ Not Supported Not exposed by MLKit GenAI
Cancellation ✅ Supported Via Android CancellationSignal

Note: The sample app uses OpenAI for embeddings on Android since Gemini Nano doesn't provide a local embedding API.


Architecture: Kotlin Native Layer & C# Integration

The Gemini Nano integration uses a two-layer architecture to bridge .NET with Google's Kotlin coroutine-based MLKit GenAI:

┌─────────────────────────────────────────────────────────────────┐
│                    .NET MAUI Application                        │
│                        IChatClient                              │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                   C# Layer (Essentials.AI)                      │
│  GeminiNanoChatClient : IChatClient                             │
│  • Implements Microsoft.Extensions.AI abstractions              │
│  • Converts ChatMessage → GenerateContentRequest                │
│  • Uses GenerativeModelExtensions for async operations          │
│  GenerativeModelExtensions                                      │
│  • Task-based wrappers calling Kotlin listener interfaces       │
│  • IAsyncEnumerable streaming via Channel<T>                    │
└─────────────────────────────────────────────────────────────────┘
                              │ (Java Binding)
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                Kotlin Layer (AndroidNative)                     │
│  GenerativeModelExtensions.kt                                   │
│  • Extension functions for GenerativeModel                      │
│  • Bridges Kotlin coroutines → CancellationSignal + listeners   │
│  Listener Interfaces                                            │
│  • ContentGenerationListener (non-streaming)                    │
│  • StreamContentGenerationListener (streaming)                  │
│  • ModelStatusListener, ModelWarmupListener                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              Google MLKit GenAI (Gemini Nano)                   │
│  com.google.mlkit.genai.prompt.GenerativeModel                  │
└─────────────────────────────────────────────────────────────────┘

Why Kotlin is Required:

  • MLKit GenAI uses Kotlin coroutines (suspend functions, Flow<T>)
  • No direct Java/C# API for async operations
  • Kotlin extension functions provide callback-based bridge for C# consumption

Layer Responsibilities:

Layer Responsibility
C# (GeminiNanoChatClient) IChatClient implementation, message conversion, response mapping
C# (GenerativeModelExtensions) Task/IAsyncEnumerable wrappers using Kotlin listeners
Kotlin (GenerativeModelExtensions.kt) Coroutine → CancellationSignal + listener bridge
Kotlin (Listener interfaces) Callback contracts for C# to implement

Why

  1. Unified AI API: Same IChatClient abstractions work with cloud AI (OpenAI, Azure) or on-device (Gemini Nano, Apple Intelligence, Windows Copilot Runtime)

  2. On-Device Privacy: Gemini Nano runs entirely on-device, keeping user data private

  3. Cross-Platform Parity: Android developers get the same AI capabilities as Apple and Windows platform developers


Native Library Build

The Kotlin native library is located at src/AI/src/AndroidNative/:

AndroidNative/
├── build.gradle
├── settings.gradle
├── gradle.properties
├── gradlew / gradlew.bat
└── essentialsai/
    ├── build.gradle
    └── src/main/kotlin/com/microsoft/maui/essentials/ai/
        ├── GenerativeModelExtensions.kt
        ├── ContentGenerationListener.kt
        ├── StreamContentGenerationListener.kt
        ├── ModelStatusListener.kt
        └── ModelWarmupListener.kt

Build with: ./gradlew :essentialsai:assembleRelease


Public API Surface

Namespace: Microsoft.Maui.Essentials.AI

// Android Gemini Nano Chat Client
public sealed class GeminiNanoChatClient : IChatClient
{
    public GeminiNanoChatClient();
    public GeminiNanoChatClient(IGenerativeModel model);
    public ChatClientMetadata Metadata { get; }
    public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default);
    public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default);
}

public static class GeminiNanoExtensions
{
    public static IChatClient AsIChatClient(this IGenerativeModel model);
}

Usage Examples:

// Chat with Gemini Nano
var chatClient = new GeminiNanoChatClient();

// Simple response
var response = await chatClient.GetResponseAsync([new ChatMessage(ChatRole.User, "Hello!")]);

// Streaming response
await foreach (var update in chatClient.GetStreamingResponseAsync(messages))
{
    Console.Write(update.Text); // Progressive output
}

// With middleware pipeline
var client = new GeminiNanoChatClient()
    .AsBuilder()
    .UseLogging(loggerFactory)
    .Build();

// From existing IGenerativeModel
var model = Generation.Instance.Client;
var chatClient = model.AsIChatClient();

Sample App Changes

The Essentials.AI.Sample now automatically uses:

  • Android: Gemini Nano for chat (on-device) + OpenAI for embeddings (cloud)
  • Other platforms: OpenAI (cloud)

This is configured via conditional compilation in MauiProgram.cs.


Requirements

  • Android device with Gemini Nano support (Pixel 8+, Samsung Galaxy S24+, etc.)
  • Google Play Services with MLKit GenAI
  • Model must be downloaded before use (handled by MLKit)

Files Changed

Category Files Description
C# Implementation 3 GeminiNanoChatClient, Extensions, GenerativeModelExtensions
Kotlin Native 5 Extension functions and listener interfaces
Gradle Build 7 Build configuration, wrapper, properties
Sample 1 MauiProgram.cs with Android-specific DI
Public API 1 PublicAPI.Unshipped.txt
Bindings 1 Metadata.xml transforms

Total: 23 files changed, +1094/-2 lines

mattleibow and others added 17 commits February 2, 2026 18:33
… workflow

Introduces the Essentials.AI module with a Trip Planner sample demonstrating
production-ready AI integration patterns using Microsoft.Extensions.AI and
Microsoft.Agents.AI.

Sample Application (Essentials.AI.Sample):
- 4-agent workflow: TravelPlanner → Researcher → ItineraryPlanner → Translator
- RAG with embedding-based semantic search for destination matching
- Streaming JSON responses with partial deserialization for progressive UI
- Function calling (findPointsOfInterest tool) and structured JSON output
- Conditional translation branching for non-English requests

Services & Utilities:
- StreamingJsonDeserializer, JsonMerger, BufferedChatClient
- DataService (landmarks + embeddings), TaggingService, ItineraryService

Library Skeleton (Essentials.AI):
- Multi-platform TFM support with PublicAPI tracking
- Foundation for future Apple Intelligence, Windows Copilot, Gemini Nano

Test Infrastructure:
- Unit tests: JSON streaming, merging, buffering (real AI response data)
- Device tests: Reusable IChatClient/IEmbeddingGenerator base classes
- Benchmarks: BenchmarkDotNet infrastructure
- Add AppleBindings.targets for reusable XcodeProject logic
  - Automatic XcodeProject handling (macOS only)
  - Pre-built artifact inclusion for Windows CI builds
  - IntelliSense support for generated binding files

- Update Core.csproj to use XcodeProject instead of pre-built xcframework
  - Build MauiPlatformInterop.xcframework from source on macOS
  - Use downloaded artifacts on Windows CI

- Update stage-pack.yml pipeline
  - Add pack_net_macOS job to build and publish Mac native binaries
  - Add download task to pack_net_Windows job to retrieve artifacts
  - Windows pack job now depends on macOS job completion

- Remove obsolete files
  - Deleted MauiPlatformInterop.xcframework.zip (now built dynamically)
  - Deleted build-xcframework.sh (XcodeProject handles this)
Introduce AI capabilities for Android platform including:

Core Components:
- GeminiNanoChatClient: Chat completion client using Google MLKit
  GenAI (Gemini Nano) with streaming support via IChatClient
- GeminiNanoExtensions: Convenience method to convert IGenerativeModel
  to IChatClient
- (internal) GenerativeModelExtensions: C# async wrappers for Kotlin
  coroutine-based model operations

Native Kotlin Library (AndroidNative/essentialsai):
- Kotlin implementation for MLKit GenAI coroutine bridging
- GenerativeModelExtensions with CancellationSignal support
- Listener interfaces for async callbacks (ContentGenerationListener,
  StreamContentGenerationListener, ModelStatusListener, ModelWarmupListener)

Samples:
- Update Essentials.AI.Sample to use Gemini Nano on Android,
  falling back to OpenAI on other platforms
- Cloud embeddings via OpenAI (Android lacks local embedding API)

Note: Android/Gemini Nano does not provide a local embedding API,
so embeddings still require cloud services (OpenAI/Azure).
- Wrap 'using Microsoft.Maui.Essentials.AI' in #if ANDROID since the namespace only exists on Android (GeminiNanoChatClient)
- Remove condition from Android SupportedOSPlatformVersion in Directory.Build.targets to ensure all Android projects use API 23+ (required by AndroidX updates)
- Update explicit minSdkVersion from 21 to 23 in affected projects and templates
- Benchmarks.Droid: Update from 21 to 23 (AndroidX requires 23)
- Essentials.AI.Sample: Add explicit minSdkVersion 26 (MLKit GenAI requires 26)
Use MSBuild::VersionLessThan to only set Android minSdkVersion to 23 if
the project doesn't already specify a higher value. This allows projects
like Essentials.AI.Sample (API 26) to keep their higher requirement.
@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

@MauiBot

This comment has been minimized.

@kubaflo

kubaflo commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer -p android

@MauiBot

MauiBot commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

⚠️ Merge Conflict Detected — This PR has merge conflicts with its target branch. Please rebase onto the target branch and resolve the conflicts.

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.

4 participants