Python / .NET Samples - Restructure and Improve Samples (Feature Branc… (#4092)

* Python: .NET Samples - Restructure and Improve Samples (Feature Branch) (#4091)

* Moved by agent (#4094)

* Fix readme links

* .NET Samples - Create `04-hosting` learning path step (#4098)

* Agent move

* Agent reorderd

* Remove A2A section from README 

Removed A2A section from the Getting Started README.

* Agent fixed links

* Fix broken sample links in durable-agents README (#4101)

* Initial plan

* Fix broken internal links in documentation

Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

* Revert template link changes; keep only durable-agents README fix

Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

* .NET Samples - Create `03-workflows` learning path step (#4102)

* Fix solution project path

* Python: Fix broken markdown links to repo resources (outside /docs) (#4105)

* Initial plan

* Fix broken markdown links to repo resources

Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

* Update README to rename .NET Workflows Samples section

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

* .NET Samples - Create `02-agents` learning path step (#4107)

* .NET: Fix broken relative link in GroupChatToolApproval README (#4108)

* Initial plan

* Fix broken link in GroupChatToolApproval README

Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

* Update labeler configuration for workflow samples

* .NET - Reorder Agents samples to start from Step01 instead of Step04 (#4110)

* Fix solution

* Resolve new sample paths

* Move new AgentSkills and AgentWithMemory_Step04 samples

* Fix link

* Fix readme path

* fix: update stale dotnet/samples/Durable path reference in AGENTS.md

Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

* Moved new sample

* Update solution

* Resolve merge (new sample)

* Sync to new sample - FoundryAgents_Step21_BingCustomSearch

* Updated README

* .NET Samples - Configuration Naming Update (#4149)

* .NET: Restore AzureFunctions index parity with ConsoleApps under DurableAgents samples (#4221)

* Clean-up `05_host_your_agent`

* Config setting consistency

* Refine samples

* AGENTS.md

* Move new samples

* Re-order samples

* Move new project and fixup solution

* Fixup model config

* Fix up new UT project

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Chris
2026-02-26 00:56:10 +00:00
committed by GitHub
co-authored by copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Copilot
parent 425f27f989
commit 904a5b843e
764 changed files with 1746 additions and 1373 deletions
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>
<InjectIsExternalInitOnLegacy>true</InjectIsExternalInitOnLegacy>
<InjectSharedFoundryAgents>true</InjectSharedFoundryAgents>
<InjectSharedWorkflowsExecution>true</InjectSharedWorkflowsExecution>
<InjectSharedWorkflowsSettings>true</InjectSharedWorkflowsSettings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Logging" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative\Microsoft.Agents.AI.Workflows.Declarative.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows.Declarative.AzureAI\Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="InvokeFunctionTool.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,55 @@
#
# This workflow demonstrates using InvokeFunctionTool to call functions directly
# from the workflow without going through an AI agent first.
#
# InvokeFunctionTool allows workflows to:
# - Pre-fetch data before calling an AI agent
# - Execute operations directly without AI involvement
# - Store function results in workflow variables for later use
#
# Example input:
# What are the specials in the menu?
#
kind: Workflow
trigger:
kind: OnConversationStart
id: workflow_invoke_function_tool_demo
actions:
# Invoke GetSpecials function to get today's specials directly from the workflow
- kind: InvokeFunctionTool
id: invoke_get_specials
conversationId: =System.ConversationId
requireApproval: true
functionName: GetSpecials
output:
autoSend: true
result: Local.Specials
messages: Local.FunctionMessage
# Display a message showing we retrieved the specials
- kind: SendMessage
id: show_specials_intro
message: "Today's specials have been retrieved. Here they are: {Local.Specials}"
# Now use an agent to format and present the specials to the user
- kind: InvokeAzureAgent
id: invoke_menu_agent
conversationId: =System.ConversationId
agent:
name: FunctionMenuAgent
input:
messages: =UserMessage("Please describe today's specials in an appealing way.")
output:
messages: Local.AgentResponse
# Allow the user to ask follow-up questions in a loop
- kind: InvokeAzureAgent
id: invoke_followup
conversationId: =System.ConversationId
agent:
name: FunctionMenuAgent
input:
externalLoop:
when: =Upper(System.LastMessage.Text) <> "EXIT"
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
namespace Demo.Workflows.Declarative.InvokeFunctionTool;
#pragma warning disable CA1822 // Mark members as static
/// <summary>
/// Plugin providing menu-related functions that can be invoked directly by the workflow
/// using the InvokeFunctionTool action.
/// </summary>
public sealed class MenuPlugin
{
[Description("Provides a list items on the menu.")]
public MenuItem[] GetMenu()
{
return s_menuItems;
}
[Description("Provides a list of specials from the menu.")]
public MenuItem[] GetSpecials()
{
return [.. s_menuItems.Where(i => i.IsSpecial)];
}
[Description("Provides the price of the requested menu item.")]
public float? GetItemPrice(
[Description("The name of the menu item.")]
string name)
{
return s_menuItems.FirstOrDefault(i => i.Name.Equals(name, StringComparison.OrdinalIgnoreCase))?.Price;
}
private static readonly MenuItem[] s_menuItems =
[
new()
{
Category = "Soup",
Name = "Clam Chowder",
Price = 4.95f,
IsSpecial = true,
},
new()
{
Category = "Soup",
Name = "Tomato Soup",
Price = 4.95f,
IsSpecial = false,
},
new()
{
Category = "Salad",
Name = "Cobb Salad",
Price = 9.99f,
},
new()
{
Category = "Salad",
Name = "House Salad",
Price = 4.95f,
},
new()
{
Category = "Drink",
Name = "Chai Tea",
Price = 2.95f,
IsSpecial = true,
},
new()
{
Category = "Drink",
Name = "Soda",
Price = 1.95f,
},
];
public sealed class MenuItem
{
public string Category { get; init; } = string.Empty;
public string Name { get; init; } = string.Empty;
public float Price { get; init; }
public bool IsSpecial { get; init; }
}
}
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
using Shared.Foundry;
using Shared.Workflows;
namespace Demo.Workflows.Declarative.InvokeFunctionTool;
/// <summary>
/// Demonstrate a workflow that uses InvokeFunctionTool to call functions directly
/// from the workflow without going through an AI agent first.
/// </summary>
/// <remarks>
/// The InvokeFunctionTool action allows workflows to invoke function tools directly,
/// enabling pre-fetching of data or executing operations before calling an AI agent.
/// See the README.md file in the parent folder (../README.md) for detailed
/// information about the configuration required to run this sample.
/// </remarks>
internal sealed class Program
{
public static async Task Main(string[] args)
{
// Initialize configuration
IConfiguration configuration = Application.InitializeConfig();
Uri foundryEndpoint = new(configuration.GetValue(Application.Settings.FoundryEndpoint));
// Create the menu plugin with functions that can be invoked directly by the workflow
MenuPlugin menuPlugin = new();
AIFunction[] functions =
[
AIFunctionFactory.Create(menuPlugin.GetMenu),
AIFunctionFactory.Create(menuPlugin.GetSpecials),
AIFunctionFactory.Create(menuPlugin.GetItemPrice),
];
// Ensure sample agent exists in Foundry
await CreateAgentAsync(foundryEndpoint, configuration);
// Get input from command line or console
string workflowInput = Application.GetInput(args);
// Create the workflow factory.
WorkflowFactory workflowFactory = new("InvokeFunctionTool.yaml", foundryEndpoint);
// Execute the workflow
WorkflowRunner runner = new(functions) { UseJsonCheckpoints = true };
await runner.ExecuteAsync(workflowFactory.CreateWorkflow, workflowInput);
}
private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration)
{
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential());
await aiProjectClient.CreateAgentAsync(
agentName: "FunctionMenuAgent",
agentDefinition: DefineMenuAgent(configuration, []), // Create Agent with no function tool in the definition.
agentDescription: "Provides information about the restaurant menu");
}
private static PromptAgentDefinition DefineMenuAgent(IConfiguration configuration, AIFunction[] functions)
{
PromptAgentDefinition agentDefinition =
new(configuration.GetValue(Application.Settings.FoundryModel))
{
Instructions =
"""
Answer the users questions about the menu.
Use the information provided in the conversation history to answer questions.
If the information is already available in the conversation, use it directly.
For questions or input that do not require searching the documentation, inform the
user that you can only answer questions about what's on the menu.
"""
};
foreach (AIFunction function in functions)
{
agentDefinition.Tools.Add(function.AsOpenAIResponseTool());
}
return agentDefinition;
}
}