Skip to content

Sample: Foundry Local + .NET MAUI on-device AI#749

Open
davidortinau wants to merge 1 commit intomainfrom
sample/foundry-local-chat
Open

Sample: Foundry Local + .NET MAUI on-device AI#749
davidortinau wants to merge 1 commit intomainfrom
sample/foundry-local-chat

Conversation

@davidortinau
Copy link
Copy Markdown
Contributor

Summary

Guide and code samples showing how to integrate Foundry Local (on-device AI) into a .NET MAUI app alongside Microsoft Foundry (cloud), with a runtime toggle between providers.

What's included

  • Architecture overview with IChatClient provider swap pattern
  • Platform-specific integration:
    • Windows: Native SDK via Microsoft.AI.Foundry.Local.WinML
    • Mac Catalyst: REST API discovery via Foundry Local CLI (no NuGet needed)
    • iOS: Notes on using ONNX Runtime GenAI directly
  • ThinkingTagStripperChatClient middleware for models that emit <think> reasoning tags
  • Manual JSON extraction for reliable structured output from small local models
  • Settings UI toggle with download progress
  • Offline detection and auto-fallback patterns
  • Model selection guide and platform support matrix

Tested end-to-end on

  • macOS (Mac Catalyst) with qwen3-0.6b via Foundry Local CLI
  • Streaming chat completions, structured JSON output, tool calling

Related

/cc @mattleibow @jfversluis

Sample guide showing how to add on-device AI via Foundry Local
alongside Microsoft Foundry (cloud) in the Telepathy .NET MAUI app.

Features:
- IChatClient provider swap via DI (cloud ↔ local at runtime)
- Windows: native SDK with WinML acceleration
- Mac Catalyst: CLI REST API discovery (no NuGet needed)
- ThinkingTagStripperChatClient for models that emit <think> tags
- Manual JSON extraction for reliable structured output
- Settings toggle with download progress UX
- Platform support matrix and offline fallback patterns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member

@jfversluis jfversluis left a comment

Choose a reason for hiding this comment

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

Code Multi-Model Consensus (Sonnet 4.5, GPT-5.2, Opus 4.6)Review

**1. Mac Catalyst won't no NuGet package for FoundryLocalManager**compile
Code uses FoundryLocalManager under #if WINDOWS || MACCATALYST (lines ~176-197), but the csproj only adds Microsoft.AI.Foundry.Local.WinML conditioned on -windows. The guide says macOS needs "no NuGet package" yet the C# directly references types from Microsoft.AI.Foundry.Local. The Mac build will fail on unresolved types.

Fix: Either add a Mac Catalyst package reference, or split the code paths so MACCATALYST uses a CLI-based discovery approach instead of the managed API.


2. AddChatClient(async sp => ...) won't compile
The alternative DI registration example (line ~584) uses an async factory lambda. Standard .NET DI factory delegates are synchronous (Func<IServiceProvider, there is no Func<IServiceProvider, Task> overload.T>)

Fix: Remove this example (the recommended IChatClientService pattern already handles async init correctly), or show a sync factory with .GetAwaiter().GetResult().


3. Missing OpenAI NuGet package in csproj snippet
Code uses using OpenAI; and new OpenAIClient(...), but the csproj snippet (lines ~65-68) only lists Azure.AI.OpenAI, Microsoft.Extensions.AI, and Microsoft.Extensions.AI.OpenAI. Readers following the guide will get compile errors.

Fix: Add the OpenAI package to the csproj snippet.


4. Mismatched package versions
Microsoft.Extensions.AI is shown at 9.9.1 while Microsoft.Extensions.AI.OpenAI is at 9.4.4-preview. These packages should be version-aligned to avoid binding issues.


5. ProgressBar value range mismatch
DownloadProgress = p.PercentComplete; (line ~676) assigns a 100 value, but MAUI ProgressBar.Progress expects 0.1.0. The bar will appear full as soon as download reaches 1%.00

Fix: DownloadProgress = p.PercentComplete / 100.0;


6. JSON extraction regex fails on nested objects
The generic GetStructuredResponseAsync<T> helper (line ~951) uses (\{[\s\S]*?\}) (lazy quantifier), which matches from the first { to the nearest }. For any JSON with nested objects this produces invalid JSON.

Fix: Use a greedy match (\{[\s\S]*\}) or implement brace-balancing extraction.


**7. async void crash risk + lazy resolution**ConnectivityWatcher
The OnConnectivityChanged handler is async void with no try/catch. Unhandled exceptions will crash the app. Additionally, the singleton is registered but never explicitly resolved, so the constructor (which hooks the event) may never run.

Fix: Add try/catch in the handler, and ensure the watcher is resolved at startup.


Review generated with Claude Sonnet 4.5, GPT-5.2, and Claude Opus 4.6.

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.

2 participants