Skip to content
Draft
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
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
<PackageVersion Include="DeviceRunners.VisualRunners.Maui" Version="$(DeviceRunnersVersion)" />
<PackageVersion Include="DeviceRunners.XHarness.Xunit" Version="$(DeviceRunnersVersion)" />
<PackageVersion Include="DeviceRunners.XHarness.Maui" Version="$(DeviceRunnersVersion)" />
<!-- Windows: enables dotnet run for packaged MSIX apps -->
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools.WinApp" Version="$(MicrosoftWindowsSDKBuildToolsWinAppVersion)" />
</ItemGroup>

<!-- Testing (Microsoft.NET.Test.Sdk and xunit.runner.visualstudio are
Expand Down
1 change: 1 addition & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" protocolVersion="3" />
<add key="dotnet10" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10/nuget/v3/index.json" protocolVersion="3" />
<add key="dotnet11" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/index.json" protocolVersion="3" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />

</packageSources>
<disabledPackageSources>
Expand Down
273 changes: 273 additions & 0 deletions docs/ai/TOOL-CALLING-README.md

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
<MicrosoftAspNetCoreComponentsWebViewMauiVersion>$(MicrosoftMauiControlsVersion)</MicrosoftAspNetCoreComponentsWebViewMauiVersion>
</PropertyGroup>
<PropertyGroup Label="Dotnet Runtime / Extensions">
<MicrosoftExtensionsDependencyInjectionVersion>10.0.5</MicrosoftExtensionsDependencyInjectionVersion>
<MicrosoftExtensionsDependencyInjectionVersion>10.0.6</MicrosoftExtensionsDependencyInjectionVersion>
<MicrosoftExtensionsConfigurationVersion>10.0.5</MicrosoftExtensionsConfigurationVersion>
<MicrosoftExtensionsHttpVersion>10.0.5</MicrosoftExtensionsHttpVersion>
<MicrosoftExtensionsHostingVersion>10.0.5</MicrosoftExtensionsHostingVersion>
<MicrosoftExtensionsLoggingAbstractionsVersion>10.0.5</MicrosoftExtensionsLoggingAbstractionsVersion>
<MicrosoftExtensionsLoggingAbstractionsVersion>10.0.6</MicrosoftExtensionsLoggingAbstractionsVersion>
<MicrosoftExtensionsLoggingVersion>10.0.5</MicrosoftExtensionsLoggingVersion>
<MicrosoftExtensionsLoggingDebugVersion>10.0.5</MicrosoftExtensionsLoggingDebugVersion>
<SystemTextJsonVersion>10.0.5</SystemTextJsonVersion>
Expand Down Expand Up @@ -71,15 +71,17 @@
<PropertyGroup Label="EssentialsAI">
<EssentialsAIPreviewVersionIteration>1</EssentialsAIPreviewVersionIteration>
<!-- Microsoft Extensions AI -->
<MicrosoftExtensionsAIVersion>10.3.0</MicrosoftExtensionsAIVersion>
<MicrosoftExtensionsAIAbstractionsVersion>10.3.0</MicrosoftExtensionsAIAbstractionsVersion>
<MicrosoftExtensionsAIVersion>10.5.2</MicrosoftExtensionsAIVersion>
<MicrosoftExtensionsAIAbstractionsVersion>10.5.2</MicrosoftExtensionsAIAbstractionsVersion>
<!-- Microsoft Agents AI (sample only) -->
<MicrosoftAgentsAIVersion>1.0.0-rc2</MicrosoftAgentsAIVersion>
<MicrosoftAgentsAIWorkflowsVersion>1.0.0-rc2</MicrosoftAgentsAIWorkflowsVersion>
<MicrosoftAgentsAIWorkflowsGeneratorsVersion>1.0.0-rc2</MicrosoftAgentsAIWorkflowsGeneratorsVersion>
<MicrosoftAgentsAIHostingVersion>1.0.0-preview.260225.1</MicrosoftAgentsAIHostingVersion>
<MicrosoftAgentsAIVersion>1.5.0</MicrosoftAgentsAIVersion>
<MicrosoftAgentsAIWorkflowsVersion>1.5.0</MicrosoftAgentsAIWorkflowsVersion>
<MicrosoftAgentsAIWorkflowsGeneratorsVersion>1.5.0</MicrosoftAgentsAIWorkflowsGeneratorsVersion>
<MicrosoftAgentsAIHostingVersion>1.5.0-preview.260507.1</MicrosoftAgentsAIHostingVersion>
<!-- DeviceRunners (device test runner OSS replacement) -->
<DeviceRunnersVersion>0.1.0-preview.6</DeviceRunnersVersion>
<!-- Windows: enables dotnet run for packaged MSIX apps -->
<MicrosoftWindowsSDKBuildToolsWinAppVersion>0.3.0</MicrosoftWindowsSDKBuildToolsWinAppVersion>
</PropertyGroup>
<PropertyGroup Label="Testing">
<MicrosoftNETTestSdkVersion>18.3.0</MicrosoftNETTestSdkVersion>
Expand Down
25 changes: 23 additions & 2 deletions samples/EssentialsAISample/EssentialsAISample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@
<ApplicationId>com.microsoft.maui.essentials.ai</ApplicationId>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<WindowsPackageType>None</WindowsPackageType>
<!-- Prevent MSBuild from overriding MaxVersionTested (required for systemAIModels capability) -->
<AppxOSMinVersionReplaceManifestVersion>false</AppxOSMinVersionReplaceManifestVersion>
<AppxOSMaxVersionTestedReplaceManifestVersion>false</AppxOSMaxVersionTestedReplaceManifestVersion>
</PropertyGroup>

<!-- Windows: self-contained publish to bundle WinAppSDK experimental runtime -->
<PropertyGroup Condition="$(TargetFramework.Contains('-windows'))">
<SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -64,11 +72,24 @@
<PackageReference Include="OpenAI" />
</ItemGroup>

<!-- Windows Phi Silica: requires experimental WindowsAppSDK for WinRT AI APIs -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows'))">
<PackageReference Include="Microsoft.WindowsAppSDK" VersionOverride="2.0.0-experimental6" />
<!-- Enables dotnet run for packaged MSIX apps (registers loose layout, activates via AUMID) -->
<PackageReference Include="Microsoft.Windows.SDK.BuildTools.WinApp" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\AI\Microsoft.Maui.Essentials.AI\Microsoft.Maui.Essentials.AI.csproj" />
</ItemGroup>

<Target Name="AddUserSecrets" BeforeTargets="PrepareForBuild" Condition=" '$(UserSecretsId)' != '' ">
<!-- Exclude Windows-only services from non-Windows TFMs -->
<ItemGroup Condition="!$(TargetFramework.Contains('-windows'))">
<Compile Remove="Services\AppContentIndexerSearchService.cs" />
<Compile Remove="Services\PhiSilicaToolsAndSchemaClient.cs" />
</ItemGroup>

<Target Name="AddUserSecrets" BeforeTargets="PrepareForBuild" Condition=" '$(UserSecretsId)' != '' And '$(Configuration)' == 'Debug' ">
<PropertyGroup>
<UserSecretsFilePath Condition=" '$(OS)' == 'Windows_NT' ">
$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))\AppData\Roaming\Microsoft\UserSecrets\$(UserSecretsId)\secrets.json
Expand Down
59 changes: 58 additions & 1 deletion samples/EssentialsAISample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public static MauiApp CreateMauiApp()
// Register AI agents and workflow
#if IOS || MACCATALYST
builder.AddAppleIntelligenceServices();
#elif WINDOWS
builder.AddPhiSilicaServices();
#else
builder.AddOpenAIServices();
#endif
Expand All @@ -64,9 +66,12 @@ public static MauiApp CreateMauiApp()
builder.Services.AddHttpClient<WeatherService>();
builder.Services.AddSingleton<ChatService>();

