From fb18d165c747a5248c004d098ff553072f6976b5 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 01:37:26 -0800 Subject: [PATCH 01/22] Initial Microsoft.Agents.AI.Templates structure --- .../Microsoft.Agents.AI.Templates.csproj | 43 ++++++ .../PROJECT_STRUCTURE.md | 116 ++++++++++++++++ .../THIRD-PARTY-NOTICES.TXT | 9 ++ .../AgentTemplateSnapshotTests.cs | 108 +++++++++++++++ .../Infrastructure/DotNetCommand.cs | 19 +++ .../Infrastructure/ProcessExtensions.cs | 19 +++ .../Infrastructure/TestCommand.cs | 125 ++++++++++++++++++ .../Infrastructure/TestCommandResult.cs | 16 +++ .../Infrastructure/WellKnownPaths.cs | 80 +++++++++++ ...Microsoft.Agents.AI.Templates.Tests.csproj | 32 +++++ .../ProjectRootHelper.cs | 27 ++++ .../README.md | 7 + .../Snapshots/README.md | 5 + .../TemplateSandbox/.editorconfig | 2 + .../TemplateSandbox/.gitignore | 2 + .../TemplateSandbox/README.md | 11 ++ .../TemplateSandbox/activate.ps1 | 109 +++++++++++++++ .../VerifyScrubbers.cs | 64 +++++++++ 18 files changed, 794 insertions(+) create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/AgentTemplateSnapshotTests.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj new file mode 100644 index 00000000000..5221b70ce5a --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj @@ -0,0 +1,43 @@ + + + + Template + $(NetCoreTargetFrameworks) + Project templates for Microsoft.Agents.AI. + dotnet-new;templates;agents;ai + + preview + 1 + AI + 0 + 0 + + true + false + true + false + false + content + false + true + true + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md new file mode 100644 index 00000000000..c5f3c0f9742 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md @@ -0,0 +1,116 @@ +# Microsoft.Agents.AI.Templates Project Structure + +This document describes the newly created `Microsoft.Agents.AI.Templates` project structure and infrastructure. + +## Project Overview + +The `Microsoft.Agents.AI.Templates` project is a .NET project template package designed to provide project templates for Microsoft.Agents.AI applications. It follows the same structure and infrastructure as `Microsoft.Extensions.AI.Templates`. + +## Project Structure + +### Source Project +**Location**: `src/ProjectTemplates/Microsoft.Agents.AI.Templates/` + +**Key Files**: +- `Microsoft.Agents.AI.Templates.csproj` - The template package project file +- `THIRD-PARTY-NOTICES.TXT` - Third-party license notices (currently a placeholder) + +**Configuration**: +- Package type: `Template` +- Target frameworks: `$(NetCoreTargetFrameworks)` (net8.0, net9.0, net10.0) +- Description: "Project templates for Microsoft.Agents.AI." +- Package tags: `dotnet-new;templates;agents;ai` +- Workstream: AI +- Stage: preview +- PreReleaseVersionIteration: 1 + +**Project References**: +- References `GenerateTemplateContent` project to ensure template content is generated before building + +**Content Items**: +- Template content will be added to the `` ItemGroup once actual templates are implemented +- Currently includes only `THIRD-PARTY-NOTICES.TXT` + +### Test Project +**Location**: `test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/` + +**Key Files**: +- `Microsoft.Agents.AI.Templates.Tests.csproj` - The integration test project +- `README.md` - Documentation for running and updating template tests +- `AgentTemplateSnapshotTests.cs` - Placeholder snapshot test class (test is skipped until template content is added) +- `ProjectRootHelper.cs` - Helper to locate the test project root +- `VerifyScrubbers.cs` - Utilities for scrubbing variable content from test snapshots + +**Infrastructure Files** (in `Infrastructure/` subdirectory): +- `WellKnownPaths.cs` - Constants for important paths (repo root, template feed location, sandbox paths, etc.) +- `TestCommand.cs` - Base class for executing commands during tests +- `DotNetCommand.cs` - Specialized command for executing dotnet CLI commands +- `TestCommandResult.cs` - Result data from command execution +- `ProcessExtensions.cs` - Extension methods for working with processes + +**Test Support Directories**: + +1. **TemplateSandbox/** - For manual template testing and debugging + - `.editorconfig` - Prevents repo settings from applying to generated templates + - `.gitignore` - Ignores the `output/` directory + - `activate.ps1` - PowerShell script to set up environment for manual template testing + - `README.md` - Instructions for debugging templates manually + +2. **Snapshots/** - For verified template output snapshots + - `README.md` - Documentation about snapshot testing + - Subdirectories for each test scenario will be added when templates are implemented + +**Configuration**: +- Project type: Integration Test (`IsIntegrationTestProject = true`) +- Suppressed warnings: CA1063, CA1861, CA2201, VSTHRD003, S104, S125, S2699 + +**Package References**: +- `Microsoft.TemplateEngine.Authoring.TemplateVerifier` - For template verification +- `Microsoft.TemplateEngine.TestHelper` - For template testing utilities + +**Project References**: +- `TestUtilities` - Common test utilities + +## Next Steps + +When actual template content is ready to be added: + +1. **Create Template Directory Structure**: + - Create template directories under `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/` + - Each template should have a `.template.config/` directory with a `template.json` file + +2. **Update Source Project**: + - Add `` items to the `.csproj` file for the new template directories + - Define exclusion patterns to match the template structure + - Update `THIRD-PARTY-NOTICES.TXT` if the templates use third-party libraries + +3. **Add Template Tests**: + - Remove the `Skip` attribute from tests in `AgentTemplateSnapshotTests.cs` + - Add new test methods for different template configurations + - Update the `_verificationExcludePatterns` array to match template output patterns + - Update the template location and short name in test methods + +4. **Generate Snapshots**: + - Run the tests to generate initial snapshots + - Use `DiffEngineTray` to review and accept the generated snapshots + +5. **Add Execution Tests** (optional): + - Create an `AgentTemplateExecutionTests.cs` file similar to `AIChatWebExecutionTests.cs` if you want to test that generated templates compile and run + +## Integration with Build System + +The project uses the `GenerateTemplateContent` approach for generating template content, which is consistent with other template projects in the repository. + +The test infrastructure is designed to: +- Use the locally built .NET SDK from the repository +- Reference locally built NuGet packages +- Support debugging through the TemplateSandbox directory +- Verify template output matches expected snapshots + +## Validation + +All created files have been validated: +- Projects compile successfully +- No build errors or warnings +- Infrastructure follows repository conventions +- Namespace consistency (`Microsoft.Agents.AI.Templates.Tests`) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT new file mode 100644 index 00000000000..ac67471b6e5 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT @@ -0,0 +1,9 @@ +.NET Core uses third-party libraries or other resources that may be +distributed under licenses different than the .NET Core software. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + dotnet@microsoft.com + +The attached notices are provided for information only. diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/AgentTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/AgentTemplateSnapshotTests.cs new file mode 100644 index 00000000000..fb25ef530ce --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/AgentTemplateSnapshotTests.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.TemplateEngine.Authoring.TemplateVerifier; +using Microsoft.TemplateEngine.TestHelper; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Agents.AI.Templates.Tests; + +public class AgentTemplateSnapshotTests +{ + // Exclude patterns will be defined here when actual template content is added. + private static readonly string[] _verificationExcludePatterns = [ + "**/bin/**", + "**/obj/**", + "**/.vs/**", + "**/node_modules/**", + "**/*.user", + "**/NuGet.config", + "**/Directory.Build.targets", + "**/Directory.Build.props", + ]; + + private readonly ILogger _log; + + public AgentTemplateSnapshotTests(ITestOutputHelper log) + { +#pragma warning disable CA2000 // Dispose objects before losing scope + _log = new XunitLoggerProvider(log).CreateLogger("TestRun"); +#pragma warning restore CA2000 // Dispose objects before losing scope + } + + // Placeholder test - actual template tests will be added when template content is implemented + [Fact(Skip = "Template content not yet implemented")] + public async Task BasicTest() + { + await TestTemplateCoreAsync(scenarioName: "Basic"); + } + + private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable? templateArgs = null) + { + string workingDir = TestUtils.CreateTemporaryFolder(); + string templateShortName = "agentapp"; // Placeholder - will be updated with actual template short name + + // Get the template location - will be updated when actual template is implemented + string templateLocation = Path.Combine(WellKnownPaths.TemplateFeedLocation, "Microsoft.Agents.AI.Templates", "src", "PlaceholderTemplate"); + + var verificationExcludePatterns = Path.DirectorySeparatorChar is '/' + ? _verificationExcludePatterns + : _verificationExcludePatterns.Select(p => p.Replace('/', Path.DirectorySeparatorChar)).ToArray(); + + TemplateVerifierOptions options = new TemplateVerifierOptions(templateName: templateShortName) + { + TemplatePath = templateLocation, + TemplateSpecificArgs = templateArgs, + SnapshotsDirectory = "Snapshots", + OutputDirectory = workingDir, + DoNotPrependCallerMethodNameToScenarioName = true, + DoNotAppendTemplateArgsToScenarioName = true, + ScenarioName = scenarioName, + VerificationExcludePatterns = verificationExcludePatterns, + } + .WithCustomScrubbers( + ScrubbersDefinition.Empty.AddScrubber((path, content) => + { + string filePath = path.UnixifyDirSeparators(); + if (filePath.EndsWith(".sln")) + { + // Scrub .sln file GUIDs. + content.ScrubByRegex(pattern: @"\{.{36}\}", replacement: "{00000000-0000-0000-0000-000000000000}"); + } + + if (filePath.EndsWith(".csproj")) + { + content.ScrubByRegex("(.*)<\\/UserSecretsId>", "secret"); + + // Scrub references to just-built packages and remove the suffix, if it exists. + var pattern = @"(?<=)"; + content.ScrubByRegex(pattern, replacement: "$1"); + } + + if (filePath.EndsWith("launchSettings.json")) + { + content.ScrubByRegex("(http(s?):\\/\\/localhost)\\:(\\d*)", "$1:9999"); + } + })); + + VerificationEngine engine = new VerificationEngine(_log); + await engine.Execute(options); + +#pragma warning disable CA1031 // Do not catch general exception types + try + { + Directory.Delete(workingDir, recursive: true); + } + catch + { + /* don't care */ + } +#pragma warning restore CA1031 // Do not catch general exception types + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs new file mode 100644 index 00000000000..5a4fa723ecb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Agents.AI.Templates.Tests; + +public class DotNetCommand : TestCommand +{ + public DotNetCommand(params ReadOnlySpan args) + { + FileName = WellKnownPaths.RepoDotNetExePath; + + foreach (var arg in args) + { + Arguments.Add(arg); + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs new file mode 100644 index 00000000000..38886c52f28 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics; + +public static class ProcessExtensions +{ + public static bool TryGetHasExited(this Process process) + { + try + { + return process.HasExited; + } + catch (InvalidOperationException ex) when (ex.Message.Contains("No process is associated with this object")) + { + return true; + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs new file mode 100644 index 00000000000..daf6d0bcb95 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Microsoft.Agents.AI.Templates.Tests; + +public abstract class TestCommand +{ + public string? FileName { get; set; } + + public string? WorkingDirectory { get; set; } + + public TimeSpan? Timeout { get; set; } + + public List Arguments { get; } = []; + + public Dictionary EnvironmentVariables = []; + + public virtual async Task ExecuteAsync(ITestOutputHelper outputHelper) + { + if (string.IsNullOrEmpty(FileName)) + { + throw new InvalidOperationException($"The {nameof(TestCommand)} did not specify an executable file name."); + } + + var processStartInfo = new ProcessStartInfo(FileName, Arguments) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false, + }; + + if (WorkingDirectory is not null) + { + processStartInfo.WorkingDirectory = WorkingDirectory; + } + + foreach (var (key, value) in EnvironmentVariables) + { + processStartInfo.EnvironmentVariables[key] = value; + } + + var exitedTcs = new TaskCompletionSource(); + var standardOutputBuilder = new StringBuilder(); + var standardErrorBuilder = new StringBuilder(); + + using var process = new Process + { + StartInfo = processStartInfo, + }; + + process.EnableRaisingEvents = true; + process.OutputDataReceived += MakeOnDataReceivedHandler(standardOutputBuilder); + process.ErrorDataReceived += MakeOnDataReceivedHandler(standardErrorBuilder); + process.Exited += (sender, args) => + { + exitedTcs.SetResult(); + }; + + DataReceivedEventHandler MakeOnDataReceivedHandler(StringBuilder outputBuilder) => (sender, args) => + { + if (args.Data is null) + { + return; + } + + lock (outputBuilder) + { + outputBuilder.AppendLine(args.Data); + } + + lock (outputHelper) + { + outputHelper.WriteLine(args.Data); + } + }; + + outputHelper.WriteLine($"Executing '{processStartInfo.FileName} {string.Join(" ", Arguments)}' in working directory '{processStartInfo.WorkingDirectory}'"); + + using var timeoutCts = new CancellationTokenSource(); + if (Timeout is { } timeout) + { + timeoutCts.CancelAfter(timeout); + } + + var startTimestamp = Stopwatch.GetTimestamp(); + + try + { + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + await exitedTcs.Task.WaitAsync(timeoutCts.Token).ConfigureAwait(false); + await process.WaitForExitAsync(timeoutCts.Token).ConfigureAwait(false); + + var elapsedTime = Stopwatch.GetElapsedTime(startTimestamp); + outputHelper.WriteLine($"Process ran for {elapsedTime} seconds."); + + return new(standardOutputBuilder, standardErrorBuilder, process.ExitCode); + } + catch (Exception ex) + { + outputHelper.WriteLine($"An exception occurred: {ex}"); + throw; + } + finally + { + if (!process.TryGetHasExited()) + { + var elapsedTime = Stopwatch.GetElapsedTime(startTimestamp); + outputHelper.WriteLine($"The process has been running for {elapsedTime} seconds. Terminating the process."); + process.Kill(entireProcessTree: true); + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs new file mode 100644 index 00000000000..2b51b9ebe20 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; + +namespace Microsoft.Agents.AI.Templates.Tests; + +public sealed class TestCommandResult(StringBuilder standardOutputBuilder, StringBuilder standardErrorBuilder, int exitCode) +{ + public string StandardOutput => field ??= standardOutputBuilder.ToString(); + + public string StandardError => field ??= standardErrorBuilder.ToString(); + + public int ExitCode => exitCode; +} + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs new file mode 100644 index 00000000000..82091538998 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Agents.AI.Templates.Tests; + +internal static class WellKnownPaths +{ + public static readonly string RepoRoot; + public static readonly string RepoDotNetExePath; + public static readonly string ThisProjectRoot; + + public static readonly string TemplateFeedLocation; + public static readonly string TemplateSandboxRoot; + public static readonly string TemplateSandboxOutputRoot; + public static readonly string TemplateInstallNuGetConfigPath; + public static readonly string TemplateTestNuGetConfigPath; + public static readonly string LocalShippingPackagesPath; + public static readonly string NuGetPackagesPath; + + static WellKnownPaths() + { + RepoRoot = GetRepoRoot(); + RepoDotNetExePath = GetRepoDotNetExePath(); + ThisProjectRoot = ProjectRootHelper.GetThisProjectRoot(); + + TemplateFeedLocation = Path.Combine(RepoRoot, "src", "ProjectTemplates"); + TemplateSandboxRoot = Path.Combine(ThisProjectRoot, "TemplateSandbox"); + TemplateSandboxOutputRoot = Path.Combine(TemplateSandboxRoot, "output"); + TemplateInstallNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_install.config"); + TemplateTestNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_test.config"); + + const string BuildConfigurationFolder = +#if DEBUG + "Debug"; +#else + "Release"; +#endif + LocalShippingPackagesPath = Path.Combine(RepoRoot, "artifacts", "packages", BuildConfigurationFolder, "Shipping"); + NuGetPackagesPath = Path.Combine(TemplateSandboxOutputRoot, "packages"); + } + + private static string GetRepoRoot() + { + string? directory = AppContext.BaseDirectory; + + while (directory is not null) + { + var gitPath = Path.Combine(directory, ".git"); + if (Directory.Exists(gitPath) || File.Exists(gitPath)) + { + // Found the repo root, which should either have a .git folder or, if the repo + // is part of a Git worktree, a .git file. + return directory; + } + + directory = Directory.GetParent(directory)?.FullName; + } + + throw new InvalidOperationException("Failed to establish root of the repository"); + } + + private static string GetRepoDotNetExePath() + { + var dotNetExeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? "dotnet.exe" : "dotnet"; + + var dotNetExePath = Path.Combine(RepoRoot, ".dotnet", dotNetExeName); + + if (!File.Exists(dotNetExePath)) + { + throw new InvalidOperationException($"Expected to find '{dotNetExeName}' at '{dotNetExePath}', but it was not found."); + } + + return dotNetExePath; + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj new file mode 100644 index 00000000000..465399f5e50 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj @@ -0,0 +1,32 @@ + + + + Tests for Microsoft.Agents.AI.Templates. + false + true + + + + $(NoWarn);CA1063;CA1861;CA2201;VSTHRD003;S104;S125;S2699 + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs new file mode 100644 index 00000000000..b8da0e8fc74 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; + +namespace Microsoft.Agents.AI.Templates.Tests; + +internal static class ProjectRootHelper +{ + public static string GetThisProjectRoot() + { + string? directory = Directory.GetCurrentDirectory(); + + while (directory is not null) + { + string projectFile = Path.Combine(directory, "Microsoft.Agents.AI.Templates.Tests.csproj"); + if (File.Exists(projectFile)) + { + return directory; + } + + directory = Directory.GetParent(directory)?.FullName; + } + + throw new System.InvalidOperationException("Failed to find project root"); + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/README.md new file mode 100644 index 00000000000..babe77c2f9e --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/README.md @@ -0,0 +1,7 @@ +# Microsoft.Agents.AI.Templates tests + +Contains snapshot and execution tests for `Microsoft.Agents.AI.Templates`. + +To update test snapshots, install and run the `DiffEngineTray` tool following [these instructions](https://github.com/VerifyTests/DiffEngine/blob/main/docs/tray.md), run the snapshot tests either in VS or using `dotnet test`, and use `DiffEngineTray` to accept or discard changes. + +For information on debugging template execution tests, see [this README](./TemplateSandbox/README.md). diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/README.md new file mode 100644 index 00000000000..99247d4574c --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/README.md @@ -0,0 +1,5 @@ +# Snapshots + +This directory contains verified snapshots of generated template output. These snapshots are used by the template snapshot tests to ensure templates generate expected output. + +When template content is added, this directory will contain subdirectories for each test scenario. diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig new file mode 100644 index 00000000000..b8f20c69836 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig @@ -0,0 +1,2 @@ +# Don't apply the repo's editorconfig settings to generated templates. +root = true diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore new file mode 100644 index 00000000000..ee80e74117d --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore @@ -0,0 +1,2 @@ +# Template test output +output/ diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md new file mode 100644 index 00000000000..51682c3aaf7 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md @@ -0,0 +1,11 @@ +# Template Sandbox + +This directory is used for debugging template execution tests. To debug a template: + +1. Copy the contents of the template you want to test from `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/` to this directory. +2. Run `dotnet new install .` from this directory to install the template locally. +3. Create a new directory (e.g., `output/`) and run `dotnet new ` with desired parameters. +4. Debug and iterate on the template as needed. +5. When done, run `dotnet new uninstall .` to uninstall the local template. + +The `output` directory is git-ignored and can be used for template instantiation testing. diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 new file mode 100644 index 00000000000..ddd6b0bfe86 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 @@ -0,0 +1,109 @@ +# +# This file creates an environment similar to the one that the template tests use. +# This makes it convenient to restore, build, and run projects generated by the template tests +# to debug test failures. +# +# This file must be used by invoking ". .\activate.ps1" from the command line. +# You cannot run it directly. See https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_scripts#script-scope-and-dot-sourcing +# +# To exit from the environment this creates, execute the 'deactivate' function. +# + +[CmdletBinding(PositionalBinding=$false)] +Param( + [string][Alias('c')]$configuration = "Debug" +) + +if ($MyInvocation.CommandOrigin -eq 'runspace') { + $cwd = (Get-Location).Path + $scriptPath = $MyInvocation.MyCommand.Path + $relativePath = [System.IO.Path]::GetRelativePath($cwd, $scriptPath) + Write-Host -f Red "This script cannot be invoked directly." + Write-Host -f Red "To function correctly, this script file must be 'dot sourced' by calling `". .\$relativePath`" (notice the dot at the beginning)." + exit 1 +} + +function deactivate ([switch]$init) { + # reset old environment variables + if (Test-Path variable:_OLD_PATH) { + $env:PATH = $_OLD_PATH + Remove-Item variable:_OLD_PATH + } + + if (test-path function:_old_prompt) { + Set-Item Function:prompt -Value $function:_old_prompt -ea ignore + remove-item function:_old_prompt + } + + Remove-Item env:DOTNET_ROOT -ea Ignore + Remove-Item 'env:DOTNET_ROOT(x86)' -ea Ignore +Remove-Item env:DOTNET_MULTILEVEL_LOOKUP -ea Ignore + Remove-Item env:NUGET_PACKAGES -ea Ignore + Remove-Item env:LOCAL_SHIPPING_PATH -ea Ignore + if (-not $init) { + # Remove functions defined + Remove-Item function:deactivate + Remove-Item function:Get-RepoRoot + } +} + +# Cleanup the environment +deactivate -init + +function Get-RepoRoot { + $directory = $PSScriptRoot + + while ($directory) { + $gitPath = Join-Path $directory ".git" + + if (Test-Path $gitPath) { + return $directory + } + + $parent = Split-Path $directory -Parent + if ($parent -eq $directory) { + # We've reached the filesystem root + break + } + + $directory = $parent + } + + throw "Failed to establish root of the repository" +} + +# Find the root of the repository +$repoRoot = Get-RepoRoot + +$_OLD_PATH = $env:PATH +# Tell dotnet where to find itself +$env:DOTNET_ROOT = "$repoRoot\.dotnet" +${env:DOTNET_ROOT(x86)} = "$repoRoot\.dotnet\x86" +# Tell dotnet not to look beyond the DOTNET_ROOT folder for more dotnet things +$env:DOTNET_MULTILEVEL_LOOKUP = 0 +# Put dotnet first on PATH +$env:PATH = "${env:DOTNET_ROOT};${env:PATH}" +# Set NUGET_PACKAGES and LOCAL_SHIPPING_PATH +$env:NUGET_PACKAGES = "$PSScriptRoot\output\packages" +$env:LOCAL_SHIPPING_PATH = "$repoRoot\artifacts\packages\$configuration\Shipping\" + +# Set the shell prompt +$function:_old_prompt = $function:prompt +function dotnet_prompt { + # Add a prefix to the current prompt, but don't discard it. + write-host "($( split-path $PSScriptRoot -leaf )) " -nonewline + & $function:_old_prompt +} + +Set-Item Function:prompt -Value $function:dotnet_prompt -ea ignore + +Write-Host -f Magenta "Enabled the template testing environment. Execute 'deactivate' to exit." +Write-Host -f Magenta "Using the '$configuration' configuration. Use the -c option to specify a different configuration." +if (-not (Test-Path "${env:DOTNET_ROOT}\dotnet.exe")) { + Write-Host -f Yellow ".NET Core has not been installed yet. Run $repoRoot\build.cmd -restore to install it." +} +else { + Write-Host "dotnet = ${env:DOTNET_ROOT}\dotnet.exe" +} +Write-Host "NUGET_PACKAGES = ${env:NUGET_PACKAGES}" +Write-Host "LOCAL_SHIPPING_PATH = ${env:LOCAL_SHIPPING_PATH}" diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs new file mode 100644 index 00000000000..5dfeedaf15a --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using System.Text.RegularExpressions; + +namespace Microsoft.Agents.AI.Templates.Tests; + +internal static class VerifyScrubbers +{ + /// + /// Removes content after "Details: ". + /// + internal static void ScrubDetails(this StringBuilder output) + { + output.ScrubByRegex("(Details: )([^\\r\\n]*)", $"Details: %DETAILS%"); + } + + /// + /// Removes table header delimiter. + /// + internal static void ScrubTableHeaderDelimiter(this StringBuilder output) + { + output.ScrubByRegex("---[- ]*", "%TABLE HEADER DELIMITER%"); + } + + /// + /// Replaces Windows newlines (CRLF) with Unix style newlines (LF). + /// + /// + internal static StringBuilder UnixifyNewlines(this StringBuilder output) + { + return output.Replace("\r\n", "\n"); + } + + /// + /// Replaces Windows Directory separator char (\) with Unix Directory separator char (/). + /// + /// + internal static string UnixifyDirSeparators(this string output) + { + return output.Replace('\\', '/'); + } + + /// + /// Replaces content matching with . + /// + internal static void ScrubByRegex(this StringBuilder output, string pattern, string replacement, RegexOptions regexOptions = RegexOptions.None) + { + string finalOutput = Regex.Replace(output.ToString(), pattern, replacement, regexOptions); + output.Clear(); + output.Append(finalOutput); + } + + /// + /// Replaces content matching with . + /// + internal static void ScrubAndReplace(this StringBuilder output, string textToReplace, string replacement) + { + string finalOutput = output.ToString().Replace(textToReplace, replacement); + output.Clear(); + output.Append(finalOutput); + } +} From 577cf996ba149ff8724ecbe8467b9e7f5f5cbb7d Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 01:55:01 -0800 Subject: [PATCH 02/22] Refine Microsoft.Agents.AI.Templates infrastructure --- .../Microsoft.Agents.AI.Templates.csproj | 17 +++++- .../THIRD-PARTY-NOTICES.TXT | 55 +++++++++++++++++++ .../Infrastructure/DotNetCommand.cs | 2 +- ...s => WebApiAgentsTemplateSnapshotTests.cs} | 49 +++++++++-------- 4 files changed, 95 insertions(+), 28 deletions(-) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/{AgentTemplateSnapshotTests.cs => WebApiAgentsTemplateSnapshotTests.cs} (66%) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj index 5221b70ce5a..1b290432ede 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj @@ -26,7 +26,7 @@ - - + + + diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT index ac67471b6e5..f5eee59e294 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT @@ -7,3 +7,58 @@ bring it to our attention. Post an issue or email us: dotnet@microsoft.com The attached notices are provided for information only. + +License notice for OllamaSharp +------------------------------ + +https://github.com/awaescher/OllamaSharp + +MIT License + +Copyright (c) 2024 Andreas Wäscher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +License notice for openai-dotnet +------------------------- + +https://github.com/openai/openai-dotnet + +The MIT License (MIT) + +Copyright (c) 2024 OpenAI (https://openai.com) + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs index 5a4fa723ecb..bd2f8fe06a0 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs @@ -13,7 +13,7 @@ public DotNetCommand(params ReadOnlySpan args) foreach (var arg in args) { - Arguments.Add(arg); + Arguments.Add(arg); } } } diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/AgentTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs similarity index 66% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/AgentTemplateSnapshotTests.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs index fb25ef530ce..8648b491081 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/AgentTemplateSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs @@ -13,15 +13,15 @@ namespace Microsoft.Agents.AI.Templates.Tests; -public class AgentTemplateSnapshotTests +public class WebApiAgentsTemplateSnapshotTests { // Exclude patterns will be defined here when actual template content is added. private static readonly string[] _verificationExcludePatterns = [ "**/bin/**", "**/obj/**", "**/.vs/**", - "**/node_modules/**", "**/*.user", + "**/*.in", "**/NuGet.config", "**/Directory.Build.targets", "**/Directory.Build.props", @@ -29,7 +29,7 @@ public class AgentTemplateSnapshotTests private readonly ILogger _log; - public AgentTemplateSnapshotTests(ITestOutputHelper log) + public WebApiAgentsTemplateSnapshotTests(ITestOutputHelper log) { #pragma warning disable CA2000 // Dispose objects before losing scope _log = new XunitLoggerProvider(log).CreateLogger("TestRun"); @@ -52,8 +52,8 @@ private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable p.Replace('/', Path.DirectorySeparatorChar)).ToArray(); + ? _verificationExcludePatterns + : _verificationExcludePatterns.Select(p => p.Replace('/', Path.DirectorySeparatorChar)).ToArray(); TemplateVerifierOptions options = new TemplateVerifierOptions(templateName: templateShortName) { @@ -68,28 +68,29 @@ private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable - { - string filePath = path.UnixifyDirSeparators(); - if (filePath.EndsWith(".sln")) - { - // Scrub .sln file GUIDs. - content.ScrubByRegex(pattern: @"\{.{36}\}", replacement: "{00000000-0000-0000-0000-000000000000}"); - } + { + string filePath = path.UnixifyDirSeparators(); + if (filePath.EndsWith(".sln")) + { + // Scrub .sln file GUIDs. + content.ScrubByRegex(pattern: @"\{.{36}\}", replacement: "{00000000-0000-0000-0000-000000000000}"); + } - if (filePath.EndsWith(".csproj")) - { - content.ScrubByRegex("(.*)<\\/UserSecretsId>", "secret"); + if (filePath.EndsWith(".csproj")) + { + content.ScrubByRegex("(.*)<\\/UserSecretsId>", "secret"); - // Scrub references to just-built packages and remove the suffix, if it exists. - var pattern = @"(?<=)"; - content.ScrubByRegex(pattern, replacement: "$1"); - } + // Scrub references to just-built packages and remove the suffix, if it exists. + // This allows the snapshots to remain the same regardless of where the repo is built (e.g., locally, public CI, internal CI). + var pattern = @"(?<=)"; + content.ScrubByRegex(pattern, replacement: "$1"); + } - if (filePath.EndsWith("launchSettings.json")) - { - content.ScrubByRegex("(http(s?):\\/\\/localhost)\\:(\\d*)", "$1:9999"); - } - })); + if (filePath.EndsWith("launchSettings.json")) + { + content.ScrubByRegex("(http(s?):\\/\\/localhost)\\:(\\d*)", "$1:9999"); + } + })); VerificationEngine engine = new VerificationEngine(_log); await engine.Execute(options); From 785d8fd26b80b98c3882b1de5408c635899f68ce Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 03:22:15 -0800 Subject: [PATCH 03/22] Move project template infrastructure utilities into a shared folder --- .../Infrastructure/DotNetCommand.cs | 19 --- .../Infrastructure/ProcessExtensions.cs | 19 --- .../Infrastructure/TestCommand.cs | 125 ------------------ .../Infrastructure/TestCommandResult.cs | 16 --- .../Infrastructure/WellKnownPaths.cs | 80 ----------- ...Microsoft.Agents.AI.Templates.Tests.csproj | 4 +- .../ProjectRootHelper.cs | 27 ---- .../VerifyScrubbers.cs | 64 --------- .../WebApiAgentsTemplateSnapshotTests.cs | 1 + .../AIChatWebExecutionTests.cs | 1 + .../AIChatWebSnapshotTests.cs | 1 + .../TemplateExecutionTestBase.cs | 1 + .../TemplateExecutionTestClassFixtureBase.cs | 1 + .../TemplateExecutionTestCollection.cs | 1 + .../TestCommandResultExtensions.cs | 1 + .../McpServerSnapshotTests.cs | 1 + ...osoft.Extensions.AI.Templates.Tests.csproj | 2 + .../ProjectTemplates}/DotNetCommand.cs | 2 +- .../ProjectTemplates}/DotNetNewCommand.cs | 2 +- ...plateExecutionTestConfigurationProvider.cs | 2 +- .../MessageSinkTestOutputHelper.cs | 2 +- .../ProjectTemplates}/ProcessExtensions.cs | 5 +- .../ProjectTemplates}/Project.cs | 2 +- .../ProjectTemplates}/ProjectRootHelper.cs | 2 +- .../TemplateExecutionTestCollectionFixture.cs | 3 +- .../TemplateExecutionTestConfiguration.cs | 2 +- .../ProjectTemplates}/TestCommand.cs | 2 +- .../TestCommandExtensions.cs | 3 +- .../ProjectTemplates}/TestCommandResult.cs | 2 +- .../ProjectTemplates}/VerifyScrubbers.cs | 2 +- .../ProjectTemplates}/WellKnownPaths.cs | 2 +- 31 files changed, 30 insertions(+), 367 deletions(-) delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/DotNetCommand.cs (89%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/DotNetNewCommand.cs (95%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/ITemplateExecutionTestConfigurationProvider.cs (84%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/MessageSinkTestOutputHelper.cs (93%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/ProcessExtensions.cs (84%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/Project.cs (94%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/ProjectRootHelper.cs (96%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TemplateExecutionTestCollectionFixture.cs (92%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TemplateExecutionTestConfiguration.cs (86%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TestCommand.cs (98%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TestCommandExtensions.cs (89%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TestCommandResult.cs (90%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/VerifyScrubbers.cs (97%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/WellKnownPaths.cs (98%) diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs deleted file mode 100644 index bd2f8fe06a0..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace Microsoft.Agents.AI.Templates.Tests; - -public class DotNetCommand : TestCommand -{ - public DotNetCommand(params ReadOnlySpan args) - { - FileName = WellKnownPaths.RepoDotNetExePath; - - foreach (var arg in args) - { - Arguments.Add(arg); - } - } -} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs deleted file mode 100644 index 38886c52f28..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Diagnostics; - -public static class ProcessExtensions -{ - public static bool TryGetHasExited(this Process process) - { - try - { - return process.HasExited; - } - catch (InvalidOperationException ex) when (ex.Message.Contains("No process is associated with this object")) - { - return true; - } - } -} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs deleted file mode 100644 index daf6d0bcb95..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; - -namespace Microsoft.Agents.AI.Templates.Tests; - -public abstract class TestCommand -{ - public string? FileName { get; set; } - - public string? WorkingDirectory { get; set; } - - public TimeSpan? Timeout { get; set; } - - public List Arguments { get; } = []; - - public Dictionary EnvironmentVariables = []; - - public virtual async Task ExecuteAsync(ITestOutputHelper outputHelper) - { - if (string.IsNullOrEmpty(FileName)) - { - throw new InvalidOperationException($"The {nameof(TestCommand)} did not specify an executable file name."); - } - - var processStartInfo = new ProcessStartInfo(FileName, Arguments) - { - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true, - UseShellExecute = false, - }; - - if (WorkingDirectory is not null) - { - processStartInfo.WorkingDirectory = WorkingDirectory; - } - - foreach (var (key, value) in EnvironmentVariables) - { - processStartInfo.EnvironmentVariables[key] = value; - } - - var exitedTcs = new TaskCompletionSource(); - var standardOutputBuilder = new StringBuilder(); - var standardErrorBuilder = new StringBuilder(); - - using var process = new Process - { - StartInfo = processStartInfo, - }; - - process.EnableRaisingEvents = true; - process.OutputDataReceived += MakeOnDataReceivedHandler(standardOutputBuilder); - process.ErrorDataReceived += MakeOnDataReceivedHandler(standardErrorBuilder); - process.Exited += (sender, args) => - { - exitedTcs.SetResult(); - }; - - DataReceivedEventHandler MakeOnDataReceivedHandler(StringBuilder outputBuilder) => (sender, args) => - { - if (args.Data is null) - { - return; - } - - lock (outputBuilder) - { - outputBuilder.AppendLine(args.Data); - } - - lock (outputHelper) - { - outputHelper.WriteLine(args.Data); - } - }; - - outputHelper.WriteLine($"Executing '{processStartInfo.FileName} {string.Join(" ", Arguments)}' in working directory '{processStartInfo.WorkingDirectory}'"); - - using var timeoutCts = new CancellationTokenSource(); - if (Timeout is { } timeout) - { - timeoutCts.CancelAfter(timeout); - } - - var startTimestamp = Stopwatch.GetTimestamp(); - - try - { - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - await exitedTcs.Task.WaitAsync(timeoutCts.Token).ConfigureAwait(false); - await process.WaitForExitAsync(timeoutCts.Token).ConfigureAwait(false); - - var elapsedTime = Stopwatch.GetElapsedTime(startTimestamp); - outputHelper.WriteLine($"Process ran for {elapsedTime} seconds."); - - return new(standardOutputBuilder, standardErrorBuilder, process.ExitCode); - } - catch (Exception ex) - { - outputHelper.WriteLine($"An exception occurred: {ex}"); - throw; - } - finally - { - if (!process.TryGetHasExited()) - { - var elapsedTime = Stopwatch.GetElapsedTime(startTimestamp); - outputHelper.WriteLine($"The process has been running for {elapsedTime} seconds. Terminating the process."); - process.Kill(entireProcessTree: true); - } - } - } -} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs deleted file mode 100644 index 2b51b9ebe20..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text; - -namespace Microsoft.Agents.AI.Templates.Tests; - -public sealed class TestCommandResult(StringBuilder standardOutputBuilder, StringBuilder standardErrorBuilder, int exitCode) -{ - public string StandardOutput => field ??= standardOutputBuilder.ToString(); - - public string StandardError => field ??= standardErrorBuilder.ToString(); - - public int ExitCode => exitCode; -} - diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs deleted file mode 100644 index 82091538998..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using System.Runtime.InteropServices; - -namespace Microsoft.Agents.AI.Templates.Tests; - -internal static class WellKnownPaths -{ - public static readonly string RepoRoot; - public static readonly string RepoDotNetExePath; - public static readonly string ThisProjectRoot; - - public static readonly string TemplateFeedLocation; - public static readonly string TemplateSandboxRoot; - public static readonly string TemplateSandboxOutputRoot; - public static readonly string TemplateInstallNuGetConfigPath; - public static readonly string TemplateTestNuGetConfigPath; - public static readonly string LocalShippingPackagesPath; - public static readonly string NuGetPackagesPath; - - static WellKnownPaths() - { - RepoRoot = GetRepoRoot(); - RepoDotNetExePath = GetRepoDotNetExePath(); - ThisProjectRoot = ProjectRootHelper.GetThisProjectRoot(); - - TemplateFeedLocation = Path.Combine(RepoRoot, "src", "ProjectTemplates"); - TemplateSandboxRoot = Path.Combine(ThisProjectRoot, "TemplateSandbox"); - TemplateSandboxOutputRoot = Path.Combine(TemplateSandboxRoot, "output"); - TemplateInstallNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_install.config"); - TemplateTestNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_test.config"); - - const string BuildConfigurationFolder = -#if DEBUG - "Debug"; -#else - "Release"; -#endif - LocalShippingPackagesPath = Path.Combine(RepoRoot, "artifacts", "packages", BuildConfigurationFolder, "Shipping"); - NuGetPackagesPath = Path.Combine(TemplateSandboxOutputRoot, "packages"); - } - - private static string GetRepoRoot() - { - string? directory = AppContext.BaseDirectory; - - while (directory is not null) - { - var gitPath = Path.Combine(directory, ".git"); - if (Directory.Exists(gitPath) || File.Exists(gitPath)) - { - // Found the repo root, which should either have a .git folder or, if the repo - // is part of a Git worktree, a .git file. - return directory; - } - - directory = Directory.GetParent(directory)?.FullName; - } - - throw new InvalidOperationException("Failed to establish root of the repository"); - } - - private static string GetRepoDotNetExePath() - { - var dotNetExeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? "dotnet.exe" : "dotnet"; - - var dotNetExePath = Path.Combine(RepoRoot, ".dotnet", dotNetExeName); - - if (!File.Exists(dotNetExePath)) - { - throw new InvalidOperationException($"Expected to find '{dotNetExeName}' at '{dotNetExePath}', but it was not found."); - } - - return dotNetExePath; - } -} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj index 465399f5e50..3b6fd7e1acf 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj @@ -16,10 +16,8 @@ - - + - diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs deleted file mode 100644 index b8da0e8fc74..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; - -namespace Microsoft.Agents.AI.Templates.Tests; - -internal static class ProjectRootHelper -{ - public static string GetThisProjectRoot() - { - string? directory = Directory.GetCurrentDirectory(); - - while (directory is not null) - { - string projectFile = Path.Combine(directory, "Microsoft.Agents.AI.Templates.Tests.csproj"); - if (File.Exists(projectFile)) - { - return directory; - } - - directory = Directory.GetParent(directory)?.FullName; - } - - throw new System.InvalidOperationException("Failed to find project root"); - } -} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs deleted file mode 100644 index 5dfeedaf15a..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/VerifyScrubbers.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text; -using System.Text.RegularExpressions; - -namespace Microsoft.Agents.AI.Templates.Tests; - -internal static class VerifyScrubbers -{ - /// - /// Removes content after "Details: ". - /// - internal static void ScrubDetails(this StringBuilder output) - { - output.ScrubByRegex("(Details: )([^\\r\\n]*)", $"Details: %DETAILS%"); - } - - /// - /// Removes table header delimiter. - /// - internal static void ScrubTableHeaderDelimiter(this StringBuilder output) - { - output.ScrubByRegex("---[- ]*", "%TABLE HEADER DELIMITER%"); - } - - /// - /// Replaces Windows newlines (CRLF) with Unix style newlines (LF). - /// - /// - internal static StringBuilder UnixifyNewlines(this StringBuilder output) - { - return output.Replace("\r\n", "\n"); - } - - /// - /// Replaces Windows Directory separator char (\) with Unix Directory separator char (/). - /// - /// - internal static string UnixifyDirSeparators(this string output) - { - return output.Replace('\\', '/'); - } - - /// - /// Replaces content matching with . - /// - internal static void ScrubByRegex(this StringBuilder output, string pattern, string replacement, RegexOptions regexOptions = RegexOptions.None) - { - string finalOutput = Regex.Replace(output.ToString(), pattern, replacement, regexOptions); - output.Clear(); - output.Append(finalOutput); - } - - /// - /// Replaces content matching with . - /// - internal static void ScrubAndReplace(this StringBuilder output, string textToReplace, string replacement) - { - string finalOutput = output.ToString().Replace(textToReplace, replacement); - output.Clear(); - output.Append(finalOutput); - } -} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs index 8648b491081..c4674664afd 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Shared.ProjectTemplates.Tests; using Microsoft.TemplateEngine.Authoring.TemplateVerifier; using Microsoft.TemplateEngine.TestHelper; using Xunit; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index f5d2bc52e3a..6926bff2333 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Shared.ProjectTemplates.Tests; using Microsoft.TestUtilities; using Xunit; using Xunit.Abstractions; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebSnapshotTests.cs index 4bf84ab4bd3..487d83834a4 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebSnapshotTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Shared.ProjectTemplates.Tests; using Microsoft.TemplateEngine.Authoring.TemplateVerifier; using Microsoft.TemplateEngine.TestHelper; using Xunit; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs index b52e8cda3a6..ed67ca6697c 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; using Xunit.Abstractions; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs index 3592fd6474b..2ac81b2b8ac 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; +using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; using Xunit.Abstractions; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs index 9f10ffdf974..ffa8e67edca 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; namespace Microsoft.Extensions.AI.Templates.Tests; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs index 867cc2303ac..ac8278f6c4d 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; namespace Microsoft.Extensions.AI.Templates.Tests; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/McpServerSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/McpServerSnapshotTests.cs index 5fa1723af83..ccfce67367a 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/McpServerSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/McpServerSnapshotTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Shared.ProjectTemplates.Tests; using Microsoft.TemplateEngine.Authoring.TemplateVerifier; using Microsoft.TemplateEngine.TestHelper; using Xunit; diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj index e4c52714b79..e6f3c7af217 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj @@ -20,6 +20,8 @@ + + diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs b/test/Shared/ProjectTemplates/DotNetCommand.cs similarity index 89% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs rename to test/Shared/ProjectTemplates/DotNetCommand.cs index 4758e14dc1f..df6ed848c3e 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs +++ b/test/Shared/ProjectTemplates/DotNetCommand.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public class DotNetCommand : TestCommand { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetNewCommand.cs b/test/Shared/ProjectTemplates/DotNetNewCommand.cs similarity index 95% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetNewCommand.cs rename to test/Shared/ProjectTemplates/DotNetNewCommand.cs index cdd6ab73f03..e013d0d4e4e 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetNewCommand.cs +++ b/test/Shared/ProjectTemplates/DotNetNewCommand.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Xunit.Abstractions; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public sealed class DotNetNewCommand : DotNetCommand { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs b/test/Shared/ProjectTemplates/ITemplateExecutionTestConfigurationProvider.cs similarity index 84% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs rename to test/Shared/ProjectTemplates/ITemplateExecutionTestConfigurationProvider.cs index 3a499013495..5a05ff8e32a 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs +++ b/test/Shared/ProjectTemplates/ITemplateExecutionTestConfigurationProvider.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public interface ITemplateExecutionTestConfigurationProvider { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/MessageSinkTestOutputHelper.cs b/test/Shared/ProjectTemplates/MessageSinkTestOutputHelper.cs similarity index 93% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/MessageSinkTestOutputHelper.cs rename to test/Shared/ProjectTemplates/MessageSinkTestOutputHelper.cs index d81c1f7c434..6118a0ad4e2 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/MessageSinkTestOutputHelper.cs +++ b/test/Shared/ProjectTemplates/MessageSinkTestOutputHelper.cs @@ -4,7 +4,7 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public sealed class MessageSinkTestOutputHelper : ITestOutputHelper { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs b/test/Shared/ProjectTemplates/ProcessExtensions.cs similarity index 84% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs rename to test/Shared/ProjectTemplates/ProcessExtensions.cs index a20d390794d..cbedf75e354 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs +++ b/test/Shared/ProjectTemplates/ProcessExtensions.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace System.Diagnostics; +using System; +using System.Diagnostics; + +namespace Microsoft.Shared.ProjectTemplates.Tests; public static class ProcessExtensions { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/Project.cs b/test/Shared/ProjectTemplates/Project.cs similarity index 94% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/Project.cs rename to test/Shared/ProjectTemplates/Project.cs index 317e81a661f..129866f3fef 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/Project.cs +++ b/test/Shared/ProjectTemplates/Project.cs @@ -4,7 +4,7 @@ using System; using System.IO; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public sealed class Project(string rootPath, string name) { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs b/test/Shared/ProjectTemplates/ProjectRootHelper.cs similarity index 96% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs rename to test/Shared/ProjectTemplates/ProjectRootHelper.cs index 3d076a438ad..baca09bf468 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs +++ b/test/Shared/ProjectTemplates/ProjectRootHelper.cs @@ -5,7 +5,7 @@ using System.IO; using System.Runtime.CompilerServices; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; /// /// Contains a helper for determining the disk location of the containing project folder. diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollectionFixture.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs similarity index 92% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollectionFixture.cs rename to test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs index 13140b0599e..203b35b37c9 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollectionFixture.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using Microsoft.Shared.ProjectTemplates.Tests; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; /// /// Provides functionality scoped to the lifetime of all tests defined in diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestConfiguration.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestConfiguration.cs similarity index 86% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestConfiguration.cs rename to test/Shared/ProjectTemplates/TemplateExecutionTestConfiguration.cs index ce621e58528..060f28fe368 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestConfiguration.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestConfiguration.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public sealed class TemplateExecutionTestConfiguration { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs b/test/Shared/ProjectTemplates/TestCommand.cs similarity index 98% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs rename to test/Shared/ProjectTemplates/TestCommand.cs index 697bf009f9c..dfe89030cad 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs +++ b/test/Shared/ProjectTemplates/TestCommand.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using Xunit.Abstractions; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public abstract class TestCommand { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandExtensions.cs b/test/Shared/ProjectTemplates/TestCommandExtensions.cs similarity index 89% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandExtensions.cs rename to test/Shared/ProjectTemplates/TestCommandExtensions.cs index 957c0efbb79..288b1238078 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandExtensions.cs +++ b/test/Shared/ProjectTemplates/TestCommandExtensions.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Shared.ProjectTemplates.Tests; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public static class TestCommandExtensions { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs b/test/Shared/ProjectTemplates/TestCommandResult.cs similarity index 90% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs rename to test/Shared/ProjectTemplates/TestCommandResult.cs index 4b5e2dd2a28..21b4b7f7592 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs +++ b/test/Shared/ProjectTemplates/TestCommandResult.cs @@ -3,7 +3,7 @@ using System.Text; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public sealed class TestCommandResult(StringBuilder standardOutputBuilder, StringBuilder standardErrorBuilder, int exitCode) { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/VerifyScrubbers.cs b/test/Shared/ProjectTemplates/VerifyScrubbers.cs similarity index 97% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/VerifyScrubbers.cs rename to test/Shared/ProjectTemplates/VerifyScrubbers.cs index 75f42ba64ea..505feb99c14 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/VerifyScrubbers.cs +++ b/test/Shared/ProjectTemplates/VerifyScrubbers.cs @@ -4,7 +4,7 @@ using System.Text; using System.Text.RegularExpressions; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; internal static class VerifyScrubbers { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs b/test/Shared/ProjectTemplates/WellKnownPaths.cs similarity index 98% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs rename to test/Shared/ProjectTemplates/WellKnownPaths.cs index 0d399dfcfe7..b314fc563b5 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs +++ b/test/Shared/ProjectTemplates/WellKnownPaths.cs @@ -5,7 +5,7 @@ using System.IO; using System.Runtime.InteropServices; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; internal static class WellKnownPaths { From d293cc124a680058e47964c15d22a1c8b1e385a5 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 03:58:27 -0800 Subject: [PATCH 04/22] Add the webapi-agents project template content with GitHub models --- src/ProjectTemplates/GeneratedContent.targets | 8 + .../Microsoft.Agents.AI.Templates.csproj | 6 +- .../PROJECT_STRUCTURE.md | 183 ++++++++++++++---- .../Microsoft.Agents.AI.Templates/README.md | 3 + .../.template.config/template.json | 116 +++++++++++ .../WebApiAgents-CSharp/Program.cs | 25 +++ .../Properties/launchSettings.json | 23 +++ .../WebApiAgents-CSharp/README.md | 118 +++++++++++ .../WebApiAgents-CSharp.csproj.in | 18 ++ .../WebApiAgents-CSharp/appsettings.json | 9 + .../WebApiAgentsTemplateSnapshotTests.cs | 78 ++++---- 11 files changed, 502 insertions(+), 85 deletions(-) create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/README.md create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Properties/launchSettings.json create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/appsettings.json diff --git a/src/ProjectTemplates/GeneratedContent.targets b/src/ProjectTemplates/GeneratedContent.targets index 403beadc3a0..10bf0a42205 100644 --- a/src/ProjectTemplates/GeneratedContent.targets +++ b/src/ProjectTemplates/GeneratedContent.targets @@ -9,6 +9,7 @@ --> <_LocalChatTemplateVariant>aspire + <_WebApiAgentsRoot>$(MSBuildThisFileDirectory)Microsoft.Agents.AI.Templates\src\WebApiAgents\ <_ChatWithCustomDataContentRoot>$(MSBuildThisFileDirectory)Microsoft.Extensions.AI.Templates\src\ChatWithCustomData\ <_McpServerContentRoot>$(MSBuildThisFileDirectory)Microsoft.Extensions.AI.Templates\src\McpServer\ @@ -33,6 +34,8 @@ Specifies external packages that get referenced in generated template content. --> + 1.0.0-preview.251104.1 + 1.0.0-alpha.251104.1 9.5.1 9.5.1-preview.1.25502.11 1.0.0 @@ -59,6 +62,8 @@ ArtifactsShippingPackagesDir=$(ArtifactsShippingPackagesDir); + TemplatePackageVersion_MicrosoftAgentsAI=$(TemplatePackageVersion_MicrosoftAgentsAI); + TemplatePackageVersion_MicrosoftAgentsAIHostingOpenAI=$(TemplatePackageVersion_MicrosoftAgentsAIHostingOpenAI); TemplatePackageVersion_Aspire=$(TemplatePackageVersion_Aspire); TemplatePackageVersion_Aspire_Preview=$(TemplatePackageVersion_Aspire_Preview); TemplatePackageVersion_AzureAIProjects=$(TemplatePackageVersion_AzureAIProjects); @@ -90,6 +95,9 @@ + diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj index 1b290432ede..2921dd9bc5d 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj @@ -37,17 +37,17 @@ - + + diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md index c5f3c0f9742..332cc33474d 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md @@ -1,10 +1,10 @@ # Microsoft.Agents.AI.Templates Project Structure -This document describes the newly created `Microsoft.Agents.AI.Templates` project structure and infrastructure. +This document describes the Microsoft.Agents.AI.Templates project structure. ## Project Overview -The `Microsoft.Agents.AI.Templates` project is a .NET project template package designed to provide project templates for Microsoft.Agents.AI applications. It follows the same structure and infrastructure as `Microsoft.Extensions.AI.Templates`. +The `Microsoft.Agents.AI.Templates` project is a .NET project template package that provides project templates for Microsoft.Agents.AI applications. It follows the same structure and infrastructure as `Microsoft.Extensions.AI.Templates`. ## Project Structure @@ -13,7 +13,7 @@ The `Microsoft.Agents.AI.Templates` project is a .NET project template package d **Key Files**: - `Microsoft.Agents.AI.Templates.csproj` - The template package project file -- `THIRD-PARTY-NOTICES.TXT` - Third-party license notices (currently a placeholder) +- `THIRD-PARTY-NOTICES.TXT` - Third-party license notices **Configuration**: - Package type: `Template` @@ -27,9 +27,49 @@ The `Microsoft.Agents.AI.Templates` project is a .NET project template package d **Project References**: - References `GenerateTemplateContent` project to ensure template content is generated before building -**Content Items**: -- Template content will be added to the `` ItemGroup once actual templates are implemented -- Currently includes only `THIRD-PARTY-NOTICES.TXT` +**Template Content**: + +#### WebApiAgents Template (`webapi-agents`) +**Location**: `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/` + +A simple ASP.NET Core Web API template that demonstrates the AI Agents framework with: +- **Writer Agent**: Writes short stories (300 words or less) about specified topics +- **Editor Agent**: Edits stories to improve grammar and style +- **Publisher Workflow**: A sequential workflow combining writer and editor agents +- OpenAI-compatible API endpoints via `MapOpenAIResponses()` + +**Template Files**: +- `.template.config/template.json` - Template definition + - Short name: `webapi-agents` + - Default name: `AgentsApp` + - Source name: `WebApiAgents-CSharp` + - Target framework: net10.0 + - Configurable HTTP/HTTPS ports via template parameters +- `WebApiAgents-CSharp.csproj` - Project file with Microsoft.Agents.AI package references +- `Program.cs` - Application entry point with AI agent configuration +- `Properties/launchSettings.json` - Launch profiles with port configurations +- `appsettings.json` - Application configuration +- `README.md` - Comprehensive getting started guide with: + - GitHub Models token configuration instructions + - Multiple configuration methods (user secrets, environment variables, appsettings) + - Example API usage with curl +- Troubleshooting guidance +- `.gitignore` - Git ignore file to prevent committing sensitive data + +**Package References**: +- `Microsoft.Agents.AI` (1.0.0-preview.251104.1) +- `Microsoft.Agents.AI.Hosting` (1.0.0-preview.251104.1) +- `Microsoft.Agents.AI.Hosting.OpenAI` (1.0.0-alpha.251104.1) +- `Microsoft.Agents.AI.OpenAI` (1.0.0-preview.251104.1) +- `Microsoft.Agents.AI.Workflows` (1.0.0-preview.251104.1) + +**Template Features**: +- Uses GitHub Models (gpt-4o-mini) via Azure AI Inference endpoint +- Demonstrates sequential workflow pattern +- Exposes agents via OpenAI-compatible REST API +- Includes comprehensive README with setup instructions +- Configured for HTTPS by default +- Minimal API design pattern ### Test Project **Location**: `test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/` @@ -37,17 +77,10 @@ The `Microsoft.Agents.AI.Templates` project is a .NET project template package d **Key Files**: - `Microsoft.Agents.AI.Templates.Tests.csproj` - The integration test project - `README.md` - Documentation for running and updating template tests -- `AgentTemplateSnapshotTests.cs` - Placeholder snapshot test class (test is skipped until template content is added) +- `WebApiAgentsTemplateSnapshotTests.cs` - Snapshot tests for webapi-agents template - `ProjectRootHelper.cs` - Helper to locate the test project root - `VerifyScrubbers.cs` - Utilities for scrubbing variable content from test snapshots -**Infrastructure Files** (in `Infrastructure/` subdirectory): -- `WellKnownPaths.cs` - Constants for important paths (repo root, template feed location, sandbox paths, etc.) -- `TestCommand.cs` - Base class for executing commands during tests -- `DotNetCommand.cs` - Specialized command for executing dotnet CLI commands -- `TestCommandResult.cs` - Result data from command execution -- `ProcessExtensions.cs` - Extension methods for working with processes - **Test Support Directories**: 1. **TemplateSandbox/** - For manual template testing and debugging @@ -58,7 +91,7 @@ The `Microsoft.Agents.AI.Templates` project is a .NET project template package d 2. **Snapshots/** - For verified template output snapshots - `README.md` - Documentation about snapshot testing - - Subdirectories for each test scenario will be added when templates are implemented + - Subdirectories for each test scenario (created after first test run) **Configuration**: - Project type: Integration Test (`IsIntegrationTestProject = true`) @@ -69,37 +102,93 @@ The `Microsoft.Agents.AI.Templates` project is a .NET project template package d - `Microsoft.TemplateEngine.TestHelper` - For template testing utilities **Project References**: -- `TestUtilities` - Common test utilities +- `TestUtilities` - Shared test utilities (uses ProjectTemplates utilities from test/Shared/ProjectTemplates) + +**Test Infrastructure**: +The test project uses shared infrastructure from `test/Shared/ProjectTemplates` including: +- `WellKnownPaths` - Path management +- `DotNetCommand` / `DotNetNewCommand` - Command execution +- `VerifyScrubbers` - Content scrubbing for consistent snapshots +- Template verification and execution test bases + +## Template Usage + +### Installing the Template + +```bash +# From the repository root after building +dotnet new install artifacts/packages/Debug/Shipping/Microsoft.Agents.AI.Templates.*.nupkg +``` + +### Creating a New Project + +```bash +# Basic usage +dotnet new webapi-agents -n MyAgentsApp + +# With custom ports +dotnet new webapi-agents -n MyAgentsApp --httpPort 5100 --httpsPort 7100 +``` + +### Configuring the Project + +After creating a project, configure the GitHub Models token: -## Next Steps +```bash +cd MyAgentsApp +dotnet user-secrets set "GitHubModels:Token" "your-token-here" +``` -When actual template content is ready to be added: +### Running the Project -1. **Create Template Directory Structure**: - - Create template directories under `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/` - - Each template should have a `.template.config/` directory with a `template.json` file +```bash +dotnet run +``` -2. **Update Source Project**: - - Add `` items to the `.csproj` file for the new template directories - - Define exclusion patterns to match the template structure - - Update `THIRD-PARTY-NOTICES.TXT` if the templates use third-party libraries +The application exposes OpenAI-compatible endpoints at: +- `POST /v1/chat/completions` - Chat with AI agents +- Model options: `writer`, `editor`, `publisher` (workflow) -3. **Add Template Tests**: - - Remove the `Skip` attribute from tests in `AgentTemplateSnapshotTests.cs` - - Add new test methods for different template configurations - - Update the `_verificationExcludePatterns` array to match template output patterns - - Update the template location and short name in test methods +## Testing the Template -4. **Generate Snapshots**: - - Run the tests to generate initial snapshots - - Use `DiffEngineTray` to review and accept the generated snapshots +### Running Snapshot Tests -5. **Add Execution Tests** (optional): - - Create an `AgentTemplateExecutionTests.cs` file similar to `AIChatWebExecutionTests.cs` if you want to test that generated templates compile and run +```bash +cd test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests +dotnet test +``` + +### Updating Snapshots + +1. Install `DiffEngineTray` following [these instructions](https://github.com/VerifyTests/DiffEngine/blob/main/docs/tray.md) +2. Run the snapshot tests +3. Use `DiffEngineTray` to review and accept changes + +### Manual Testing + +```bash +cd test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox +. ./activate.ps1 + +# Install template locally +dotnet new install ../../src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents + +# Create test project +mkdir output/test1 +cd output/test1 +dotnet new webapi-agents + +# Configure and test +dotnet user-secrets set "GitHubModels:Token" "your-token" +dotnet run + +# Cleanup +dotnet new uninstall ../../src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents +``` ## Integration with Build System -The project uses the `GenerateTemplateContent` approach for generating template content, which is consistent with other template projects in the repository. +The project uses the `GenerateTemplateContent` approach for template content management, consistent with other template projects in the repository. The test infrastructure is designed to: - Use the locally built .NET SDK from the repository @@ -107,10 +196,20 @@ The test infrastructure is designed to: - Support debugging through the TemplateSandbox directory - Verify template output matches expected snapshots -## Validation +## Package Version Scrubbing + +The snapshot tests include scrubbers to normalize package versions: +- Removes pre-release suffixes from Microsoft.Agents.* and Microsoft.Extensions.* package references +- Normalizes localhost ports in launchSettings.json +- Scrubs UserSecretsId values from project files +- Normalizes solution GUIDs + +This ensures snapshots remain consistent across different build environments (local, public CI, internal CI). + +## Future Enhancements -All created files have been validated: -- Projects compile successfully -- No build errors or warnings -- Infrastructure follows repository conventions -- Namespace consistency (`Microsoft.Agents.AI.Templates.Tests`) +Potential areas for expansion: +- Additional agent workflow patterns (parallel, conditional) +- Multiple AI provider options (Azure OpenAI, Ollama) +- Aspire orchestration support +- Additional template variations (minimal, with authentication, with Swagger) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/README.md new file mode 100644 index 00000000000..59056439776 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/README.md @@ -0,0 +1,3 @@ +# Microsoft.Agents.AI.Templates + +Provides project templates for Microsoft.Agents.AI. diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json new file mode 100644 index 00000000000..0825fe97943 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json @@ -0,0 +1,116 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Microsoft", + "classifications": [ "Common", "AI", "Web", "WebAPI", "Agents" ], + "identity": "Microsoft.Agents.AI.Templates.WebApiAgents.CSharp", + "name": "AI Agents Web API", + "description": "A project template for creating an AI Agents Web API application.", + "shortName": "webapi-agents", + "defaultName": "WebApiAgents", + "sourceName": "WebApiAgents-CSharp", + "preferNameDirectory": true, + "tags": { + "language": "C#", + "type": "project" + }, + "guids": [ + "d5681fae-b21b-4114-b781-48180f08c0c4" + ], + "primaryOutputs": [ + { + "path": "./WebApiAgents-CSharp.csproj" + } + ], + "sources": [ + { + "source": "./", + "target": "./" + } + ], + "symbols": { + "Framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "net10.0", + "description": "Target net10.0" + } + ], + "replaces": "net10.0", + "defaultValue": "net10.0", + "displayName": "Framework" + }, + "hostIdentifier": { + "type": "bind", + "binding": "HostIdentifier" + }, + "httpPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTP endpoint in launchSettings.json." + }, + "httpPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 5000, + "high": 5300 + } + }, + "httpPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "httpPort", + "fallbackVariableName": "httpPortGenerated" + }, + "replaces": "5056", + "onlyIf": [ + { + "after": "localhost:" + } + ] + }, + "httpsPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTPS endpoint in launchSettings.json." + }, + "httpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 7000, + "high": 7300 + } + }, + "httpsPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "httpsPort", + "fallbackVariableName": "httpsPortGenerated" + }, + "replaces": "7041", + "onlyIf": [ + { + "after": "localhost:" + } + ] + } + }, + "postActions": [ + { + "condition": "(hostIdentifier != \"dotnetcli\" && hostIdentifier != \"dotnetcli-preview\")", + "description": "Opens README file in the editor", + "manualInstructions": [], + "actionId": "84C0DA21-51C8-4541-9940-6CA19AF04EE6", + "args": { + "files": "0" + }, + "continueOnError": true + } + ] +} diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs new file mode 100644 index 00000000000..d78c8646da7 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs @@ -0,0 +1,25 @@ +using System.ClientModel; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Extensions.AI; +using OpenAI; + +var builder = WebApplication.CreateBuilder(args); + +var credential = new ApiKeyCredential(builder.Configuration["GitHubModels:Token"] ?? throw new InvalidOperationException("Missing configuration: GitHubModels:Token. See README for details.")); +var openAIOptions = new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }; + +var ghModelsClient = new OpenAIClient(credential, openAIOptions) + .GetChatClient("gpt-4o-mini").AsIChatClient(); + +builder.Services.AddChatClient(ghModelsClient); +var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); +var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); + +builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.MapOpenAIResponses(); + +app.Run(); diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Properties/launchSettings.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Properties/launchSettings.json new file mode 100644 index 00000000000..9bc26dbafff --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5056", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7041;http://localhost:5056", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md new file mode 100644 index 00000000000..f01d03a70ac --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md @@ -0,0 +1,118 @@ +# AI Agents Web API + +This is an AI Agents Web API application created from the `webapi-agents` template. + +## Prerequisites + +- .NET 10.0 SDK or later +- A GitHub Models API token (free to get started) + +## Getting Started + +### 1. Configure Your API Token + +This application uses GitHub Models for AI functionality. You'll need to configure your GitHub Models API token: + +**Option A: Using User Secrets (Recommended for Development)** + +```bash +dotnet user-secrets set "GitHubModels:Token" "your-github-models-token-here" +``` + +**Option B: Using Environment Variables** + +Set the `GitHubModels__Token` environment variable: + +- **Windows (PowerShell)**: + ```powershell + $env:GitHubModels__Token = "your-github-models-token-here" + ``` + +- **Linux/macOS**: + ```bash + export GitHubModels__Token="your-github-models-token-here" + ``` + +**Option C: Using appsettings.Development.json (Not Recommended - Do Not Commit)** + +Create an `appsettings.Development.json` file: + +```json +{ + "GitHubModels": { + "Token": "your-github-models-token-here" + } +} +``` + +?? **Important**: Never commit API tokens to source control. Add `appsettings.Development.json` to your `.gitignore` file. + +### 2. Get a GitHub Models Token + +1. Visit [GitHub Models](https://github.com/marketplace/models) +2. Sign in with your GitHub account +3. Select a model (e.g., gpt-4o-mini) +4. Click "Get API Key" or follow the authentication instructions +5. Copy your personal access token + +### 3. Run the Application + +```bash +dotnet run +``` + +The application will start and listen on: +- HTTP: `http://localhost:5056` +- HTTPS: `https://localhost:7041` + +### 4. Test the Application + +The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. + +Example using `curl`: + +```bash +curl -X POST https://localhost:7041/v1/chat/completions \ + -H "Content-Type: application/json" \ +-d '{ + "model": "publisher", + "messages": [ + { +"role": "user", + "content": "Write a story about a robot learning to paint." + } + ] + }' +``` + +## How It Works + +This application demonstrates the AI Agents framework with: + +1. **Writer Agent**: Writes short stories (300 words or less) about specified topics +2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words +3. **Publisher Workflow**: A sequential workflow that combines the writer and editor agents + +The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. + +## Project Structure + +- `Program.cs` - Application entry point and configuration +- `appsettings.json` - Application configuration +- `Properties/launchSettings.json` - Launch profiles for development + +## Learn More + +- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [GitHub Models](https://github.com/marketplace/models) +- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) + +## Troubleshooting + +**Problem**: Application fails with "Missing configuration: GitHubModels:Token" + +**Solution**: Make sure you've configured your GitHub Models API token using one of the methods described above. + +**Problem**: API requests fail with authentication errors + +**Solution**: Verify your GitHub Models token is valid and hasn't expired. You may need to regenerate it from the GitHub Models website. diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in new file mode 100644 index 00000000000..72975367dbd --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + d5681fae-b21b-4114-b781-48180f08c0c4 + + + + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/appsettings.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs index c4674664afd..199a1a87445 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs @@ -16,13 +16,12 @@ namespace Microsoft.Agents.AI.Templates.Tests; public class WebApiAgentsTemplateSnapshotTests { - // Exclude patterns will be defined here when actual template content is added. + // Keep the exclude patterns below in sync with those in Microsoft.Agents.AI.Templates.csproj. private static readonly string[] _verificationExcludePatterns = [ "**/bin/**", "**/obj/**", "**/.vs/**", "**/*.user", - "**/*.in", "**/NuGet.config", "**/Directory.Build.targets", "**/Directory.Build.props", @@ -37,8 +36,7 @@ public WebApiAgentsTemplateSnapshotTests(ITestOutputHelper log) #pragma warning restore CA2000 // Dispose objects before losing scope } - // Placeholder test - actual template tests will be added when template content is implemented - [Fact(Skip = "Template content not yet implemented")] + [Fact] public async Task BasicTest() { await TestTemplateCoreAsync(scenarioName: "Basic"); @@ -47,64 +45,64 @@ public async Task BasicTest() private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable? templateArgs = null) { string workingDir = TestUtils.CreateTemporaryFolder(); - string templateShortName = "agentapp"; // Placeholder - will be updated with actual template short name + string templateShortName = "webapi-agents"; - // Get the template location - will be updated when actual template is implemented - string templateLocation = Path.Combine(WellKnownPaths.TemplateFeedLocation, "Microsoft.Agents.AI.Templates", "src", "PlaceholderTemplate"); + // Get the template location + string templateLocation = Path.Combine(WellKnownPaths.TemplateFeedLocation, "Microsoft.Agents.AI.Templates", "src", "WebApiAgents"); var verificationExcludePatterns = Path.DirectorySeparatorChar is '/' - ? _verificationExcludePatterns + ? _verificationExcludePatterns : _verificationExcludePatterns.Select(p => p.Replace('/', Path.DirectorySeparatorChar)).ToArray(); - TemplateVerifierOptions options = new TemplateVerifierOptions(templateName: templateShortName) - { - TemplatePath = templateLocation, - TemplateSpecificArgs = templateArgs, + TemplateVerifierOptions options = new TemplateVerifierOptions(templateName: templateShortName) + { + TemplatePath = templateLocation, + TemplateSpecificArgs = templateArgs, SnapshotsDirectory = "Snapshots", - OutputDirectory = workingDir, - DoNotPrependCallerMethodNameToScenarioName = true, + OutputDirectory = workingDir, + DoNotPrependCallerMethodNameToScenarioName = true, DoNotAppendTemplateArgsToScenarioName = true, - ScenarioName = scenarioName, + ScenarioName = scenarioName, VerificationExcludePatterns = verificationExcludePatterns, } .WithCustomScrubbers( - ScrubbersDefinition.Empty.AddScrubber((path, content) => - { - string filePath = path.UnixifyDirSeparators(); - if (filePath.EndsWith(".sln")) - { - // Scrub .sln file GUIDs. - content.ScrubByRegex(pattern: @"\{.{36}\}", replacement: "{00000000-0000-0000-0000-000000000000}"); - } + ScrubbersDefinition.Empty.AddScrubber((path, content) => + { + string filePath = path.UnixifyDirSeparators(); + if (filePath.EndsWith(".sln")) + { + // Scrub .sln file GUIDs. + content.ScrubByRegex(pattern: @"\{.{36}\}", replacement: "{00000000-0000-0000-0000-000000000000}"); + } - if (filePath.EndsWith(".csproj")) - { - content.ScrubByRegex("(.*)<\\/UserSecretsId>", "secret"); + if (filePath.EndsWith(".csproj")) + { + content.ScrubByRegex("(.*)<\\/UserSecretsId>", "secret"); - // Scrub references to just-built packages and remove the suffix, if it exists. - // This allows the snapshots to remain the same regardless of where the repo is built (e.g., locally, public CI, internal CI). - var pattern = @"(?<=)"; - content.ScrubByRegex(pattern, replacement: "$1"); - } + // Scrub references to just-built packages and remove the suffix, if it exists. + // This allows the snapshots to remain the same regardless of where the repo is built (e.g., locally, public CI, internal CI). + var pattern = @"(?<=)"; + content.ScrubByRegex(pattern, replacement: "$1"); + } if (filePath.EndsWith("launchSettings.json")) - { - content.ScrubByRegex("(http(s?):\\/\\/localhost)\\:(\\d*)", "$1:9999"); - } - })); + { + content.ScrubByRegex("(http(s?):\\/\\/localhost)\\:(\\d*)", "$1:9999"); + } + })); VerificationEngine engine = new VerificationEngine(_log); await engine.Execute(options); #pragma warning disable CA1031 // Do not catch general exception types try - { + { Directory.Delete(workingDir, recursive: true); - } + } catch - { - /* don't care */ + { + /* don't care */ } #pragma warning restore CA1031 // Do not catch general exception types - } + } } From 0b077ed1c1a7572435ddf9cbc4761ae7f881b1e1 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 04:52:39 -0800 Subject: [PATCH 05/22] Support parameterized AI Service Provider --- .../.template.config/dotnetcli.host.json | 16 ++ .../.template.config/ide.host.json | 16 ++ .../.template.config/template.json | 64 ++++++ .../WebApiAgents-CSharp/Program.cs | 60 ++++++ .../WebApiAgents-CSharp/README.md | 191 ++++++++++++++++-- .../WebApiAgents-CSharp.csproj.in | 8 + 6 files changed, 333 insertions(+), 22 deletions(-) create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json new file mode 100644 index 00000000000..931cd9ff2bc --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "AiServiceProvider": { + "longName": "provider", + "shortName": "" + }, + "UseManagedIdentity": { + "longName": "managed-identity", + "shortName": "" + } + }, + "usageExamples": [ + "" + ] +} diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json new file mode 100644 index 00000000000..ca9900c3b85 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/ide.host", + "order": 0, + "icon": "ide/icon.ico", + "displayOverviewPage": "", + "symbolInfo": [ + { + "id": "AiServiceProvider", + "isVisible": true + }, + { + "id": "UseManagedIdentity", + "isVisible": true + } + ] +} diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json index 0825fe97943..4af93490d98 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json @@ -36,6 +36,14 @@ { "choice": "net10.0", "description": "Target net10.0" + }, + { + "choice": "net9.0", + "description": "Target net9.0" + }, + { + "choice": "net8.0", + "description": "Target net8.0" } ], "replaces": "net10.0", @@ -46,6 +54,62 @@ "type": "bind", "binding": "HostIdentifier" }, + "AiServiceProvider": { + "type": "parameter", + "displayName": "_AI service provider", + "datatype": "choice", + "defaultValue": "githubmodels", + "choices": [ + { + "choice": "azureopenai", + "displayName": "Azure OpenAI", + "description": "Uses Azure OpenAI service" + }, + { + "choice": "githubmodels", + "displayName": "GitHub Models", + "description": "Uses GitHub Models" + }, + { + "choice": "ollama", + "displayName": "Ollama (for local development)", + "description": "Uses Ollama with the llama3.2 model" + }, + { + "choice": "openai", + "displayName": "OpenAI Platform", + "description": "Uses the OpenAI Platform" + } + ] + }, + "UseManagedIdentity": { + "type": "parameter", + "displayName": "Use keyless authentication for Azure services", + "datatype": "bool", + "defaultValue": "true", + "isEnabled": "(AiServiceProvider == \"azureopenai\")", + "description": "Use managed identity to access Azure services" + }, + "IsManagedIdentity": { + "type": "computed", + "value": "(UseManagedIdentity)" + }, + "IsAzureOpenAI": { + "type": "computed", + "value": "(AiServiceProvider == \"azureopenai\")" + }, + "IsOpenAI": { + "type": "computed", + "value": "(AiServiceProvider == \"openai\")" + }, + "IsGHModels": { + "type": "computed", + "value": "(AiServiceProvider == \"githubmodels\")" + }, + "IsOllama": { + "type": "computed", + "value": "(AiServiceProvider == \"ollama\")" + }, "httpPort": { "type": "parameter", "datatype": "integer", diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs index d78c8646da7..24337017821 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/Program.cs @@ -1,10 +1,24 @@ +#if (IsGHModels || IsOpenAI || (IsAzureOpenAI && !IsManagedIdentity)) using System.ClientModel; +#elif (IsAzureOpenAI && IsManagedIdentity) +using System.ClientModel.Primitives; +using Azure.Identity; +#endif using Microsoft.Agents.AI.Hosting; using Microsoft.Extensions.AI; +#if (IsOllama) +using OllamaSharp; +#elif (IsGHModels || IsOpenAI || IsAzureOpenAI) using OpenAI; +#endif var builder = WebApplication.CreateBuilder(args); +#if (IsGHModels) +// You will need to set the token to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set GitHubModels:Token YOUR-GITHUB-TOKEN var credential = new ApiKeyCredential(builder.Configuration["GitHubModels:Token"] ?? throw new InvalidOperationException("Missing configuration: GitHubModels:Token. See README for details.")); var openAIOptions = new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }; @@ -12,6 +26,52 @@ .GetChatClient("gpt-4o-mini").AsIChatClient(); builder.Services.AddChatClient(ghModelsClient); +#elif (IsOllama) +// You will need to have Ollama running locally with the llama3.2 model installed +// Visit https://ollama.com for installation instructions +IChatClient chatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2"); + +builder.Services.AddChatClient(chatClient); +#elif (IsOpenAI) +// You will need to set the API key to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set OpenAI:Key YOUR-API-KEY +var openAIClient = new OpenAIClient( + new ApiKeyCredential(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: OpenAI:Key. See README for details."))); + +#pragma warning disable OPENAI001 // GetOpenAIResponseClient(string) is experimental and subject to change or removal in future updates. +var chatClient = openAIClient.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); +#pragma warning restore OPENAI001 + +builder.Services.AddChatClient(chatClient); +#elif (IsAzureOpenAI) +// You will need to set the endpoint to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set AzureOpenAI:Endpoint https://YOUR-DEPLOYMENT-NAME.openai.azure.com +#if (!IsManagedIdentity) +// dotnet user-secrets set AzureOpenAI:Key YOUR-API-KEY +#endif +var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAI:Endpoint. See README for details.")), "/openai/v1"); +#if (IsManagedIdentity) +#pragma warning disable OPENAI001 // OpenAIClient(AuthenticationPolicy, OpenAIClientOptions) and GetOpenAIResponseClient(string) are experimental and subject to change or removal in future updates. +var azureOpenAI = new OpenAIClient( + new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), + new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }); + +#elif (!IsManagedIdentity) +var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; +var azureOpenAI = new OpenAIClient(new ApiKeyCredential(builder.Configuration["AzureOpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAI:Key. See README for details.")), openAIOptions); + +#pragma warning disable OPENAI001 // GetOpenAIResponseClient(string) is experimental and subject to change or removal in future updates. +#endif +var chatClient = azureOpenAI.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); +#pragma warning restore OPENAI001 + +builder.Services.AddChatClient(chatClient); +#endif + var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md index f01d03a70ac..f38c922755a 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md @@ -5,11 +5,22 @@ This is an AI Agents Web API application created from the `webapi-agents` templa ## Prerequisites - .NET 10.0 SDK or later + - A GitHub Models API token (free to get started) + +- An OpenAI API key + +- An Azure OpenAI service deployment + +- Ollama installed locally with the llama3.2 model + ## Getting Started -### 1. Configure Your API Token +### 1. Configure Your AI Service + + +#### GitHub Models Configuration This application uses GitHub Models for AI functionality. You'll need to configure your GitHub Models API token: @@ -33,21 +44,7 @@ Set the `GitHubModels__Token` environment variable: export GitHubModels__Token="your-github-models-token-here" ``` -**Option C: Using appsettings.Development.json (Not Recommended - Do Not Commit)** - -Create an `appsettings.Development.json` file: - -```json -{ - "GitHubModels": { - "Token": "your-github-models-token-here" - } -} -``` - -?? **Important**: Never commit API tokens to source control. Add `appsettings.Development.json` to your `.gitignore` file. - -### 2. Get a GitHub Models Token +#### Get a GitHub Models Token 1. Visit [GitHub Models](https://github.com/marketplace/models) 2. Sign in with your GitHub account @@ -55,7 +52,107 @@ Create an `appsettings.Development.json` file: 4. Click "Get API Key" or follow the authentication instructions 5. Copy your personal access token -### 3. Run the Application + +#### OpenAI Configuration + +This application uses the OpenAI Platform. You'll need to configure your OpenAI API key: + +**Using User Secrets (Recommended for Development)** + +```bash +dotnet user-secrets set "OpenAI:Key" "your-openai-api-key-here" +``` + +**Using Environment Variables** + +Set the `OpenAI__Key` environment variable: + +- **Windows (PowerShell)**: + ```powershell + $env:OpenAI__Key = "your-openai-api-key-here" + ``` + +- **Linux/macOS**: + ```bash + export OpenAI__Key="your-openai-api-key-here" + ``` + +#### Get an OpenAI API Key + +1. Visit [OpenAI Platform](https://platform.openai.com) +2. Sign in or create an account +3. Navigate to API Keys +4. Create a new API key +5. Copy your API key + + +#### Azure OpenAI Configuration + +This application uses Azure OpenAI service. You'll need to configure your Azure OpenAI endpoint and API key: + +**Using User Secrets (Recommended for Development)** + +```bash +dotnet user-secrets set "AzureOpenAI:Endpoint" "https://YOUR-DEPLOYMENT-NAME.openai.azure.com" + +dotnet user-secrets set "AzureOpenAI:Key" "your-azure-openai-key-here" + +``` + +**Using Environment Variables** + +- **Windows (PowerShell)**: + ```powershell + $env:AzureOpenAI__Endpoint = "https://YOUR-DEPLOYMENT-NAME.openai.azure.com" + + $env:AzureOpenAI__Key = "your-azure-openai-key-here" + + ``` + +- **Linux/macOS**: + ```bash + export AzureOpenAI__Endpoint="https://YOUR-DEPLOYMENT-NAME.openai.azure.com" + + export AzureOpenAI__Key="your-azure-openai-key-here" + + ``` + + +#### Managed Identity Authentication + +This application is configured to use Azure Managed Identity for authentication. When deploying to Azure: + +1. Ensure your Azure resource (App Service, Container Apps, etc.) has a managed identity enabled +2. Grant the managed identity access to your Azure OpenAI resource with the "Cognitive Services OpenAI User" role +3. No API key configuration is needed + +For local development, ensure you're signed in to Azure CLI or have configured DefaultAzureCredential appropriately. + + +#### Set Up Azure OpenAI + +1. Visit [Azure Portal](https://portal.azure.com) +2. Create an Azure OpenAI resource +3. Deploy a model (e.g., gpt-4o-mini) +4. Copy your endpoint and API key + + +#### Ollama Configuration + +This application uses Ollama running locally. You'll need to have Ollama installed and the llama3.2 model downloaded: + +1. Visit [Ollama](https://ollama.com) and follow the installation instructions for your platform +2. Once installed, download the llama3.2 model: + ```bash + ollama pull llama3.2 + ``` +3. Ensure Ollama is running (it starts automatically after installation) + +The application is configured to connect to Ollama at `http://localhost:11434`. + + + +### 2. Run the Application ```bash dotnet run @@ -65,7 +162,7 @@ The application will start and listen on: - HTTP: `http://localhost:5056` - HTTPS: `https://localhost:7041` -### 4. Test the Application +### 3. Test the Application The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. @@ -74,12 +171,12 @@ Example using `curl`: ```bash curl -X POST https://localhost:7041/v1/chat/completions \ -H "Content-Type: application/json" \ --d '{ + -d '{ "model": "publisher", "messages": [ - { -"role": "user", - "content": "Write a story about a robot learning to paint." + { + "role": "user", + "content": "Write a story about a robot learning to paint." } ] }' @@ -104,11 +201,20 @@ The agents are exposed through OpenAI-compatible API endpoints, making them easy ## Learn More - [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) + - [GitHub Models](https://github.com/marketplace/models) + +- [OpenAI Platform](https://platform.openai.com) + +- [Azure OpenAI Service](https://azure.microsoft.com/products/ai-services/openai-service) + +- [Ollama](https://ollama.com) + - [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting + **Problem**: Application fails with "Missing configuration: GitHubModels:Token" **Solution**: Make sure you've configured your GitHub Models API token using one of the methods described above. @@ -116,3 +222,44 @@ The agents are exposed through OpenAI-compatible API endpoints, making them easy **Problem**: API requests fail with authentication errors **Solution**: Verify your GitHub Models token is valid and hasn't expired. You may need to regenerate it from the GitHub Models website. + + +**Problem**: Application fails with "Missing configuration: OpenAI:Key" + +**Solution**: Make sure you've configured your OpenAI API key using one of the methods described above. + +**Problem**: API requests fail with authentication errors + +**Solution**: Verify your OpenAI API key is valid. Check your usage limits and billing status on the OpenAI Platform. + + +**Problem**: Application fails with "Missing configuration: AzureOpenAI:Endpoint" or "Missing configuration: AzureOpenAI:Key" + +**Solution**: Make sure you've configured your Azure OpenAI endpoint and API key using one of the methods described above. + + +**Problem**: Managed identity authentication fails + +**Solution**: +- Ensure your Azure resource has a system-assigned or user-assigned managed identity enabled +- Verify the managed identity has been granted the "Cognitive Services OpenAI User" role on your Azure OpenAI resource +- For local development, ensure you're signed in to Azure CLI: `az login` + + +**Problem**: API requests fail with authentication errors + +**Solution**: Verify your Azure OpenAI endpoint is correct and your API key is valid your managed identity has the correct permissions. + + +**Problem**: Application fails to connect to Ollama + +**Solution**: +- Ensure Ollama is running. On macOS/Linux, check with `pgrep ollama`. On Windows, check Task Manager. +- Verify Ollama is accessible at `http://localhost:11434` +- Make sure you've downloaded the llama3.2 model: `ollama pull llama3.2` + +**Problem**: Model responses are slow or time out + +**Solution**: Ollama runs locally and performance depends on your hardware. Consider using a smaller model or ensuring your system has adequate resources (RAM, GPU if available). + + diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in index 72975367dbd..a5a3198b33f 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in @@ -10,8 +10,16 @@ + + + + + + + + From 457c817a2c85ab83a21b3b4785555d0b0ad0550e Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 05:13:19 -0800 Subject: [PATCH 06/22] Rename to aiagents-webapi --- .../PROJECT_STRUCTURE.md | 12 ++++++------ .../.template.config/template.json | 4 ++-- .../src/WebApiAgents/WebApiAgents-CSharp/README.md | 2 +- .../WebApiAgentsTemplateSnapshotTests.cs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md index 332cc33474d..9180e6147bd 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md @@ -29,7 +29,7 @@ The `Microsoft.Agents.AI.Templates` project is a .NET project template package t **Template Content**: -#### WebApiAgents Template (`webapi-agents`) +#### WebApiAgents Template (`aiagents-webapi`) **Location**: `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/` A simple ASP.NET Core Web API template that demonstrates the AI Agents framework with: @@ -40,7 +40,7 @@ A simple ASP.NET Core Web API template that demonstrates the AI Agents framework **Template Files**: - `.template.config/template.json` - Template definition - - Short name: `webapi-agents` + - Short name: `aiagents-webapi` - Default name: `AgentsApp` - Source name: `WebApiAgents-CSharp` - Target framework: net10.0 @@ -77,7 +77,7 @@ A simple ASP.NET Core Web API template that demonstrates the AI Agents framework **Key Files**: - `Microsoft.Agents.AI.Templates.Tests.csproj` - The integration test project - `README.md` - Documentation for running and updating template tests -- `WebApiAgentsTemplateSnapshotTests.cs` - Snapshot tests for webapi-agents template +- `WebApiAgentsTemplateSnapshotTests.cs` - Snapshot tests for aiagents-webapi template - `ProjectRootHelper.cs` - Helper to locate the test project root - `VerifyScrubbers.cs` - Utilities for scrubbing variable content from test snapshots @@ -124,10 +124,10 @@ dotnet new install artifacts/packages/Debug/Shipping/Microsoft.Agents.AI.Templat ```bash # Basic usage -dotnet new webapi-agents -n MyAgentsApp +dotnet new aiagents-webapi -n MyAgentsApp # With custom ports -dotnet new webapi-agents -n MyAgentsApp --httpPort 5100 --httpsPort 7100 +dotnet new aiagents-webapi -n MyAgentsApp --httpPort 5100 --httpsPort 7100 ``` ### Configuring the Project @@ -176,7 +176,7 @@ dotnet new install ../../src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/ # Create test project mkdir output/test1 cd output/test1 -dotnet new webapi-agents +dotnet new aiagents-webapi # Configure and test dotnet user-secrets set "GitHubModels:Token" "your-token" diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json index 4af93490d98..bbe4c15e30e 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json @@ -1,11 +1,11 @@ { "$schema": "http://json.schemastore.org/template", "author": "Microsoft", - "classifications": [ "Common", "AI", "Web", "WebAPI", "Agents" ], + "classifications": [ "Common", "AI", "Agents", "API", "Web", "Web API", "WebAPI", "Service" ], "identity": "Microsoft.Agents.AI.Templates.WebApiAgents.CSharp", "name": "AI Agents Web API", "description": "A project template for creating an AI Agents Web API application.", - "shortName": "webapi-agents", + "shortName": "aiagents-webapi", "defaultName": "WebApiAgents", "sourceName": "WebApiAgents-CSharp", "preferNameDirectory": true, diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md index f38c922755a..4813f73b2e0 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md @@ -1,6 +1,6 @@ # AI Agents Web API -This is an AI Agents Web API application created from the `webapi-agents` template. +This is an AI Agents Web API application created from the `aiagents-webapi` template. ## Prerequisites diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs index 199a1a87445..27c52d137f6 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs @@ -45,7 +45,7 @@ public async Task BasicTest() private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable? templateArgs = null) { string workingDir = TestUtils.CreateTemporaryFolder(); - string templateShortName = "webapi-agents"; + string templateShortName = "aiagents-webapi"; // Get the template location string templateLocation = Path.Combine(WellKnownPaths.TemplateFeedLocation, "Microsoft.Agents.AI.Templates", "src", "WebApiAgents"); From c8f72a6a8d7abe2a7b9713c3bb4a82e764af7a69 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 05:23:20 -0800 Subject: [PATCH 07/22] Support parameterized chatmodel and update docs with renames --- .../PROJECT_STRUCTURE.md | 113 +++++++++++++----- .../.template.config/template.json | 37 ++++++ .../WebApiAgents-CSharp/README.md | 44 ++++++- 3 files changed, 162 insertions(+), 32 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md index 9180e6147bd..b7f45e52de7 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md @@ -30,7 +30,7 @@ The `Microsoft.Agents.AI.Templates` project is a .NET project template package t **Template Content**: #### WebApiAgents Template (`aiagents-webapi`) -**Location**: `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/` +**Location**: `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/` A simple ASP.NET Core Web API template that demonstrates the AI Agents framework with: - **Writer Agent**: Writes short stories (300 words or less) about specified topics @@ -41,34 +41,55 @@ A simple ASP.NET Core Web API template that demonstrates the AI Agents framework **Template Files**: - `.template.config/template.json` - Template definition - Short name: `aiagents-webapi` - - Default name: `AgentsApp` + - Default name: `WebApiAgents` - Source name: `WebApiAgents-CSharp` - - Target framework: net10.0 + - Target frameworks: net10.0 (default), net9.0, net8.0 - Configurable HTTP/HTTPS ports via template parameters -- `WebApiAgents-CSharp.csproj` - Project file with Microsoft.Agents.AI package references -- `Program.cs` - Application entry point with AI agent configuration + - **AI Service Provider Parameter** (`--provider`): + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + - **Chat Model Parameter** (`--ChatModel`): + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + - **Use Managed Identity Parameter** (`--UseManagedIdentity`): + - Default: `true` (when using Azure OpenAI) + - Enables keyless authentication for Azure services +- `WebApiAgents-CSharp.csproj.in` - Project file template with conditional package references +- `WebApiAgents-CSharp.csproj` - Static project file (for development/testing) +- `Program.cs` - Application entry point with conditional AI service configuration - `Properties/launchSettings.json` - Launch profiles with port configurations - `appsettings.json` - Application configuration - `README.md` - Comprehensive getting started guide with: - - GitHub Models token configuration instructions - - Multiple configuration methods (user secrets, environment variables, appsettings) + - Provider-specific configuration instructions + - Multiple configuration methods (user secrets, environment variables) + - Template parameter documentation - Example API usage with curl -- Troubleshooting guidance + - Troubleshooting guidance - `.gitignore` - Git ignore file to prevent committing sensitive data -**Package References**: -- `Microsoft.Agents.AI` (1.0.0-preview.251104.1) -- `Microsoft.Agents.AI.Hosting` (1.0.0-preview.251104.1) -- `Microsoft.Agents.AI.Hosting.OpenAI` (1.0.0-alpha.251104.1) -- `Microsoft.Agents.AI.OpenAI` (1.0.0-preview.251104.1) -- `Microsoft.Agents.AI.Workflows` (1.0.0-preview.251104.1) +**Conditional Package References** (based on AI Service Provider): +- **All providers**: + - `Microsoft.Agents.AI` (1.0.0-preview.251104.1) + - `Microsoft.Agents.AI.Hosting` (1.0.0-preview.251104.1) + - `Microsoft.Agents.AI.Workflows` (1.0.0-preview.251104.1) +- **OpenAI/Azure OpenAI/GitHub Models**: + - `Microsoft.Agents.AI.Hosting.OpenAI` (1.0.0-alpha.251104.1) + - `Microsoft.Agents.AI.OpenAI` (1.0.0-preview.251104.1) +- **Ollama**: + - `OllamaSharp` (4.1.2) +- **Azure OpenAI with Managed Identity**: + - `Azure.Identity` (1.13.1) **Template Features**: -- Uses GitHub Models (gpt-4o-mini) via Azure AI Inference endpoint -- Demonstrates sequential workflow pattern -- Exposes agents via OpenAI-compatible REST API -- Includes comprehensive README with setup instructions -- Configured for HTTPS by default +- Multi-provider support (GitHub Models, OpenAI, Azure OpenAI, Ollama) +- Configurable chat models with provider-specific defaults +- Managed identity support for Azure OpenAI +- Sequential workflow pattern demonstration +- OpenAI-compatible REST API endpoints +- Comprehensive documentation with provider-specific guidance +- HTTPS configured by default - Minimal API design pattern ### Test Project @@ -123,20 +144,48 @@ dotnet new install artifacts/packages/Debug/Shipping/Microsoft.Agents.AI.Templat ### Creating a New Project ```bash -# Basic usage +# Basic usage (GitHub Models with gpt-4o-mini) dotnet new aiagents-webapi -n MyAgentsApp # With custom ports dotnet new aiagents-webapi -n MyAgentsApp --httpPort 5100 --httpsPort 7100 + +# Using Azure OpenAI with managed identity +dotnet new aiagents-webapi -n MyAgentsApp --provider azureopenai + +# Using Azure OpenAI with API key +dotnet new aiagents-webapi -n MyAgentsApp --provider azureopenai --UseManagedIdentity false + +# Using OpenAI Platform with custom model +dotnet new aiagents-webapi -n MyAgentsApp --provider openai --ChatModel gpt-4o + +# Using Ollama with custom model +dotnet new aiagents-webapi -n MyAgentsApp --provider ollama --ChatModel llama3.1 + +# Target specific framework +dotnet new aiagents-webapi -n MyAgentsApp --Framework net9.0 ``` ### Configuring the Project -After creating a project, configure the GitHub Models token: +After creating a project, configure the appropriate credentials: ```bash cd MyAgentsApp + +# For GitHub Models dotnet user-secrets set "GitHubModels:Token" "your-token-here" + +# For OpenAI Platform +dotnet user-secrets set "OpenAI:Key" "your-api-key-here" + +# For Azure OpenAI (with API key) +dotnet user-secrets set "AzureOpenAI:Endpoint" "https://your-resource.openai.azure.com" +dotnet user-secrets set "AzureOpenAI:Key" "your-api-key-here" + +# For Azure OpenAI (with managed identity) +dotnet user-secrets set "AzureOpenAI:Endpoint" "https://your-resource.openai.azure.com" +# Ensure you're signed in: az login ``` ### Running the Project @@ -147,7 +196,7 @@ dotnet run The application exposes OpenAI-compatible endpoints at: - `POST /v1/chat/completions` - Chat with AI agents -- Model options: `writer`, `editor`, `publisher` (workflow) +- Available agents: `writer`, `editor`, `publisher` (workflow) ## Testing the Template @@ -173,13 +222,21 @@ cd test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Template # Install template locally dotnet new install ../../src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents -# Create test project -mkdir output/test1 -cd output/test1 +# Create test project with different configurations +mkdir output/test-github-models +cd output/test-github-models dotnet new aiagents-webapi +mkdir ../test-azure-openai +cd ../test-azure-openai +dotnet new aiagents-webapi --provider azureopenai + +mkdir ../test-ollama +cd ../test-ollama +dotnet new aiagents-webapi --provider ollama + # Configure and test -dotnet user-secrets set "GitHubModels:Token" "your-token" +dotnet user-secrets set "GitHubModels:Token" "your-token" # or appropriate config dotnet run # Cleanup @@ -210,6 +267,6 @@ This ensures snapshots remain consistent across different build environments (lo Potential areas for expansion: - Additional agent workflow patterns (parallel, conditional) -- Multiple AI provider options (Azure OpenAI, Ollama) -- Aspire orchestration support - Additional template variations (minimal, with authentication, with Swagger) +- Aspire orchestration support +- More AI service provider options diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json index bbe4c15e30e..009bebd0600 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/template.json @@ -90,6 +90,11 @@ "isEnabled": "(AiServiceProvider == \"azureopenai\")", "description": "Use managed identity to access Azure services" }, + "ChatModel": { + "type": "parameter", + "displayName": "Model/deployment for chat completions. Example: gpt-4o-mini", + "description": "Model/deployment for chat completions. Example: gpt-4o-mini" + }, "IsManagedIdentity": { "type": "computed", "value": "(UseManagedIdentity)" @@ -110,6 +115,38 @@ "type": "computed", "value": "(AiServiceProvider == \"ollama\")" }, + "OpenAiChatModelDefault": { + "type": "generated", + "generator": "constant", + "parameters": { + "value": "gpt-4o-mini" + } + }, + "OpenAiChatModel": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "ChatModel", + "fallbackVariableName": "OpenAiChatModelDefault" + }, + "replaces": "gpt-4o-mini" + }, + "OllamaChatModelDefault": { + "type": "generated", + "generator": "constant", + "parameters": { + "value": "llama3.2" + } + }, + "OllamaChatModel": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "ChatModel", + "fallbackVariableName": "OllamaChatModelDefault" + }, + "replaces": "llama3.2" + }, "httpPort": { "type": "parameter", "datatype": "integer", diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md index 4813f73b2e0..61acb9aea42 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md @@ -22,7 +22,7 @@ This is an AI Agents Web API application created from the `aiagents-webapi` temp #### GitHub Models Configuration -This application uses GitHub Models for AI functionality. You'll need to configure your GitHub Models API token: +This application uses GitHub Models (model: gpt-4o-mini) for AI functionality. You'll need to configure your GitHub Models API token: **Option A: Using User Secrets (Recommended for Development)** @@ -55,7 +55,7 @@ Set the `GitHubModels__Token` environment variable: #### OpenAI Configuration -This application uses the OpenAI Platform. You'll need to configure your OpenAI API key: +This application uses the OpenAI Platform (model: gpt-4o-mini). You'll need to configure your OpenAI API key: **Using User Secrets (Recommended for Development)** @@ -88,7 +88,7 @@ Set the `OpenAI__Key` environment variable: #### Azure OpenAI Configuration -This application uses Azure OpenAI service. You'll need to configure your Azure OpenAI endpoint and API key: +This application uses Azure OpenAI service (model: gpt-4o-mini). You'll need to configure your Azure OpenAI endpoint and API key: **Using User Secrets (Recommended for Development)** @@ -139,7 +139,7 @@ For local development, ensure you're signed in to Azure CLI or have configured D #### Ollama Configuration -This application uses Ollama running locally. You'll need to have Ollama installed and the llama3.2 model downloaded: +This application uses Ollama running locally (model: llama3.2). You'll need to have Ollama installed and the llama3.2 model downloaded: 1. Visit [Ollama](https://ollama.com) and follow the installation instructions for your platform 2. Once installed, download the llama3.2 model: @@ -192,6 +192,42 @@ This application demonstrates the AI Agents framework with: The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. +## Template Parameters + +When creating a new project, you can customize it using template parameters: + +```bash +# Specify AI service provider +dotnet new aiagents-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagents-webapi --ChatModel gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagents-webapi --provider azureopenai --UseManagedIdentity false + +# Use Ollama with a different model +dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--ChatModel`**: Specify the chat model/deployment name + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + +- **`--UseManagedIdentity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--AiServiceProvider azureopenai` + +- **`--Framework`**: Target framework (default: `net10.0`) + - Options: `net10.0`, `net9.0`, `net8.0` + ## Project Structure - `Program.cs` - Application entry point and configuration From 9d3fced510cb6488c5536821620f6ba25d08d1e8 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 05:35:59 -0800 Subject: [PATCH 08/22] Add Snapshot tests --- .../PROJECT_STRUCTURE.md | 4 +-- .../.template.config/ide.host.json | 8 +++-- .../.template.config/ide/agent-framework.ico | Bin 0 -> 99678 bytes .../.template.config/template.json | 29 +++++++++--------- .../WebApiAgents-CSharp/README.md | 27 +++------------- .../WebApiAgents-CSharp.csproj.in | 8 ++--- 6 files changed, 32 insertions(+), 44 deletions(-) create mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide/agent-framework.ico diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md index b7f45e52de7..c6a2fdaf8b9 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md @@ -53,7 +53,7 @@ A simple ASP.NET Core Web API template that demonstrates the AI Agents framework - **Chat Model Parameter** (`--ChatModel`): - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - - **Use Managed Identity Parameter** (`--UseManagedIdentity`): + - **Use Managed Identity Parameter** (`--managed-identity`): - Default: `true` (when using Azure OpenAI) - Enables keyless authentication for Azure services - `WebApiAgents-CSharp.csproj.in` - Project file template with conditional package references @@ -154,7 +154,7 @@ dotnet new aiagents-webapi -n MyAgentsApp --httpPort 5100 --httpsPort 7100 dotnet new aiagents-webapi -n MyAgentsApp --provider azureopenai # Using Azure OpenAI with API key -dotnet new aiagents-webapi -n MyAgentsApp --provider azureopenai --UseManagedIdentity false +dotnet new aiagents-webapi -n MyAgentsApp --provider azureopenai --managed-identity false # Using OpenAI Platform with custom model dotnet new aiagents-webapi -n MyAgentsApp --provider openai --ChatModel gpt-4o diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json index ca9900c3b85..f91c03c283d 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide.host.json @@ -1,13 +1,17 @@ { "$schema": "https://json.schemastore.org/ide.host", - "order": 0, - "icon": "ide/icon.ico", + "order": 1, + "icon": "ide/agent-framework.ico", "displayOverviewPage": "", "symbolInfo": [ { "id": "AiServiceProvider", "isVisible": true }, + { + "id": "ChatModel", + "isVisible": true + }, { "id": "UseManagedIdentity", "isVisible": true diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide/agent-framework.ico b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/ide/agent-framework.ico new file mode 100644 index 0000000000000000000000000000000000000000..b644a8f362783d0794182e1310c393bc96b91f1b GIT binary patch literal 99678 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 - A GitHub Models API token (free to get started) @@ -155,7 +154,7 @@ The application is configured to connect to Ollama at `http://localhost:11434`. ### 2. Run the Application ```bash -dotnet run +dotnet run -lp https ``` The application will start and listen on: @@ -166,29 +165,13 @@ The application will start and listen on: The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. -Example using `curl`: - -```bash -curl -X POST https://localhost:7041/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "publisher", - "messages": [ - { - "role": "user", - "content": "Write a story about a robot learning to paint." - } - ] - }' -``` - ## How It Works This application demonstrates the AI Agents framework with: 1. **Writer Agent**: Writes short stories (300 words or less) about specified topics 2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words -3. **Publisher Workflow**: A sequential workflow that combines the writer and editor agents +3. **Publisher Workflow Agent**: A sequential workflow agent that combines the writer and editor agents The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. @@ -204,7 +187,7 @@ dotnet new aiagents-webapi --provider azureopenai dotnet new aiagents-webapi --ChatModel gpt-4o # Use API key authentication for Azure OpenAI -dotnet new aiagents-webapi --provider azureopenai --UseManagedIdentity false +dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 @@ -222,8 +205,8 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` -- **`--UseManagedIdentity`**: Use managed identity for Azure services (default: `true`) - - Only applicable when `--AiServiceProvider azureopenai` +- **`--managed-identity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--provider azureopenai` - **`--Framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in index a5a3198b33f..7ab75edde39 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in @@ -4,21 +4,21 @@ net10.0 enable enable - d5681fae-b21b-4114-b781-48180f08c0c4 + c7e8f3d2-1a4b-5c6d-8e9f-0a1b2c3d4e5f - + - + - + From 78d399f7043ec79e168467815b213cef5051e12b Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 07:09:01 -0800 Subject: [PATCH 09/22] Add aiagents-webapi snapshot tests --- .../aiagents-webapi/Program.cs | 33 +++++ .../Properties/launchSettings.json | 23 ++++ .../aiagents-webapi/README.md | 122 +++++++++++++++++ .../aiagents-webapi/aiagents-webapi.csproj | 18 +++ .../aiagents-webapi/appsettings.json | 9 ++ .../aiagents-webapi/Program.cs | 34 +++++ .../Properties/launchSettings.json | 23 ++++ .../aiagents-webapi/README.md | 89 ++++++++++++ .../aiagents-webapi/aiagents-webapi.csproj | 19 +++ .../aiagents-webapi/appsettings.json | 9 ++ .../aiagents-webapi/Program.cs | 30 +++++ .../Properties/launchSettings.json | 23 ++++ .../aiagents-webapi/README.md | 127 ++++++++++++++++++ .../aiagents-webapi/aiagents-webapi.csproj | 18 +++ .../aiagents-webapi/appsettings.json | 9 ++ .../aiagents-webapi/Program.cs | 30 +++++ .../Properties/launchSettings.json | 23 ++++ .../aiagents-webapi/README.md | 127 ++++++++++++++++++ .../aiagents-webapi/aiagents-webapi.csproj | 18 +++ .../aiagents-webapi/appsettings.json | 9 ++ .../aiagents-webapi/Program.cs | 23 ++++ .../Properties/launchSettings.json | 23 ++++ .../aiagents-webapi/README.md | 111 +++++++++++++++ .../aiagents-webapi/aiagents-webapi.csproj | 18 +++ .../aiagents-webapi/appsettings.json | 9 ++ .../aiagents-webapi/Program.cs | 31 +++++ .../Properties/launchSettings.json | 23 ++++ .../aiagents-webapi/README.md | 127 ++++++++++++++++++ .../aiagents-webapi/aiagents-webapi.csproj | 18 +++ .../aiagents-webapi/appsettings.json | 9 ++ .../WebApiAgentsTemplateSnapshotTests.cs | 116 ++++++++++------ 31 files changed, 1260 insertions(+), 41 deletions(-) create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Program.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Properties/launchSettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/aiagents-webapi.csproj create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/appsettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Program.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Properties/launchSettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/aiagents-webapi.csproj create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/appsettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Program.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Properties/launchSettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/aiagents-webapi.csproj create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/appsettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Program.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Properties/launchSettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/aiagents-webapi.csproj create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/appsettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Program.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Properties/launchSettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/aiagents-webapi.csproj create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/appsettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Program.cs create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Properties/launchSettings.json create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/aiagents-webapi.csproj create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Program.cs new file mode 100644 index 00000000000..3715d7ed8e1 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Program.cs @@ -0,0 +1,33 @@ +using System.ClientModel; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Extensions.AI; +using OpenAI; + +var builder = WebApplication.CreateBuilder(args); + +// You will need to set the endpoint to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set AzureOpenAI:Endpoint https://YOUR-DEPLOYMENT-NAME.openai.azure.com +// dotnet user-secrets set AzureOpenAI:Key YOUR-API-KEY +var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAI:Endpoint. See README for details.")), "/openai/v1"); +var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }; +var azureOpenAI = new OpenAIClient(new ApiKeyCredential(builder.Configuration["AzureOpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAI:Key. See README for details.")), openAIOptions); + +#pragma warning disable OPENAI001 // GetOpenAIResponseClient(string) is experimental and subject to change or removal in future updates. +var chatClient = azureOpenAI.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); +#pragma warning restore OPENAI001 + +builder.Services.AddChatClient(chatClient); + +var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); +var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); + +builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.MapOpenAIResponses(); + +app.Run(); diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:9999;http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md new file mode 100644 index 00000000000..d77c1d81152 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md @@ -0,0 +1,122 @@ +# AI Agents Web API + +This is an AI Agents Web API application created from the `aiagents-webapi` template. + +## Prerequisites + +- An Azure OpenAI service deployment + +## Getting Started + +### 1. Configure Your AI Service + +#### Azure OpenAI Configuration + + +**Using User Secrets (Recommended for Development)** + +```bash +dotnet user-secrets set "AzureOpenAI:Endpoint" "https://YOUR-DEPLOYMENT-NAME.openai.azure.com" +dotnet user-secrets set "AzureOpenAI:Key" "your-azure-openai-key-here" +``` + +**Using Environment Variables** + +- **Windows (PowerShell)**: + ```powershell + $env:AzureOpenAI__Endpoint = "https://YOUR-DEPLOYMENT-NAME.openai.azure.com" + $env:AzureOpenAI__Key = "your-azure-openai-key-here" + ``` + +- **Linux/macOS**: + ```bash + export AzureOpenAI__Endpoint="https://YOUR-DEPLOYMENT-NAME.openai.azure.com" + export AzureOpenAI__Key="your-azure-openai-key-here" + ``` + +#### Set Up Azure OpenAI + +1. Visit [Azure Portal](https://portal.azure.com) +2. Create an Azure OpenAI resource +3. Deploy a model (e.g., gpt-4o-mini) + + +### 2. Run the Application + +```bash +dotnet run -lp https +``` + +The application will start and listen on: +- HTTP: `http://localhost:9999` +- HTTPS: `https://localhost:9999` + +### 3. Test the Application + +The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. + +## How It Works + +This application demonstrates the AI Agents framework with: + +1. **Writer Agent**: Writes short stories (300 words or less) about specified topics +2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words +3. **Publisher Workflow Agent**: A sequential workflow agent that combines the writer and editor agents + +The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. + +## Template Parameters + +When creating a new project, you can customize it using template parameters: + +```bash +# Specify AI service provider +dotnet new aiagents-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagents-webapi --ChatModel gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagents-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--ChatModel`**: Specify the chat model/deployment name + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + +- **`--managed-identity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--provider azureopenai` + +- **`--Framework`**: Target framework (default: `net10.0`) + - Options: `net10.0`, `net9.0`, `net8.0` + +## Project Structure + +- `Program.cs` - Application entry point and configuration +- `appsettings.json` - Application configuration +- `Properties/launchSettings.json` - Launch profiles for development + +## Learn More + +- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [Azure OpenAI Service](https://azure.microsoft.com/products/ai-services/openai-service) +- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) + +## Troubleshooting + + + +**Problem**: API requests fail with authentication errors + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/aiagents-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/aiagents-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Program.cs new file mode 100644 index 00000000000..744ee0d506a --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Program.cs @@ -0,0 +1,34 @@ +using System.ClientModel.Primitives; +using Azure.Identity; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Extensions.AI; +using OpenAI; + +var builder = WebApplication.CreateBuilder(args); + +// You will need to set the endpoint to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set AzureOpenAI:Endpoint https://YOUR-DEPLOYMENT-NAME.openai.azure.com +var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAI:Endpoint. See README for details.")), "/openai/v1"); +#pragma warning disable OPENAI001 // OpenAIClient(AuthenticationPolicy, OpenAIClientOptions) and GetOpenAIResponseClient(string) are experimental and subject to change or removal in future updates. +var azureOpenAI = new OpenAIClient( + new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), + new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint }); + +var chatClient = azureOpenAI.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); +#pragma warning restore OPENAI001 + +builder.Services.AddChatClient(chatClient); + +var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); +var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); + +builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.MapOpenAIResponses(); + +app.Run(); diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:9999;http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md new file mode 100644 index 00000000000..4a720b1878e --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md @@ -0,0 +1,89 @@ +# AI Agents Web API + +This is an AI Agents Web API application created from the `aiagents-webapi` template. + +## Prerequisites + +- An Azure OpenAI service deployment + +## Getting Started + +### 1. Configure Your AI Service + +#### Azure OpenAI Configuration + + +### 2. Run the Application + +```bash +dotnet run -lp https +``` + +The application will start and listen on: +- HTTP: `http://localhost:9999` +- HTTPS: `https://localhost:9999` + +### 3. Test the Application + +The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. + +## How It Works + +This application demonstrates the AI Agents framework with: + +1. **Writer Agent**: Writes short stories (300 words or less) about specified topics +2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words +3. **Publisher Workflow Agent**: A sequential workflow agent that combines the writer and editor agents + +The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. + +## Template Parameters + +When creating a new project, you can customize it using template parameters: + +```bash +# Specify AI service provider +dotnet new aiagents-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagents-webapi --ChatModel gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagents-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--ChatModel`**: Specify the chat model/deployment name + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + +- **`--managed-identity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--provider azureopenai` + +- **`--Framework`**: Target framework (default: `net10.0`) + - Options: `net10.0`, `net9.0`, `net8.0` + +## Project Structure + +- `Program.cs` - Application entry point and configuration +- `appsettings.json` - Application configuration +- `Properties/launchSettings.json` - Launch profiles for development + +## Learn More + +- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [Azure OpenAI Service](https://azure.microsoft.com/products/ai-services/openai-service) +- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) + +## Troubleshooting + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/aiagents-webapi.csproj new file mode 100644 index 00000000000..d1029ebe6a1 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/aiagents-webapi.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Program.cs new file mode 100644 index 00000000000..edc4f80c68e --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Program.cs @@ -0,0 +1,30 @@ +using System.ClientModel; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Extensions.AI; +using OpenAI; + +var builder = WebApplication.CreateBuilder(args); + +// You will need to set the token to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set GitHubModels:Token YOUR-GITHUB-TOKEN +var credential = new ApiKeyCredential(builder.Configuration["GitHubModels:Token"] ?? throw new InvalidOperationException("Missing configuration: GitHubModels:Token. See README for details.")); +var openAIOptions = new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }; + +var ghModelsClient = new OpenAIClient(credential, openAIOptions) + .GetChatClient("gpt-4o-mini").AsIChatClient(); + +builder.Services.AddChatClient(ghModelsClient); + +var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); +var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); + +builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.MapOpenAIResponses(); + +app.Run(); diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:9999;http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md new file mode 100644 index 00000000000..79fef0c4dba --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md @@ -0,0 +1,127 @@ +# AI Agents Web API + +This is an AI Agents Web API application created from the `aiagents-webapi` template. + +## Prerequisites + +- A GitHub Models API token (free to get started) + +## Getting Started + +### 1. Configure Your AI Service + +#### GitHub Models Configuration + +This application uses GitHub Models (model: gpt-4o-mini) for AI functionality. You'll need to configure your GitHub Models API token: + +**Option A: Using User Secrets (Recommended for Development)** + +```bash +dotnet user-secrets set "GitHubModels:Token" "your-github-models-token-here" +``` + +**Option B: Using Environment Variables** + +Set the `GitHubModels__Token` environment variable: + +- **Windows (PowerShell)**: + ```powershell + $env:GitHubModels__Token = "your-github-models-token-here" + ``` + +- **Linux/macOS**: + ```bash + export GitHubModels__Token="your-github-models-token-here" + ``` + +#### Get a GitHub Models Token + +1. Visit [GitHub Models](https://github.com/marketplace/models) +2. Sign in with your GitHub account +3. Select a model (e.g., gpt-4o-mini) +4. Click "Get API Key" or follow the authentication instructions +5. Copy your personal access token + + +### 2. Run the Application + +```bash +dotnet run -lp https +``` + +The application will start and listen on: +- HTTP: `http://localhost:9999` +- HTTPS: `https://localhost:9999` + +### 3. Test the Application + +The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. + +## How It Works + +This application demonstrates the AI Agents framework with: + +1. **Writer Agent**: Writes short stories (300 words or less) about specified topics +2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words +3. **Publisher Workflow Agent**: A sequential workflow agent that combines the writer and editor agents + +The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. + +## Template Parameters + +When creating a new project, you can customize it using template parameters: + +```bash +# Specify AI service provider +dotnet new aiagents-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagents-webapi --ChatModel gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagents-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--ChatModel`**: Specify the chat model/deployment name + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + +- **`--managed-identity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--provider azureopenai` + +- **`--Framework`**: Target framework (default: `net10.0`) + - Options: `net10.0`, `net9.0`, `net8.0` + +## Project Structure + +- `Program.cs` - Application entry point and configuration +- `appsettings.json` - Application configuration +- `Properties/launchSettings.json` - Launch profiles for development + +## Learn More + +- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [GitHub Models](https://github.com/marketplace/models) +- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) + +## Troubleshooting + +**Problem**: Application fails with "Missing configuration: GitHubModels:Token" + +**Solution**: Make sure you've configured your GitHub Models API token using one of the methods described above. + +**Problem**: API requests fail with authentication errors + +**Solution**: Verify your GitHub Models token is valid and hasn't expired. You may need to regenerate it from the GitHub Models website. + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/aiagents-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/aiagents-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Program.cs new file mode 100644 index 00000000000..edc4f80c68e --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Program.cs @@ -0,0 +1,30 @@ +using System.ClientModel; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Extensions.AI; +using OpenAI; + +var builder = WebApplication.CreateBuilder(args); + +// You will need to set the token to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set GitHubModels:Token YOUR-GITHUB-TOKEN +var credential = new ApiKeyCredential(builder.Configuration["GitHubModels:Token"] ?? throw new InvalidOperationException("Missing configuration: GitHubModels:Token. See README for details.")); +var openAIOptions = new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }; + +var ghModelsClient = new OpenAIClient(credential, openAIOptions) + .GetChatClient("gpt-4o-mini").AsIChatClient(); + +builder.Services.AddChatClient(ghModelsClient); + +var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); +var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); + +builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.MapOpenAIResponses(); + +app.Run(); diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:9999;http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md new file mode 100644 index 00000000000..79fef0c4dba --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md @@ -0,0 +1,127 @@ +# AI Agents Web API + +This is an AI Agents Web API application created from the `aiagents-webapi` template. + +## Prerequisites + +- A GitHub Models API token (free to get started) + +## Getting Started + +### 1. Configure Your AI Service + +#### GitHub Models Configuration + +This application uses GitHub Models (model: gpt-4o-mini) for AI functionality. You'll need to configure your GitHub Models API token: + +**Option A: Using User Secrets (Recommended for Development)** + +```bash +dotnet user-secrets set "GitHubModels:Token" "your-github-models-token-here" +``` + +**Option B: Using Environment Variables** + +Set the `GitHubModels__Token` environment variable: + +- **Windows (PowerShell)**: + ```powershell + $env:GitHubModels__Token = "your-github-models-token-here" + ``` + +- **Linux/macOS**: + ```bash + export GitHubModels__Token="your-github-models-token-here" + ``` + +#### Get a GitHub Models Token + +1. Visit [GitHub Models](https://github.com/marketplace/models) +2. Sign in with your GitHub account +3. Select a model (e.g., gpt-4o-mini) +4. Click "Get API Key" or follow the authentication instructions +5. Copy your personal access token + + +### 2. Run the Application + +```bash +dotnet run -lp https +``` + +The application will start and listen on: +- HTTP: `http://localhost:9999` +- HTTPS: `https://localhost:9999` + +### 3. Test the Application + +The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. + +## How It Works + +This application demonstrates the AI Agents framework with: + +1. **Writer Agent**: Writes short stories (300 words or less) about specified topics +2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words +3. **Publisher Workflow Agent**: A sequential workflow agent that combines the writer and editor agents + +The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. + +## Template Parameters + +When creating a new project, you can customize it using template parameters: + +```bash +# Specify AI service provider +dotnet new aiagents-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagents-webapi --ChatModel gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagents-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--ChatModel`**: Specify the chat model/deployment name + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + +- **`--managed-identity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--provider azureopenai` + +- **`--Framework`**: Target framework (default: `net10.0`) + - Options: `net10.0`, `net9.0`, `net8.0` + +## Project Structure + +- `Program.cs` - Application entry point and configuration +- `appsettings.json` - Application configuration +- `Properties/launchSettings.json` - Launch profiles for development + +## Learn More + +- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [GitHub Models](https://github.com/marketplace/models) +- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) + +## Troubleshooting + +**Problem**: Application fails with "Missing configuration: GitHubModels:Token" + +**Solution**: Make sure you've configured your GitHub Models API token using one of the methods described above. + +**Problem**: API requests fail with authentication errors + +**Solution**: Verify your GitHub Models token is valid and hasn't expired. You may need to regenerate it from the GitHub Models website. + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/aiagents-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/aiagents-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Program.cs new file mode 100644 index 00000000000..6410937d52b --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Program.cs @@ -0,0 +1,23 @@ +using Microsoft.Agents.AI.Hosting; +using Microsoft.Extensions.AI; +using OllamaSharp; + +var builder = WebApplication.CreateBuilder(args); + +// You will need to have Ollama running locally with the llama3.2 model installed +// Visit https://ollama.com for installation instructions +IChatClient chatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2"); + +builder.Services.AddChatClient(chatClient); + +var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); +var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); + +builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.MapOpenAIResponses(); + +app.Run(); diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:9999;http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md new file mode 100644 index 00000000000..a158c517625 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md @@ -0,0 +1,111 @@ +# AI Agents Web API + +This is an AI Agents Web API application created from the `aiagents-webapi` template. + +## Prerequisites + +- Ollama installed locally with the llama3.2 model + +## Getting Started + +### 1. Configure Your AI Service + +#### Ollama Configuration + +This application uses Ollama running locally (model: llama3.2). You'll need to have Ollama installed and the llama3.2 model downloaded: + +1. Visit [Ollama](https://ollama.com) and follow the installation instructions for your platform +2. Once installed, download the llama3.2 model: + ```bash + ollama pull llama3.2 + ``` +3. Ensure Ollama is running (it starts automatically after installation) + +The application is configured to connect to Ollama at `http://localhost:9999`. + + +### 2. Run the Application + +```bash +dotnet run -lp https +``` + +The application will start and listen on: +- HTTP: `http://localhost:9999` +- HTTPS: `https://localhost:9999` + +### 3. Test the Application + +The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. + +## How It Works + +This application demonstrates the AI Agents framework with: + +1. **Writer Agent**: Writes short stories (300 words or less) about specified topics +2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words +3. **Publisher Workflow Agent**: A sequential workflow agent that combines the writer and editor agents + +The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. + +## Template Parameters + +When creating a new project, you can customize it using template parameters: + +```bash +# Specify AI service provider +dotnet new aiagents-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagents-webapi --ChatModel gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagents-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--ChatModel`**: Specify the chat model/deployment name + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + +- **`--managed-identity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--provider azureopenai` + +- **`--Framework`**: Target framework (default: `net10.0`) + - Options: `net10.0`, `net9.0`, `net8.0` + +## Project Structure + +- `Program.cs` - Application entry point and configuration +- `appsettings.json` - Application configuration +- `Properties/launchSettings.json` - Launch profiles for development + +## Learn More + +- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [Ollama](https://ollama.com) +- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) + +## Troubleshooting + +**Problem**: Application fails to connect to Ollama + +**Solution**: +- Ensure Ollama is running. On macOS/Linux, check with `pgrep ollama`. On Windows, check Task Manager. +- Verify Ollama is accessible at `http://localhost:9999` +- Make sure you've downloaded the llama3.2 model: `ollama pull llama3.2` + +**Problem**: Model responses are slow or time out + +**Solution**: Ollama runs locally and performance depends on your hardware. Consider using a smaller model or ensuring your system has adequate resources (RAM, GPU if available). + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/aiagents-webapi.csproj new file mode 100644 index 00000000000..09e26e73acc --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/aiagents-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Program.cs new file mode 100644 index 00000000000..3958c3d5993 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Program.cs @@ -0,0 +1,31 @@ +using System.ClientModel; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Extensions.AI; +using OpenAI; + +var builder = WebApplication.CreateBuilder(args); + +// You will need to set the API key to your own value +// You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: +// cd this-project-directory +// dotnet user-secrets set OpenAI:Key YOUR-API-KEY +var openAIClient = new OpenAIClient( + new ApiKeyCredential(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: OpenAI:Key. See README for details."))); + +#pragma warning disable OPENAI001 // GetOpenAIResponseClient(string) is experimental and subject to change or removal in future updates. +var chatClient = openAIClient.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); +#pragma warning restore OPENAI001 + +builder.Services.AddChatClient(chatClient); + +var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); +var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); + +builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +app.MapOpenAIResponses(); + +app.Run(); diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:9999;http://localhost:9999", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md new file mode 100644 index 00000000000..08bbb27a46f --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md @@ -0,0 +1,127 @@ +# AI Agents Web API + +This is an AI Agents Web API application created from the `aiagents-webapi` template. + +## Prerequisites + +- An OpenAI API key + +## Getting Started + +### 1. Configure Your AI Service + +#### OpenAI Configuration + +This application uses the OpenAI Platform (model: gpt-4o-mini). You'll need to configure your OpenAI API key: + +**Using User Secrets (Recommended for Development)** + +```bash +dotnet user-secrets set "OpenAI:Key" "your-openai-api-key-here" +``` + +**Using Environment Variables** + +Set the `OpenAI__Key` environment variable: + +- **Windows (PowerShell)**: + ```powershell + $env:OpenAI__Key = "your-openai-api-key-here" + ``` + +- **Linux/macOS**: + ```bash + export OpenAI__Key="your-openai-api-key-here" + ``` + +#### Get an OpenAI API Key + +1. Visit [OpenAI Platform](https://platform.openai.com) +2. Sign in or create an account +3. Navigate to API Keys +4. Create a new API key +5. Copy your API key + + +### 2. Run the Application + +```bash +dotnet run -lp https +``` + +The application will start and listen on: +- HTTP: `http://localhost:9999` +- HTTPS: `https://localhost:9999` + +### 3. Test the Application + +The application exposes OpenAI-compatible API endpoints. You can interact with the AI agents using any OpenAI-compatible client or tools. + +## How It Works + +This application demonstrates the AI Agents framework with: + +1. **Writer Agent**: Writes short stories (300 words or less) about specified topics +2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words +3. **Publisher Workflow Agent**: A sequential workflow agent that combines the writer and editor agents + +The agents are exposed through OpenAI-compatible API endpoints, making them easy to integrate with existing tools and applications. + +## Template Parameters + +When creating a new project, you can customize it using template parameters: + +```bash +# Specify AI service provider +dotnet new aiagents-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagents-webapi --ChatModel gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagents-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--ChatModel`**: Specify the chat model/deployment name + - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` + - Default for Ollama: `llama3.2` + +- **`--managed-identity`**: Use managed identity for Azure services (default: `true`) + - Only applicable when `--provider azureopenai` + +- **`--Framework`**: Target framework (default: `net10.0`) + - Options: `net10.0`, `net9.0`, `net8.0` + +## Project Structure + +- `Program.cs` - Application entry point and configuration +- `appsettings.json` - Application configuration +- `Properties/launchSettings.json` - Launch profiles for development + +## Learn More + +- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [OpenAI Platform](https://platform.openai.com) +- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) + +## Troubleshooting + +**Problem**: Application fails with "Missing configuration: OpenAI:Key" + +**Solution**: Make sure you've configured your OpenAI API key using one of the methods described above. + +**Problem**: API requests fail with authentication errors + +**Solution**: Verify your OpenAI API key is valid. Check your usage limits and billing status on the OpenAI Platform. + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/aiagents-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/aiagents-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs index 27c52d137f6..e3273a70a1c 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs @@ -16,15 +16,19 @@ namespace Microsoft.Agents.AI.Templates.Tests; public class WebApiAgentsTemplateSnapshotTests { - // Keep the exclude patterns below in sync with those in Microsoft.Agents.AI.Templates.csproj. private static readonly string[] _verificationExcludePatterns = [ + + // Exclude any templated content files + "**/*.in", + + // Keep the exclude patterns below in sync with those in Microsoft.Agents.AI.Templates.csproj. "**/bin/**", "**/obj/**", "**/.vs/**", "**/*.user", "**/NuGet.config", "**/Directory.Build.targets", - "**/Directory.Build.props", + "**/Directory.Build.props" ]; private readonly ILogger _log; @@ -37,9 +41,39 @@ public WebApiAgentsTemplateSnapshotTests(ITestOutputHelper log) } [Fact] - public async Task BasicTest() + public async Task DefaultParameters() + { + await TestTemplateCoreAsync(scenarioName: nameof(DefaultParameters)); + } + + [Fact] + public async Task GitHubModels() { - await TestTemplateCoreAsync(scenarioName: "Basic"); + await TestTemplateCoreAsync(scenarioName: nameof(GitHubModels), templateArgs: ["--provider", "githubmodels"]); + } + + [Fact] + public async Task OpenAI() + { + await TestTemplateCoreAsync(scenarioName: nameof(OpenAI), templateArgs: ["--provider", "openai"]); + } + + [Fact] + public async Task AzureOpenAI_ManagedIdentity() + { + await TestTemplateCoreAsync(scenarioName: nameof(AzureOpenAI_ManagedIdentity), templateArgs: ["--provider", "azureopenai"]); + } + + [Fact] + public async Task AzureOpenAI_ApiKey() + { + await TestTemplateCoreAsync(scenarioName: nameof(AzureOpenAI_ApiKey), templateArgs: ["--provider", "azureopenai", "--managed-identity", "false"]); + } + + [Fact] + public async Task Ollama() + { + await TestTemplateCoreAsync(scenarioName: nameof(Ollama), templateArgs: ["--provider", "ollama"]); } private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable? templateArgs = null) @@ -51,58 +85,58 @@ private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable p.Replace('/', Path.DirectorySeparatorChar)).ToArray(); - TemplateVerifierOptions options = new TemplateVerifierOptions(templateName: templateShortName) - { - TemplatePath = templateLocation, - TemplateSpecificArgs = templateArgs, + TemplateVerifierOptions options = new TemplateVerifierOptions(templateName: templateShortName) + { + TemplatePath = templateLocation, + TemplateSpecificArgs = templateArgs, SnapshotsDirectory = "Snapshots", - OutputDirectory = workingDir, - DoNotPrependCallerMethodNameToScenarioName = true, + OutputDirectory = workingDir, + DoNotPrependCallerMethodNameToScenarioName = true, DoNotAppendTemplateArgsToScenarioName = true, - ScenarioName = scenarioName, + ScenarioName = scenarioName, VerificationExcludePatterns = verificationExcludePatterns, } .WithCustomScrubbers( - ScrubbersDefinition.Empty.AddScrubber((path, content) => - { - string filePath = path.UnixifyDirSeparators(); - if (filePath.EndsWith(".sln")) - { - // Scrub .sln file GUIDs. - content.ScrubByRegex(pattern: @"\{.{36}\}", replacement: "{00000000-0000-0000-0000-000000000000}"); - } - - if (filePath.EndsWith(".csproj")) - { - content.ScrubByRegex("(.*)<\\/UserSecretsId>", "secret"); - - // Scrub references to just-built packages and remove the suffix, if it exists. - // This allows the snapshots to remain the same regardless of where the repo is built (e.g., locally, public CI, internal CI). - var pattern = @"(?<=)"; - content.ScrubByRegex(pattern, replacement: "$1"); - } - - if (filePath.EndsWith("launchSettings.json")) - { - content.ScrubByRegex("(http(s?):\\/\\/localhost)\\:(\\d*)", "$1:9999"); - } - })); + ScrubbersDefinition.Empty.AddScrubber((path, content) => + { + string filePath = path.UnixifyDirSeparators(); + if (filePath.EndsWith(".sln")) + { + // Scrub .sln file GUIDs. + content.ScrubByRegex(pattern: @"\{.{36}\}", replacement: "{00000000-0000-0000-0000-000000000000}"); + } + + if (filePath.EndsWith(".csproj")) + { + content.ScrubByRegex("(.*)<\\/UserSecretsId>", "secret"); + + // Scrub references to just-built packages and remove the suffix, if it exists. + // This allows the snapshots to remain the same regardless of where the repo is built (e.g., locally, public CI, internal CI). + var pattern = @"(?<=)"; + content.ScrubByRegex(pattern, replacement: "$2"); + } + + if (filePath.EndsWith("launchSettings.json") || filePath.EndsWith("README.md")) + { + content.ScrubByRegex("(http(s?):\\/\\/localhost)\\:(\\d*)", "$1:9999"); + } + })); VerificationEngine engine = new VerificationEngine(_log); await engine.Execute(options); #pragma warning disable CA1031 // Do not catch general exception types try - { + { Directory.Delete(workingDir, recursive: true); - } + } catch - { - /* don't care */ + { + /* don't care */ } #pragma warning restore CA1031 // Do not catch general exception types - } + } } From 94a1d1f59a327568ae19a37f10a00ef96c867059 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 07:29:26 -0800 Subject: [PATCH 10/22] Add aiagents-webapi execution tests (and component governance) --- test/ProjectTemplates/.gitignore | 1 + .../ProjectRootHelper.cs | 0 .../TemplateSandbox/README.md | 11 -- .../TemplateSandbox/activate.ps1 | 109 --------------- .../WebApiAgentsTemplateExecutionTests.cs | 126 ++++++++++++++++++ .../ProjectRootHelper.cs | 35 +++++ .../TemplateSandbox/.editorconfig | 2 - .../TemplateSandbox/.gitignore | 2 - .../TemplateExecutionTestBase.cs | 2 +- .../TemplateExecutionTestClassFixtureBase.cs | 2 +- .../TemplateExecutionTestCollection.cs | 2 +- .../TemplateExecutionTestCollectionFixture.cs | 10 ++ .../TemplateSandbox/.editorconfig | 0 .../TemplateSandbox/.gitignore | 0 .../TemplateSandbox/Directory.Build.props | 0 .../TemplateSandbox/Directory.Build.targets | 0 .../TemplateSandbox/README.md | 0 .../TemplateSandbox/activate.ps1 | 0 .../nuget.template_install.config | 0 .../nuget.template_test.config | 0 .../TestCommandResultExtensions.cs | 2 +- .../Shared/ProjectTemplates/WellKnownPaths.cs | 4 +- 22 files changed, 178 insertions(+), 130 deletions(-) create mode 100644 test/ProjectTemplates/.gitignore rename test/{Shared/ProjectTemplates => ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests}/ProjectRootHelper.cs (100%) delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md delete mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 create mode 100644 test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs create mode 100644 test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs delete mode 100644 test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig delete mode 100644 test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TemplateExecutionTestBase.cs (97%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TemplateExecutionTestClassFixtureBase.cs (99%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TemplateExecutionTestCollection.cs (89%) rename test/{ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/.editorconfig (100%) rename test/{ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/.gitignore (100%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/Directory.Build.props (100%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/Directory.Build.targets (100%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/README.md (100%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/activate.ps1 (100%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/nuget.template_install.config (100%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests => Shared/ProjectTemplates}/TemplateSandbox/nuget.template_test.config (100%) rename test/{ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure => Shared/ProjectTemplates}/TestCommandResultExtensions.cs (94%) diff --git a/test/ProjectTemplates/.gitignore b/test/ProjectTemplates/.gitignore new file mode 100644 index 00000000000..b44b98ebd24 --- /dev/null +++ b/test/ProjectTemplates/.gitignore @@ -0,0 +1 @@ +*/TemplateSandbox diff --git a/test/Shared/ProjectTemplates/ProjectRootHelper.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs similarity index 100% rename from test/Shared/ProjectTemplates/ProjectRootHelper.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/ProjectRootHelper.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md deleted file mode 100644 index 51682c3aaf7..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Template Sandbox - -This directory is used for debugging template execution tests. To debug a template: - -1. Copy the contents of the template you want to test from `src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/` to this directory. -2. Run `dotnet new install .` from this directory to install the template locally. -3. Create a new directory (e.g., `output/`) and run `dotnet new ` with desired parameters. -4. Debug and iterate on the template as needed. -5. When done, run `dotnet new uninstall .` to uninstall the local template. - -The `output` directory is git-ignored and can be used for template instantiation testing. diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 deleted file mode 100644 index ddd6b0bfe86..00000000000 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 +++ /dev/null @@ -1,109 +0,0 @@ -# -# This file creates an environment similar to the one that the template tests use. -# This makes it convenient to restore, build, and run projects generated by the template tests -# to debug test failures. -# -# This file must be used by invoking ". .\activate.ps1" from the command line. -# You cannot run it directly. See https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_scripts#script-scope-and-dot-sourcing -# -# To exit from the environment this creates, execute the 'deactivate' function. -# - -[CmdletBinding(PositionalBinding=$false)] -Param( - [string][Alias('c')]$configuration = "Debug" -) - -if ($MyInvocation.CommandOrigin -eq 'runspace') { - $cwd = (Get-Location).Path - $scriptPath = $MyInvocation.MyCommand.Path - $relativePath = [System.IO.Path]::GetRelativePath($cwd, $scriptPath) - Write-Host -f Red "This script cannot be invoked directly." - Write-Host -f Red "To function correctly, this script file must be 'dot sourced' by calling `". .\$relativePath`" (notice the dot at the beginning)." - exit 1 -} - -function deactivate ([switch]$init) { - # reset old environment variables - if (Test-Path variable:_OLD_PATH) { - $env:PATH = $_OLD_PATH - Remove-Item variable:_OLD_PATH - } - - if (test-path function:_old_prompt) { - Set-Item Function:prompt -Value $function:_old_prompt -ea ignore - remove-item function:_old_prompt - } - - Remove-Item env:DOTNET_ROOT -ea Ignore - Remove-Item 'env:DOTNET_ROOT(x86)' -ea Ignore -Remove-Item env:DOTNET_MULTILEVEL_LOOKUP -ea Ignore - Remove-Item env:NUGET_PACKAGES -ea Ignore - Remove-Item env:LOCAL_SHIPPING_PATH -ea Ignore - if (-not $init) { - # Remove functions defined - Remove-Item function:deactivate - Remove-Item function:Get-RepoRoot - } -} - -# Cleanup the environment -deactivate -init - -function Get-RepoRoot { - $directory = $PSScriptRoot - - while ($directory) { - $gitPath = Join-Path $directory ".git" - - if (Test-Path $gitPath) { - return $directory - } - - $parent = Split-Path $directory -Parent - if ($parent -eq $directory) { - # We've reached the filesystem root - break - } - - $directory = $parent - } - - throw "Failed to establish root of the repository" -} - -# Find the root of the repository -$repoRoot = Get-RepoRoot - -$_OLD_PATH = $env:PATH -# Tell dotnet where to find itself -$env:DOTNET_ROOT = "$repoRoot\.dotnet" -${env:DOTNET_ROOT(x86)} = "$repoRoot\.dotnet\x86" -# Tell dotnet not to look beyond the DOTNET_ROOT folder for more dotnet things -$env:DOTNET_MULTILEVEL_LOOKUP = 0 -# Put dotnet first on PATH -$env:PATH = "${env:DOTNET_ROOT};${env:PATH}" -# Set NUGET_PACKAGES and LOCAL_SHIPPING_PATH -$env:NUGET_PACKAGES = "$PSScriptRoot\output\packages" -$env:LOCAL_SHIPPING_PATH = "$repoRoot\artifacts\packages\$configuration\Shipping\" - -# Set the shell prompt -$function:_old_prompt = $function:prompt -function dotnet_prompt { - # Add a prefix to the current prompt, but don't discard it. - write-host "($( split-path $PSScriptRoot -leaf )) " -nonewline - & $function:_old_prompt -} - -Set-Item Function:prompt -Value $function:dotnet_prompt -ea ignore - -Write-Host -f Magenta "Enabled the template testing environment. Execute 'deactivate' to exit." -Write-Host -f Magenta "Using the '$configuration' configuration. Use the -c option to specify a different configuration." -if (-not (Test-Path "${env:DOTNET_ROOT}\dotnet.exe")) { - Write-Host -f Yellow ".NET Core has not been installed yet. Run $repoRoot\build.cmd -restore to install it." -} -else { - Write-Host "dotnet = ${env:DOTNET_ROOT}\dotnet.exe" -} -Write-Host "NUGET_PACKAGES = ${env:NUGET_PACKAGES}" -Write-Host "LOCAL_SHIPPING_PATH = ${env:LOCAL_SHIPPING_PATH}" diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs new file mode 100644 index 00000000000..b751cc9337f --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Shared.ProjectTemplates.Tests; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Agents.AI.Templates.Tests; + +/// +/// Contains execution tests for the "AI Agents Web API" template. +/// +/// +/// In addition to validating that the templates build and restore correctly, +/// these tests are also responsible for template component governance reporting. +/// This is because the generated output is left on disk after tests complete, +/// most importantly the project.assets.json file that gets created during restore. +/// Therefore, it's *critical* that these tests remain in a working state, +/// as disabling them will also disable CG reporting. +/// +public class WebApiAgentsTemplateExecutionTests : TemplateExecutionTestBase, ITemplateExecutionTestConfigurationProvider +{ + public WebApiAgentsTemplateExecutionTests(TemplateExecutionTestFixture fixture, ITestOutputHelper outputHelper) + : base(fixture, outputHelper) + { + } + + public static TemplateExecutionTestConfiguration Configuration { get; } = new() + { + TemplatePackageName = "Microsoft.Agents.AI.Templates", + TestOutputFolderPrefix = "WebApiAgents" + }; + + public static IEnumerable GetTemplateOptions() + => GetFilteredTemplateOptions(); + + // Do not skip. See XML docs for this test class. + [Theory] + [MemberData(nameof(GetTemplateOptions))] + public async Task CreateRestoreAndBuild(params string[] args) + { + const string ProjectName = "WebApiAgentsApp"; + var project = await Fixture.CreateProjectAsync( + templateName: "aiagents-webapi", + projectName: ProjectName, + args); + + await Fixture.RestoreProjectAsync(project); + await Fixture.BuildProjectAsync(project); + } + + private static readonly (string name, string[] values)[] _templateOptions = [ + ("--provider", ["azureopenai", "githubmodels", "ollama", "openai"]), + ("--managed-identity", ["true", "false"]), + ]; + + private static IEnumerable GetFilteredTemplateOptions(params string[] filter) + { + foreach (var options in GetAllPossibleOptions(_templateOptions)) + { + if (!MatchesFilter()) + { + continue; + } + + if (HasOption("--managed-identity", "true")) + { + if (!HasOption("--provider", "azureopenai")) + { + // The managed identity option is only valid for Azure OpenAI. + continue; + } + } + + yield return options; + + bool MatchesFilter() + { + for (var i = 0; i < filter.Length; i += 2) + { + if (!HasOption(filter[i], filter[i + 1])) + { + return false; + } + } + + return true; + } + + bool HasOption(string name, string value) + { + for (var i = 0; i < options.Length; i += 2) + { + if (string.Equals(name, options[i], StringComparison.Ordinal) && + string.Equals(value, options[i + 1], StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + } + } + + private static IEnumerable GetAllPossibleOptions(ReadOnlyMemory<(string name, string[] values)> options) + { + if (options.Length == 0) + { + yield return []; + yield break; + } + + var first = options.Span[0]; + foreach (var restSelection in GetAllPossibleOptions(options[1..])) + { + foreach (var value in first.values) + { + yield return [first.name, value, .. restSelection]; + } + } + } +} diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs new file mode 100644 index 00000000000..97be1100c35 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Microsoft.Shared.ProjectTemplates.Tests; + +/// +/// Contains a helper for determining the disk location of the containing project folder. +/// +/// +/// It's important that this file resides in the root of the containing project, or the returned +/// project root path will be incorrect. +/// +internal static class ProjectRootHelper +{ + public static string GetThisProjectRoot() + => GetThisProjectRootCore(); + + // This helper method is defined separately from its public variant because it extracts the + // caller file path via the [CallerFilePath] attribute. + // Therefore, the caller must be in a known location, i.e., this source file, to produce + // a reliable result. + private static string GetThisProjectRootCore([CallerFilePath] string callerFilePath = "") + { + if (Path.GetDirectoryName(callerFilePath) is not { Length: > 0 } testProjectRoot) + { + throw new InvalidOperationException("Could not determine the root of the test project."); + } + + return testProjectRoot; + } +} diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig deleted file mode 100644 index b8f20c69836..00000000000 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -# Don't apply the repo's editorconfig settings to generated templates. -root = true diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore deleted file mode 100644 index ee80e74117d..00000000000 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Template test output -output/ diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs similarity index 97% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs rename to test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs index ed67ca6697c..ceffe625d71 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs @@ -6,7 +6,7 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; /// /// Represents a test that executes a project template (create, restore, build, and run). diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs similarity index 99% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs rename to test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs index 2ac81b2b8ac..69875206a33 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs @@ -8,7 +8,7 @@ using Xunit; using Xunit.Abstractions; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; /// /// Provides functionality scoped to the duration of all the tests in a single test class diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs similarity index 89% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs rename to test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs index ffa8e67edca..f5802a58b18 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs @@ -4,7 +4,7 @@ using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; [CollectionDefinition(name: Name)] public sealed class TemplateExecutionTestCollection : ICollectionFixture diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs index 203b35b37c9..4dce99435de 100644 --- a/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs @@ -26,5 +26,15 @@ public TemplateExecutionTestCollectionFixture() { Directory.Delete(WellKnownPaths.TemplateSandboxOutputRoot, recursive: true); } + + // Then we copy the template sandbox infrastructure to the output location for use during tests. + Directory.CreateDirectory(WellKnownPaths.TemplateSandboxOutputRoot); + + foreach (var filePath in Directory.EnumerateFiles(WellKnownPaths.TemplateSandboxRoot)) + { + var fileName = Path.GetFileName(filePath); + var destFilePath = Path.Combine(WellKnownPaths.TemplateSandboxOutputRoot, fileName); + File.Copy(filePath, destFilePath); + } } } diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig b/test/Shared/ProjectTemplates/TemplateSandbox/.editorconfig similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig rename to test/Shared/ProjectTemplates/TemplateSandbox/.editorconfig diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore b/test/Shared/ProjectTemplates/TemplateSandbox/.gitignore similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/TemplateSandbox/.gitignore rename to test/Shared/ProjectTemplates/TemplateSandbox/.gitignore diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.props b/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.props similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.props rename to test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.props diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.targets b/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.targets similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.targets rename to test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.targets diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/README.md b/test/Shared/ProjectTemplates/TemplateSandbox/README.md similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/README.md rename to test/Shared/ProjectTemplates/TemplateSandbox/README.md diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 b/test/Shared/ProjectTemplates/TemplateSandbox/activate.ps1 similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 rename to test/Shared/ProjectTemplates/TemplateSandbox/activate.ps1 diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/nuget.template_install.config b/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_install.config similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/nuget.template_install.config rename to test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_install.config diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/nuget.template_test.config b/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_test.config similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/nuget.template_test.config rename to test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_test.config diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs b/test/Shared/ProjectTemplates/TestCommandResultExtensions.cs similarity index 94% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs rename to test/Shared/ProjectTemplates/TestCommandResultExtensions.cs index ac8278f6c4d..e0de4cb1740 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs +++ b/test/Shared/ProjectTemplates/TestCommandResultExtensions.cs @@ -4,7 +4,7 @@ using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public static class TestCommandResultExtensions { diff --git a/test/Shared/ProjectTemplates/WellKnownPaths.cs b/test/Shared/ProjectTemplates/WellKnownPaths.cs index b314fc563b5..535f3bf5d40 100644 --- a/test/Shared/ProjectTemplates/WellKnownPaths.cs +++ b/test/Shared/ProjectTemplates/WellKnownPaths.cs @@ -28,8 +28,8 @@ static WellKnownPaths() ThisProjectRoot = ProjectRootHelper.GetThisProjectRoot(); TemplateFeedLocation = Path.Combine(RepoRoot, "src", "ProjectTemplates"); - TemplateSandboxRoot = Path.Combine(ThisProjectRoot, "TemplateSandbox"); - TemplateSandboxOutputRoot = Path.Combine(TemplateSandboxRoot, "output"); + TemplateSandboxRoot = Path.Combine(RepoRoot, "test", "Shared", "ProjectTemplates", "TemplateSandbox"); + TemplateSandboxOutputRoot = Path.Combine(ThisProjectRoot, "TemplateSandbox", "output"); TemplateInstallNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_install.config"); TemplateTestNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_test.config"); From d28616a0ad637856c74e1adc5207281fba4c409d Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 08:12:34 -0800 Subject: [PATCH 11/22] Improve aiagents-webapi template parameters --- .../PROJECT_STRUCTURE.md | 8 ++++---- .../.template.config/dotnetcli.host.json | 17 +++++++++++++++++ .../WebApiAgents/WebApiAgents-CSharp/README.md | 8 ++++---- .../aiagents-webapi/README.md | 8 ++++---- .../aiagents-webapi/README.md | 8 ++++---- .../aiagents-webapi/README.md | 8 ++++---- .../aiagents-webapi/README.md | 8 ++++---- .../aiagents-webapi/README.md | 8 ++++---- .../aiagents-webapi/README.md | 8 ++++---- 9 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md index c6a2fdaf8b9..b6bee9cbd40 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md @@ -50,7 +50,7 @@ A simple ASP.NET Core Web API template that demonstrates the AI Agents framework - `azureopenai` - Azure OpenAI - `openai` - OpenAI Platform - `ollama` - Ollama (local development) - - **Chat Model Parameter** (`--ChatModel`): + - **Chat Model Parameter** (`--chat-model`): - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **Use Managed Identity Parameter** (`--managed-identity`): @@ -157,13 +157,13 @@ dotnet new aiagents-webapi -n MyAgentsApp --provider azureopenai dotnet new aiagents-webapi -n MyAgentsApp --provider azureopenai --managed-identity false # Using OpenAI Platform with custom model -dotnet new aiagents-webapi -n MyAgentsApp --provider openai --ChatModel gpt-4o +dotnet new aiagents-webapi -n MyAgentsApp --provider openai --chat-model gpt-4o # Using Ollama with custom model -dotnet new aiagents-webapi -n MyAgentsApp --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi -n MyAgentsApp --provider ollama --chat-model llama3.1 # Target specific framework -dotnet new aiagents-webapi -n MyAgentsApp --Framework net9.0 +dotnet new aiagents-webapi -n MyAgentsApp --framework net9.0 ``` ### Configuring the Project diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json index 931cd9ff2bc..94096fec7c7 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/.template.config/dotnetcli.host.json @@ -1,13 +1,30 @@ { "$schema": "https://json.schemastore.org/dotnetcli.host", "symbolInfo": { + "Framework": { + "longName": "framework" + }, "AiServiceProvider": { "longName": "provider", "shortName": "" }, + "ChatModel": { + "longName": "chat-model", + "shortName": "" + }, "UseManagedIdentity": { "longName": "managed-identity", "shortName": "" + }, + "httpPort": { + "isHidden": "true", + "longName": "httpPort", + "shortName": "" + }, + "httpsPort": { + "isHidden": "true", + "longName": "httpsPort", + "shortName": "" } }, "usageExamples": [ diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md index 6bc319476a9..2b70249b397 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/README.md @@ -184,13 +184,13 @@ When creating a new project, you can customize it using template parameters: dotnet new aiagents-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --ChatModel gpt-4o +dotnet new aiagents-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -201,14 +201,14 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - `openai` - OpenAI Platform - `ollama` - Ollama (local development) -- **`--ChatModel`**: Specify the chat model/deployment name +- **`--chat-model`**: Specify the chat model/deployment name - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **`--managed-identity`**: Use managed identity for Azure services (default: `true`) - Only applicable when `--provider azureopenai` -- **`--Framework`**: Target framework (default: `net10.0`) +- **`--framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` ## Project Structure diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md index d77c1d81152..6b318475d6d 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md @@ -74,13 +74,13 @@ When creating a new project, you can customize it using template parameters: dotnet new aiagents-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --ChatModel gpt-4o +dotnet new aiagents-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -91,14 +91,14 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - `openai` - OpenAI Platform - `ollama` - Ollama (local development) -- **`--ChatModel`**: Specify the chat model/deployment name +- **`--chat-model`**: Specify the chat model/deployment name - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **`--managed-identity`**: Use managed identity for Azure services (default: `true`) - Only applicable when `--provider azureopenai` -- **`--Framework`**: Target framework (default: `net10.0`) +- **`--framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` ## Project Structure diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md index 4a720b1878e..c2ed7685cca 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md @@ -46,13 +46,13 @@ When creating a new project, you can customize it using template parameters: dotnet new aiagents-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --ChatModel gpt-4o +dotnet new aiagents-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -63,14 +63,14 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - `openai` - OpenAI Platform - `ollama` - Ollama (local development) -- **`--ChatModel`**: Specify the chat model/deployment name +- **`--chat-model`**: Specify the chat model/deployment name - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **`--managed-identity`**: Use managed identity for Azure services (default: `true`) - Only applicable when `--provider azureopenai` -- **`--Framework`**: Target framework (default: `net10.0`) +- **`--framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` ## Project Structure diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md index 79fef0c4dba..e61e9ee4937 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md @@ -76,13 +76,13 @@ When creating a new project, you can customize it using template parameters: dotnet new aiagents-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --ChatModel gpt-4o +dotnet new aiagents-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -93,14 +93,14 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - `openai` - OpenAI Platform - `ollama` - Ollama (local development) -- **`--ChatModel`**: Specify the chat model/deployment name +- **`--chat-model`**: Specify the chat model/deployment name - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **`--managed-identity`**: Use managed identity for Azure services (default: `true`) - Only applicable when `--provider azureopenai` -- **`--Framework`**: Target framework (default: `net10.0`) +- **`--framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` ## Project Structure diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md index 79fef0c4dba..e61e9ee4937 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md @@ -76,13 +76,13 @@ When creating a new project, you can customize it using template parameters: dotnet new aiagents-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --ChatModel gpt-4o +dotnet new aiagents-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -93,14 +93,14 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - `openai` - OpenAI Platform - `ollama` - Ollama (local development) -- **`--ChatModel`**: Specify the chat model/deployment name +- **`--chat-model`**: Specify the chat model/deployment name - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **`--managed-identity`**: Use managed identity for Azure services (default: `true`) - Only applicable when `--provider azureopenai` -- **`--Framework`**: Target framework (default: `net10.0`) +- **`--framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` ## Project Structure diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md index a158c517625..3f674e0934b 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md @@ -57,13 +57,13 @@ When creating a new project, you can customize it using template parameters: dotnet new aiagents-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --ChatModel gpt-4o +dotnet new aiagents-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -74,14 +74,14 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - `openai` - OpenAI Platform - `ollama` - Ollama (local development) -- **`--ChatModel`**: Specify the chat model/deployment name +- **`--chat-model`**: Specify the chat model/deployment name - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **`--managed-identity`**: Use managed identity for Azure services (default: `true`) - Only applicable when `--provider azureopenai` -- **`--Framework`**: Target framework (default: `net10.0`) +- **`--framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` ## Project Structure diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md index 08bbb27a46f..92356eca2eb 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md @@ -76,13 +76,13 @@ When creating a new project, you can customize it using template parameters: dotnet new aiagents-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --ChatModel gpt-4o +dotnet new aiagents-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI dotnet new aiagents-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 +dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -93,14 +93,14 @@ dotnet new aiagents-webapi --provider ollama --ChatModel llama3.1 - `openai` - OpenAI Platform - `ollama` - Ollama (local development) -- **`--ChatModel`**: Specify the chat model/deployment name +- **`--chat-model`**: Specify the chat model/deployment name - Default for OpenAI/Azure OpenAI/GitHub Models: `gpt-4o-mini` - Default for Ollama: `llama3.2` - **`--managed-identity`**: Use managed identity for Azure services (default: `true`) - Only applicable when `--provider azureopenai` -- **`--Framework`**: Target framework (default: `net10.0`) +- **`--framework`**: Target framework (default: `net10.0`) - Options: `net10.0`, `net9.0`, `net8.0` ## Project Structure From 0422af778bbf66a94c0d1e5f36464cf6047dd350 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 08:37:34 -0800 Subject: [PATCH 12/22] Apply suggestions from copilot code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../WebApiAgentsTemplateExecutionTests.cs | 9 +++------ .../Shared/ProjectTemplates/TemplateExecutionTestBase.cs | 1 - .../TemplateExecutionTestClassFixtureBase.cs | 1 - .../ProjectTemplates/TemplateExecutionTestCollection.cs | 1 - .../TemplateExecutionTestCollectionFixture.cs | 1 - test/Shared/ProjectTemplates/TestCommandExtensions.cs | 1 - .../ProjectTemplates/TestCommandResultExtensions.cs | 1 - 7 files changed, 3 insertions(+), 12 deletions(-) diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs index b751cc9337f..9211cd276da 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs @@ -66,13 +66,10 @@ private static IEnumerable GetFilteredTemplateOptions(params string[] continue; } - if (HasOption("--managed-identity", "true")) + if (HasOption("--managed-identity", "true") && !HasOption("--provider", "azureopenai")) { - if (!HasOption("--provider", "azureopenai")) - { - // The managed identity option is only valid for Azure OpenAI. - continue; - } + // The managed identity option is only valid for Azure OpenAI. + continue; } yield return options; diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs index ceffe625d71..430f731e0c8 100644 --- a/test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; using Xunit.Abstractions; diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs index 69875206a33..0b978655c35 100644 --- a/test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Threading.Tasks; -using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; using Xunit.Abstractions; diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs index f5802a58b18..dfabfb9a089 100644 --- a/test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; namespace Microsoft.Shared.ProjectTemplates.Tests; diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs b/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs index 4dce99435de..062954266ee 100644 --- a/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs +++ b/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; -using Microsoft.Shared.ProjectTemplates.Tests; namespace Microsoft.Shared.ProjectTemplates.Tests; diff --git a/test/Shared/ProjectTemplates/TestCommandExtensions.cs b/test/Shared/ProjectTemplates/TestCommandExtensions.cs index 288b1238078..bc8f17dd89a 100644 --- a/test/Shared/ProjectTemplates/TestCommandExtensions.cs +++ b/test/Shared/ProjectTemplates/TestCommandExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Shared.ProjectTemplates.Tests; namespace Microsoft.Shared.ProjectTemplates.Tests; diff --git a/test/Shared/ProjectTemplates/TestCommandResultExtensions.cs b/test/Shared/ProjectTemplates/TestCommandResultExtensions.cs index e0de4cb1740..d623c3643b8 100644 --- a/test/Shared/ProjectTemplates/TestCommandResultExtensions.cs +++ b/test/Shared/ProjectTemplates/TestCommandResultExtensions.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Shared.ProjectTemplates.Tests; using Xunit; namespace Microsoft.Shared.ProjectTemplates.Tests; From 8f6e5738c881f3b0db7ca1b269b641f32213015a Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 10:35:20 -0800 Subject: [PATCH 13/22] Move shared ProjectTemplate infrastructure to not get included in Shared.Tests --- .../Infrastructure}/DotNetCommand.cs | 0 .../Infrastructure}/DotNetNewCommand.cs | 0 ...plateExecutionTestConfigurationProvider.cs | 0 .../MessageSinkTestOutputHelper.cs | 0 .../Infrastructure}/ProcessExtensions.cs | 0 .../Infrastructure}/Project.cs | 0 .../TemplateExecutionTestBase.cs | 0 .../TemplateExecutionTestClassFixtureBase.cs | 0 .../TemplateExecutionTestCollection.cs | 0 .../TemplateExecutionTestCollectionFixture.cs | 0 .../TemplateExecutionTestConfiguration.cs | 0 .../Infrastructure}/TestCommand.cs | 0 .../Infrastructure}/TestCommandExtensions.cs | 0 .../Infrastructure}/TestCommandResult.cs | 0 .../TestCommandResultExtensions.cs | 0 .../Infrastructure}/VerifyScrubbers.cs | 0 .../Infrastructure}/WellKnownPaths.cs | 0 ...Microsoft.Agents.AI.Templates.Tests.csproj | 2 +- ...osoft.Extensions.AI.Templates.Tests.csproj | 2 +- .../TemplateSandbox/.editorconfig | 2 - .../TemplateSandbox/.gitignore | 2 - .../TemplateSandbox/Directory.Build.props | 11 -- .../TemplateSandbox/Directory.Build.targets | 6 - .../TemplateSandbox/README.md | 31 ----- .../TemplateSandbox/activate.ps1 | 109 ------------------ .../nuget.template_install.config | 27 ----- .../nuget.template_test.config | 26 ----- 27 files changed, 2 insertions(+), 216 deletions(-) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/DotNetCommand.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/DotNetNewCommand.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/ITemplateExecutionTestConfigurationProvider.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/MessageSinkTestOutputHelper.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/ProcessExtensions.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/Project.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TemplateExecutionTestBase.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TemplateExecutionTestClassFixtureBase.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TemplateExecutionTestCollection.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TemplateExecutionTestCollectionFixture.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TemplateExecutionTestConfiguration.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TestCommand.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TestCommandExtensions.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TestCommandResult.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/TestCommandResultExtensions.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/VerifyScrubbers.cs (100%) rename test/{Shared/ProjectTemplates => ProjectTemplates/Infrastructure}/WellKnownPaths.cs (100%) delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/.editorconfig delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/.gitignore delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.props delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.targets delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/README.md delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/activate.ps1 delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_install.config delete mode 100644 test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_test.config diff --git a/test/Shared/ProjectTemplates/DotNetCommand.cs b/test/ProjectTemplates/Infrastructure/DotNetCommand.cs similarity index 100% rename from test/Shared/ProjectTemplates/DotNetCommand.cs rename to test/ProjectTemplates/Infrastructure/DotNetCommand.cs diff --git a/test/Shared/ProjectTemplates/DotNetNewCommand.cs b/test/ProjectTemplates/Infrastructure/DotNetNewCommand.cs similarity index 100% rename from test/Shared/ProjectTemplates/DotNetNewCommand.cs rename to test/ProjectTemplates/Infrastructure/DotNetNewCommand.cs diff --git a/test/Shared/ProjectTemplates/ITemplateExecutionTestConfigurationProvider.cs b/test/ProjectTemplates/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs similarity index 100% rename from test/Shared/ProjectTemplates/ITemplateExecutionTestConfigurationProvider.cs rename to test/ProjectTemplates/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs diff --git a/test/Shared/ProjectTemplates/MessageSinkTestOutputHelper.cs b/test/ProjectTemplates/Infrastructure/MessageSinkTestOutputHelper.cs similarity index 100% rename from test/Shared/ProjectTemplates/MessageSinkTestOutputHelper.cs rename to test/ProjectTemplates/Infrastructure/MessageSinkTestOutputHelper.cs diff --git a/test/Shared/ProjectTemplates/ProcessExtensions.cs b/test/ProjectTemplates/Infrastructure/ProcessExtensions.cs similarity index 100% rename from test/Shared/ProjectTemplates/ProcessExtensions.cs rename to test/ProjectTemplates/Infrastructure/ProcessExtensions.cs diff --git a/test/Shared/ProjectTemplates/Project.cs b/test/ProjectTemplates/Infrastructure/Project.cs similarity index 100% rename from test/Shared/ProjectTemplates/Project.cs rename to test/ProjectTemplates/Infrastructure/Project.cs diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestBase.cs similarity index 100% rename from test/Shared/ProjectTemplates/TemplateExecutionTestBase.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestBase.cs diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestClassFixtureBase.cs similarity index 100% rename from test/Shared/ProjectTemplates/TemplateExecutionTestClassFixtureBase.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestClassFixtureBase.cs diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollection.cs similarity index 100% rename from test/Shared/ProjectTemplates/TemplateExecutionTestCollection.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollection.cs diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs similarity index 100% rename from test/Shared/ProjectTemplates/TemplateExecutionTestCollectionFixture.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs diff --git a/test/Shared/ProjectTemplates/TemplateExecutionTestConfiguration.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestConfiguration.cs similarity index 100% rename from test/Shared/ProjectTemplates/TemplateExecutionTestConfiguration.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestConfiguration.cs diff --git a/test/Shared/ProjectTemplates/TestCommand.cs b/test/ProjectTemplates/Infrastructure/TestCommand.cs similarity index 100% rename from test/Shared/ProjectTemplates/TestCommand.cs rename to test/ProjectTemplates/Infrastructure/TestCommand.cs diff --git a/test/Shared/ProjectTemplates/TestCommandExtensions.cs b/test/ProjectTemplates/Infrastructure/TestCommandExtensions.cs similarity index 100% rename from test/Shared/ProjectTemplates/TestCommandExtensions.cs rename to test/ProjectTemplates/Infrastructure/TestCommandExtensions.cs diff --git a/test/Shared/ProjectTemplates/TestCommandResult.cs b/test/ProjectTemplates/Infrastructure/TestCommandResult.cs similarity index 100% rename from test/Shared/ProjectTemplates/TestCommandResult.cs rename to test/ProjectTemplates/Infrastructure/TestCommandResult.cs diff --git a/test/Shared/ProjectTemplates/TestCommandResultExtensions.cs b/test/ProjectTemplates/Infrastructure/TestCommandResultExtensions.cs similarity index 100% rename from test/Shared/ProjectTemplates/TestCommandResultExtensions.cs rename to test/ProjectTemplates/Infrastructure/TestCommandResultExtensions.cs diff --git a/test/Shared/ProjectTemplates/VerifyScrubbers.cs b/test/ProjectTemplates/Infrastructure/VerifyScrubbers.cs similarity index 100% rename from test/Shared/ProjectTemplates/VerifyScrubbers.cs rename to test/ProjectTemplates/Infrastructure/VerifyScrubbers.cs diff --git a/test/Shared/ProjectTemplates/WellKnownPaths.cs b/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs similarity index 100% rename from test/Shared/ProjectTemplates/WellKnownPaths.cs rename to test/ProjectTemplates/Infrastructure/WellKnownPaths.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj index 3b6fd7e1acf..9d86986d1ca 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj index e6f3c7af217..4fd76efee62 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/.editorconfig b/test/Shared/ProjectTemplates/TemplateSandbox/.editorconfig deleted file mode 100644 index b8f20c69836..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -# Don't apply the repo's editorconfig settings to generated templates. -root = true diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/.gitignore b/test/Shared/ProjectTemplates/TemplateSandbox/.gitignore deleted file mode 100644 index ee80e74117d..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Template test output -output/ diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.props b/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.props deleted file mode 100644 index e3b34086f94..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - true - - diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.targets b/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.targets deleted file mode 100644 index ecef22f1080..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/Directory.Build.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/README.md b/test/Shared/ProjectTemplates/TemplateSandbox/README.md deleted file mode 100644 index 7db29aaab19..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Template test sandbox - -This folder exists to serve as an isolated environment for template execution tests. - -## Debugging template execution tests - -Before running template execution tests, make sure that packages defined in this solution have been packed by running the following commands from the repo root: -```sh -./build.cmd -build -./build.cmd -pack -``` - -**Note:** These commands currently need to be run separately so that generated template content gets included in the template `.nupkg`. - -Template tests can be debugged either in VS or by running `dotnet test`. - -However, it's sometimes helpful to debug failures by building, running, and modifying the generated projects directly instead of tinkering with test code. - -To help with this scenario: -* The `output/` folder containing the generated projects doesn't get cleared until the start of the next test run. -* An `activate.ps1` script can be used to simulate the environment that the template execution tests use. This script: - * Sets the active .NET installation to `/.dotnet`. - * Sets the `NUGET_PACKAGES` environment variable to the `output/packages` folder to use the isolated package cache. - * Sets a `LOCAL_SHIPPING_PATH` environment variable so that locally-built packages can get picked up during restore. - -As an example, here's how you can build a project generated by the tests: -```sh -. ./activate.ps1 -cd ./output/[test_collection]/[generated_template] -dotnet build -``` diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/activate.ps1 b/test/Shared/ProjectTemplates/TemplateSandbox/activate.ps1 deleted file mode 100644 index a3dea765dd5..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/activate.ps1 +++ /dev/null @@ -1,109 +0,0 @@ -# -# This file creates an environment similar to the one that the template tests use. -# This makes it convenient to restore, build, and run projects generated by the template tests -# to debug test failures. -# -# This file must be used by invoking ". .\activate.ps1" from the command line. -# You cannot run it directly. See https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_scripts#script-scope-and-dot-sourcing -# -# To exit from the environment this creates, execute the 'deactivate' function. -# - -[CmdletBinding(PositionalBinding=$false)] -Param( - [string][Alias('c')]$configuration = "Debug" -) - -if ($MyInvocation.CommandOrigin -eq 'runspace') { - $cwd = (Get-Location).Path - $scriptPath = $MyInvocation.MyCommand.Path - $relativePath = [System.IO.Path]::GetRelativePath($cwd, $scriptPath) - Write-Host -f Red "This script cannot be invoked directly." - Write-Host -f Red "To function correctly, this script file must be 'dot sourced' by calling `". .\$relativePath`" (notice the dot at the beginning)." - exit 1 -} - -function deactivate ([switch]$init) { - # reset old environment variables - if (Test-Path variable:_OLD_PATH) { - $env:PATH = $_OLD_PATH - Remove-Item variable:_OLD_PATH - } - - if (test-path function:_old_prompt) { - Set-Item Function:prompt -Value $function:_old_prompt -ea ignore - remove-item function:_old_prompt - } - - Remove-Item env:DOTNET_ROOT -ea Ignore - Remove-Item 'env:DOTNET_ROOT(x86)' -ea Ignore - Remove-Item env:DOTNET_MULTILEVEL_LOOKUP -ea Ignore - Remove-Item env:NUGET_PACKAGES -ea Ignore - Remove-Item env:LOCAL_SHIPPING_PATH -ea Ignore - if (-not $init) { - # Remove functions defined - Remove-Item function:deactivate - Remove-Item function:Get-RepoRoot - } -} - -# Cleanup the environment -deactivate -init - -function Get-RepoRoot { - $directory = $PSScriptRoot - - while ($directory) { - $gitPath = Join-Path $directory ".git" - - if (Test-Path $gitPath) { - return $directory - } - - $parent = Split-Path $directory -Parent - if ($parent -eq $directory) { - # We've reached the filesystem root - break - } - - $directory = $parent - } - - throw "Failed to establish root of the repository" -} - -# Find the root of the repository -$repoRoot = Get-RepoRoot - -$_OLD_PATH = $env:PATH -# Tell dotnet where to find itself -$env:DOTNET_ROOT = "$repoRoot\.dotnet" -${env:DOTNET_ROOT(x86)} = "$repoRoot\.dotnet\x86" -# Tell dotnet not to look beyond the DOTNET_ROOT folder for more dotnet things -$env:DOTNET_MULTILEVEL_LOOKUP = 0 -# Put dotnet first on PATH -$env:PATH = "${env:DOTNET_ROOT};${env:PATH}" -# Set NUGET_PACKAGES and LOCAL_SHIPPING_PATH -$env:NUGET_PACKAGES = "$PSScriptRoot\output\packages" -$env:LOCAL_SHIPPING_PATH = "$repoRoot\artifacts\packages\$configuration\Shipping\" - -# Set the shell prompt -$function:_old_prompt = $function:prompt -function dotnet_prompt { - # Add a prefix to the current prompt, but don't discard it. - write-host "($( split-path $PSScriptRoot -leaf )) " -nonewline - & $function:_old_prompt -} - -Set-Item Function:prompt -Value $function:dotnet_prompt -ea ignore - -Write-Host -f Magenta "Enabled the template testing environment. Execute 'deactivate' to exit." -Write-Host -f Magenta "Using the '$configuration' configuration. Use the -c option to specify a different configuration." -if (-not (Test-Path "${env:DOTNET_ROOT}\dotnet.exe")) { - Write-Host -f Yellow ".NET Core has not been installed yet. Run $repoRoot\build.cmd -restore to install it." -} -else { - Write-Host "dotnet = ${env:DOTNET_ROOT}\dotnet.exe" -} -Write-Host "NUGET_PACKAGES = ${env:NUGET_PACKAGES}" -Write-Host "LOCAL_SHIPPING_PATH = ${env:LOCAL_SHIPPING_PATH}" diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_install.config b/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_install.config deleted file mode 100644 index 4d77494baee..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_install.config +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_test.config b/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_test.config deleted file mode 100644 index c98b9777011..00000000000 --- a/test/Shared/ProjectTemplates/TemplateSandbox/nuget.template_test.config +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - From 4f3e89be00cd7d0e6fd3b9a80781b5fb421dc5b8 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 10:53:04 -0800 Subject: [PATCH 14/22] Fix the template sandbox / execution tests after moving infrastructure --- test/ProjectTemplates/.gitignore | 2 +- .../TemplateSandbox/.editorconfig | 2 + .../Infrastructure/TemplateSandbox/.gitignore | 2 + .../TemplateSandbox/Directory.Build.props | 11 ++ .../TemplateSandbox/Directory.Build.targets | 6 + .../Infrastructure/TemplateSandbox/README.md | 31 +++++ .../TemplateSandbox/activate.ps1 | 109 ++++++++++++++++++ .../nuget.template_install.config | 27 +++++ .../nuget.template_test.config | 26 +++++ .../Infrastructure/WellKnownPaths.cs | 4 +- 10 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/.editorconfig create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.props create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.targets create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_install.config create mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_test.config diff --git a/test/ProjectTemplates/.gitignore b/test/ProjectTemplates/.gitignore index b44b98ebd24..2345847897e 100644 --- a/test/ProjectTemplates/.gitignore +++ b/test/ProjectTemplates/.gitignore @@ -1 +1 @@ -*/TemplateSandbox +*/TemplateSandboxOutput diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/.editorconfig b/test/ProjectTemplates/Infrastructure/TemplateSandbox/.editorconfig new file mode 100644 index 00000000000..b8f20c69836 --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/.editorconfig @@ -0,0 +1,2 @@ +# Don't apply the repo's editorconfig settings to generated templates. +root = true diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore b/test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore new file mode 100644 index 00000000000..ee80e74117d --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore @@ -0,0 +1,2 @@ +# Template test output +output/ diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.props b/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.props new file mode 100644 index 00000000000..e3b34086f94 --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + true + + diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.targets b/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.targets new file mode 100644 index 00000000000..ecef22f1080 --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.targets @@ -0,0 +1,6 @@ + + + diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md b/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md new file mode 100644 index 00000000000..7db29aaab19 --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md @@ -0,0 +1,31 @@ +# Template test sandbox + +This folder exists to serve as an isolated environment for template execution tests. + +## Debugging template execution tests + +Before running template execution tests, make sure that packages defined in this solution have been packed by running the following commands from the repo root: +```sh +./build.cmd -build +./build.cmd -pack +``` + +**Note:** These commands currently need to be run separately so that generated template content gets included in the template `.nupkg`. + +Template tests can be debugged either in VS or by running `dotnet test`. + +However, it's sometimes helpful to debug failures by building, running, and modifying the generated projects directly instead of tinkering with test code. + +To help with this scenario: +* The `output/` folder containing the generated projects doesn't get cleared until the start of the next test run. +* An `activate.ps1` script can be used to simulate the environment that the template execution tests use. This script: + * Sets the active .NET installation to `/.dotnet`. + * Sets the `NUGET_PACKAGES` environment variable to the `output/packages` folder to use the isolated package cache. + * Sets a `LOCAL_SHIPPING_PATH` environment variable so that locally-built packages can get picked up during restore. + +As an example, here's how you can build a project generated by the tests: +```sh +. ./activate.ps1 +cd ./output/[test_collection]/[generated_template] +dotnet build +``` diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 b/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 new file mode 100644 index 00000000000..a3dea765dd5 --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 @@ -0,0 +1,109 @@ +# +# This file creates an environment similar to the one that the template tests use. +# This makes it convenient to restore, build, and run projects generated by the template tests +# to debug test failures. +# +# This file must be used by invoking ". .\activate.ps1" from the command line. +# You cannot run it directly. See https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_scripts#script-scope-and-dot-sourcing +# +# To exit from the environment this creates, execute the 'deactivate' function. +# + +[CmdletBinding(PositionalBinding=$false)] +Param( + [string][Alias('c')]$configuration = "Debug" +) + +if ($MyInvocation.CommandOrigin -eq 'runspace') { + $cwd = (Get-Location).Path + $scriptPath = $MyInvocation.MyCommand.Path + $relativePath = [System.IO.Path]::GetRelativePath($cwd, $scriptPath) + Write-Host -f Red "This script cannot be invoked directly." + Write-Host -f Red "To function correctly, this script file must be 'dot sourced' by calling `". .\$relativePath`" (notice the dot at the beginning)." + exit 1 +} + +function deactivate ([switch]$init) { + # reset old environment variables + if (Test-Path variable:_OLD_PATH) { + $env:PATH = $_OLD_PATH + Remove-Item variable:_OLD_PATH + } + + if (test-path function:_old_prompt) { + Set-Item Function:prompt -Value $function:_old_prompt -ea ignore + remove-item function:_old_prompt + } + + Remove-Item env:DOTNET_ROOT -ea Ignore + Remove-Item 'env:DOTNET_ROOT(x86)' -ea Ignore + Remove-Item env:DOTNET_MULTILEVEL_LOOKUP -ea Ignore + Remove-Item env:NUGET_PACKAGES -ea Ignore + Remove-Item env:LOCAL_SHIPPING_PATH -ea Ignore + if (-not $init) { + # Remove functions defined + Remove-Item function:deactivate + Remove-Item function:Get-RepoRoot + } +} + +# Cleanup the environment +deactivate -init + +function Get-RepoRoot { + $directory = $PSScriptRoot + + while ($directory) { + $gitPath = Join-Path $directory ".git" + + if (Test-Path $gitPath) { + return $directory + } + + $parent = Split-Path $directory -Parent + if ($parent -eq $directory) { + # We've reached the filesystem root + break + } + + $directory = $parent + } + + throw "Failed to establish root of the repository" +} + +# Find the root of the repository +$repoRoot = Get-RepoRoot + +$_OLD_PATH = $env:PATH +# Tell dotnet where to find itself +$env:DOTNET_ROOT = "$repoRoot\.dotnet" +${env:DOTNET_ROOT(x86)} = "$repoRoot\.dotnet\x86" +# Tell dotnet not to look beyond the DOTNET_ROOT folder for more dotnet things +$env:DOTNET_MULTILEVEL_LOOKUP = 0 +# Put dotnet first on PATH +$env:PATH = "${env:DOTNET_ROOT};${env:PATH}" +# Set NUGET_PACKAGES and LOCAL_SHIPPING_PATH +$env:NUGET_PACKAGES = "$PSScriptRoot\output\packages" +$env:LOCAL_SHIPPING_PATH = "$repoRoot\artifacts\packages\$configuration\Shipping\" + +# Set the shell prompt +$function:_old_prompt = $function:prompt +function dotnet_prompt { + # Add a prefix to the current prompt, but don't discard it. + write-host "($( split-path $PSScriptRoot -leaf )) " -nonewline + & $function:_old_prompt +} + +Set-Item Function:prompt -Value $function:dotnet_prompt -ea ignore + +Write-Host -f Magenta "Enabled the template testing environment. Execute 'deactivate' to exit." +Write-Host -f Magenta "Using the '$configuration' configuration. Use the -c option to specify a different configuration." +if (-not (Test-Path "${env:DOTNET_ROOT}\dotnet.exe")) { + Write-Host -f Yellow ".NET Core has not been installed yet. Run $repoRoot\build.cmd -restore to install it." +} +else { + Write-Host "dotnet = ${env:DOTNET_ROOT}\dotnet.exe" +} +Write-Host "NUGET_PACKAGES = ${env:NUGET_PACKAGES}" +Write-Host "LOCAL_SHIPPING_PATH = ${env:LOCAL_SHIPPING_PATH}" diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_install.config b/test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_install.config new file mode 100644 index 00000000000..4d77494baee --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_install.config @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_test.config b/test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_test.config new file mode 100644 index 00000000000..c98b9777011 --- /dev/null +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_test.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs b/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs index 535f3bf5d40..7c04453dab6 100644 --- a/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs +++ b/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs @@ -28,8 +28,8 @@ static WellKnownPaths() ThisProjectRoot = ProjectRootHelper.GetThisProjectRoot(); TemplateFeedLocation = Path.Combine(RepoRoot, "src", "ProjectTemplates"); - TemplateSandboxRoot = Path.Combine(RepoRoot, "test", "Shared", "ProjectTemplates", "TemplateSandbox"); - TemplateSandboxOutputRoot = Path.Combine(ThisProjectRoot, "TemplateSandbox", "output"); + TemplateSandboxRoot = Path.Combine(RepoRoot, "test", "ProjectTemplates", "Infrastructure", "TemplateSandbox"); + TemplateSandboxOutputRoot = Path.Combine(ThisProjectRoot, "TemplateSandboxOutput"); TemplateInstallNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_install.config"); TemplateTestNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_test.config"); From 6d85d1d96bf3c16eee296192eb2a5ab623a12077 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 13:50:22 -0800 Subject: [PATCH 15/22] Ignore CA1716 warning about 'Shared' namespace in template tests --- .../Microsoft.Agents.AI.Templates.Tests.csproj | 2 +- .../Microsoft.Extensions.AI.Templates.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj index 9d86986d1ca..521086abc6e 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj @@ -7,7 +7,7 @@ - $(NoWarn);CA1063;CA1861;CA2201;VSTHRD003;S104;S125;S2699 + $(NoWarn);CA1063;CA1716;CA1861;CA2201;VSTHRD003;S104;S125;S2699 diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj index 4fd76efee62..05b28ca3546 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj @@ -7,7 +7,7 @@ - $(NoWarn);CA1063;CA1861;CA2201;VSTHRD003;S104;S125;S2699 + $(NoWarn);CA1063;CA1716;CA1861;CA2201;VSTHRD003;S104;S125;S2699 From 650fbfb142fb24fb6eb9a05b31aa88821b459599 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 14:01:18 -0800 Subject: [PATCH 16/22] Clean up template sandbox source/output --- test/ProjectTemplates/.gitignore | 2 +- .../TemplateExecutionTestCollectionFixture.cs | 2 +- .../Infrastructure/TemplateSandbox/.gitignore | 2 -- test/ProjectTemplates/Infrastructure/WellKnownPaths.cs | 10 +++++----- 4 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore diff --git a/test/ProjectTemplates/.gitignore b/test/ProjectTemplates/.gitignore index 2345847897e..6fbeae3c400 100644 --- a/test/ProjectTemplates/.gitignore +++ b/test/ProjectTemplates/.gitignore @@ -1 +1 @@ -*/TemplateSandboxOutput +*/ExecutionTestSandbox diff --git a/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs index 062954266ee..3b015d80202 100644 --- a/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs +++ b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs @@ -29,7 +29,7 @@ public TemplateExecutionTestCollectionFixture() // Then we copy the template sandbox infrastructure to the output location for use during tests. Directory.CreateDirectory(WellKnownPaths.TemplateSandboxOutputRoot); - foreach (var filePath in Directory.EnumerateFiles(WellKnownPaths.TemplateSandboxRoot)) + foreach (var filePath in Directory.EnumerateFiles(WellKnownPaths.TemplateSandboxSource)) { var fileName = Path.GetFileName(filePath); var destFilePath = Path.Combine(WellKnownPaths.TemplateSandboxOutputRoot, fileName); diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore b/test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore deleted file mode 100644 index ee80e74117d..00000000000 --- a/test/ProjectTemplates/Infrastructure/TemplateSandbox/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Template test output -output/ diff --git a/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs b/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs index 7c04453dab6..0ebb196fe84 100644 --- a/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs +++ b/test/ProjectTemplates/Infrastructure/WellKnownPaths.cs @@ -14,7 +14,7 @@ internal static class WellKnownPaths public static readonly string ThisProjectRoot; public static readonly string TemplateFeedLocation; - public static readonly string TemplateSandboxRoot; + public static readonly string TemplateSandboxSource; public static readonly string TemplateSandboxOutputRoot; public static readonly string TemplateInstallNuGetConfigPath; public static readonly string TemplateTestNuGetConfigPath; @@ -28,10 +28,10 @@ static WellKnownPaths() ThisProjectRoot = ProjectRootHelper.GetThisProjectRoot(); TemplateFeedLocation = Path.Combine(RepoRoot, "src", "ProjectTemplates"); - TemplateSandboxRoot = Path.Combine(RepoRoot, "test", "ProjectTemplates", "Infrastructure", "TemplateSandbox"); - TemplateSandboxOutputRoot = Path.Combine(ThisProjectRoot, "TemplateSandboxOutput"); - TemplateInstallNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_install.config"); - TemplateTestNuGetConfigPath = Path.Combine(TemplateSandboxRoot, "nuget.template_test.config"); + TemplateSandboxSource = Path.Combine(RepoRoot, "test", "ProjectTemplates", "Infrastructure", "TemplateSandbox"); + TemplateSandboxOutputRoot = Path.Combine(ThisProjectRoot, "ExecutionTestSandbox"); + TemplateInstallNuGetConfigPath = Path.Combine(TemplateSandboxSource, "nuget.template_install.config"); + TemplateTestNuGetConfigPath = Path.Combine(TemplateSandboxSource, "nuget.template_test.config"); const string BuildConfigurationFolder = #if DEBUG From 36f45f1cb14172df720a4fb3bcf16edffc9fbabc Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 14:47:41 -0800 Subject: [PATCH 17/22] Rename to "aiagent-webapi" and favor singular "Agent". Docs cleanup. --- src/ProjectTemplates/GeneratedContent.targets | 6 +- .../Microsoft.Agents.AI.Templates.csproj | 6 +- .../PROJECT_STRUCTURE.md | 272 ------------------ .../.template.config/dotnetcli.host.json | 0 .../.template.config/ide.host.json | 0 .../.template.config/ide/agent-framework.ico | Bin .../.template.config/template.json | 14 +- .../WebApiAgent-CSharp}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../WebApiAgent-CSharp}/README.md | 18 +- .../WebApiAgent-CSharp.csproj.in} | 0 .../WebApiAgent-CSharp}/appsettings.json | 0 .../aiagent-webapi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../aiagent-webapi}/README.md | 18 +- .../aiagent-webapi/aiagent-webapi.csproj} | 0 .../aiagent-webapi}/appsettings.json | 0 .../aiagent-webapi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../aiagent-webapi}/README.md | 18 +- .../aiagent-webapi/aiagent-webapi.csproj} | 0 .../aiagent-webapi}/appsettings.json | 0 .../aiagent-webapi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../aiagent-webapi}/README.md | 18 +- .../aiagent-webapi/aiagent-webapi.csproj} | 0 .../aiagent-webapi}/appsettings.json | 0 .../aiagent-webapi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../aiagent-webapi}/README.md | 18 +- .../aiagent-webapi/aiagent-webapi.csproj} | 0 .../aiagent-webapi}/appsettings.json | 0 .../aiagent-webapi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../aiagent-webapi}/README.md | 18 +- .../aiagent-webapi/aiagent-webapi.csproj} | 0 .../aiagent-webapi}/appsettings.json | 0 .../aiagent-webapi}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../aiagent-webapi}/README.md | 18 +- .../aiagent-webapi/aiagent-webapi.csproj} | 0 .../aiagent-webapi}/appsettings.json | 0 ...s => WebApiAgentTemplateExecutionTests.cs} | 12 +- ...cs => WebApiAgentTemplateSnapshotTests.cs} | 8 +- 44 files changed, 86 insertions(+), 358 deletions(-) delete mode 100644 src/ProjectTemplates/Microsoft.Agents.AI.Templates/PROJECT_STRUCTURE.md rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/.template.config/dotnetcli.host.json (100%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/.template.config/ide.host.json (100%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/.template.config/ide/agent-framework.ico (100%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/.template.config/template.json (94%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/Program.cs (100%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/Properties/launchSettings.json (100%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/README.md (94%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in => WebApiAgent/WebApiAgent-CSharp/WebApiAgent-CSharp.csproj.in} (100%) rename src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/{WebApiAgents/WebApiAgents-CSharp => WebApiAgent/WebApiAgent-CSharp}/appsettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi}/Program.cs (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi}/Properties/launchSettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi}/README.md (84%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/aiagents-webapi.csproj => aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/aiagent-webapi.csproj} (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi}/appsettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi}/Program.cs (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi}/Properties/launchSettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi}/README.md (79%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/aiagents-webapi.csproj => aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/aiagent-webapi.csproj} (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi => aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi}/appsettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.DefaultParameters.verified/aiagents-webapi => aiagent-webapi.DefaultParameters.verified/aiagent-webapi}/Program.cs (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.DefaultParameters.verified/aiagents-webapi => aiagent-webapi.DefaultParameters.verified/aiagent-webapi}/Properties/launchSettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.DefaultParameters.verified/aiagents-webapi => aiagent-webapi.DefaultParameters.verified/aiagent-webapi}/README.md (85%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.DefaultParameters.verified/aiagents-webapi/aiagents-webapi.csproj => aiagent-webapi.DefaultParameters.verified/aiagent-webapi/aiagent-webapi.csproj} (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.DefaultParameters.verified/aiagents-webapi => aiagent-webapi.DefaultParameters.verified/aiagent-webapi}/appsettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.GitHubModels.verified/aiagents-webapi => aiagent-webapi.GitHubModels.verified/aiagent-webapi}/Program.cs (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.GitHubModels.verified/aiagents-webapi => aiagent-webapi.GitHubModels.verified/aiagent-webapi}/Properties/launchSettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.GitHubModels.verified/aiagents-webapi => aiagent-webapi.GitHubModels.verified/aiagent-webapi}/README.md (85%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.GitHubModels.verified/aiagents-webapi/aiagents-webapi.csproj => aiagent-webapi.GitHubModels.verified/aiagent-webapi/aiagent-webapi.csproj} (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.GitHubModels.verified/aiagents-webapi => aiagent-webapi.GitHubModels.verified/aiagent-webapi}/appsettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.Ollama.verified/aiagents-webapi => aiagent-webapi.Ollama.verified/aiagent-webapi}/Program.cs (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.Ollama.verified/aiagents-webapi => aiagent-webapi.Ollama.verified/aiagent-webapi}/Properties/launchSettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.Ollama.verified/aiagents-webapi => aiagent-webapi.Ollama.verified/aiagent-webapi}/README.md (84%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.Ollama.verified/aiagents-webapi/aiagents-webapi.csproj => aiagent-webapi.Ollama.verified/aiagent-webapi/aiagent-webapi.csproj} (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.Ollama.verified/aiagents-webapi => aiagent-webapi.Ollama.verified/aiagent-webapi}/appsettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.OpenAI.verified/aiagents-webapi => aiagent-webapi.OpenAI.verified/aiagent-webapi}/Program.cs (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.OpenAI.verified/aiagents-webapi => aiagent-webapi.OpenAI.verified/aiagent-webapi}/Properties/launchSettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.OpenAI.verified/aiagents-webapi => aiagent-webapi.OpenAI.verified/aiagent-webapi}/README.md (84%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.OpenAI.verified/aiagents-webapi/aiagents-webapi.csproj => aiagent-webapi.OpenAI.verified/aiagent-webapi/aiagent-webapi.csproj} (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/{aiagents-webapi.OpenAI.verified/aiagents-webapi => aiagent-webapi.OpenAI.verified/aiagent-webapi}/appsettings.json (100%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/{WebApiAgentsTemplateExecutionTests.cs => WebApiAgentTemplateExecutionTests.cs} (88%) rename test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/{WebApiAgentsTemplateSnapshotTests.cs => WebApiAgentTemplateSnapshotTests.cs} (96%) diff --git a/src/ProjectTemplates/GeneratedContent.targets b/src/ProjectTemplates/GeneratedContent.targets index 10bf0a42205..72f571684ef 100644 --- a/src/ProjectTemplates/GeneratedContent.targets +++ b/src/ProjectTemplates/GeneratedContent.targets @@ -9,7 +9,7 @@ --> <_LocalChatTemplateVariant>aspire - <_WebApiAgentsRoot>$(MSBuildThisFileDirectory)Microsoft.Agents.AI.Templates\src\WebApiAgents\ + <_WebApiAgentRoot>$(MSBuildThisFileDirectory)Microsoft.Agents.AI.Templates\src\WebApiAgent\ <_ChatWithCustomDataContentRoot>$(MSBuildThisFileDirectory)Microsoft.Extensions.AI.Templates\src\ChatWithCustomData\ <_McpServerContentRoot>$(MSBuildThisFileDirectory)Microsoft.Extensions.AI.Templates\src\McpServer\ @@ -96,8 +96,8 @@ Include="$(_ChatWithCustomDataContentRoot)Directory.Build.props.in" OutputPath="$(_ChatWithCustomDataContentRoot)Directory.Build.props" /> + Include="$(_WebApiAgentRoot)WebApiAgent-CSharp\WebApiAgent-CSharp.csproj.in" + OutputPath="$(_WebApiAgentRoot)WebApiAgent-CSharp\WebApiAgent-CSharp.csproj" /> diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj index 2921dd9bc5d..311d6351a99 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj @@ -4,7 +4,7 @@ Template $(NetCoreTargetFrameworks) Project templates for Microsoft.Agents.AI. - dotnet-new;templates;agents;ai + dotnet-new;templates;ai;agent preview 1 @@ -35,9 +35,9 @@ - + - [GitHub Models](https://github.com/marketplace/models) @@ -229,7 +230,6 @@ dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 - [Ollama](https://ollama.com) -- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/WebApiAgent-CSharp.csproj.in similarity index 100% rename from src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/WebApiAgents-CSharp.csproj.in rename to src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/WebApiAgent-CSharp.csproj.in diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/appsettings.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/appsettings.json similarity index 100% rename from src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgents/WebApiAgents-CSharp/appsettings.json rename to src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Program.cs similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Program.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Program.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Properties/launchSettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/Properties/launchSettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Properties/launchSettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md similarity index 84% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md index 6b318475d6d..933b54e2297 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ -# AI Agents Web API +# AI Agent Web API -This is an AI Agents Web API application created from the `aiagents-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. ## Prerequisites @@ -57,7 +57,7 @@ The application exposes OpenAI-compatible API endpoints. You can interact with t ## How It Works -This application demonstrates the AI Agents framework with: +This application demonstrates Agent Framework with: 1. **Writer Agent**: Writes short stories (300 words or less) about specified topics 2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words @@ -71,16 +71,16 @@ When creating a new project, you can customize it using template parameters: ```bash # Specify AI service provider -dotnet new aiagents-webapi --provider azureopenai +dotnet new aiagent-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --chat-model gpt-4o +dotnet new aiagent-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI -dotnet new aiagents-webapi --provider azureopenai --managed-identity false +dotnet new aiagent-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -109,9 +109,9 @@ dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ## Learn More -- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [AI apps for .NET developers](https://learn.microsoft.com/dotnet/ai) +- [Microsoft Agent Framework Documentation](https://aka.ms/dotnet/agent-framework/docs) - [Azure OpenAI Service](https://azure.microsoft.com/products/ai-services/openai-service) -- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/aiagent-webapi.csproj similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/aiagents-webapi.csproj rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/aiagent-webapi.csproj diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/appsettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ApiKey.verified/aiagents-webapi/appsettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Program.cs similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Program.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Program.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Properties/launchSettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/Properties/launchSettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Properties/launchSettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md similarity index 79% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md index c2ed7685cca..4189c425023 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ -# AI Agents Web API +# AI Agent Web API -This is an AI Agents Web API application created from the `aiagents-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. ## Prerequisites @@ -29,7 +29,7 @@ The application exposes OpenAI-compatible API endpoints. You can interact with t ## How It Works -This application demonstrates the AI Agents framework with: +This application demonstrates Agent Framework with: 1. **Writer Agent**: Writes short stories (300 words or less) about specified topics 2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words @@ -43,16 +43,16 @@ When creating a new project, you can customize it using template parameters: ```bash # Specify AI service provider -dotnet new aiagents-webapi --provider azureopenai +dotnet new aiagent-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --chat-model gpt-4o +dotnet new aiagent-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI -dotnet new aiagents-webapi --provider azureopenai --managed-identity false +dotnet new aiagent-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -81,9 +81,9 @@ dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ## Learn More -- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [AI apps for .NET developers](https://learn.microsoft.com/dotnet/ai) +- [Microsoft Agent Framework Documentation](https://aka.ms/dotnet/agent-framework/docs) - [Azure OpenAI Service](https://azure.microsoft.com/products/ai-services/openai-service) -- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/aiagent-webapi.csproj similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/aiagents-webapi.csproj rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/aiagent-webapi.csproj diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/appsettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.AzureOpenAI_ManagedIdentity.verified/aiagents-webapi/appsettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Program.cs similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Program.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Program.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Properties/launchSettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/Properties/launchSettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Properties/launchSettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md similarity index 85% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md index e61e9ee4937..72f56ea964a 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ -# AI Agents Web API +# AI Agent Web API -This is an AI Agents Web API application created from the `aiagents-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. ## Prerequisites @@ -59,7 +59,7 @@ The application exposes OpenAI-compatible API endpoints. You can interact with t ## How It Works -This application demonstrates the AI Agents framework with: +This application demonstrates Agent Framework with: 1. **Writer Agent**: Writes short stories (300 words or less) about specified topics 2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words @@ -73,16 +73,16 @@ When creating a new project, you can customize it using template parameters: ```bash # Specify AI service provider -dotnet new aiagents-webapi --provider azureopenai +dotnet new aiagent-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --chat-model gpt-4o +dotnet new aiagent-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI -dotnet new aiagents-webapi --provider azureopenai --managed-identity false +dotnet new aiagent-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -111,9 +111,9 @@ dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ## Learn More -- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [AI apps for .NET developers](https://learn.microsoft.com/dotnet/ai) +- [Microsoft Agent Framework Documentation](https://aka.ms/dotnet/agent-framework/docs) - [GitHub Models](https://github.com/marketplace/models) -- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/aiagent-webapi.csproj similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/aiagents-webapi.csproj rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/aiagent-webapi.csproj diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/appsettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.DefaultParameters.verified/aiagents-webapi/appsettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Program.cs similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Program.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Program.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Properties/launchSettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/Properties/launchSettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Properties/launchSettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md similarity index 85% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md index e61e9ee4937..72f56ea964a 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ -# AI Agents Web API +# AI Agent Web API -This is an AI Agents Web API application created from the `aiagents-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. ## Prerequisites @@ -59,7 +59,7 @@ The application exposes OpenAI-compatible API endpoints. You can interact with t ## How It Works -This application demonstrates the AI Agents framework with: +This application demonstrates Agent Framework with: 1. **Writer Agent**: Writes short stories (300 words or less) about specified topics 2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words @@ -73,16 +73,16 @@ When creating a new project, you can customize it using template parameters: ```bash # Specify AI service provider -dotnet new aiagents-webapi --provider azureopenai +dotnet new aiagent-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --chat-model gpt-4o +dotnet new aiagent-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI -dotnet new aiagents-webapi --provider azureopenai --managed-identity false +dotnet new aiagent-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -111,9 +111,9 @@ dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ## Learn More -- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [AI apps for .NET developers](https://learn.microsoft.com/dotnet/ai) +- [Microsoft Agent Framework Documentation](https://aka.ms/dotnet/agent-framework/docs) - [GitHub Models](https://github.com/marketplace/models) -- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/aiagent-webapi.csproj similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/aiagents-webapi.csproj rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/aiagent-webapi.csproj diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/appsettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.GitHubModels.verified/aiagents-webapi/appsettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Program.cs similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Program.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Program.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Properties/launchSettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/Properties/launchSettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Properties/launchSettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md similarity index 84% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md index 3f674e0934b..6e10ff2079d 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ -# AI Agents Web API +# AI Agent Web API -This is an AI Agents Web API application created from the `aiagents-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. ## Prerequisites @@ -40,7 +40,7 @@ The application exposes OpenAI-compatible API endpoints. You can interact with t ## How It Works -This application demonstrates the AI Agents framework with: +This application demonstrates Agent Framework with: 1. **Writer Agent**: Writes short stories (300 words or less) about specified topics 2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words @@ -54,16 +54,16 @@ When creating a new project, you can customize it using template parameters: ```bash # Specify AI service provider -dotnet new aiagents-webapi --provider azureopenai +dotnet new aiagent-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --chat-model gpt-4o +dotnet new aiagent-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI -dotnet new aiagents-webapi --provider azureopenai --managed-identity false +dotnet new aiagent-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -92,9 +92,9 @@ dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ## Learn More -- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [AI apps for .NET developers](https://learn.microsoft.com/dotnet/ai) +- [Microsoft Agent Framework Documentation](https://aka.ms/dotnet/agent-framework/docs) - [Ollama](https://ollama.com) -- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/aiagent-webapi.csproj similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/aiagents-webapi.csproj rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/aiagent-webapi.csproj diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/appsettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.Ollama.verified/aiagents-webapi/appsettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Program.cs similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Program.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Program.cs diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Properties/launchSettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/Properties/launchSettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Properties/launchSettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md similarity index 84% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md index 92356eca2eb..c1eb5b9b81f 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ -# AI Agents Web API +# AI Agent Web API -This is an AI Agents Web API application created from the `aiagents-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. ## Prerequisites @@ -59,7 +59,7 @@ The application exposes OpenAI-compatible API endpoints. You can interact with t ## How It Works -This application demonstrates the AI Agents framework with: +This application demonstrates Agent Framework with: 1. **Writer Agent**: Writes short stories (300 words or less) about specified topics 2. **Editor Agent**: Edits stories to improve grammar and style, ensuring they stay under 300 words @@ -73,16 +73,16 @@ When creating a new project, you can customize it using template parameters: ```bash # Specify AI service provider -dotnet new aiagents-webapi --provider azureopenai +dotnet new aiagent-webapi --provider azureopenai # Specify a custom chat model -dotnet new aiagents-webapi --chat-model gpt-4o +dotnet new aiagent-webapi --chat-model gpt-4o # Use API key authentication for Azure OpenAI -dotnet new aiagents-webapi --provider azureopenai --managed-identity false +dotnet new aiagent-webapi --provider azureopenai --managed-identity false # Use Ollama with a different model -dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 ``` ### Available Parameters @@ -111,9 +111,9 @@ dotnet new aiagents-webapi --provider ollama --chat-model llama3.1 ## Learn More -- [Microsoft.Agents.AI Documentation](https://learn.microsoft.com/dotnet/ai/agents) +- [AI apps for .NET developers](https://learn.microsoft.com/dotnet/ai) +- [Microsoft Agent Framework Documentation](https://aka.ms/dotnet/agent-framework/docs) - [OpenAI Platform](https://platform.openai.com) -- [.NET AI Libraries](https://learn.microsoft.com/dotnet/ai/) ## Troubleshooting diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/aiagents-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/aiagent-webapi.csproj similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/aiagents-webapi.csproj rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/aiagent-webapi.csproj diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/appsettings.json similarity index 100% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagents-webapi.OpenAI.verified/aiagents-webapi/appsettings.json rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/appsettings.json diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateExecutionTests.cs similarity index 88% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateExecutionTests.cs index 9211cd276da..f9dc4861c65 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateExecutionTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.Agents.AI.Templates.Tests; /// -/// Contains execution tests for the "AI Agents Web API" template. +/// Contains execution tests for the "AI Agent Web API" template. /// /// /// In addition to validating that the templates build and restore correctly, @@ -21,9 +21,9 @@ namespace Microsoft.Agents.AI.Templates.Tests; /// Therefore, it's *critical* that these tests remain in a working state, /// as disabling them will also disable CG reporting. /// -public class WebApiAgentsTemplateExecutionTests : TemplateExecutionTestBase, ITemplateExecutionTestConfigurationProvider +public class WebApiAgentTemplateExecutionTests : TemplateExecutionTestBase, ITemplateExecutionTestConfigurationProvider { - public WebApiAgentsTemplateExecutionTests(TemplateExecutionTestFixture fixture, ITestOutputHelper outputHelper) + public WebApiAgentTemplateExecutionTests(TemplateExecutionTestFixture fixture, ITestOutputHelper outputHelper) : base(fixture, outputHelper) { } @@ -31,7 +31,7 @@ public WebApiAgentsTemplateExecutionTests(TemplateExecutionTestFixture fixture, public static TemplateExecutionTestConfiguration Configuration { get; } = new() { TemplatePackageName = "Microsoft.Agents.AI.Templates", - TestOutputFolderPrefix = "WebApiAgents" + TestOutputFolderPrefix = "WebApiAgent" }; public static IEnumerable GetTemplateOptions() @@ -42,9 +42,9 @@ public static IEnumerable GetTemplateOptions() [MemberData(nameof(GetTemplateOptions))] public async Task CreateRestoreAndBuild(params string[] args) { - const string ProjectName = "WebApiAgentsApp"; + const string ProjectName = "WebApiAgentApp"; var project = await Fixture.CreateProjectAsync( - templateName: "aiagents-webapi", + templateName: "aiagent-webapi", projectName: ProjectName, args); diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs similarity index 96% rename from test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs rename to test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs index e3273a70a1c..c644bed38ea 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentsTemplateSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Agents.AI.Templates.Tests; -public class WebApiAgentsTemplateSnapshotTests +public class WebApiAgentTemplateSnapshotTests { private static readonly string[] _verificationExcludePatterns = [ @@ -33,7 +33,7 @@ public class WebApiAgentsTemplateSnapshotTests private readonly ILogger _log; - public WebApiAgentsTemplateSnapshotTests(ITestOutputHelper log) + public WebApiAgentTemplateSnapshotTests(ITestOutputHelper log) { #pragma warning disable CA2000 // Dispose objects before losing scope _log = new XunitLoggerProvider(log).CreateLogger("TestRun"); @@ -79,10 +79,10 @@ public async Task Ollama() private async Task TestTemplateCoreAsync(string scenarioName, IEnumerable? templateArgs = null) { string workingDir = TestUtils.CreateTemporaryFolder(); - string templateShortName = "aiagents-webapi"; + string templateShortName = "aiagent-webapi"; // Get the template location - string templateLocation = Path.Combine(WellKnownPaths.TemplateFeedLocation, "Microsoft.Agents.AI.Templates", "src", "WebApiAgents"); + string templateLocation = Path.Combine(WellKnownPaths.TemplateFeedLocation, "Microsoft.Agents.AI.Templates", "src", "WebApiAgent"); var verificationExcludePatterns = Path.DirectorySeparatorChar is '/' ? _verificationExcludePatterns From 0fa70aa5ff2e0ac8acaa030a6403a0714a813cba Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 15:32:16 -0800 Subject: [PATCH 18/22] Update templates dev doc to cover Microsoft.Agents.AI.Templates too --- src/ProjectTemplates/README.md | 35 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/ProjectTemplates/README.md b/src/ProjectTemplates/README.md index 5ac7bb096f8..c3fa66cf6ff 100644 --- a/src/ProjectTemplates/README.md +++ b/src/ProjectTemplates/README.md @@ -1,4 +1,6 @@ -# Updating project template JavaScript dependencies +# Project Template Local Development and Maintenance + +## Updating project template JavaScript dependencies To update project template JavaScript dependencies: 1. Install a recent build of Node.js @@ -11,38 +13,38 @@ To update project template JavaScript dependencies: To add a new dependency, run `npm install ` and update the `scripts` section in `package.json` to specify how the new dependency should be copied into its template. -# Component governance +## Component governance There are two types of template dependencies that need to get scanned for component governance (CG): * .NET dependencies (specified via `` in each `.csproj` file) * JS dependencies (everything in the `wwwroot/lib` folder of the `.Web` project) -There are template execution tests in the `test/ProjectTemplates` folder of this repo that create, restore, and build each possible variation of the template. These tests execute before the CG step of the internal CI pipeline, which scans the build artifacts from each generated project (namely the `project.assets.json` file and the local NuGet package cache) to detect which .NET dependencies got pulled in. +There are template execution tests in the `test/ProjectTemplates` folders that create, restore, and build each possible variation of the project templates. These tests execute before the CG step of the internal CI pipeline, which scans the build artifacts from each generated project (namely the `project.assets.json` file and the local NuGet package cache) to detect which .NET dependencies got pulled in. However, CG can't detect JS dependencies by scanning execution test output, because the generated projects don't contain manifests describing JS dependencies. Instead, we have a `package.json` and `package-lock.json` in the same folder as this README that define which JS dependencies get included in the template and how they get copied into template content (see previous section in this document). CG then automatically tracks packages listed in this `package-lock.json`. -# Running AI templates - ## Build the templates using just-built library package versions By default the templates use just-built versions of library packages from this repository, so NuGet packages must be produced before the templates can be run: ```pwsh -.\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 +.\build.cmd -vs AI -noLaunch # Generate an SDK.sln for projects matching the AI filter +.\build.cmd -build -pack # Build a NuGet package for each project in the generated SDK.sln ``` -Once the library packages are built, the `Microsoft.Extensions.AI.Templates` package is built with references to the local package versions using the following commands: +Once the library packages are built, the template packages can be built with references to the local package versions using the following commands: ```pwsh +.\build.cmd -pack -projects .\src\ProjectTemplates\Microsoft.Agents.AI.Templates\Microsoft.Extensions.AI.Templates.csproj .\build.cmd -pack -projects .\src\ProjectTemplates\Microsoft.Extensions.AI.Templates\Microsoft.Extensions.AI.Templates.csproj ``` ## Build the templates using pinned library package versions -The templates can also be built to reference pinned versions of the library packages. This approach is used when the `Microsoft.Extensions.AI.Templates` package is updated off-cycle from the library packages. The pinned versions are hard-coded in the `GeneratedContent.targets` file in this directory. To build the templates package using the pinned versions, run: +The templates can also be built to reference pinned versions of the library packages. This approach is used when a templates package is updated off-cycle from the library packages. The pinned versions are hard-coded in the `GeneratedContent.targets` file in this directory. To build the templates package using the pinned versions, run: ```pwsh +.\build.cmd -pack -projects .\src\ProjectTemplates\Microsoft.Agents.AI.Templates\Microsoft.Agents.AI.Templates.csproj /p:TemplateUsePinnedPackageVersions=true .\build.cmd -pack -projects .\src\ProjectTemplates\Microsoft.Extensions.AI.Templates\Microsoft.Extensions.AI.Templates.csproj /p:TemplateUsePinnedPackageVersions=true ``` @@ -54,24 +56,33 @@ Setting `/p:TemplateUsePinnedPackageVersions=true` will apply three different ca ## Installing the templates locally -After building the templates package using one of the approaches above, it can be installed locally. **Note:** Since package versions don't change between local builds, the recommended steps include clearing the `Microsoft.Extensions.AI*` packages from your local nuget cache. +After building the templates package using one of the approaches above, it can be installed locally. **Note:** Since package versions don't change between local builds, the recommended steps include clearing the `Microsoft.Extensions.AI*` and `Microsoft.Agents.AI*` packages from your local nuget cache. **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. ```pwsh # Uninstall any existing version of the templates +dotnet new uninstall Microsoft.Agents.AI.Templates dotnet new uninstall Microsoft.Extensions.AI.Templates -# Clear the Microsoft.Extensions.AI packages from the NuGet cache since the local package version does not change +# Clear the packages from the NuGet cache since the local package version does not change +Remove-Item ~\.nuget\packages\Microsoft.Agents.AI* -Recurse -Force Remove-Item ~\.nuget\packages\Microsoft.Extensions.AI* -Recurse -Force -# Install the template from the generated .nupkg file (in the artifacts/packages folder) +# Install the templates from the generated .nupkg file (in the artifacts/packages folder) +dotnet new install .\artifacts\packages\Debug\Shipping\Microsoft.Agents.AI.Templates*.nupkg dotnet new install .\artifacts\packages\Debug\Shipping\Microsoft.Extensions.AI.Templates*.nupkg ``` Finally, create a project from the template and run it: ```pwsh +dotnet new aiagent-webapi ` + [--provider ] ` + [--managed-identity] + +# or + dotnet new aichatweb ` [--provider ] ` [--vector-store ] ` From e02675a632390bfbd6089d63922e50724792d568 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 19:09:18 -0800 Subject: [PATCH 19/22] Fix remaining template sandbox references with new paths --- eng/build.proj | 2 +- .../Infrastructure/TemplateSandbox/README.md | 6 +++--- .../Infrastructure/TemplateSandbox/activate.ps1 | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eng/build.proj b/eng/build.proj index e59868907db..202437c6e9b 100644 --- a/eng/build.proj +++ b/eng/build.proj @@ -1,7 +1,7 @@ <_SnapshotsToExclude Include="$(MSBuildThisFileDirectory)..\test\**\Snapshots\**\*.*proj" /> - <_GeneratedContentToExclude Include="$(MSBuildThisFileDirectory)..\test\**\TemplateSandbox\**\*.*proj" /> + <_GeneratedContentToExclude Include="$(MSBuildThisFileDirectory)..\test\**\ExecutionTemplateSandbox\**\*.*proj" /> <_ProjectsToBuild Include="$(MSBuildThisFileDirectory)..\src\**\*.csproj" /> diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md b/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md index 7db29aaab19..6a9ecb84b86 100644 --- a/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md @@ -17,15 +17,15 @@ Template tests can be debugged either in VS or by running `dotnet test`. However, it's sometimes helpful to debug failures by building, running, and modifying the generated projects directly instead of tinkering with test code. To help with this scenario: -* The `output/` folder containing the generated projects doesn't get cleared until the start of the next test run. +* The `ExecutionTestSandbox` folder containing the generated projects doesn't get cleared until the start of the next test run. * An `activate.ps1` script can be used to simulate the environment that the template execution tests use. This script: * Sets the active .NET installation to `/.dotnet`. - * Sets the `NUGET_PACKAGES` environment variable to the `output/packages` folder to use the isolated package cache. + * Sets the `NUGET_PACKAGES` environment variable to the `ExecutionTestSandbox/packages` folder to use the isolated package cache. * Sets a `LOCAL_SHIPPING_PATH` environment variable so that locally-built packages can get picked up during restore. As an example, here's how you can build a project generated by the tests: ```sh . ./activate.ps1 -cd ./output/[test_collection]/[generated_template] +cd ./[test_collection]/[generated_template] dotnet build ``` diff --git a/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 b/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 index a3dea765dd5..43773c3fa19 100644 --- a/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 +++ b/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 @@ -84,7 +84,7 @@ $env:DOTNET_MULTILEVEL_LOOKUP = 0 # Put dotnet first on PATH $env:PATH = "${env:DOTNET_ROOT};${env:PATH}" # Set NUGET_PACKAGES and LOCAL_SHIPPING_PATH -$env:NUGET_PACKAGES = "$PSScriptRoot\output\packages" +$env:NUGET_PACKAGES = "$PSScriptRoot\packages" $env:LOCAL_SHIPPING_PATH = "$repoRoot\artifacts\packages\$configuration\Shipping\" # Set the shell prompt From 5640da3855f7d630ab9e07e2a9eb166139d54075 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 20:50:03 -0800 Subject: [PATCH 20/22] Add a tool call in aiagent-webapi. Update workflow API usage for upcoming change. Fix snapshots --- .../WebApiAgent/WebApiAgent-CSharp/Program.cs | 41 +++++++++++++------ .../aiagent-webapi/Program.cs | 27 ++++++++++-- .../aiagent-webapi/Program.cs | 27 ++++++++++-- .../aiagent-webapi/Program.cs | 31 +++++++++++--- .../aiagent-webapi/Program.cs | 31 +++++++++++--- .../aiagent-webapi/Program.cs | 31 +++++++++++--- .../aiagent-webapi/Program.cs | 27 ++++++++++-- 7 files changed, 179 insertions(+), 36 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Program.cs b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Program.cs index 24337017821..aadc8b70181 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Program.cs +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Program.cs @@ -2,9 +2,14 @@ using System.ClientModel; #elif (IsAzureOpenAI && IsManagedIdentity) using System.ClientModel.Primitives; +#endif +using System.ComponentModel; +#if (IsAzureOpenAI && IsManagedIdentity) using Azure.Identity; #endif +using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; #if (IsOllama) using OllamaSharp; @@ -22,16 +27,12 @@ var credential = new ApiKeyCredential(builder.Configuration["GitHubModels:Token"] ?? throw new InvalidOperationException("Missing configuration: GitHubModels:Token. See README for details.")); var openAIOptions = new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }; -var ghModelsClient = new OpenAIClient(credential, openAIOptions) +var chatClient = new OpenAIClient(credential, openAIOptions) .GetChatClient("gpt-4o-mini").AsIChatClient(); - -builder.Services.AddChatClient(ghModelsClient); #elif (IsOllama) // You will need to have Ollama running locally with the llama3.2 model installed // Visit https://ollama.com for installation instructions -IChatClient chatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2"); - -builder.Services.AddChatClient(chatClient); +var chatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2"); #elif (IsOpenAI) // You will need to set the API key to your own value // You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: @@ -43,8 +44,6 @@ #pragma warning disable OPENAI001 // GetOpenAIResponseClient(string) is experimental and subject to change or removal in future updates. var chatClient = openAIClient.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); #pragma warning restore OPENAI001 - -builder.Services.AddChatClient(chatClient); #elif (IsAzureOpenAI) // You will need to set the endpoint to your own value // You can do this using Visual Studio's "Manage User Secrets" UI, or on the command line: @@ -68,14 +67,24 @@ #endif var chatClient = azureOpenAI.GetOpenAIResponseClient("gpt-4o-mini").AsIChatClient(); #pragma warning restore OPENAI001 +#endif builder.Services.AddChatClient(chatClient); -#endif -var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); +builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); + +builder.AddAIAgent("editor", (sp, key) => new ChatClientAgent( + chatClient, + name: key, + instructions: "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words.", + tools: [ AIFunctionFactory.Create(FormatStory) ] +)); -builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); +builder.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); var app = builder.Build(); @@ -83,3 +92,11 @@ app.MapOpenAIResponses(); app.Run(); + +[Description("Formats the story for display.")] +string FormatStory(string title, string story) => $""" + **Title**: {title} + **Date**: {DateTime.Today.ToShortDateString()} + + {story} + """; diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Program.cs index 3715d7ed8e1..da628ffb782 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Program.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Program.cs @@ -1,5 +1,8 @@ using System.ClientModel; +using System.ComponentModel; +using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; using OpenAI; @@ -20,10 +23,20 @@ builder.Services.AddChatClient(chatClient); -var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); +builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); +builder.AddAIAgent("editor", (sp, key) => new ChatClientAgent( + chatClient, + name: key, + instructions: "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words.", + tools: [ AIFunctionFactory.Create(FormatStory) ] +)); + +builder.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); var app = builder.Build(); @@ -31,3 +44,11 @@ app.MapOpenAIResponses(); app.Run(); + +[Description("Formats the story for display.")] +string FormatStory(string title, string story) => $""" + **Title**: {title} + **Date**: {DateTime.Today.ToShortDateString()} + + {story} + """; diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Program.cs index 744ee0d506a..ced383f0234 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Program.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Program.cs @@ -1,6 +1,9 @@ using System.ClientModel.Primitives; +using System.ComponentModel; using Azure.Identity; +using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; using OpenAI; @@ -21,10 +24,20 @@ builder.Services.AddChatClient(chatClient); -var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); +builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); +builder.AddAIAgent("editor", (sp, key) => new ChatClientAgent( + chatClient, + name: key, + instructions: "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words.", + tools: [ AIFunctionFactory.Create(FormatStory) ] +)); + +builder.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); var app = builder.Build(); @@ -32,3 +45,11 @@ app.MapOpenAIResponses(); app.Run(); + +[Description("Formats the story for display.")] +string FormatStory(string title, string story) => $""" + **Title**: {title} + **Date**: {DateTime.Today.ToShortDateString()} + + {story} + """; diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Program.cs index edc4f80c68e..9ed14c2c652 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Program.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Program.cs @@ -1,5 +1,8 @@ using System.ClientModel; +using System.ComponentModel; +using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; using OpenAI; @@ -12,15 +15,25 @@ var credential = new ApiKeyCredential(builder.Configuration["GitHubModels:Token"] ?? throw new InvalidOperationException("Missing configuration: GitHubModels:Token. See README for details.")); var openAIOptions = new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }; -var ghModelsClient = new OpenAIClient(credential, openAIOptions) +var chatClient = new OpenAIClient(credential, openAIOptions) .GetChatClient("gpt-4o-mini").AsIChatClient(); -builder.Services.AddChatClient(ghModelsClient); +builder.Services.AddChatClient(chatClient); -var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); +builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); +builder.AddAIAgent("editor", (sp, key) => new ChatClientAgent( + chatClient, + name: key, + instructions: "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words.", + tools: [ AIFunctionFactory.Create(FormatStory) ] +)); + +builder.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); var app = builder.Build(); @@ -28,3 +41,11 @@ app.MapOpenAIResponses(); app.Run(); + +[Description("Formats the story for display.")] +string FormatStory(string title, string story) => $""" + **Title**: {title} + **Date**: {DateTime.Today.ToShortDateString()} + + {story} + """; diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Program.cs index edc4f80c68e..9ed14c2c652 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Program.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Program.cs @@ -1,5 +1,8 @@ using System.ClientModel; +using System.ComponentModel; +using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; using OpenAI; @@ -12,15 +15,25 @@ var credential = new ApiKeyCredential(builder.Configuration["GitHubModels:Token"] ?? throw new InvalidOperationException("Missing configuration: GitHubModels:Token. See README for details.")); var openAIOptions = new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }; -var ghModelsClient = new OpenAIClient(credential, openAIOptions) +var chatClient = new OpenAIClient(credential, openAIOptions) .GetChatClient("gpt-4o-mini").AsIChatClient(); -builder.Services.AddChatClient(ghModelsClient); +builder.Services.AddChatClient(chatClient); -var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); +builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); +builder.AddAIAgent("editor", (sp, key) => new ChatClientAgent( + chatClient, + name: key, + instructions: "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words.", + tools: [ AIFunctionFactory.Create(FormatStory) ] +)); + +builder.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); var app = builder.Build(); @@ -28,3 +41,11 @@ app.MapOpenAIResponses(); app.Run(); + +[Description("Formats the story for display.")] +string FormatStory(string title, string story) => $""" + **Title**: {title} + **Date**: {DateTime.Today.ToShortDateString()} + + {story} + """; diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Program.cs index 6410937d52b..4015d7fdc98 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Program.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Program.cs @@ -1,4 +1,7 @@ -using Microsoft.Agents.AI.Hosting; +using System.ComponentModel; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; using OllamaSharp; @@ -6,14 +9,24 @@ // You will need to have Ollama running locally with the llama3.2 model installed // Visit https://ollama.com for installation instructions -IChatClient chatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2"); +var chatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2"); builder.Services.AddChatClient(chatClient); -var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); +builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); +builder.AddAIAgent("editor", (sp, key) => new ChatClientAgent( + chatClient, + name: key, + instructions: "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words.", + tools: [ AIFunctionFactory.Create(FormatStory) ] +)); + +builder.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); var app = builder.Build(); @@ -21,3 +34,11 @@ app.MapOpenAIResponses(); app.Run(); + +[Description("Formats the story for display.")] +string FormatStory(string title, string story) => $""" + **Title**: {title} + **Date**: {DateTime.Today.ToShortDateString()} + + {story} + """; diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Program.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Program.cs index 3958c3d5993..4bf9e2b74b8 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Program.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Program.cs @@ -1,5 +1,8 @@ using System.ClientModel; +using System.ComponentModel; +using Microsoft.Agents.AI; using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; using Microsoft.Extensions.AI; using OpenAI; @@ -18,10 +21,20 @@ builder.Services.AddChatClient(chatClient); -var writer = builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -var editor = builder.AddAIAgent("editor", "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words."); +builder.AddAIAgent("writer", "You write short stories (300 words or less) about the specified topic."); -builder.AddSequentialWorkflow("publisher", [writer, editor]).AddAsAIAgent(); +builder.AddAIAgent("editor", (sp, key) => new ChatClientAgent( + chatClient, + name: key, + instructions: "You edit short stories to improve grammar and style. You ensure the stories are less than 300 words.", + tools: [ AIFunctionFactory.Create(FormatStory) ] +)); + +builder.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); var app = builder.Build(); @@ -29,3 +42,11 @@ app.MapOpenAIResponses(); app.Run(); + +[Description("Formats the story for display.")] +string FormatStory(string title, string story) => $""" + **Title**: {title} + **Date**: {DateTime.Today.ToShortDateString()} + + {story} + """; From 7327c814e3eadb11ad29b16131c5757e99cf33d3 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 21:08:21 -0800 Subject: [PATCH 21/22] Exclude csproj.in file from template package --- .../Microsoft.Agents.AI.Templates.csproj | 1 + .../WebApiAgentTemplateSnapshotTests.cs | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj index 311d6351a99..ca7eeb7faf0 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj @@ -43,6 +43,7 @@ **\obj\**; **\.vs\**; **\*.user; + **\*.in; **\NuGet.config; **\Directory.Build.targets; **\Directory.Build.props;" /> diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs index c644bed38ea..fb907ca36fd 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs @@ -16,16 +16,13 @@ namespace Microsoft.Agents.AI.Templates.Tests; public class WebApiAgentTemplateSnapshotTests { + // Keep the exclude patterns below in sync with those in Microsoft.Agents.AI.Templates.csproj. private static readonly string[] _verificationExcludePatterns = [ - - // Exclude any templated content files - "**/*.in", - - // Keep the exclude patterns below in sync with those in Microsoft.Agents.AI.Templates.csproj. "**/bin/**", "**/obj/**", "**/.vs/**", "**/*.user", + "**/*.in", "**/NuGet.config", "**/Directory.Build.targets", "**/Directory.Build.props" From 19b1d1249d35b12bac062477fb9a0ea96356843a Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 5 Nov 2025 21:34:24 -0800 Subject: [PATCH 22/22] Add a survey link to the aiagent-webapi template's generated readme --- .../src/WebApiAgent/WebApiAgent-CSharp/README.md | 2 +- .../aiagent-webapi/README.md | 2 +- .../aiagent-webapi/README.md | 2 +- .../aiagent-webapi/README.md | 2 +- .../aiagent-webapi/README.md | 2 +- .../aiagent-webapi.Ollama.verified/aiagent-webapi/README.md | 2 +- .../aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/README.md index 3f852c3225d..9355df92141 100644 --- a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/README.md +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/README.md @@ -1,6 +1,6 @@ # AI Agent Web API -This is an AI Agent Web API application created from the `aiagent-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. This template is currently in a preview stage. If you have feedback, please take a [brief survey](https://aka.ms/dotnet/aiagent-webapi/preview1/survey). ## Prerequisites diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md index 933b54e2297..f524a1b6191 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ # AI Agent Web API -This is an AI Agent Web API application created from the `aiagent-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. This template is currently in a preview stage. If you have feedback, please take a [brief survey](https://aka.ms/dotnet/aiagent-webapi/preview1/survey). ## Prerequisites diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md index 4189c425023..f912ccb7bc0 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ # AI Agent Web API -This is an AI Agent Web API application created from the `aiagent-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. This template is currently in a preview stage. If you have feedback, please take a [brief survey](https://aka.ms/dotnet/aiagent-webapi/preview1/survey). ## Prerequisites diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md index 72f56ea964a..b400819b591 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ # AI Agent Web API -This is an AI Agent Web API application created from the `aiagent-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. This template is currently in a preview stage. If you have feedback, please take a [brief survey](https://aka.ms/dotnet/aiagent-webapi/preview1/survey). ## Prerequisites diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md index 72f56ea964a..b400819b591 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ # AI Agent Web API -This is an AI Agent Web API application created from the `aiagent-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. This template is currently in a preview stage. If you have feedback, please take a [brief survey](https://aka.ms/dotnet/aiagent-webapi/preview1/survey). ## Prerequisites diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md index 6e10ff2079d..3cb101869fe 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ # AI Agent Web API -This is an AI Agent Web API application created from the `aiagent-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. This template is currently in a preview stage. If you have feedback, please take a [brief survey](https://aka.ms/dotnet/aiagent-webapi/preview1/survey). ## Prerequisites diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md index c1eb5b9b81f..898c82c2098 100644 --- a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md @@ -1,6 +1,6 @@ # AI Agent Web API -This is an AI Agent Web API application created from the `aiagent-webapi` template. +This is an AI Agent Web API application created from the `aiagent-webapi` template. This template is currently in a preview stage. If you have feedback, please take a [brief survey](https://aka.ms/dotnet/aiagent-webapi/preview1/survey). ## Prerequisites