Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion samples/cs/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@
<ItemGroup>
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="*-*" />
<PackageVersion Include="Betalgo.Ranul.OpenAI" Version="9.1.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.15" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.15" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.10" />
<PackageVersion Include="NAudio" Version="2.2.1" />
<PackageVersion Include="OpenAI" Version="2.5.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="10.0.1" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
</ItemGroup>
</Project>
</Project>
3 changes: 3 additions & 0 deletions samples/cs/ModelManagement/ModelManagement.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="ModelManagement/ModelManagement.csproj" />
</Solution>
130 changes: 130 additions & 0 deletions samples/cs/ModelManagement/ModelManagement/CatalogManagement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using Microsoft.AI.Foundry.Local;
using Microsoft.Extensions.Logging;
using ModelManagement.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;

namespace ModelManagement
{
public class CatalogManagement : ICatalogManagement
{
private readonly FoundryLocalManager _mgr;
private readonly ILogger<CatalogManagement> _logger;

public CatalogManagement(FoundryLocalManager mgr, ILogger<CatalogManagement> logger)
{
_mgr = mgr;
_logger = logger;
}

public async Task DownloadModelsAsync(List<string> modelNames, CancellationToken ct = default)
{
ICatalog catalog = await _mgr.GetCatalogAsync(ct);

List<IModel> cachedModels = await catalog.GetCachedModelsAsync(ct);
List<IModel> availableModels = await catalog.ListModelsAsync(ct);

foreach (var modelName in modelNames)
{
var model = availableModels.Find(m => m.Alias == modelName);
if (model != null)
{
if (cachedModels.Find(m => m.Alias == modelName) != null)
{
_logger.LogInformation($"Model already cached: {model.Alias}");
continue;
}

_logger.LogInformation($"Downloading model: {model.Alias}");
await model.DownloadAsync();
}
else
{
_logger.LogWarning($"Model not found in catalog: {modelName}");
}
}
}

public async Task<List<(string, IModel)>> LoadModelsAsync(List<string> modelNames, CancellationToken ct = default)
{
List<(string, IModel)> result = new List<(string, IModel)>();

ICatalog catalog = await _mgr.GetCatalogAsync(ct);

List<IModel> cachedModels = await catalog.GetCachedModelsAsync(ct);
List<IModel> loadedModels = await catalog.GetLoadedModelsAsync(ct);
List<IModel> availableModels = await catalog.ListModelsAsync(ct);

foreach (var modelName in modelNames)
{
var model = availableModels.Find(m => m.Alias == modelName);
if (model != null)
{
if (loadedModels.Find(m => m.Alias == modelName) == null)
{
_logger.LogInformation($"Model not loaded: {model.Alias}");

if (cachedModels.Find(m => m.Alias == modelName) == null)
{
_logger.LogInformation($"Downloading model: {model.Alias}");
await model.DownloadAsync();
}
else
{
_logger.LogInformation($"Model already cached: {model.Alias}");
}

_logger.LogInformation($"Loading model: {model.Alias}");
await model.LoadAsync();
}
else
{
_logger.LogInformation($"Model already loaded: {model.Alias}");
}

result.Add((modelName, model));
}
else
{
_logger.LogWarning($"Model not found in catalog: {modelName}");
}

}

return result;
}


public async Task UnloadModels(List<IModel> models, CancellationToken ct = default)
{
foreach (var model in models)
{
_logger.LogInformation($"Unloading model: {model.Alias}");
await model.UnloadAsync(ct);
}
}

public async Task ClearCacheAsync(CancellationToken ct = default)
{
ICatalog catalog = await _mgr.GetCatalogAsync(ct);

List<IModel> cachedModels = await catalog.GetCachedModelsAsync(ct);
foreach(var model in cachedModels)
{
_logger.LogInformation($"Removing model from cache: {model.Alias}");
await model.RemoveFromCacheAsync(ct);
}
}

public async Task RemoveModelsFromCacheAsync(List<IModel> models, CancellationToken ct = default)
{
foreach (var model in models)
{
_logger.LogInformation($"Removing model from cache: {model.Alias}");
await model.RemoveFromCacheAsync(ct);
}
}

}
}
54 changes: 54 additions & 0 deletions samples/cs/ModelManagement/ModelManagement/EPManagement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Microsoft.AI.Foundry.Local;
using Microsoft.Extensions.Logging;
using ModelManagement.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;