// Semantic search — uses whatever IEmbeddingGenerator is registered (Apple NL or OpenAI)
// Semantic search — uses whatever IEmbeddingGenerator is registered (Apple NL or OpenAI).
// On Windows, AddPhiSilicaServices registers AppContentIndexerSearchService instead.
#if !WINDOWS
builder.Services.AddSingleton<ISemanticSearchService>(sp =>
new EmbeddingSearchService(sp.GetRequiredService<IEmbeddingGenerator<string, Embedding<float>>>()));
#endif

// Configure Logging
builder.Services.AddLogging();
Expand Down Expand Up @@ -153,6 +158,58 @@ private static MauiAppBuilder AddAppleIntelligenceServices(this MauiAppBuilder b
return builder;
}
#pragma warning restore CA1416
#endif

#if WINDOWS
#pragma warning disable CA1416 // Validate platform compatibility - this sample requires Windows 10.0.26100.0+
private static MauiAppBuilder AddPhiSilicaServices(this MauiAppBuilder builder)
{
// Register the base Phi Silica client
builder.Services.AddSingleton<PhiSilicaChatClient>();

// Register the Phi Silica client as IChatClient to allow direct use
builder.Services.AddSingleton<IChatClient>(sp =>
{
var phiClient = sp.GetRequiredService<PhiSilicaChatClient>();
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
return phiClient
.AsBuilder()
.Use(cc => new PhiSilicaToolsAndSchemaClient(cc))
.UseLogging(loggerFactory)
.Build();
});

// Register the Agent Framework wrapper as "local-model"
builder.Services.AddKeyedSingleton<IChatClient>("local-model", (sp, _) =>
{
var phiClient = sp.GetRequiredService<PhiSilicaChatClient>();
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
return phiClient
.AsBuilder()
.Use(cc => new PhiSilicaToolsAndSchemaClient(cc))
.UseLogging(loggerFactory)
.Build();
});

// Register "cloud-model" with buffering
builder.Services.AddKeyedSingleton<IChatClient>("cloud-model", (sp, _) =>
{
var phiClient = sp.GetRequiredService<PhiSilicaChatClient>();
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
return phiClient
.AsBuilder()
.Use(cc => new PhiSilicaToolsAndSchemaClient(cc))
.Use(cc => new BufferedChatClient(cc))
.UseLogging(loggerFactory)
.Build();
});

// Semantic search using AppContentIndexer — OS handles embeddings internally.
builder.Services.AddSingleton<ISemanticSearchService, AppContentIndexerSearchService>();
Comment thread
mattleibow marked this conversation as resolved.

return builder;
}
#pragma warning restore CA1416
#endif

