-
Notifications
You must be signed in to change notification settings - Fork 1.6k
.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
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
8ee9c56
dotnet: Add server-side Foundry Toolbox support and fix SDK beta.4 br…
94f0bab
fix: reuse endpoint variable in CreateSampleToolboxAsync
569fce2
fix: pass endpoint through static local functions to avoid capture
8a338c9
refactor: remove unused projectClient param from CreateSampleToolboxA…
3db0c7e
Update dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_Toolbo…
alliscode 2ea4110
Update dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_Toolbo…
alliscode d70062f
Update dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_Toolbo…
alliscode 3608d1c
Removing GetToolbocVersion.
4681f2b
Merge branch 'toolbox-improvements' of https://github.com/alliscode/a…
7663c63
Removing tests for GetToolboxVersion
be8da29
fix: map cached/reasoning token counts in ConvertUsage instead of har…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...ithFoundry/Agent_Step25_ToolboxServerSideTools/Agent_Step25_ToolboxServerSideTools.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> |
148 changes: 148 additions & 0 deletions
148
dotnet/samples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/Program.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| // 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 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") | ||
| ?? throw new InvalidOperationException("Set FOUNDRY_PROJECT_ENDPOINT to your Foundry project endpoint."); | ||
| 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, endpoint); | ||
| // await CombineToolboxes(projectClient, model, endpoint); | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Main: single toolbox | ||
| // --------------------------------------------------------------------------- | ||
| static async Task Main(AIProjectClient projectClient, string model, string endpoint) | ||
| { | ||
| Console.WriteLine("=== Foundry Toolbox Server-Side Tools Example ==="); | ||
|
|
||
| // Comment out if the toolbox already exists in your Foundry project. | ||
| await CreateSampleToolboxAsync(ToolboxName, endpoint); | ||
|
|
||
| // Omit the version to resolve the toolbox's current default version at runtime. | ||
| var tools = await projectClient.GetToolboxToolsAsync(ToolboxName); | ||
|
|
||
| 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, string endpoint) | ||
| { | ||
| Console.WriteLine("=== Combine Toolboxes Example ==="); | ||
|
|
||
| // Comment out if the toolboxes already exist in your Foundry project. | ||
| await CreateSampleToolboxAsync(ToolboxName, endpoint); | ||
| await CreateSampleToolboxAsync(SecondToolboxName, endpoint); | ||
|
|
||
| var toolboxA = await projectClient.GetToolboxToolsAsync(ToolboxName); | ||
| var toolboxB = await projectClient.GetToolboxToolsAsync(SecondToolboxName); | ||
|
|
||
| var allTools = toolboxA.Concat(toolboxB).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(string name, string endpoint) | ||
| { | ||
| // 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), | ||
| new DefaultAzureCredential(), | ||
| options); | ||
| var toolboxClient = adminClient.GetAgentToolboxes(); | ||
|
|
||
| // 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); | ||
| } | ||
| } | ||
46 changes: 46 additions & 0 deletions
46
...mples/02-agents/AgentsWithFoundry/Agent_Step25_ToolboxServerSideTools/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # 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 recreates the toolbox on each run, replacing any existing toolbox with | ||
| the same name. Comment out the `CreateSampleToolboxAsync` call if you want to keep | ||
| an existing toolbox unchanged. | ||
|
|
||
| ## 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 | ||
| ``` | ||
57 changes: 57 additions & 0 deletions
57
dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/AIProjectClientToolboxExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| 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 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) | ||
| { | ||
| Throw.IfNull(projectClient); | ||
| Throw.IfNullOrWhitespace(name); | ||
|
|
||
| var toolboxClient = projectClient.AgentAdministrationClient.GetAgentToolboxes(); | ||
| var toolboxVersion = await FoundryToolbox.GetToolboxVersionCoreAsync(toolboxClient, name, version, cancellationToken).ConfigureAwait(false); | ||
| return toolboxVersion.ToAITools(); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.