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 12 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
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)' == ''">true</TemplateUsePinnedMicrosoftExtensionsAIVersion>
<TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion Condition="'$(TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion)' == ''">true</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 chunk in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token))
{
AddNonTextContentToConversation(chunk.Contents);
responseText.Text += chunk.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 All @@ -108,6 +110,24 @@
await chatInput!.FocusAsync();
}

@*#if (!IsOllama)*@
private void AddNonTextContentToConversation(IList<AIContent> contents)
{
foreach (var content in contents)
{
switch (content)
{
case FunctionCallContent fcc:
messages.Add(new ChatMessage(ChatRole.Assistant, [fcc]));
break;
case FunctionResultContent frc:
messages.Add(new ChatMessage(ChatRole.Tool, [frc]));
break;
}
}
}

@*#endif*@
[Description("Searches for information using a phrase or keyword")]
private async Task<IEnumerable<string>> SearchAsync(
[Description("The phrase to search for.")] string searchPhrase,
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
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.IO;
using System.Threading.Tasks;
using EmptyFiles;
using Microsoft.Extensions.AI.Templates.IntegrationTests;
using Microsoft.Extensions.AI.Templates.Tests;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -34,16 +33,28 @@ public async Task BasicTest()
// Get the template location
string templateLocation = Path.Combine(TemplateFeedLocation, "Microsoft.Extensions.AI.Templates", "src", "ChatWithCustomData");

// Treat *.in files as text, see https://github.com/VerifyTests/EmptyFiles#istext
FileExtensions.AddTextExtension(".in");

TemplateVerifierOptions options = new TemplateVerifierOptions(templateName: templateShortName)
{
TemplatePath = templateLocation,
SnapshotsDirectory = "Snapshots",
OutputDirectory = workingDir,
DoNotPrependCallerMethodNameToScenarioName = true,
ScenarioName = "Basic",

// Keep the exclude patterns below in sync with those in Microsoft.Extensions.AI.Templates.csproj.
VerificationExcludePatterns = [
"*\\bin\\**",
"**\\obj\\**",
"**\\node_modules\\**",
"**\\*.user",
"**\\*.in",
"**\\*.out.js",
"**\\*.generated.css",
"**\\package-lock.json",
"**\\ingestioncache.db",
"**\\NuGet.config",
"**\\Directory.Build.targets",
],
}
.WithCustomScrubbers(
ScrubbersDefinition.Empty.AddScrubber((path, content) =>
Expand Down
Loading
Loading