-
Notifications
You must be signed in to change notification settings - Fork 2k
.NET: dotnet: Add server-side Foundry Toolbox support and fix SDK beta.4 br… #5450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
8ee9c56
94f0bab
569fce2
8a338c9
3db0c7e
2ea4110
d70062f
3608d1c
4681f2b
7663c63
be8da29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFrameworks>net10.0</TargetFrameworks> | ||
|
|
||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
|
|
||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry.Hosting\Microsoft.Agents.AI.Foundry.Hosting.csproj" /> | ||
| <PackageReference Include="Azure.AI.Projects" VersionOverride="2.1.0-beta.1" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| // This sample shows how to load a Foundry toolbox and pass its tools as server-side | ||
| // tools when creating an agent. The Foundry platform handles tool execution — the agent | ||
| // process does not invoke tools locally. | ||
|
|
||
| using System.ClientModel; | ||
| using System.ClientModel.Primitives; | ||
| using Azure.AI.Projects; | ||
| using Azure.AI.Projects.Agents; | ||
| using Azure.Identity; | ||
| using Microsoft.Agents.AI; | ||
| using Microsoft.Agents.AI.Foundry.Hosting; | ||
| using OpenAI.Responses; | ||
|
|
||
| #pragma warning disable OPENAI001 // Experimental API | ||
| #pragma warning disable AAIP001 // AgentToolboxes is experimental | ||
| #pragma warning disable CS8321 // Local functions may be commented-out alternatives | ||
|
|
||
| // Replace with your own Foundry toolbox name. | ||
| const string ToolboxName = "research_toolbox"; | ||
| // Used only by CombineToolboxes — swap in a second toolbox you own. | ||
| const string SecondToolboxName = "analysis_toolbox"; | ||
| // Replace with any question that exercises the tools configured in your toolbox. | ||
| const string Query = "Introduce yourself and briefly describe the tools you can use to help me."; | ||
|
|
||
| string endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT_2") | ||
| ?? throw new InvalidOperationException("Set AZURE_AI_PROJECT_ENDPOINT to your Foundry project endpoint."); | ||
|
alliscode marked this conversation as resolved.
Outdated
|
||
| string model = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini"; | ||
|
|
||
| // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. | ||
| // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid | ||
| // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. | ||
| var projectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential()); | ||
|
|
||
| await Main(projectClient, model); | ||
| // await CombineToolboxes(projectClient, model); | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Main: single toolbox | ||
| // --------------------------------------------------------------------------- | ||
| static async Task Main(AIProjectClient projectClient, string model) | ||
| { | ||
| Console.WriteLine("=== Foundry Toolbox Server-Side Tools Example ==="); | ||
|
|
||
| // Comment out if the toolbox already exists in your Foundry project. | ||
| await CreateSampleToolboxAsync(projectClient, ToolboxName); | ||
|
|
||
| // Omit the version to resolve the toolbox's current default version at runtime. | ||
| var toolbox = await projectClient.GetToolboxVersionAsync(ToolboxName); | ||
| Console.WriteLine($"Loaded toolbox '{toolbox.Name}' v{toolbox.Version} ({toolbox.Tools.Count} tool(s))"); | ||
|
|
||
| // Convert toolbox tools to AITool instances and pass to the agent. | ||
| // These are sent as server-side tool definitions in the Responses API request — | ||
| // the Foundry platform handles execution, so no local tool invocation is needed. | ||
| var tools = toolbox.ToAITools(); | ||
|
|
||
| AIAgent agent = projectClient | ||
| .AsAIAgent( | ||
| model: model, | ||
| instructions: "You are a research assistant. Use the available tools to answer questions.", | ||
| tools: tools.ToList()); | ||
|
|
||
| Console.WriteLine($"User: {Query}"); | ||
| Console.WriteLine($"Result: {await agent.RunAsync(Query)}\n"); | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Alternative: combine tools from multiple toolboxes | ||
| // --------------------------------------------------------------------------- | ||
| static async Task CombineToolboxes(AIProjectClient projectClient, string model) | ||
| { | ||
| Console.WriteLine("=== Combine Toolboxes Example ==="); | ||
|
|
||
| // Comment out if the toolboxes already exist in your Foundry project. | ||
| await CreateSampleToolboxAsync(projectClient, ToolboxName); | ||
| await CreateSampleToolboxAsync(projectClient, SecondToolboxName); | ||
|
|
||
| var toolboxA = await projectClient.GetToolboxVersionAsync(ToolboxName); | ||
| var toolboxB = await projectClient.GetToolboxVersionAsync(SecondToolboxName); | ||
| Console.WriteLine( | ||
| $"Loaded toolboxes: {toolboxA.Name}@{toolboxA.Version} ({toolboxA.Tools.Count} tool(s)), " | ||
| + $"{toolboxB.Name}@{toolboxB.Version} ({toolboxB.Tools.Count} tool(s))"); | ||
|
|
||
| var allTools = toolboxA.ToAITools().Concat(toolboxB.ToAITools()).ToList(); | ||
|
|
||
| AIAgent agent = projectClient | ||
| .AsAIAgent( | ||
| model: model, | ||
| instructions: "You are a research assistant. Use all available tools to answer questions.", | ||
| tools: allTools); | ||
|
|
||
| Console.WriteLine($"User: {Query}"); | ||
| Console.WriteLine($"Combined-toolbox result: {await agent.RunAsync(Query)}\n"); | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Helper: create (or replace) a sample toolbox so the sample works out-of-the-box | ||
| // --------------------------------------------------------------------------- | ||
| static async Task CreateSampleToolboxAsync(AIProjectClient projectClient, string name) | ||
| { | ||
| // Toolboxes are normally configured in the Foundry portal or a deployment | ||
| // script, not the application itself. This helper exists so the sample can | ||
| // be run end-to-end without first setting a toolbox up by hand. | ||
|
|
||
| // The Foundry-Features header is currently required for toolbox CRUD operations. | ||
| var options = new AgentAdministrationClientOptions(); | ||
| options.AddPolicy(new FoundryFeaturesPolicy("Toolboxes=V1Preview"), PipelinePosition.PerCall); | ||
| var adminClient = new AgentAdministrationClient( | ||
| new Uri(endpoint), | ||
|
Check failure on line 110 in dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Program.cs
|
||
| new DefaultAzureCredential(), | ||
| options); | ||
| var toolboxClient = adminClient.GetAgentToolboxes(); | ||
|
alliscode marked this conversation as resolved.
|
||
|
|
||
| // Delete existing toolbox if present (ignore 404). | ||
| try | ||
| { | ||
|
alliscode marked this conversation as resolved.
|
||
| await toolboxClient.DeleteToolboxAsync(name); | ||
| Console.WriteLine($"Deleted existing toolbox '{name}'"); | ||
| } | ||
| catch (ClientResultException ex) when (ex.Status == 404) | ||
| { | ||
| // Toolbox does not exist — nothing to delete. | ||
| } | ||
|
|
||
| // Create a fresh version with a single MCP tool. | ||
| ProjectsAgentTool mcpTool = ProjectsAgentTool.AsProjectTool(ResponseTool.CreateMcpTool( | ||
| serverLabel: "api-specs", | ||
| serverUri: new Uri("https://gitmcp.io/Azure/azure-rest-api-specs"), | ||
| toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval))); | ||
|
|
||
| var created = (await toolboxClient.CreateToolboxVersionAsync( | ||
| name: name, | ||
| tools: [mcpTool], | ||
| description: "Sample toolbox with an MCP tool — created by Agent_Step25 sample.")).Value; | ||
|
|
||
| Console.WriteLine($"Created toolbox '{created.Name}' v{created.Version} ({created.Tools.Count} tool(s))"); | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Pipeline policy that adds the Foundry-Features header for toolbox CRUD | ||
| // --------------------------------------------------------------------------- | ||
| internal sealed class FoundryFeaturesPolicy(string feature) : PipelinePolicy | ||
| { | ||
| private const string FeatureHeader = "Foundry-Features"; | ||
|
|
||
| public override void Process(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex) | ||
| { | ||
| message.Request.Headers.Add(FeatureHeader, feature); | ||
| ProcessNext(message, pipeline, currentIndex); | ||
| } | ||
|
|
||
| public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList<PipelinePolicy> pipeline, int currentIndex) | ||
| { | ||
| message.Request.Headers.Add(FeatureHeader, feature); | ||
| return ProcessNextAsync(message, pipeline, currentIndex); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Agent_Step25_ToolboxServerSideTools | ||
|
|
||
| This sample demonstrates loading a named Foundry toolbox and passing its tools as | ||
| **server-side tools** when creating an agent via `AsAIAgent()`. | ||
|
|
||
| When tools from a toolbox are passed this way, they are sent as tool definitions in | ||
| the Responses API request. The Foundry platform handles tool execution — the agent | ||
| process does not invoke tools locally. | ||
|
|
||
| This is the dotnet equivalent of the Python sample: | ||
| `python/samples/02-agents/providers/foundry/foundry_chat_client_with_toolbox.py` | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - A Microsoft Foundry project | ||
| - `AZURE_AI_PROJECT_ENDPOINT` environment variable set to your Foundry project endpoint | ||
|
alliscode marked this conversation as resolved.
|
||
| - `AZURE_AI_MODEL_DEPLOYMENT_NAME` environment variable set (defaults to `gpt-5.4-mini`) | ||
|
alliscode marked this conversation as resolved.
|
||
|
|
||
| The sample creates a toolbox automatically on first run. Comment out the | ||
| `CreateSampleToolboxAsync` call if your toolbox already exists. | ||
|
alliscode marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## How it works | ||
|
|
||
| 1. `projectClient.GetToolboxVersionAsync(name)` fetches the toolbox definition from the | ||
| Foundry project API (resolving the default version if none is specified) | ||
| 2. `ToolboxVersion.ToAITools()` converts each tool definition to an `AITool` instance | ||
| 3. The tools are passed to `AsAIAgent(tools: ...)` which includes them in the Responses | ||
| API request as server-side tool definitions | ||
|
|
||
| For a one-liner, use `projectClient.GetToolboxToolsAsync(name)` to fetch and convert in one call. | ||
|
|
||
| ## Sample flows | ||
|
|
||
| | Flow | Description | | ||
| |------|-------------| | ||
| | `Main` (default) | Loads a single toolbox and runs an agent with its tools | | ||
| | `CombineToolboxes` | Loads two toolboxes and merges their tools into one agent | | ||
|
|
||
| Uncomment the desired flow in the top-level statements to try each one. | ||
|
|
||
| ## Running the sample | ||
|
|
||
| ```bash | ||
| dotnet run | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Azure.AI.Projects.Agents; | ||
| using Microsoft.Agents.AI.Foundry.Hosting; | ||
| using Microsoft.Extensions.AI; | ||
| using Microsoft.Shared.DiagnosticIds; | ||
| using Microsoft.Shared.Diagnostics; | ||
|
|
||
| #pragma warning disable OPENAI001 | ||
| #pragma warning disable AAIP001 // AgentToolboxes is experimental in Azure.AI.Projects.Agents | ||
|
|
||
| namespace Azure.AI.Projects; | ||
|
|
||
| /// <summary> | ||
| /// Provides extension methods on <see cref="AIProjectClient"/> for fetching | ||
| /// Foundry toolbox definitions as server-side tools. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// These extensions mirror Python's <c>FoundryChatClient.get_toolbox()</c> pattern, | ||
| /// allowing a single call on the project client to retrieve tools ready for use | ||
| /// with <c>AsAIAgent(model, instructions, tools: ...)</c>. | ||
| /// </remarks> | ||
| [Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)] | ||
| public static class AIProjectClientToolboxExtensions | ||
| { | ||
| /// <summary> | ||
| /// Fetches a toolbox version from the Foundry project and returns the raw SDK <see cref="ToolboxVersion"/>. | ||
| /// </summary> | ||
| /// <param name="projectClient">The <see cref="AIProjectClient"/> to use. Cannot be <see langword="null"/>.</param> | ||
| /// <param name="name">The name of the toolbox to fetch.</param> | ||
| /// <param name="version"> | ||
| /// The specific toolbox version to fetch. When <see langword="null"/>, the toolbox's | ||
| /// default version is resolved automatically (requires an additional API call). | ||
| /// </param> | ||
| /// <param name="cancellationToken">A token to monitor for cancellation requests.</param> | ||
| /// <returns>The <see cref="ToolboxVersion"/> containing tool definitions.</returns> | ||
| /// <exception cref="System.ArgumentNullException"> | ||
| /// Thrown when <paramref name="projectClient"/> or <paramref name="name"/> is <see langword="null"/>. | ||
| /// </exception> | ||
| public static async Task<ToolboxVersion> GetToolboxVersionAsync( | ||
| this AIProjectClient projectClient, | ||
|
alliscode marked this conversation as resolved.
Outdated
|
||
| string name, | ||
| string? version = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Throw.IfNull(projectClient); | ||
| Throw.IfNullOrWhitespace(name); | ||
|
|
||
| var toolboxClient = projectClient.AgentAdministrationClient.GetAgentToolboxes(); | ||
| return await FoundryToolbox.GetToolboxVersionCoreAsync(toolboxClient, name, version, cancellationToken).ConfigureAwait(false); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Fetches a toolbox from the Foundry project and returns its tools as <see cref="AITool"/> instances | ||
| /// ready for use as server-side tools in the Responses API. | ||
| /// </summary> | ||
| /// <param name="projectClient">The <see cref="AIProjectClient"/> to use. Cannot be <see langword="null"/>.</param> | ||
| /// <param name="name">The name of the toolbox to fetch.</param> | ||
| /// <param name="version"> | ||
| /// The specific toolbox version to fetch. When <see langword="null"/>, the toolbox's | ||
| /// default version is resolved automatically. | ||
| /// </param> | ||
| /// <param name="cancellationToken">A token to monitor for cancellation requests.</param> | ||
| /// <returns>A read-only list of <see cref="AITool"/> instances from the toolbox.</returns> | ||
| /// <exception cref="System.ArgumentNullException"> | ||
| /// Thrown when <paramref name="projectClient"/> or <paramref name="name"/> is <see langword="null"/>. | ||
| /// </exception> | ||
| public static async Task<IReadOnlyList<AITool>> GetToolboxToolsAsync( | ||
| this AIProjectClient projectClient, | ||
| string name, | ||
| string? version = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| var toolboxVersion = await GetToolboxVersionAsync(projectClient, name, version, cancellationToken).ConfigureAwait(false); | ||
| return toolboxVersion.ToAITools(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.