Skip to content

Commit 2810fc5

Browse files
Use just-built packages in the AI chat template by default (#6096)
Co-authored-by: Jeff Handley <[email protected]>
1 parent 6dec70a commit 2810fc5

File tree

16 files changed

+218
-94
lines changed

16 files changed

+218
-94
lines changed

eng/Versions.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154
<AzureSearchDocumentsVersion>11.6.0</AzureSearchDocumentsVersion>
155155
<MicrosoftSemanticKernelConnectorsAzureAISearchVersion>1.37.0-preview</MicrosoftSemanticKernelConnectorsAzureAISearchVersion>
156156
<MicrosoftSemanticKernelCoreVersion>1.37.0</MicrosoftSemanticKernelCoreVersion>
157-
<OllamaSharpVersion>5.0.7</OllamaSharpVersion>
157+
<OllamaSharpVersion>5.1.5</OllamaSharpVersion>
158158
<OpenAIVersion>2.2.0-beta.1</OpenAIVersion>
159159
<PdfPigVersion>0.1.9</PdfPigVersion>
160160
<SystemLinqAsyncVersion>6.0.1</SystemLinqAsyncVersion>

src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs

+29-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

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

69+
/// <summary>Converts the <paramref name="update"/> into a <see cref="ChatMessage"/> instance and adds it to <paramref name="list"/>.</summary>
70+
/// <param name="list">The destination list to which the newly constructed message should be added.</param>
71+
/// <param name="update">The <see cref="ChatResponseUpdate"/> instance to convert to a message and add to the list.</param>
72+
/// <param name="filter">A predicate to filter which <see cref="AIContent"/> gets included in the message.</param>
73+
/// <exception cref="ArgumentNullException"><paramref name="list"/> is <see langword="null"/>.</exception>
74+
/// <exception cref="ArgumentNullException"><paramref name="update"/> is <see langword="null"/>.</exception>
75+
/// <remarks>
76+
/// If the <see cref="ChatResponseUpdate"/> has no content, or all its content gets excluded by <paramref name="filter"/>, then
77+
/// no <see cref="ChatMessage"/> will be added to the <paramref name="list"/>.
78+
/// </remarks>
79+
public static void AddMessages(this IList<ChatMessage> list, ChatResponseUpdate update, Func<AIContent, bool>? filter = null)
80+
{
81+
_ = Throw.IfNull(list);
82+
_ = Throw.IfNull(update);
83+
84+
var contentsList = filter is null ? update.Contents : update.Contents.Where(filter).ToList();
85+
if (contentsList.Count > 0)
86+
{
87+
list.Add(new ChatMessage(update.Role ?? ChatRole.Assistant, contentsList)
88+
{
89+
AuthorName = update.AuthorName,
90+
RawRepresentation = update.RawRepresentation,
91+
AdditionalProperties = update.AdditionalProperties,
92+
});
93+
}
94+
}
95+
6896
/// <summary>Converts the <paramref name="updates"/> into <see cref="ChatMessage"/> instances and adds them to <paramref name="list"/>.</summary>
6997
/// <param name="list">The list to which the newly constructed messages should be added.</param>
7098
/// <param name="updates">The <see cref="ChatResponseUpdate"/> instances to convert to messages and add to the list.</param>

src/ProjectTemplates/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ package-lock.json
66

77
# Don't track files generated for debugging templates locally.
88
*/src/**/*.csproj
9+
*/src/**/NuGet.config
10+
*/src/**/Directory.Build.targets
11+
*/src/**/ingestioncache.db
912

1013
# launchSettings.json files are required for the templates.
1114
!launchSettings.json

src/ProjectTemplates/GenerateTemplateContent/GenerateTemplateContent.csproj

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<Import Project="$(MSBuildThisFileDirectory)..\GeneratedContent.props" />
4-
53
<PropertyGroup>
64
<TargetFramework>netstandard2.0</TargetFramework>
5+
<SuppressFinalPackageVersion>true</SuppressFinalPackageVersion>
76

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

48+
<ItemGroup>
49+
<GeneratedContent Remove="@(GeneratedContentToDelete)" />
50+
</ItemGroup>
51+
52+
<Delete Files="@(GeneratedContentToDelete->'%(OutputPath)')" />
53+
4854
<GenerateFileFromTemplate
4955
TemplateFile="%(GeneratedContent.Identity)"
5056
Properties="$(GeneratedContentProperties);%(GeneratedContent.AdditionalProperties)"
@@ -55,4 +61,6 @@
5561
</GenerateFileFromTemplate>
5662
</Target>
5763

64+
<Import Project="$(MSBuildThisFileDirectory)..\GeneratedContent.targets" />
65+
5866
</Project>

src/ProjectTemplates/GeneratedContent.props

-34
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<Project>
2+
3+
<PropertyGroup>
4+
<_ChatWithCustomDataWebContentRoot>$(MSBuildThisFileDirectory)Microsoft.Extensions.AI.Templates\src\ChatWithCustomData\ChatWithCustomData.Web-CSharp\</_ChatWithCustomDataWebContentRoot>
5+
</PropertyGroup>
6+
7+
<Target Name="ComputeGeneratedContentProperties">
8+
<PropertyGroup>
9+
<!-- Define optional pinned versions of certain dependencies. -->
10+
<TemplatePinnedMicrosoftExtensionsAIVersion>9.3.0-preview.1.25161.3</TemplatePinnedMicrosoftExtensionsAIVersion>
11+
<TemplatePinnedMicrosoftEntityFrameworkCoreSqliteVersion>9.0.3</TemplatePinnedMicrosoftEntityFrameworkCoreSqliteVersion>
12+
13+
<!-- By default, use pinned dependency versions. -->
14+
<TemplateUsePinnedMicrosoftExtensionsAIVersion Condition="'$(TemplateUsePinnedMicrosoftExtensionsAIVersion)' == ''">false</TemplateUsePinnedMicrosoftExtensionsAIVersion>
15+
<TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion Condition="'$(TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion)' == ''">false</TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion>
16+
17+
<!-- Apply pinned dependency versions if enabled. -->
18+
<TemplateMicrosoftExtensionsAIVersion Condition="'$(TemplateUsePinnedMicrosoftExtensionsAIVersion)' == 'true'">$(TemplatePinnedMicrosoftExtensionsAIVersion)</TemplateMicrosoftExtensionsAIVersion>
19+
<TemplateMicrosoftEntityFrameworkCoreSqliteVersion Condition="'$(TemplateUsePinnedMicrosoftEntityFrameworkCoreSqliteVersion)' == 'true'">$(TemplatePinnedMicrosoftEntityFrameworkCoreSqliteVersion)</TemplateMicrosoftEntityFrameworkCoreSqliteVersion>
20+
21+
<!-- Fall back on default dependency versions if pinned versions were not applied. -->
22+
<TemplateMicrosoftExtensionsAIVersion Condition="'$(TemplateMicrosoftExtensionsAIVersion)' == ''">$(Version)</TemplateMicrosoftExtensionsAIVersion>
23+
<TemplateMicrosoftEntityFrameworkCoreSqliteVersion Condition="'$(TemplateMicrosoftEntityFrameworkCoreSqliteVersion)' == ''">$(MicrosoftEntityFrameworkCoreSqliteVersion)</TemplateMicrosoftEntityFrameworkCoreSqliteVersion>
24+
25+
<_TemplateUsingJustBuiltPackages Condition="'$(TemplateMicrosoftExtensionsAIVersion)' == '$(Version)'">true</_TemplateUsingJustBuiltPackages>
26+
27+
<!-- Specify package version variables used in template content. -->
28+
<GeneratedContentProperties>
29+
$(GeneratedContentProperties);
30+
31+
<!-- Repo properties -->
32+
ArtifactsShippingPackagesDir=$(ArtifactsShippingPackagesDir);
33+
34+
<!-- Package version properties -->
35+
OllamaSharpVersion=$(OllamaSharpVersion);
36+
OpenAIVersion=$(OpenAIVersion);
37+
AzureAIProjectsVersion=$(AzureAIProjectsVersion);
38+
AzureAIOpenAIVersion=$(AzureAIOpenAIVersion);
39+
AzureIdentityVersion=$(AzureIdentityVersion);
40+
MicrosoftEntityFrameworkCoreSqliteVersion=$(TemplateMicrosoftEntityFrameworkCoreSqliteVersion);
41+
MicrosoftExtensionsAIVersion=$(TemplateMicrosoftExtensionsAIVersion);
42+
MicrosoftSemanticKernelCoreVersion=$(MicrosoftSemanticKernelCoreVersion);
43+
PdfPigVersion=$(PdfPigVersion);
44+
SystemLinqAsyncVersion=$(SystemLinqAsyncVersion);
45+
AzureSearchDocumentsVersion=$(AzureSearchDocumentsVersion);
46+
MicrosoftSemanticKernelConnectorsAzureAISearchVersion=$(MicrosoftSemanticKernelConnectorsAzureAISearchVersion);
47+
</GeneratedContentProperties>
48+
</PropertyGroup>
49+
50+
<ItemGroup>
51+
<GeneratedContent
52+
Include="$(_ChatWithCustomDataWebContentRoot)ChatWithCustomData.Web-CSharp.csproj.in"
53+
OutputPath="$(_ChatWithCustomDataWebContentRoot)ChatWithCustomData.Web-CSharp.csproj" />
54+
55+
<!-- The following content only gets generated when using just-built packages -->
56+
<_GeneratedContentEnablingJustBuiltPackages
57+
Include="$(_ChatWithCustomDataWebContentRoot)NuGet.config.in"
58+
OutputPath="$(_ChatWithCustomDataWebContentRoot)NuGet.config" />
59+
<_GeneratedContentEnablingJustBuiltPackages
60+
Include="$(_ChatWithCustomDataWebContentRoot)Directory.Build.targets.in"
61+
OutputPath="$(_ChatWithCustomDataWebContentRoot)Directory.Build.targets" />
62+
63+
<GeneratedContent
64+
Include="@(_GeneratedContentEnablingJustBuiltPackages)"
65+
Condition="'$(_TemplateUsingJustBuiltPackages)' == 'true'" />
66+
<GeneratedContentToDelete
67+
Include="@(_GeneratedContentEnablingJustBuiltPackages)"
68+
Condition="'$(_TemplateUsingJustBuiltPackages)' != 'true'" />
69+
</ItemGroup>
70+
</Target>
71+
72+
</Project>

src/ProjectTemplates/Microsoft.Extensions.AI.Templates/Microsoft.Extensions.AI.Templates.csproj

+7-2
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,21 @@
3535
</ItemGroup>
3636

3737
<ItemGroup>
38+
<!-- Keep the exclude patterns below in sync with those in AichatwebTemplatesTests.cs -->
3839
<Content
3940
Include="src\ChatWithCustomData\**\*"
4041
Exclude="
4142
**\bin\**;
4243
**\obj\**;
4344
**\node_modules\**;
44-
**\*.user;**\*.in;
45+
**\*.user;
46+
**\*.in;
4547
**\*.out.js;
4648
**\*.generated.css;
47-
**\package-lock.json;" />
49+
**\package-lock.json;
50+
**\ingestioncache.db;
51+
**\NuGet.config;
52+
**\Directory.Build.targets;" />
4853
<None Include="THIRD-PARTY-NOTICES.TXT" Pack="true" PackagePath="." />
4954
<Compile Remove="**\*" />
5055
</ItemGroup>

src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData.Web-CSharp/ChatWithCustomData.Web-CSharp.csproj.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<!--#if (UseManagedIdentity) -->
2525
<PackageReference Include="Azure.Identity" Version="${AzureIdentityVersion}" />
2626
<!--#endif -->
27-
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
27+
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqliteVersion}" />
2828
<PackageReference Include="Microsoft.Extensions.AI" Version="${MicrosoftExtensionsAIVersion}" />
2929
<PackageReference Include="Microsoft.SemanticKernel.Core" Version="${MicrosoftSemanticKernelCoreVersion}" />
3030
<PackageReference Include="PdfPig" Version="${PdfPigVersion}" />

