Skip to content

Use just-built packages in the AI chat template by default #6096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8ca041a
Use just-built packages in chat template by default
MackinnonBuck Mar 12, 2025
0480077
Merge branch 'main' into mbuck/chat-template-build-improvements
MackinnonBuck Mar 13, 2025
e95f2f3
Update OllamaSharp version
MackinnonBuck Mar 13, 2025
61731ff
Merge branch 'main' into mbuck/chat-template-build-improvements
MackinnonBuck Mar 13, 2025
317e3af
Merge branch 'main' into mbuck/chat-template-build-improvements
jeffhandley Mar 14, 2025
718197a
Resolve conflict in Versions.props for AI templates versions
jeffhandley Mar 14, 2025
a835894
Update MEAI Template test snapshot to use just-built packages
jeffhandley Mar 14, 2025
0f5ad0c
Use pinned versions by default. Bump to latest versions.
jeffhandley Mar 14, 2025
abc357a
Merge branch 'main' into mbuck/chat-template-build-improvements
MackinnonBuck Mar 14, 2025
09b0ce1
Remove non-template content from snapshot
MackinnonBuck Mar 14, 2025
529e481
Cleanup for prev commit
MackinnonBuck Mar 14, 2025
dd0511d
Update test/ProjectTemplates/Microsoft.Extensions.AI.Templates.Integr…
MackinnonBuck Mar 14, 2025
5c16cc8
New AddMessages API, fix snapshotting
MackinnonBuck Mar 14, 2025
987652a
Merge branch 'mbuck/chat-template-build-improvements' of https://gith…
MackinnonBuck Mar 14, 2025
81963d1
Update snapshot
MackinnonBuck Mar 14, 2025
6cb1a19
Remove version suffix from snapshots
MackinnonBuck Mar 14, 2025
a2cee17
Use OS-specific exclude pattern path separators
MackinnonBuck Mar 14, 2025
e30aaf0
Fix typo
MackinnonBuck Mar 14, 2025
443cb88
Merge branch 'main' into mbuck/chat-template-build-improvements
MackinnonBuck Mar 14, 2025
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: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
<AzureSearchDocumentsVersion>11.6.0</AzureSearchDocumentsVersion>
<MicrosoftSemanticKernelConnectorsAzureAISearchVersion>1.37.0-preview</MicrosoftSemanticKernelConnectorsAzureAISearchVersion>
<MicrosoftSemanticKernelCoreVersion>1.37.0</MicrosoftSemanticKernelCoreVersion>
<OllamaSharpVersion>5.0.7</OllamaSharpVersion>
<OllamaSharpVersion>5.1.5</OllamaSharpVersion>
<OpenAIVersion>2.2.0-beta.1</OpenAIVersion>
<PdfPigVersion>0.1.9</PdfPigVersion>
<SystemLinqAsyncVersion>6.0.1</SystemLinqAsyncVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -47,7 +48,7 @@ public static void AddMessages(this IList<ChatMessage> list, ChatResponse respon
/// <exception cref="ArgumentNullException"><paramref name="list"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="updates"/> is <see langword="null"/>.</exception>
/// <remarks>
/// As part of combining <paramref name="updates"/> into a series of <see cref="ChatMessage"/> instances, tne
/// As part of combining <paramref name="updates"/> into a series of <see cref="ChatMessage"/> instances, the
/// method may use <see cref="ChatResponseUpdate.ResponseId"/> to determine message boundaries, as well as coalesce
/// contiguous <see cref="AIContent"/> items where applicable, e.g. multiple
/// <see cref="TextContent"/> instances in a row may be combined into a single <see cref="TextContent"/>.
Expand All @@ -65,6 +66,33 @@ public static void AddMessages(this IList<ChatMessage> list, IEnumerable<ChatRes
list.AddMessages(updates.ToChatResponse());
}

/// <summary>Converts the <paramref name="update"/> into a <see cref="ChatMessage"/> instance and adds it to <paramref name="list"/>.</summary>
/// <param name="list">The destination list to which the newly constructed message should be added.</param>
/// <param name="update">The <see cref="ChatResponseUpdate"/> instance to convert to a message and add to the list.</param>
/// <param name="filter">A predicate to filter which <see cref="AIContent"/> gets included in the message.</param>
/// <exception cref="ArgumentNullException"><paramref name="list"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="update"/> is <see langword="null"/>.</exception>
/// <remarks>
/// If the <see cref="ChatResponseUpdate"/> has no content, or all its content gets excluded by <paramref name="filter"/>, then
/// no <see cref="ChatMessage"/> will be added to the <paramref name="list"/>.
/// </remarks>
public static void AddMessages(this IList<ChatMessage> list, ChatResponseUpdate update, Func<AIContent, bool>? filter = null)
{
_ = Throw.IfNull(list);
_ = Throw.IfNull(update);

var contentsList = filter is null ? update.Contents : update.Contents.Where(filter).ToList();
if (contentsList.Count > 0)
{
list.Add(new ChatMessage(update.Role ?? ChatRole.Assistant, contentsList)
{
AuthorName = update.AuthorName,
RawRepresentation = update.RawRepresentation,
AdditionalProperties = update.AdditionalProperties,
});
}
}

/// <summary>Converts the <paramref name="updates"/> into <see cref="ChatMessage"/> instances and adds them to <paramref name="list"/>.</summary>
/// <param name="list">The list to which the newly constructed messages should be added.</param>
/// <param name="updates">The <see cref="ChatResponseUpdate"/> instances to convert to messages and add to the list.</param>
Expand Down
3 changes: 3 additions & 0 deletions src/ProjectTemplates/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package-lock.json

# Don't track files generated for debugging templates locally.
*/src/**/*.csproj
*/src/**/NuGet.config
*/src/**/Directory.Build.targets
*/src/**/ingestioncache.db

# launchSettings.json files are required for the templates.
!launchSettings.json
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="$(MSBuildThisFileDirectory)..\GeneratedContent.props" />

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<SuppressFinalPackageVersion>true</SuppressFinalPackageVersion>

<!-- Used for incremental builds. When versions or dependencies of templates change, this file is updated and causes a re-build. -->
<_GeneratedContentPropertiesHashFile>$(IntermediateOutputPath)$(MSBuildProjectName).content.g.cache</_GeneratedContentPropertiesHashFile>
Expand All @@ -24,7 +23,8 @@
in the _GenerateContent target.
This hash is used to determine if the generated content needs to be re-generated.
-->
<Target Name="_ComputeGeneratedContentPropertiesHash">
<Target Name="_ComputeGeneratedContentPropertiesHash"
DependsOnTargets="ComputeGeneratedContentProperties">
<Hash ItemsToHash="$(GeneratedContentProperties)">
<Output TaskParameter="HashResult" PropertyName="_GeneratedContentPropertiesHash" />
</Hash>
Expand All @@ -45,6 +45,12 @@
Inputs="$(MSBuildAllProjects);$(_GeneratedContentPropertiesHashFile);@(GeneratedContent)"
Outputs="@(GeneratedContent->'%(OutputPath)')">

<ItemGroup>
<GeneratedContent Remove="@(GeneratedContentToDelete)" />
</ItemGroup>

<Delete Files="@(GeneratedContentToDelete->'%(OutputPath)')" />

<GenerateFileFromTemplate
TemplateFile="%(GeneratedContent.Identity)"
Properties="$(GeneratedContentProperties);%(GeneratedContent.AdditionalProperties)"
Expand All @@ -55,4 +61,6 @@
</GenerateFileFromTemplate>
</Target>

<Import Project="$(MSBuildThisFileDirectory)..\GeneratedContent.targets" />

</Project>
34 changes: 0 additions & 34 deletions src/ProjectTemplates/GeneratedContent.props

This file was deleted.

72 changes: 72 additions & 0 deletions src/ProjectTemplates/GeneratedContent.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<Project>

<PropertyGroup>
<_ChatWithCustomDataWebContentRoot>$(MSBuildThisFileDirectory)Microsoft.Extensions.AI.Templates\src\ChatWithCustomData\ChatWithCustomData.Web-CSharp\</_ChatWithCustomDataWebContentRoot>
</PropertyGroup>

<Target Name="ComputeGeneratedContentProperties">
<PropertyGroup>
<!-- Define optional pinned versions of certain dependencies. -->
<TemplatePinnedMicrosoftExtensionsAIVersion>9.3.0-preview.1.25161.3</TemplatePinnedMicrosoftExtensionsAIVersion>
<TemplatePinnedMicrosoftEntityFrameworkCoreSqliteVersion>9.0.3</TemplatePinnedMicrosoftEntityFrameworkCoreSqliteVersion>

<!-- By default, use pinned dependency versions. -->
<TemplateUsePinnedMicrosoftExtensionsAIVersion Condition="'$(TemplateUsePinnedMicrosoftExtensionsAIVersion)' == ''">false</TemplateUsePinnedMicrosoftExtensionsAIVersion>
<TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion Condition="'$(TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion)' == ''">false</TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion>

<!-- Apply pinned dependency versions if enabled. -->
<TemplateMicrosoftExtensionsAIVersion Condition="'$(TemplateUsePinnedMicrosoftExtensionsAIVersion)' == 'true'">$(TemplatePinnedMicrosoftExtensionsAIVersion)</TemplateMicrosoftExtensionsAIVersion>
<TemplateMicrosoftEntityFrameworkCoreSqliteVersion Condition="'$(TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion)' == 'true'">$(TemplatePinnedMicrosoftEntityFrameworkCoreSqliteVersion)</TemplateMicrosoftEntityFrameworkCoreSqliteVersion>

<!-- Fall back on default dependency versions if pinned versions were not applied. -->
<TemplateMicrosoftExtensionsAIVersion Condition="'$(TemplateMicrosoftExtensionsAIVersion)' == ''">$(Version)</TemplateMicrosoftExtensionsAIVersion>
<TemplateMicrosoftEntityFrameworkCoreSqliteVersion Condition="'$(TemplateMicrosoftEntityFrameworkCoreSqliteVersion)' == ''">$(MicrosoftEntityFrameworkCoreSqliteVersion)</TemplateMicrosoftEntityFrameworkCoreSqliteVersion>

<_TemplateUsingJustBuiltPackages Condition="'$(TemplateMicrosoftExtensionsAIVersion)' == '$(Version)'">true</_TemplateUsingJustBuiltPackages>

<!-- Specify package version variables used in template content. -->
<GeneratedContentProperties>
$(GeneratedContentProperties);

<!-- Repo properties -->
ArtifactsShippingPackagesDir=$(ArtifactsShippingPackagesDir);

<!-- Package version properties -->
OllamaSharpVersion=$(OllamaSharpVersion);
OpenAIVersion=$(OpenAIVersion);
AzureAIProjectsVersion=$(AzureAIProjectsVersion);
AzureAIOpenAIVersion=$(AzureAIOpenAIVersion);
AzureIdentityVersion=$(AzureIdentityVersion);
MicrosoftEntityFrameworkCoreSqliteVersion=$(TemplateMicrosoftEntityFrameworkCoreSqliteVersion);
MicrosoftExtensionsAIVersion=$(TemplateMicrosoftExtensionsAIVersion);
MicrosoftSemanticKernelCoreVersion=$(MicrosoftSemanticKernelCoreVersion);
PdfPigVersion=$(PdfPigVersion);
SystemLinqAsyncVersion=$(SystemLinqAsyncVersion);
AzureSearchDocumentsVersion=$(AzureSearchDocumentsVersion);
MicrosoftSemanticKernelConnectorsAzureAISearchVersion=$(MicrosoftSemanticKernelConnectorsAzureAISearchVersion);
</GeneratedContentProperties>
</PropertyGroup>

<ItemGroup>
<GeneratedContent
Include="$(_ChatWithCustomDataWebContentRoot)ChatWithCustomData.Web-CSharp.csproj.in"
OutputPath="$(_ChatWithCustomDataWebContentRoot)ChatWithCustomData.Web-CSharp.csproj" />

<!-- The following content only gets generated when using just-built packages -->
<_GeneratedContentEnablingJustBuiltPackages
Include="$(_ChatWithCustomDataWebContentRoot)NuGet.config.in"
OutputPath="$(_ChatWithCustomDataWebContentRoot)NuGet.config" />
<_GeneratedContentEnablingJustBuiltPackages
Include="$(_ChatWithCustomDataWebContentRoot)Directory.Build.targets.in"
OutputPath="$(_ChatWithCustomDataWebContentRoot)Directory.Build.targets" />

<GeneratedContent
Include="@(_GeneratedContentEnablingJustBuiltPackages)"
Condition="'$(_TemplateUsingJustBuiltPackages)' == 'true'" />
<GeneratedContentToDelete
Include="@(_GeneratedContentEnablingJustBuiltPackages)"
Condition="'$(_TemplateUsingJustBuiltPackages)' != 'true'" />
</ItemGroup>
</Target>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,21 @@
</ItemGroup>

<ItemGroup>
<!-- Keep the exclude patterns below in sync with those in AichatwebTemplatesTests.cs -->
<Content
Include="src\ChatWithCustomData\**\*"
Exclude="
**\bin\**;
**\obj\**;
**\node_modules\**;
**\*.user;**\*.in;
**\*.user;
**\*.in;
**\*.out.js;
**\*.generated.css;
**\package-lock.json;" />
**\package-lock.json;
**\ingestioncache.db;
**\NuGet.config;
**\Directory.Build.targets;" />
<None Include="THIRD-PARTY-NOTICES.TXT" Pack="true" PackagePath="." />
<Compile Remove="**\*" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<!--#if (UseManagedIdentity) -->
<PackageReference Include="Azure.Identity" Version="${AzureIdentityVersion}" />
<!--#endif -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqliteVersion}" />
<PackageReference Include="Microsoft.Extensions.AI" Version="${MicrosoftExtensionsAIVersion}" />
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="${MicrosoftSemanticKernelCoreVersion}" />
<PackageReference Include="PdfPig" Version="${PdfPigVersion}" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,25 @@
// aren't supported because Ollama will not support both streaming and using Tools
currentResponseCancellation = new();
var response = await ChatClient.GetResponseAsync(messages, chatOptions, currentResponseCancellation.Token);
currentResponseMessage = response.Message;
ChatMessageItem.NotifyChanged(currentResponseMessage);

// Store responses in the conversation, and begin getting suggestions
messages.AddMessages(response);
#else*@
// Stream and display a new response from the IChatClient
var responseText = new TextContent("");
currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]);
currentResponseCancellation = new();
await foreach (var chunk in ChatClient.GetStreamingResponseAsync(messages, chatOptions, currentResponseCancellation.Token))
await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token))
{
responseText.Text += chunk.Text;
messages.AddMessages(update, filter: c => c is not TextContent);
responseText.Text += update.Text;
ChatMessageItem.NotifyChanged(currentResponseMessage);
}
@*#endif*@

