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/src/ProjectTemplates/GeneratedContent.targets b/src/ProjectTemplates/GeneratedContent.targets index 403beadc3a0..72f571684ef 100644 --- a/src/ProjectTemplates/GeneratedContent.targets +++ b/src/ProjectTemplates/GeneratedContent.targets @@ -9,6 +9,7 @@ --> <_LocalChatTemplateVariant>aspire + <_WebApiAgentRoot>$(MSBuildThisFileDirectory)Microsoft.Agents.AI.Templates\src\WebApiAgent\ <_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 new file mode 100644 index 00000000000..ca7eeb7faf0 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/Microsoft.Agents.AI.Templates.csproj @@ -0,0 +1,55 @@ + + + + Template + $(NetCoreTargetFrameworks) + Project templates for Microsoft.Agents.AI. + dotnet-new;templates;ai;agent + + preview + 1 + AI + 0 + 0 + + true + false + true + false + false + content + false + true + true + + + + + + + + + + + + + + + + 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/THIRD-PARTY-NOTICES.TXT b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT new file mode 100644 index 00000000000..f5eee59e294 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/THIRD-PARTY-NOTICES.TXT @@ -0,0 +1,64 @@ +.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. + +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/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/dotnetcli.host.json new file mode 100644 index 00000000000..94096fec7c7 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/dotnetcli.host.json @@ -0,0 +1,33 @@ +{ + "$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/WebApiAgent/WebApiAgent-CSharp/.template.config/ide.host.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/ide.host.json new file mode 100644 index 00000000000..f91c03c283d --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/ide.host.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/ide.host", + "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/WebApiAgent/WebApiAgent-CSharp/.template.config/ide/agent-framework.ico b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/ide/agent-framework.ico new file mode 100644 index 00000000000..b644a8f3627 Binary files /dev/null and b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/ide/agent-framework.ico differ diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/template.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/template.json new file mode 100644 index 00000000000..6aff00cbd63 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/.template.config/template.json @@ -0,0 +1,218 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Microsoft", + "classifications": [ "Common", "AI", "API", "Web", "Web API", "WebAPI", "Service" ], + "identity": "Microsoft.Agents.AI.Templates.WebApiAgent.CSharp", + "name": "AI Agent Web API", + "description": "A project template for creating an AI Agent Web API application.", + "shortName": "aiagent-webapi", + "defaultName": "WebApiAgent", + "sourceName": "WebApiAgent-CSharp", + "preferNameDirectory": true, + "tags": { + "language": "C#", + "type": "project" + }, + "guids": [ + "c7e8f3d2-1a4b-5c6d-8e9f-0a1b2c3d4e5f" + ], + "primaryOutputs": [ + { + "path": "./README.md" + }, + { + "path": "./WebApiAgent-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" + }, + { + "choice": "net9.0", + "description": "Target net9.0" + }, + { + "choice": "net8.0", + "description": "Target net8.0" + } + ], + "replaces": "net10.0", + "defaultValue": "net10.0", + "displayName": "Framework" + }, + "hostIdentifier": { + "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" + }, + "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)" + }, + "IsAzureOpenAI": { + "type": "computed", + "value": "(AiServiceProvider == \"azureopenai\")" + }, + "IsOpenAI": { + "type": "computed", + "value": "(AiServiceProvider == \"openai\")" + }, + "IsGHModels": { + "type": "computed", + "value": "(AiServiceProvider == \"githubmodels\")" + }, + "IsOllama": { + "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", + "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/WebApiAgent/WebApiAgent-CSharp/Program.cs b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Program.cs new file mode 100644 index 00000000000..aadc8b70181 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Program.cs @@ -0,0 +1,102 @@ +#if (IsGHModels || IsOpenAI || (IsAzureOpenAI && !IsManagedIdentity)) +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; +#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") }; + +var chatClient = new OpenAIClient(credential, openAIOptions) + .GetChatClient("gpt-4o-mini").AsIChatClient(); +#elif (IsOllama) +// You will need to have Ollama running locally with the llama3.2 model installed +// Visit https://ollama.com for installation instructions +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: +// 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 +#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 +#endif + +builder.Services.AddChatClient(chatClient); + +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.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +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/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Properties/launchSettings.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/Properties/launchSettings.json new file mode 100644 index 00000000000..9bc26dbafff --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-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/WebApiAgent/WebApiAgent-CSharp/README.md b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/README.md new file mode 100644 index 00000000000..9355df92141 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/README.md @@ -0,0 +1,284 @@ +# AI Agent Web API + +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 + + +- 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 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 + + +#### 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 + + +#### Azure OpenAI Configuration + +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)** + +```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 (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:11434`. + + + +### 2. Run the Application + +```bash +dotnet run -lp https +``` + +The application will start and listen on: +- HTTP: `http://localhost:5056` +- HTTPS: `https://localhost:7041` + +### 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 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 +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 aiagent-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagent-webapi --chat-model gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagent-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--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`) + - 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 + +- [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) + +- [OpenAI Platform](https://platform.openai.com) + +- [Azure OpenAI Service](https://azure.microsoft.com/products/ai-services/openai-service) + +- [Ollama](https://ollama.com) + + +## 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. + + +**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/WebApiAgent/WebApiAgent-CSharp/WebApiAgent-CSharp.csproj.in b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/WebApiAgent-CSharp.csproj.in new file mode 100644 index 00000000000..7ab75edde39 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/WebApiAgent-CSharp.csproj.in @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + c7e8f3d2-1a4b-5c6d-8e9f-0a1b2c3d4e5f + + + + + + + + + + + + + + + + + + + diff --git a/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/appsettings.json b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/src/ProjectTemplates/Microsoft.Agents.AI.Templates/src/WebApiAgent/WebApiAgent-CSharp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} 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 ] ` diff --git a/test/ProjectTemplates/.gitignore b/test/ProjectTemplates/.gitignore new file mode 100644 index 00000000000..6fbeae3c400 --- /dev/null +++ b/test/ProjectTemplates/.gitignore @@ -0,0 +1 @@ +*/ExecutionTestSandbox diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs b/test/ProjectTemplates/Infrastructure/DotNetCommand.cs similarity index 89% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs rename to test/ProjectTemplates/Infrastructure/DotNetCommand.cs index 4758e14dc1f..df6ed848c3e 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetCommand.cs +++ b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/DotNetNewCommand.cs similarity index 95% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetNewCommand.cs rename to test/ProjectTemplates/Infrastructure/DotNetNewCommand.cs index cdd6ab73f03..e013d0d4e4e 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/DotNetNewCommand.cs +++ b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs similarity index 84% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs rename to test/ProjectTemplates/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs index 3a499013495..5a05ff8e32a 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ITemplateExecutionTestConfigurationProvider.cs +++ b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/MessageSinkTestOutputHelper.cs similarity index 93% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/MessageSinkTestOutputHelper.cs rename to test/ProjectTemplates/Infrastructure/MessageSinkTestOutputHelper.cs index d81c1f7c434..6118a0ad4e2 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/MessageSinkTestOutputHelper.cs +++ b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/ProcessExtensions.cs similarity index 84% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs rename to test/ProjectTemplates/Infrastructure/ProcessExtensions.cs index a20d390794d..cbedf75e354 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/ProcessExtensions.cs +++ b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/Project.cs similarity index 94% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/Project.cs rename to test/ProjectTemplates/Infrastructure/Project.cs index 317e81a661f..129866f3fef 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/Project.cs +++ b/test/ProjectTemplates/Infrastructure/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/Infrastructure/TemplateExecutionTestBase.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestBase.cs similarity index 97% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestBase.cs index b52e8cda3a6..430f731e0c8 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestBase.cs +++ b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestBase.cs @@ -5,7 +5,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/ProjectTemplates/Infrastructure/TemplateExecutionTestClassFixtureBase.cs similarity index 99% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestClassFixtureBase.cs index 3592fd6474b..0b978655c35 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestClassFixtureBase.cs +++ b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestClassFixtureBase.cs @@ -7,7 +7,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/ProjectTemplates/Infrastructure/TemplateExecutionTestCollection.cs similarity index 87% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollection.cs index 9f10ffdf974..dfabfb9a089 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollection.cs +++ b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollection.cs @@ -3,7 +3,7 @@ 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/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollectionFixture.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs similarity index 68% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollectionFixture.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs index 13140b0599e..3b015d80202 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestCollectionFixture.cs +++ b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestCollectionFixture.cs @@ -3,7 +3,7 @@ using System.IO; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; /// /// Provides functionality scoped to the lifetime of all tests defined in @@ -25,5 +25,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.TemplateSandboxSource)) + { + var fileName = Path.GetFileName(filePath); + var destFilePath = Path.Combine(WellKnownPaths.TemplateSandboxOutputRoot, fileName); + File.Copy(filePath, destFilePath); + } } } diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestConfiguration.cs b/test/ProjectTemplates/Infrastructure/TemplateExecutionTestConfiguration.cs similarity index 86% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestConfiguration.cs rename to test/ProjectTemplates/Infrastructure/TemplateExecutionTestConfiguration.cs index ce621e58528..060f28fe368 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TemplateExecutionTestConfiguration.cs +++ b/test/ProjectTemplates/Infrastructure/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/TemplateSandbox/.editorconfig b/test/ProjectTemplates/Infrastructure/TemplateSandbox/.editorconfig similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/.editorconfig rename to test/ProjectTemplates/Infrastructure/TemplateSandbox/.editorconfig diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.props b/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.props similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.props rename to test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.props diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.targets b/test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.targets similarity index 100% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/Directory.Build.targets rename to test/ProjectTemplates/Infrastructure/TemplateSandbox/Directory.Build.targets diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/README.md b/test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md similarity index 79% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/README.md rename to test/ProjectTemplates/Infrastructure/TemplateSandbox/README.md index 7db29aaab19..6a9ecb84b86 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/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/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 b/test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 similarity index 98% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/activate.ps1 rename to test/ProjectTemplates/Infrastructure/TemplateSandbox/activate.ps1 index a3dea765dd5..43773c3fa19 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/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 diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/nuget.template_install.config b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_install.config diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/TemplateSandbox/nuget.template_test.config b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/TemplateSandbox/nuget.template_test.config diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs b/test/ProjectTemplates/Infrastructure/TestCommand.cs similarity index 98% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs rename to test/ProjectTemplates/Infrastructure/TestCommand.cs index 697bf009f9c..dfe89030cad 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommand.cs +++ b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/TestCommandExtensions.cs similarity index 94% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandExtensions.cs rename to test/ProjectTemplates/Infrastructure/TestCommandExtensions.cs index 957c0efbb79..bc8f17dd89a 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandExtensions.cs +++ b/test/ProjectTemplates/Infrastructure/TestCommandExtensions.cs @@ -3,7 +3,7 @@ using System; -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/ProjectTemplates/Infrastructure/TestCommandResult.cs similarity index 90% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs rename to test/ProjectTemplates/Infrastructure/TestCommandResult.cs index 4b5e2dd2a28..21b4b7f7592 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResult.cs +++ b/test/ProjectTemplates/Infrastructure/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/Infrastructure/TestCommandResultExtensions.cs b/test/ProjectTemplates/Infrastructure/TestCommandResultExtensions.cs similarity index 94% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs rename to test/ProjectTemplates/Infrastructure/TestCommandResultExtensions.cs index 867cc2303ac..d623c3643b8 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/TestCommandResultExtensions.cs +++ b/test/ProjectTemplates/Infrastructure/TestCommandResultExtensions.cs @@ -3,7 +3,7 @@ using Xunit; -namespace Microsoft.Extensions.AI.Templates.Tests; +namespace Microsoft.Shared.ProjectTemplates.Tests; public static class TestCommandResultExtensions { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/VerifyScrubbers.cs b/test/ProjectTemplates/Infrastructure/VerifyScrubbers.cs similarity index 97% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/VerifyScrubbers.cs rename to test/ProjectTemplates/Infrastructure/VerifyScrubbers.cs index 75f42ba64ea..505feb99c14 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/VerifyScrubbers.cs +++ b/test/ProjectTemplates/Infrastructure/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/ProjectTemplates/Infrastructure/WellKnownPaths.cs similarity index 86% rename from test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs rename to test/ProjectTemplates/Infrastructure/WellKnownPaths.cs index 0d399dfcfe7..0ebb196fe84 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Infrastructure/WellKnownPaths.cs +++ b/test/ProjectTemplates/Infrastructure/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 { @@ -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(ThisProjectRoot, "TemplateSandbox"); - TemplateSandboxOutputRoot = Path.Combine(TemplateSandboxRoot, "output"); - 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 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..521086abc6e --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Microsoft.Agents.AI.Templates.Tests.csproj @@ -0,0 +1,30 @@ + + + + Tests for Microsoft.Agents.AI.Templates. + false + true + + + + $(NoWarn);CA1063;CA1716;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..baca09bf468 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.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.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/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 new file mode 100644 index 00000000000..da628ffb782 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Program.cs @@ -0,0 +1,54 @@ +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; + +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); + +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.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +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/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-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/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 new file mode 100644 index 00000000000..f524a1b6191 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/README.md @@ -0,0 +1,122 @@ +# AI Agent Web API + +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 + +- 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 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 +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 aiagent-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagent-webapi --chat-model gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagent-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--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`) + - 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 + +- [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) + +## Troubleshooting + + + +**Problem**: API requests fail with authentication errors + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/aiagent-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/aiagent-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/aiagent-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ApiKey.verified/aiagent-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/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 new file mode 100644 index 00000000000..ced383f0234 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Program.cs @@ -0,0 +1,55 @@ +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; + +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); + +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.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +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/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-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/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 new file mode 100644 index 00000000000..f912ccb7bc0 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/README.md @@ -0,0 +1,89 @@ +# AI Agent Web API + +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 + +- 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 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 +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 aiagent-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagent-webapi --chat-model gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagent-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--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`) + - 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 + +- [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) + +## Troubleshooting + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/aiagent-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/aiagent-webapi.csproj new file mode 100644 index 00000000000..d1029ebe6a1 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/aiagent-webapi.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.AzureOpenAI_ManagedIdentity.verified/aiagent-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/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 new file mode 100644 index 00000000000..9ed14c2c652 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Program.cs @@ -0,0 +1,51 @@ +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; + +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 chatClient = new OpenAIClient(credential, openAIOptions) + .GetChatClient("gpt-4o-mini").AsIChatClient(); + +builder.Services.AddChatClient(chatClient); + +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.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +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/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-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/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 new file mode 100644 index 00000000000..b400819b591 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/README.md @@ -0,0 +1,127 @@ +# AI Agent Web API + +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 + +- 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 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 +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 aiagent-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagent-webapi --chat-model gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagent-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--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`) + - 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 + +- [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) + +## 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/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/aiagent-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/aiagent-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/aiagent-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.DefaultParameters.verified/aiagent-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/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 new file mode 100644 index 00000000000..9ed14c2c652 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Program.cs @@ -0,0 +1,51 @@ +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; + +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 chatClient = new OpenAIClient(credential, openAIOptions) + .GetChatClient("gpt-4o-mini").AsIChatClient(); + +builder.Services.AddChatClient(chatClient); + +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.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +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/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-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/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 new file mode 100644 index 00000000000..b400819b591 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/README.md @@ -0,0 +1,127 @@ +# AI Agent Web API + +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 + +- 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 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 +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 aiagent-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagent-webapi --chat-model gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagent-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--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`) + - 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 + +- [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) + +## 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/aiagent-webapi.GitHubModels.verified/aiagent-webapi/aiagent-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/aiagent-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/aiagent-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.GitHubModels.verified/aiagent-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/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 new file mode 100644 index 00000000000..4015d7fdc98 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Program.cs @@ -0,0 +1,44 @@ +using System.ComponentModel; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.Hosting; +using Microsoft.Agents.AI.Workflows; +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 +var chatClient = new OllamaApiClient(new Uri("http://localhost:11434"), "llama3.2"); + +builder.Services.AddChatClient(chatClient); + +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.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +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/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-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/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 new file mode 100644 index 00000000000..3cb101869fe --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/README.md @@ -0,0 +1,111 @@ +# AI Agent Web API + +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 + +- 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 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 +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 aiagent-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagent-webapi --chat-model gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagent-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--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`) + - 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 + +- [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) + +## 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/aiagent-webapi.Ollama.verified/aiagent-webapi/aiagent-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/aiagent-webapi.csproj new file mode 100644 index 00000000000..09e26e73acc --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/aiagent-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.Ollama.verified/aiagent-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/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 new file mode 100644 index 00000000000..4bf9e2b74b8 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Program.cs @@ -0,0 +1,52 @@ +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; + +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); + +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.AddWorkflow("publisher", (sp, key) => AgentWorkflowBuilder.BuildSequential( + workflowName: key, + sp.GetRequiredKeyedService("writer"), + sp.GetRequiredKeyedService("editor") +)).AddAsAIAgent(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); +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/Properties/launchSettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/Properties/launchSettings.json new file mode 100644 index 00000000000..61e7634bcbb --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-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/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 new file mode 100644 index 00000000000..898c82c2098 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/README.md @@ -0,0 +1,127 @@ +# AI Agent Web API + +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 + +- 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 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 +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 aiagent-webapi --provider azureopenai + +# Specify a custom chat model +dotnet new aiagent-webapi --chat-model gpt-4o + +# Use API key authentication for Azure OpenAI +dotnet new aiagent-webapi --provider azureopenai --managed-identity false + +# Use Ollama with a different model +dotnet new aiagent-webapi --provider ollama --chat-model llama3.1 +``` + +### Available Parameters + +- **`--provider`**: Choose the AI service provider + - `githubmodels` (default) - GitHub Models + - `azureopenai` - Azure OpenAI + - `openai` - OpenAI Platform + - `ollama` - Ollama (local development) + +- **`--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`) + - 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 + +- [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) + +## 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/aiagent-webapi.OpenAI.verified/aiagent-webapi/aiagent-webapi.csproj b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/aiagent-webapi.csproj new file mode 100644 index 00000000000..352213bdc90 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/aiagent-webapi.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + secret + + + + + + + + + + + diff --git a/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/appsettings.json b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-webapi/appsettings.json new file mode 100644 index 00000000000..223027717b4 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/Snapshots/aiagent-webapi.OpenAI.verified/aiagent-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/WebApiAgentTemplateExecutionTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateExecutionTests.cs new file mode 100644 index 00000000000..f9dc4861c65 --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateExecutionTests.cs @@ -0,0 +1,123 @@ +// 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 Agent 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 WebApiAgentTemplateExecutionTests : TemplateExecutionTestBase, ITemplateExecutionTestConfigurationProvider +{ + public WebApiAgentTemplateExecutionTests(TemplateExecutionTestFixture fixture, ITestOutputHelper outputHelper) + : base(fixture, outputHelper) + { + } + + public static TemplateExecutionTestConfiguration Configuration { get; } = new() + { + TemplatePackageName = "Microsoft.Agents.AI.Templates", + TestOutputFolderPrefix = "WebApiAgent" + }; + + 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 = "WebApiAgentApp"; + var project = await Fixture.CreateProjectAsync( + templateName: "aiagent-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") && !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.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs new file mode 100644 index 00000000000..fb907ca36fd --- /dev/null +++ b/test/ProjectTemplates/Microsoft.Agents.AI.Templates.IntegrationTests/WebApiAgentTemplateSnapshotTests.cs @@ -0,0 +1,139 @@ +// 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.Shared.ProjectTemplates.Tests; +using Microsoft.TemplateEngine.Authoring.TemplateVerifier; +using Microsoft.TemplateEngine.TestHelper; +using Xunit; +using Xunit.Abstractions; + +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 = [ + "**/bin/**", + "**/obj/**", + "**/.vs/**", + "**/*.user", + "**/*.in", + "**/NuGet.config", + "**/Directory.Build.targets", + "**/Directory.Build.props" + ]; + + private readonly ILogger _log; + + public WebApiAgentTemplateSnapshotTests(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 + } + + [Fact] + public async Task DefaultParameters() + { + await TestTemplateCoreAsync(scenarioName: nameof(DefaultParameters)); + } + + [Fact] + public async Task GitHubModels() + { + 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) + { + string workingDir = TestUtils.CreateTemporaryFolder(); + string templateShortName = "aiagent-webapi"; + + // Get the template location + string templateLocation = Path.Combine(WellKnownPaths.TemplateFeedLocation, "Microsoft.Agents.AI.Templates", "src", "WebApiAgent"); + + 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. + // 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 */ + } +#pragma warning restore CA1031 // Do not catch general exception types + } +} 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/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..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 @@ -20,6 +20,8 @@ + + diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs index 3d076a438ad..97be1100c35 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/ProjectRootHelper.cs @@ -1,11 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.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/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/