src/ProjectTemplates/Microsoft.Extensions.AI.Templates/src/ChatWithCustomData/ChatWithCustomData.Web-CSharp/Components/Pages/Chat/Chat.razor

+7-5
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,25 @@
6767
// aren't supported because Ollama will not support both streaming and using Tools
6868
currentResponseCancellation = new();
6969
var response = await ChatClient.GetResponseAsync(messages, chatOptions, currentResponseCancellation.Token);
70-
currentResponseMessage = response.Message;
71-
ChatMessageItem.NotifyChanged(currentResponseMessage);
70+
71+
// Store responses in the conversation, and begin getting suggestions
72+
messages.AddMessages(response);
7273
#else*@
7374
// Stream and display a new response from the IChatClient
7475
var responseText = new TextContent("");
7576
currentResponseMessage = new ChatMessage(ChatRole.Assistant, [responseText]);
7677
currentResponseCancellation = new();
77-
await foreach (var chunk in ChatClient.GetStreamingResponseAsync(messages, chatOptions, currentResponseCancellation.Token))
78+
await foreach (var update in ChatClient.GetStreamingResponseAsync([.. messages], chatOptions, currentResponseCancellation.Token))
7879
{
79-
responseText.Text += chunk.Text;
80+
messages.AddMessages(update, filter: c => c is not TextContent);
81+
responseText.Text += update.Text;
8082
ChatMessageItem.NotifyChanged(currentResponseMessage);
8183
}
82-
@*#endif*@
8384