// Store the final response in the conversation, and begin getting suggestions
messages.Add(currentResponseMessage!);
currentResponseMessage = null;
@*#endif*@
chatSuggestions?.Update(messages);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--
This file only exists to support functionality allowing running the template locally.
It will not get included in the built project template.
-->
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />

<Target
Name="_EnsurePackagesBuiltLocally"
BeforeTargets="Restore">

<Error
Condition="!Exists('${ArtifactsShippingPackagesDir}')"
Text="Repo packages must be built locally before running this project. See src/ProjectTemplates/README.md for more info." />
</Target>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file only exists to allow locally-built packages to be used when running the template locally.
It will not get included in the built project template.
-->
<configuration>
<packageSources>
<add key="local-shipping" value="${ArtifactsShippingPackagesDir}" />
</packageSources>
<packageSourceMapping>
<packageSource key="local-shipping">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
30 changes: 29 additions & 1 deletion src/ProjectTemplates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@ To update project template JavaScript dependencies:

To add a new dependency, run `npm install <package-name>` and update the `scripts` section in `package.json` to specify how the new dependency should be copied into its template.

# Installing the templates locally
# Running AI templates

By default the templates use just-built versions of `Microsoft.Extensions.AI*` packages, so NuGet packages must be produced before the templates can be run:
```sh
.\build.cmd -vs AI -noLaunch # Generate an SDK.sln for Microsoft.Extensions.AI* projects
.\build.cmd -build -pack # Build a NuGet package for each project
```

Alternatively, you can override the `TemplateMicrosoftExtensionsAIVersion` property (defined in the `GeneratedContent.targets` file in this directory) with a publicly-available version. This will disable the template generation logic that utilizes locally-built `Microsoft.Extensions.AI*` packages.

## Installing the templates locally

First, create the template NuGet package by running the following from the repo root:
```pwsh
Expand All @@ -37,3 +47,21 @@ Finally, create a project from the template and run it:
dotnet new aichatweb [-A <azureopenai | githubmodels | ollama | openai>] [-V <azureaisearch | local>]
dotnet run
```

## Running the templates directly within the repo

The project templates are structured in a way that allows them to be run directly within the repo.

**Note:** For the following commands to succeed, you'll need to either install a compatible .NET SDK globally or prepend the repo's generated `.dotnet` folder to the PATH environment variable.

Navigate to the `Microsoft.Extensions.AI.Templates` folder and run:
```sh
dotnet build
```

This will generate the necessary template content to build and run AI templates from within this repo.

Now, you can navigate to a folder containing a template's `.csproj` file and run:
```sh
dotnet run
```
Loading
Loading