private static MauiAppBuilder AddOpenAIServices(this MauiAppBuilder builder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10"
IgnorableNamespaces="uap rescap systemai">

<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />

Expand All @@ -17,8 +18,8 @@
</Properties>

<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26226.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26226.0" />
</Dependencies>

<Resources>
Expand All @@ -41,6 +42,7 @@

<Capabilities>
<rescap:Capability Name="runFullTrust" />
<systemai:Capability Name="systemAIModels" />
</Capabilities>

</Package>
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#if WINDOWS
using System.Collections.Concurrent;
using Microsoft.Windows.Search.AppContentIndex;

namespace EssentialsAISample.Services;

/// <summary>
/// Semantic search using the Windows AppContentIndexer API.
/// The OS handles embedding generation, chunking, and search internally.
/// Each collection maps to a separate index.
/// </summary>
public sealed class AppContentIndexerSearchService : ISemanticSearchService, IDisposable
{
const string IndexPrefix = "maui-ai-sample";

readonly object _indexersLock = new();
readonly Dictionary<string, AppContentIndexer> _indexers = new();
Comment thread
jfversluis marked this conversation as resolved.

AppContentIndexer GetOrCreateIndexer(string collection)
{
lock (_indexersLock)
{
if (_indexers.TryGetValue(collection, out var indexer))
return indexer;

var indexName = $"{IndexPrefix}-{collection}";
var result = AppContentIndexer.GetOrCreateIndex(indexName);
if (!result.Succeeded)
throw new InvalidOperationException($"Failed to create index '{indexName}': {result.Status}");

_indexers[collection] = result.Indexer;
return result.Indexer;
}
}

public Task IndexAsync(string collection, string id, string text, CancellationToken cancellationToken = default)
{
return Task.Run(() =>
{
var indexer = GetOrCreateIndexer(collection);
var content = AppManagedIndexableAppContent.CreateFromString(id, text);
indexer.AddOrUpdate(content);
}, cancellationToken);
}

public async Task<IReadOnlyList<SemanticSearchResult>> SearchAsync(string collection, string query, int maxResults, CancellationToken cancellationToken = default)
{
var indexer = GetOrCreateIndexer(collection);

// Run on background thread — GetNextMatches can block while the indexer processes
return await Task.Run(() =>
{
// Request extra matches since multiple regions can match per item
var textQuery = indexer.CreateTextQuery(query);
var matches = textQuery.GetNextMatches(maxResults * 4);

// Group by ContentId, take the best rank (lowest index = highest relevance)
return matches
.Select((m, i) => (Id: m.ContentId, Rank: i))
.GroupBy(m => m.Id)
.Select(g => new SemanticSearchResult(
g.Key,
// Best rank score + small boost for multiple matches
(float)(matches.Count - g.Min(m => m.Rank)) / matches.Count + g.Count() * 0.01f))
.OrderByDescending(r => r.Score)
.Take(maxResults)
.ToList() as IReadOnlyList<SemanticSearchResult>;
}, cancellationToken);
}

public async Task WaitUntilReadyAsync(CancellationToken cancellationToken = default)
{
List<AppContentIndexer> snapshot;
lock (_indexersLock)
snapshot = [.. _indexers.Values];

foreach (var indexer in snapshot)
await indexer.WaitForIndexingIdleAsync(TimeSpan.FromSeconds(60));
}

public void Dispose()
{
List<AppContentIndexer> snapshot;
lock (_indexersLock)
{
snapshot = [.. _indexers.Values];
_indexers.Clear();
}

foreach (var indexer in snapshot)
indexer.Dispose();
}
}
#endif
Loading
Loading