8485
// Store the final response in the conversation, and begin getting suggestions
8586
messages.Add(currentResponseMessage!);
8687
currentResponseMessage = null;
88+
@*#endif*@
8789
chatSuggestions?.Update(messages);
8890
}
8991

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!--
2+
This file only exists to support functionality allowing running the template locally.
3+
It will not get included in the built project template.
4+
-->
5+
<Project>
6+
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />
7+
8+
<Target
9+
Name="_EnsurePackagesBuiltLocally"
10+
BeforeTargets="Restore">
11+
12+
<Error
13+
Condition="!Exists('${ArtifactsShippingPackagesDir}')"
14+
Text="Repo packages must be built locally before running this project. See src/ProjectTemplates/README.md for more info." />
15+
</Target>
16+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
This file only exists to allow locally-built packages to be used when running the template locally.
4+
It will not get included in the built project template.
5+
-->
6+
<configuration>
7+
<packageSources>
8+
<add key="local-shipping" value="${ArtifactsShippingPackagesDir}" />
9+
</packageSources>
10+
<packageSourceMapping>
11+
<packageSource key="local-shipping">
12+
<package pattern="*" />
13+
</packageSource>
14+
</packageSourceMapping>
15+
</configuration>

src/ProjectTemplates/README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,17 @@ To update project template JavaScript dependencies:
1111

1212
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.
1313

14-
# Installing the templates locally
14+
# Running AI templates
15+
16+
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:
17+
```sh
18+
.\build.cmd -vs AI -noLaunch # Generate an SDK.sln for Microsoft.Extensions.AI* projects
19+
.\build.cmd -build -pack # Build a NuGet package for each project
20+
```
21+
22+
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.
23+
24+
## Installing the templates locally
1525

1626
First, create the template NuGet package by running the following from the repo root:
1727
```pwsh
@@ -37,3 +47,21 @@ Finally, create a project from the template and run it:
3747
dotnet new aichatweb [-A <azureopenai | githubmodels | ollama | openai>] [-V <azureaisearch | local>]
3848
dotnet run
3949
```
50+
51+
## Running the templates directly within the repo
52+
53+
The project templates are structured in a way that allows them to be run directly within the repo.
54+
55+
**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.
56+
57+
Navigate to the `Microsoft.Extensions.AI.Templates` folder and run:
58+
```sh
59+
dotnet build
60+
```
61+
62+
This will generate the necessary template content to build and run AI templates from within this repo.
63+
64+
Now, you can navigate to a folder containing a template's `.csproj` file and run:
65+
```sh
66+
dotnet run
67+
```

0 commit comments

Comments
 (0)