Skip to content

Commit 2035462

Browse files
bmehta001baijumeswaniCopilotCopilot
authored
Explicit EP Download & Per-EP Progress Reporting (#568)
Makes execution provider (EP) management explicit across all SDKs and adds real-time per-EP download progress reporting. Previously, EP downloads happened implicitly during catalog access with no granular progress visibility. Now callers explicitly discover, download, and monitor EPs with typed APIs and streaming progress callbacks. What's included Explicit EP discovery and download (all SDKs) - DiscoverEps() / discoverEps() / discover_eps() — returns typed EpInfo with name and registration status - DownloadAndRegisterEpsAsync() / downloadAndRegisterEps() / download_and_register_eps() — downloads and registers EPs, returns typed EpDownloadResult - Catalog access no longer blocks on EP downloads Per-EP progress callbacks (all SDKs) - C#: DownloadAndRegisterEpsAsync(names, Action<string, double> progressCallback, ct) — uses ExecuteCommandWithCallbackAsync; parses wire format with CultureInfo.InvariantCulture for locale safety - JS: downloadAndRegisterEpsWithProgress(names?, progressCallback?) — uses executeCommandStreaming - Python: download_and_register_eps(names, progress_callback) — uses execute_command_with_callback - Rust: download_and_register_eps_with_progress(names, FnMut(&str, f64)) — parses "name|percent" wire format inside the SDK Live Audio Transcription (C#) - New LiveAudioTranscriptionSession with real-time streaming over WebSocket - Supports start/stop/send audio chunks with configurable output types - Unit tests with mocked CoreInterop Other improvements - Typed EpInfo / EpDownloadResult in dedicated type files across all SDKs - EP unit tests for JS and Python - Removed implicit 6-hour catalog TTL caching (delegated to native core) - New CoreInterop methods for callback-based command execution (C#) - AOT-compatible JSON serialization context for EP types (C#) Testing - New unit tests for EP discovery/download in JS and Python Breaking changes - Catalog no longer implicitly triggers EP downloads — callers must explicitly call DownloadAndRegisterEpsAsync / downloadAndRegisterEps / download_and_register_eps before accessing hardware-accelerated models. --------- Co-authored-by: Baiju Meswani <bmeswani@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 80211df commit 2035462

47 files changed

Lines changed: 1572 additions & 161 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

samples/cs/audio-transcription-example/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// EP packages include dependencies and may be large.
2121
// Download is only required again if a new version of the EP is released.
2222
// For cross platform builds there is no dynamic EP download and this will return immediately.
23-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
23+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
2424
// </init>
2525

2626

@@ -56,6 +56,7 @@ await model.DownloadAsync(progress =>
5656
// <transcription>
5757
// Get an audio client
5858
var audioClient = await model.GetAudioClientAsync();
59+
audioClient.Settings.Language = "en";
5960

6061
// Get a transcription with streaming outputs
6162
var audioFile = args.Length > 0 ? args[0] : Path.Combine(AppContext.BaseDirectory, "Recording.mp3");

samples/cs/foundry-local-web-server/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
// EP packages include dependencies and may be large.
2727
// Download is only required again if a new version of the EP is released.
2828
// For cross platform builds there is no dynamic EP download and this will return immediately.
29-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
29+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
3030
// </init>
3131

3232

samples/cs/live-audio-transcription-example/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
await FoundryLocalManager.CreateAsync(config, Utils.GetAppLogger());
2121
var mgr = FoundryLocalManager.Instance;
2222

23-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
23+
await mgr.DownloadAndRegisterEpsAsync();
2424

2525
var catalog = await mgr.GetCatalogAsync();
2626

samples/cs/model-management-example/Program.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616
var mgr = FoundryLocalManager.Instance;
1717

1818

19-
// Ensure that any Execution Provider (EP) downloads run and are completed.
20-
// EP packages include dependencies and may be large.
21-
// Download is only required again if a new version of the EP is released.
22-
// For cross platform builds there is no dynamic EP download and this will return immediately.
23-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
19+
// Download and register all execution providers.
20+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
2421

2522

2623
// Model catalog operations

samples/cs/native-chat-completions/Program.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,43 @@
1919
var mgr = FoundryLocalManager.Instance;
2020

2121

22-
// Ensure that any Execution Provider (EP) downloads run and are completed.
22+
// Discover available execution providers and their registration status.
23+
var eps = mgr.DiscoverEps();
24+
Console.WriteLine("Available execution providers:");
25+
foreach (var ep in eps)
26+
{
27+
Console.WriteLine($" {ep.Name} (registered: {ep.IsRegistered})");
28+
}
29+
30+
// Download and register all execution providers with per-EP progress.
2331
// EP packages include dependencies and may be large.
2432
// Download is only required again if a new version of the EP is released.
2533
// For cross platform builds there is no dynamic EP download and this will return immediately.
26-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
34+
if (eps.Length > 0)
35+
{
36+
int maxNameLen = eps.Max(e => e.Name.Length);
37+
string currentEp = "";
38+
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
39+
{
40+
if (epName != currentEp)
41+
{
42+
if (currentEp != "")
43+
{
44+
Console.WriteLine();
45+
}
46+
currentEp = epName;
47+
}
48+
Console.Write($"\r {epName.PadRight(maxNameLen)} {percent,6:F1}%");
49+
if (percent >= 100)
50+
{
51+
Console.WriteLine();
52+
}
53+
});
54+
}
55+
else
56+
{
57+
Console.WriteLine("No execution providers to download.");
58+
}
2759
// </init>
2860

2961

samples/cs/tool-calling-foundry-local-sdk/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
// EP packages include dependencies and may be large.
2727
// Download is only required again if a new version of the EP is released.
2828
// For cross platform builds there is no dynamic EP download and this will return immediately.
29-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
29+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
3030
// </init>
3131

3232

samples/cs/tool-calling-foundry-local-web-server/Program.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@
2121
var mgr = FoundryLocalManager.Instance;
2222

2323

24-
// Ensure that any Execution Provider (EP) downloads run and are completed.
25-
// EP packages include dependencies and may be large.
26-
// Download is only required again if a new version of the EP is released.
27-
// For cross platform builds there is no dynamic EP download and this will return immediately.
28-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
24+
// Download and register all execution providers.
25+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
2926

3027

3128
// Get the model catalog

samples/js/native-chat-completions/app.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,35 @@ const manager = FoundryLocalManager.create({
1414
// </init>
1515
console.log('✓ SDK initialized successfully');
1616

17+
// Discover available execution providers and their registration status.
18+
const eps = manager.discoverEps();
19+
console.log('\nAvailable execution providers:');
20+
for (const ep of eps) {
21+
console.log(` ${ep.name} (registered: ${ep.isRegistered})`);
22+
}
23+
24+
// Download and register all execution providers with per-EP progress.
25+
// EP packages include dependencies and may be large.
26+
// Download is only required again if a new version of the EP is released.
27+
if (eps.length > 0) {
28+
const maxNameLen = Math.max(...eps.map(e => e.name.length));
29+
let currentEp = '';
30+
await manager.downloadAndRegisterEps((epName, percent) => {
31+
if (epName !== currentEp) {
32+
if (currentEp !== '') {
33+
process.stdout.write('\n');
34+
}
35+
currentEp = epName;
36+
}
37+
process.stdout.write(`\r ${epName.padEnd(maxNameLen)} ${percent.toFixed(1).padStart(5)}%`);
38+
if (percent >= 100) {
39+
process.stdout.write('\n');
40+
}
41+
});
42+
} else {
43+
console.log('No execution providers to download.');
44+
}
45+
1746
// <model_setup>
1847
// Get the model object
1948
const modelAlias = 'qwen2.5-0.5b'; // Using an available model from the list above

sdk/cs/README.md

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,60 @@ dotnet build src/Microsoft.AI.Foundry.Local.csproj /p:UseWinML=true
4848

4949
### Triggering EP download
5050

51-
EP download can be time-consuming. Call `DownloadAndRegisterEpsAsync` early (after initialization) to separate the download step from catalog access:
51+
EP management is explicit via two methods:
52+
53+
- **`DiscoverEps()`** — returns an array of `EpInfo` describing each available EP and whether it is already registered.
54+
- **`DownloadAndRegisterEpsAsync(names?, progressCallback?, ct?)`** — downloads and registers the specified EPs (or all available EPs if no names are given). Returns an `EpDownloadResult`. Overloads are provided so you can pass just a callback without specifying names.
5255

5356
```csharp
5457
// Initialize the manager first (see Quick Start)
5558
await FoundryLocalManager.CreateAsync(
5659
new Configuration { AppName = "my-app" },
5760
NullLogger.Instance);
5861

59-
await FoundryLocalManager.Instance.DownloadAndRegisterEpsAsync();
62+
var mgr = FoundryLocalManager.Instance;
6063

61-
// Now catalog access won't trigger an EP download
62-
var catalog = await FoundryLocalManager.Instance.GetCatalogAsync();
64+
// Discover what EPs are available
65+
var eps = mgr.DiscoverEps();
66+
foreach (var ep in eps)
67+
{
68+
Console.WriteLine($"{ep.Name} — registered: {ep.IsRegistered}");
69+
}
70+
71+
// Download and register all EPs
72+
var result = await mgr.DownloadAndRegisterEpsAsync();
73+
Console.WriteLine($"Success: {result.Success}, Status: {result.Status}");
74+
75+
// Or download only specific EPs
76+
var result2 = await mgr.DownloadAndRegisterEpsAsync(new[] { eps[0].Name });
77+
```
78+
79+
#### Per-EP download progress
80+
81+
Pass an optional `Action<string, double>` callback to receive `(epName, percent)` updates
82+
as each EP downloads (`percent` is 0–100):
83+
84+
```csharp
85+
string currentEp = "";
86+
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
87+
{
88+
if (epName != currentEp)
89+
{
90+
if (currentEp != "")
91+
{
92+
Console.WriteLine();
93+
}
94+
currentEp = epName;
95+
}
96+
Console.Write($"\r {epName} {percent,6:F1}%");
97+
if (percent >= 100)
98+
{
99+
Console.WriteLine();
100+
}
101+
});
63102
```
64103

65-
If you skip this step, EPs are downloaded automatically the first time you access the catalog. Once cached, subsequent calls are fast.
104+
Catalog access no longer blocks on EP downloads. Call `DownloadAndRegisterEpsAsync` explicitly when you need hardware-accelerated execution providers.
66105

67106
## Quick Start
68107

sdk/cs/docs/api/index.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
[DeviceType](./microsoft.ai.foundry.local.devicetype.md)
88

9+
[EpDownloadResult](./microsoft.ai.foundry.local.epdownloadresult.md)
10+
11+
[EpInfo](./microsoft.ai.foundry.local.epinfo.md)
12+
913
[FoundryLocalException](./microsoft.ai.foundry.local.foundrylocalexception.md)
1014

1115
[FoundryLocalManager](./microsoft.ai.foundry.local.foundrylocalmanager.md)
@@ -22,8 +26,6 @@
2226

2327
[ModelSettings](./microsoft.ai.foundry.local.modelsettings.md)
2428

25-
[ModelVariant](./microsoft.ai.foundry.local.modelvariant.md)
26-
2729
[OpenAIAudioClient](./microsoft.ai.foundry.local.openaiaudioclient.md)
2830

2931
[OpenAIChatClient](./microsoft.ai.foundry.local.openaichatclient.md)
@@ -39,3 +41,11 @@
3941
[AsyncLock](./microsoft.ai.foundry.local.detail.asynclock.md)
4042

4143
[CoreInteropRequest](./microsoft.ai.foundry.local.detail.coreinteroprequest.md)
44+
45+
## Microsoft.AI.Foundry.Local.OpenAI
46+
47+
[LiveAudioTranscriptionResponse](./microsoft.ai.foundry.local.openai.liveaudiotranscriptionresponse.md)
48+
49+
[LiveAudioTranscriptionSession](./microsoft.ai.foundry.local.openai.liveaudiotranscriptionsession.md)
50+
51+
[ResponseFormatExtended](./microsoft.ai.foundry.local.openai.responseformatextended.md)

0 commit comments

Comments
 (0)