namespace ModelManagement
{
public class EPManagement : IEPManagement
{
private readonly FoundryLocalManager _mgr;
private readonly ILogger<EPManagement> _logger;

public EPManagement(FoundryLocalManager mgr, ILogger<EPManagement> logger)
{
_mgr = mgr;
_logger = logger;
}

public async Task DownloadAndRegisterEpsAsync(CancellationToken ct = default)
{
// Discover what EPs are available
var discoveredEps = _mgr.DiscoverEps();
List<string> epNames = new List<string>();
foreach (var ep in discoveredEps)
{
_logger.LogInformation($"{ep.Name} — registered: {ep.IsRegistered}");
if (!ep.IsRegistered)
{
epNames.Add(ep.Name);
}
}

// Download and register all EPs
string currentEp = "";
var result = await _mgr.DownloadAndRegisterEpsAsync(epNames, (epName, percent) =>
{
if (epName != currentEp)
{
if (currentEp != "")
{
_logger.LogInformation("");
}
currentEp = epName;
}
_logger.LogInformation($"\r {epName} {percent,6:F1}%");
}, ct);
_logger.LogInformation("");

_logger.LogInformation($"Success: {result.Success}, Status: {result.Status}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.AI.Foundry.Local;

namespace ModelManagement.Interfaces
{
public interface ICatalogManagement
{
Task DownloadModelsAsync(List<string> modelNames, CancellationToken ct = default);
Task<List<(string, IModel)>> LoadModelsAsync(List<string> modelNames, CancellationToken ct = default);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ModelManagement.Interfaces
{
public interface IEPManagement
{
public Task DownloadAndRegisterEpsAsync(CancellationToken ct = default);
}
}
24 changes: 24 additions & 0 deletions samples/cs/ModelManagement/ModelManagement/ModelManagement.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AI.Foundry.Local" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Serilog.Extensions.Hosting" />
<PackageReference Include="Serilog.Extensions.Logging" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Console" />
</ItemGroup>

</Project>
81 changes: 81 additions & 0 deletions samples/cs/ModelManagement/ModelManagement/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Microsoft.AI.Foundry.Local;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ModelManagement.Interfaces;
using Serilog;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ModelManagement;

internal class Program
{
private static async Task Main(string[] args)
{
var _loggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information);
});
var _logger = _loggerFactory.CreateLogger("ModelManagement");

// Bootstrapping Serilog from appsettings.json explicitly so logging is active
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();

Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.FromLogContext()
.CreateLogger();

try
{
// Initialize the manager first (see Quick Start)
await FoundryLocalManager.CreateAsync(
new Configuration { AppName = "my-app" },
_logger);
var mgr = FoundryLocalManager.Instance;

Log.Information("Starting host");

var builder = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
// allow host to read configuration too
config.AddConfiguration(configuration);
})
.UseSerilog() // use the static Log.Logger we created
.ConfigureServices((context, services) =>
{
// Register the FoundryLocalManager singleton instance created earlier
services.AddSingleton(FoundryLocalManager.Instance);

// Register EPManagement for IEPManagement so it can be injected
services.AddSingleton<IEPManagement, EPManagement>();
services.AddScoped<ICatalogManagement, CatalogManagement>();

services.AddHostedService<Worker>();
});

using var host = builder.Build();

await host.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
throw;
}
finally
{
Log.CloseAndFlush();
}
}
}
37 changes: 37 additions & 0 deletions samples/cs/ModelManagement/ModelManagement/Worker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AI.Foundry.Local;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelManagement.Interfaces;

namespace ModelManagement;

internal class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly ICatalogManagement _catalogManagement;
private readonly IEPManagement _epManagement;

public Worker(IEPManagement epManagement, ICatalogManagement catalogManagement, ILogger<Worker> logger)
{
_epManagement = epManagement;
_catalogManagement = catalogManagement;
_logger = logger;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

await _epManagement.DownloadAndRegisterEpsAsync(stoppingToken);
await _catalogManagement.DownloadModelsAsync(new List<string> { "qwen3-embedding-0.6b", "phi-3.5-mini" }, stoppingToken);
List<(string, IModel)> result = await _catalogManagement.LoadModelsAsync(new List<string> { "qwen3-embedding-0.6b", "phi-3.5-mini" }, stoppingToken);

while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Heartbeat at: {time}", DateTimeOffset.Now);
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}

_logger.LogInformation("Worker stopping");
}
}
19 changes: 19 additions & 0 deletions samples/cs/ModelManagement/ModelManagement/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" }
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "ModelManagementSample"
}
}
}
Loading