diff --git a/.github/workflows/python-test-coverage-report.yml b/.github/workflows/python-test-coverage-report.yml index f5f5f8eb03..dbe5b9e9c0 100644 --- a/.github/workflows/python-test-coverage-report.yml +++ b/.github/workflows/python-test-coverage-report.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Download coverage report - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }} run-id: ${{ github.event.workflow_run.id }} diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 576d2c5c54..af7ca9f0be 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -309,7 +309,6 @@ - diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentThreadAndHITL/AgentThreadAndHITL.csproj b/dotnet/samples/05-end-to-end/HostedAgents/AgentThreadAndHITL/AgentThreadAndHITL.csproj index 1afc7a7cec..a56157fe9d 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentThreadAndHITL/AgentThreadAndHITL.csproj +++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentThreadAndHITL/AgentThreadAndHITL.csproj @@ -36,10 +36,10 @@ - + - + diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj index c0e14e74b8..4e46f10c11 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj +++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj @@ -1,4 +1,4 @@ - + Exe @@ -35,11 +35,10 @@ - - + + - - + diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/Program.cs b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/Program.cs index 8559269dff..b7b610b663 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/Program.cs +++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithHostedMCP/Program.cs @@ -31,8 +31,7 @@ AITool mcpTool = new HostedMcpServerTool(serverName: "microsoft_learn", serverAd AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) - .GetResponsesClient() - .AsIChatClient(deploymentName) + .GetResponsesClient(deploymentName) .AsAIAgent( instructions: "You answer questions by searching the Microsoft Learn content only.", name: "MicrosoftLearnAgent", diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/AgentWithLocalTools.csproj b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/AgentWithLocalTools.csproj index ccda3156e5..b7970f8c5f 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/AgentWithLocalTools.csproj +++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithLocalTools/AgentWithLocalTools.csproj @@ -36,11 +36,11 @@ - - + + - + diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj index 19e5015912..7789abd315 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj +++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj @@ -1,4 +1,4 @@ - + Exe @@ -35,10 +35,10 @@ - + - + diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/AgentWithTools.csproj b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/AgentWithTools.csproj deleted file mode 100644 index 19e5015912..0000000000 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/AgentWithTools.csproj +++ /dev/null @@ -1,68 +0,0 @@ - - - - Exe - net10.0 - - enable - enable - - - false - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/Dockerfile b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/Dockerfile deleted file mode 100644 index c9f39f9574..0000000000 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# Build the application -FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build -WORKDIR /src - -# Copy files from the current directory on the host to the working directory in the container -COPY . . - -RUN dotnet restore -RUN dotnet build -c Release --no-restore -RUN dotnet publish -c Release --no-build -o /app -f net10.0 - -# Run the application -FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final -WORKDIR /app - -# Copy everything needed to run the app from the "build" stage. -COPY --from=build /app . - -EXPOSE 8088 -ENTRYPOINT ["dotnet", "AgentWithTools.dll"] diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/Program.cs b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/Program.cs deleted file mode 100644 index f564a0d8d3..0000000000 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -// This sample demonstrates how to use Foundry tools (MCP and code interpreter) -// with an AI agent hosted using the Azure AI AgentServer SDK. - -using Azure.AI.AgentServer.AgentFramework.Extensions; -using Azure.AI.OpenAI; -using Azure.Identity; -using Microsoft.Agents.AI; -using Microsoft.Extensions.AI; - -string openAiEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); -string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; -string toolConnectionId = Environment.GetEnvironmentVariable("MCP_TOOL_CONNECTION_ID") ?? throw new InvalidOperationException("MCP_TOOL_CONNECTION_ID is not set."); - -// 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. -DefaultAzureCredential credential = new(); - -IChatClient chatClient = new AzureOpenAIClient(new Uri(openAiEndpoint), credential) - .GetChatClient(deploymentName) - .AsIChatClient() - .AsBuilder() - .UseFoundryTools(new { type = "mcp", project_connection_id = toolConnectionId }, new { type = "code_interpreter" }) - .UseOpenTelemetry(sourceName: "Agents", configure: (cfg) => cfg.EnableSensitiveData = true) - .Build(); - -AIAgent agent = chatClient.AsAIAgent( - name: "AgentWithTools", - instructions: @"You are a helpful assistant with access to tools for fetching Microsoft documentation. - - IMPORTANT: When the user asks about Microsoft Learn articles or documentation: - 1. You MUST use the microsoft_docs_fetch tool to retrieve the actual content - 2. Do NOT rely on your training data - 3. Always fetch the latest information from the provided URL - - Available tools: - - microsoft_docs_fetch: Fetches and converts Microsoft Learn documentation - - microsoft_docs_search: Searches Microsoft/Azure documentation - - microsoft_code_sample_search: Searches for code examples") - .AsBuilder() - .UseOpenTelemetry(sourceName: "Agents", configure: (cfg) => cfg.EnableSensitiveData = true) - .Build(); - -await agent.RunAIAgentAsync(telemetrySourceName: "Agents"); diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/README.md b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/README.md deleted file mode 100644 index 55333f9940..0000000000 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# What this sample demonstrates - -This sample demonstrates how to use Foundry tools with an AI agent via the `UseFoundryTools` extension. The agent is configured with two tool types: an MCP (Model Context Protocol) connection for fetching Microsoft Learn documentation and a code interpreter for running code when needed. - -Key features: - -- Configuring Foundry tools using `UseFoundryTools` with MCP and code interpreter -- Connecting to an external MCP tool via a Foundry project connection -- Using `DefaultAzureCredential` for Azure authentication -- OpenTelemetry instrumentation for both the chat client and agent - -> For common prerequisites and setup instructions, see the [Hosted Agent Samples README](../README.md). - -## Prerequisites - -In addition to the common prerequisites: - -1. An **Azure AI Foundry project** with a chat model deployed (e.g., `gpt-5.2`, `gpt-4o-mini`) -2. The **Azure AI Developer** role assigned on the Foundry resource (includes the `agents/write` data action required by `UseFoundryTools`) -3. An **MCP tool connection** configured in your Foundry project pointing to `https://learn.microsoft.com/api/mcp` - -## Environment Variables - -In addition to the common environment variables in the root README: - -```powershell -# Your Azure AI Foundry project endpoint (required by UseFoundryTools) -$env:AZURE_AI_PROJECT_ENDPOINT="https://your-resource.services.ai.azure.com/api/projects/your-project" - -# Chat model deployment name (defaults to gpt-4o-mini if not set) -$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" - -# The MCP tool connection name (just the name, not the full ARM resource ID) -$env:MCP_TOOL_CONNECTION_ID="SampleMCPTool" -``` - -## How It Works - -1. An `AzureOpenAIClient` is created with `DefaultAzureCredential` and used to get a chat client -2. The chat client is wrapped with `UseFoundryTools` which registers two Foundry tool types: - - **MCP connection**: Connects to an external MCP server (Microsoft Learn) via the project connection name, providing documentation fetch and search capabilities - - **Code interpreter**: Allows the agent to execute code snippets when needed -3. `UseFoundryTools` resolves the connection using `AZURE_AI_PROJECT_ENDPOINT` internally -4. A `ChatClientAgent` is created with instructions guiding it to use the MCP tools for documentation queries -5. The agent is hosted using `RunAIAgentAsync` which exposes the OpenAI Responses-compatible API endpoint diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/agent.yaml b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/agent.yaml deleted file mode 100644 index 5d2b1f8d8d..0000000000 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/agent.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: AgentWithTools -displayName: "Agent with Tools" -description: > - An AI agent that uses Foundry tools (MCP and code interpreter) with Azure OpenAI. - The agent can fetch Microsoft Learn documentation and run code when needed. -metadata: - authors: - - Microsoft Agent Framework Team - tags: - - Azure AI AgentServer - - Microsoft Agent Framework - - Tools - - MCP - - Code Interpreter -template: - kind: hosted - name: AgentWithTools - protocols: - - protocol: responses - version: v1 - environment_variables: - - name: AZURE_OPENAI_ENDPOINT - value: ${AZURE_OPENAI_ENDPOINT} - - name: AZURE_OPENAI_DEPLOYMENT_NAME - value: gpt-4o-mini - - name: MCP_TOOL_CONNECTION_ID - value: ${MCP_TOOL_CONNECTION_ID} -resources: - - name: "gpt-4o-mini" - kind: model - id: gpt-4o-mini diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/run-requests.http b/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/run-requests.http deleted file mode 100644 index 22a37ff54e..0000000000 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentWithTools/run-requests.http +++ /dev/null @@ -1,30 +0,0 @@ -@host = http://localhost:8088 -@endpoint = {{host}}/responses - -### Health Check -GET {{host}}/readiness - -### Simple string input -POST {{endpoint}} -Content-Type: application/json -{ - "input": "Please use the microsoft_docs_fetch tool to fetch and summarize the Microsoft Learn article at https://learn.microsoft.com/azure/ai-services/openai/overview" -} - -### Explicit input -POST {{endpoint}} -Content-Type: application/json -{ - "input": [ - { - "type": "message", - "role": "user", - "content": [ - { - "type": "input_text", - "text": "Please use the microsoft_docs_fetch tool to fetch and summarize the Microsoft Learn article at https://learn.microsoft.com/azure/ai-services/openai/overview" - } - ] - } - ] -} diff --git a/dotnet/samples/05-end-to-end/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj b/dotnet/samples/05-end-to-end/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj index 3b3af40664..7789abd315 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj +++ b/dotnet/samples/05-end-to-end/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj @@ -35,10 +35,10 @@ - + - + diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/FoundryMultiAgent.csproj b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/FoundryMultiAgent.csproj index b2fb41ac5e..e8c7a434b0 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/FoundryMultiAgent.csproj +++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/FoundryMultiAgent.csproj @@ -1,4 +1,4 @@ - + Exe net10.0 @@ -33,12 +33,12 @@ - - + + - - - + + + diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs index 6947c85e3f..8d21eef20a 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs +++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundryMultiAgent/Program.cs @@ -41,7 +41,7 @@ try .Build(); Console.WriteLine("Starting Writer-Reviewer Workflow Agent Server on http://localhost:8088"); - await workflow.AsAgent().RunAIAgentAsync(); + await workflow.AsAIAgent().RunAIAgentAsync(); } finally { diff --git a/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/FoundrySingleAgent.csproj b/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/FoundrySingleAgent.csproj index 756f3d30ee..70df458d90 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/FoundrySingleAgent.csproj +++ b/dotnet/samples/05-end-to-end/HostedAgents/FoundrySingleAgent/FoundrySingleAgent.csproj @@ -33,11 +33,11 @@ - - + + - - + + diff --git a/dotnet/samples/05-end-to-end/HostedAgents/README.md b/dotnet/samples/05-end-to-end/HostedAgents/README.md index a36a9bddd1..919aa4b580 100644 --- a/dotnet/samples/05-end-to-end/HostedAgents/README.md +++ b/dotnet/samples/05-end-to-end/HostedAgents/README.md @@ -6,7 +6,6 @@ These samples demonstrate how to build and host AI agents using the [Azure AI Ag | Sample | Description | |--------|-------------| -| [`AgentWithTools`](./AgentWithTools/) | Foundry tools (MCP + code interpreter) via `UseFoundryTools` | | [`AgentWithLocalTools`](./AgentWithLocalTools/) | Local C# function tool execution (Seattle hotel search) | | [`AgentThreadAndHITL`](./AgentThreadAndHITL/) | Human-in-the-loop with `ApprovalRequiredAIFunction` and thread persistence | | [`AgentWithHostedMCP`](./AgentWithHostedMCP/) | Hosted MCP server tool (Microsoft Learn search) | @@ -40,19 +39,18 @@ Most samples require one or more of these environment variables: |----------|---------|-------------| | `AZURE_OPENAI_ENDPOINT` | Most samples | Your Azure OpenAI resource endpoint URL | | `AZURE_OPENAI_DEPLOYMENT_NAME` | Most samples | Chat model deployment name (defaults to `gpt-4o-mini`) | -| `AZURE_AI_PROJECT_ENDPOINT` | AgentWithTools, AgentWithLocalTools, FoundryMultiAgent, FoundrySingleAgent | Azure AI Foundry project endpoint | -| `MCP_TOOL_CONNECTION_ID` | AgentWithTools | Foundry MCP tool connection name | +| `AZURE_AI_PROJECT_ENDPOINT` | AgentWithLocalTools, FoundryMultiAgent, FoundrySingleAgent | Azure AI Foundry project endpoint | | `MODEL_DEPLOYMENT_NAME` | AgentWithLocalTools, FoundryMultiAgent, FoundrySingleAgent | Chat model deployment name (defaults to `gpt-4o-mini`) | See each sample's README for the specific variables required. ## Azure AI Foundry Setup (for samples that use Foundry) -Some samples (`AgentWithTools`, `AgentWithLocalTools`) connect to an Azure AI Foundry project. If you're using these samples, you'll need additional setup. +Some samples (`AgentWithLocalTools`, `FoundrySingleAgent`, `FoundryMultiAgent`) connect to an Azure AI Foundry project. If you're using these samples, you'll need additional setup. ### Azure AI Developer Role -The `UseFoundryTools` extension requires the **Azure AI Developer** role on the Cognitive Services resource. Even if you created the project, you may not have this role by default. +Some Foundry operations require the **Azure AI Developer** role on the Cognitive Services resource. Even if you created the project, you may not have this role by default. ```powershell az role assignment create ` @@ -65,23 +63,6 @@ az role assignment create ` For more details on permissions, see [Azure AI Foundry Permissions](https://aka.ms/FoundryPermissions). -### Creating an MCP Tool Connection - -The `AgentWithTools` sample requires an MCP tool connection configured in your Foundry project: - -1. Go to the [Azure AI Foundry portal](https://ai.azure.com) -2. Navigate to your project -3. Go to **Connected resources** → **+ New connection** → **Model Context Protocol tool** -4. Fill in: - - **Name**: `SampleMCPTool` (or any name you prefer) - - **Remote MCP Server endpoint**: `https://learn.microsoft.com/api/mcp` - - **Authentication**: `Unauthenticated` -5. Click **Connect** - -The connection **name** (e.g., `SampleMCPTool`) is used as the `MCP_TOOL_CONNECTION_ID` environment variable. - -> **Important**: Use only the connection **name**, not the full ARM resource ID. - ## Running a Sample Each sample runs as a standalone hosted agent on `http://localhost:8088/`: @@ -110,14 +91,6 @@ Each sample includes a `Dockerfile` and `agent.yaml` for deployment. To deploy y Assign the **Azure AI Developer** role to your user. See [Azure AI Developer Role](#azure-ai-developer-role) above. -### `Project connection ... was not found` - -Make sure `MCP_TOOL_CONNECTION_ID` contains only the connection **name** (e.g., `SampleMCPTool`), not the full ARM resource ID path. - -### `AZURE_AI_PROJECT_ENDPOINT must be set` - -The `UseFoundryTools` extension requires `AZURE_AI_PROJECT_ENDPOINT`. Set it to your Foundry project endpoint (e.g., `https://your-resource.services.ai.azure.com/api/projects/your-project`). - ### Multi-framework error when running `dotnet run` If you see "Your project targets multiple frameworks", specify the framework: diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs index 9c1286c9b9..641825d1dc 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; @@ -147,6 +149,7 @@ public abstract class AIContextProvider // Create a filtered context for ProvideAIContextAsync, filtering input messages // to exclude non-external messages (e.g. chat history, other AI context provider messages). +#pragma warning disable MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var filteredContext = new InvokingContext( context.Agent, context.Session, @@ -156,6 +159,7 @@ public abstract class AIContextProvider Messages = inputContext.Messages is not null ? this.ProvideInputMessageFilter(inputContext.Messages) : null, Tools = inputContext.Tools }); +#pragma warning restore MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var provided = await this.ProvideAIContextAsync(filteredContext, cancellationToken).ConfigureAwait(false); @@ -294,7 +298,9 @@ public abstract class AIContextProvider return default; } +#pragma warning disable MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var subContext = new InvokedContext(context.Agent, context.Session, this.StoreInputRequestMessageFilter(context.RequestMessages), this.StoreInputResponseMessageFilter(context.ResponseMessages!)); +#pragma warning restore MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. return this.StoreAIContextAsync(subContext, cancellationToken); } @@ -372,6 +378,7 @@ public abstract class AIContextProvider /// The session associated with the agent invocation. /// The AI context to be used by the agent for this invocation. /// or is . + [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] public InvokingContext( AIAgent agent, AgentSession? session, @@ -431,6 +438,7 @@ public abstract class AIContextProvider /// that were used by the agent for this invocation. /// The response messages generated during this invocation. /// , , or is . + [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] public InvokedContext( AIAgent agent, AgentSession? session, diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs index f4f198df97..a2aa8e1ed5 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; @@ -250,7 +252,9 @@ public abstract class ChatHistoryProvider return default; } +#pragma warning disable MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var subContext = new InvokedContext(context.Agent, context.Session, this._storeInputRequestMessageFilter(context.RequestMessages), this._storeInputResponseMessageFilter(context.ResponseMessages!)); +#pragma warning restore MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. return this.StoreChatHistoryAsync(subContext, cancellationToken); } @@ -340,6 +344,7 @@ public abstract class ChatHistoryProvider /// The session associated with the agent invocation. /// The messages to be used by the agent for this invocation. /// is . + [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] public InvokingContext( AIAgent agent, AgentSession? session, @@ -399,6 +404,7 @@ public abstract class ChatHistoryProvider /// that were used by the agent for this invocation. /// The response messages generated during this invocation. /// , , or is . + [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] public InvokedContext( AIAgent agent, AgentSession? session, diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/MessageAIContextProvider.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/MessageAIContextProvider.cs index c5f367443c..041e621341 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/MessageAIContextProvider.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/MessageAIContextProvider.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; @@ -49,12 +51,14 @@ public abstract class MessageAIContextProvider : AIContextProvider { // Call ProvideMessagesAsync directly to return only additional messages. // The base AIContextProvider.InvokingCoreAsync handles merging with the original input and stamping. +#pragma warning disable MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. return new AIContext { Messages = await this.ProvideMessagesAsync( new InvokingContext(context.Agent, context.Session, context.AIContext.Messages ?? []), cancellationToken).ConfigureAwait(false) }; +#pragma warning restore MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. } /// @@ -109,10 +113,12 @@ public abstract class MessageAIContextProvider : AIContextProvider // Create a filtered context for ProvideMessagesAsync, filtering input messages // to exclude non-external messages (e.g. chat history, other AI context provider messages). +#pragma warning disable MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var filteredContext = new InvokingContext( context.Agent, context.Session, this.ProvideInputMessageFilter(inputMessages)); +#pragma warning restore MAAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var providedMessages = await this.ProvideMessagesAsync(filteredContext, cancellationToken).ConfigureAwait(false); @@ -163,6 +169,7 @@ public abstract class MessageAIContextProvider : AIContextProvider /// The session associated with the agent invocation. /// The messages to be used by the agent for this invocation. /// or is . + [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] public InvokingContext( AIAgent agent, AgentSession? session, diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj index bb02f3065a..8e14047919 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj @@ -20,8 +20,7 @@ Microsoft Agent Framework AzureAI Persistent Agents Provides Microsoft Agent Framework support for Azure AI Persistent Agents. - - false + diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/README.md b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/README.md index a01debc5ca..0513ad5d99 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/README.md +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/README.md @@ -1,19 +1,3 @@ # Microsoft.Agents.AI.AzureAI.Persistent Provides integration between the Microsoft Agent Framework and Azure AI Agents Persistent (`Azure.AI.Agents.Persistent`). - -## ⚠️ Known Compatibility Limitation - -The underlying `Azure.AI.Agents.Persistent` package (currently 1.2.0-beta.9) targets `Microsoft.Extensions.AI.Abstractions` 10.1.x and references types that were renamed in 10.4.0 (e.g., `McpServerToolApprovalResponseContent` → `ToolApprovalResponseContent`). This causes `TypeLoadException` at runtime when used with ME.AI 10.4.0+. - -**Compatible versions:** - -| Package | Compatible Version | -|---|---| -| `Azure.AI.Agents.Persistent` | 1.2.0-beta.9 (targets ME.AI 10.1.x) | -| `Microsoft.Extensions.AI.Abstractions` | ≤ 10.3.0 | -| `OpenAI` | ≤ 2.8.0 | - -**Resolution:** An updated version of `Azure.AI.Agents.Persistent` targeting ME.AI 10.4.0+ is expected in 1.2.0-beta.10. The upstream fix is tracked in [Azure/azure-sdk-for-net#56929](https://github.com/Azure/azure-sdk-for-net/pull/56929). - -**Tracking issue:** [microsoft/agent-framework#4769](https://github.com/microsoft/agent-framework/issues/4769) diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunStreamingTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunStreamingTests.cs index b4a581d81e..487dcd6878 100644 --- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunStreamingTests.cs +++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunStreamingTests.cs @@ -4,10 +4,7 @@ using AgentConformance.IntegrationTests; namespace AzureAIAgentsPersistent.IntegrationTests; -// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent -// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10). -// Tracking: https://github.com/microsoft/agent-framework/issues/4769 -[Trait("Category", "IntegrationDisabled")] +[Trait("Category", "Integration")] public class AzureAIAgentsChatClientAgentRunStreamingTests() : ChatClientAgentRunStreamingTests(() => new()) { } diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunTests.cs index 8529dac6b4..79a7c24c69 100644 --- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunTests.cs +++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsChatClientAgentRunTests.cs @@ -4,10 +4,7 @@ using AgentConformance.IntegrationTests; namespace AzureAIAgentsPersistent.IntegrationTests; -// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent -// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10). -// Tracking: https://github.com/microsoft/agent-framework/issues/4769 -[Trait("Category", "IntegrationDisabled")] +[Trait("Category", "Integration")] public class AzureAIAgentsChatClientAgentRunTests() : ChatClientAgentRunTests(() => new()) { } diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs index de9a41e1a0..971bf57cbc 100644 --- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs +++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs @@ -14,10 +14,7 @@ using Shared.IntegrationTests; namespace AzureAIAgentsPersistent.IntegrationTests; -// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent -// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10). -// Tracking: https://github.com/microsoft/agent-framework/issues/4769 -[Trait("Category", "IntegrationDisabled")] +[Trait("Category", "Integration")] public class AzureAIAgentsPersistentCreateTests { private const string SkipCodeInterpreterReason = "Azure AI Code Interpreter intermittently fails to execute uploaded files in CI"; diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunStreamingTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunStreamingTests.cs index 87f1cfbb70..1ba0ec991e 100644 --- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunStreamingTests.cs +++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunStreamingTests.cs @@ -4,10 +4,7 @@ using AgentConformance.IntegrationTests; namespace AzureAIAgentsPersistent.IntegrationTests; -// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent -// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10). -// Tracking: https://github.com/microsoft/agent-framework/issues/4769 -[Trait("Category", "IntegrationDisabled")] +[Trait("Category", "Integration")] public class AzureAIAgentsPersistentRunStreamingTests() : RunStreamingTests(() => new()) { } diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunTests.cs index fd4b92e4b9..446b27b817 100644 --- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunTests.cs +++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentRunTests.cs @@ -4,10 +4,7 @@ using AgentConformance.IntegrationTests; namespace AzureAIAgentsPersistent.IntegrationTests; -// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent -// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10). -// Tracking: https://github.com/microsoft/agent-framework/issues/4769 -[Trait("Category", "IntegrationDisabled")] +[Trait("Category", "Integration")] public class AzureAIAgentsPersistentRunTests() : RunTests(() => new()) { } diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentStructuredOutputRunTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentStructuredOutputRunTests.cs index 34663c29e4..6ec4620ce3 100644 --- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentStructuredOutputRunTests.cs +++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentStructuredOutputRunTests.cs @@ -5,10 +5,7 @@ using AgentConformance.IntegrationTests; namespace AzureAIAgentsPersistent.IntegrationTests; -// Disabled: Azure.AI.Agents.Persistent 1.2.0-beta.9 references McpServerToolApprovalResponseContent -// which was removed in ME.AI 10.4.0. Re-enable once Persistent targets ME.AI 10.4.0+ (expected in 1.2.0-beta.10). -// Tracking: https://github.com/microsoft/agent-framework/issues/4769 -[Trait("Category", "IntegrationDisabled")] +[Trait("Category", "Integration")] public class AzureAIAgentsPersistentStructuredOutputRunTests() : StructuredOutputRunTests(() => new()) { private const string SkipReason = "Fails intermittently on the build agent/CI"; diff --git a/python/packages/core/agent_framework/_tools.py b/python/packages/core/agent_framework/_tools.py index cf7384588f..f7bc3f0e15 100644 --- a/python/packages/core/agent_framework/_tools.py +++ b/python/packages/core/agent_framework/_tools.py @@ -466,9 +466,15 @@ class FunctionTool(SerializationMixin): if func is None: return create_model(f"{self.name}_input") sig = inspect.signature(func) + try: + type_hints = typing.get_type_hints(func, include_extras=True) + except Exception: + type_hints = {} fields: dict[str, Any] = { pname: ( - _parse_annotation(param.annotation) if param.annotation is not inspect.Parameter.empty else str, + _parse_annotation(type_hints.get(pname, param.annotation)) + if type_hints.get(pname, param.annotation) is not inspect.Parameter.empty + else str, param.default if param.default is not inspect.Parameter.empty else ..., ) for pname, param in sig.parameters.items() diff --git a/python/packages/core/tests/core/test_tools_future_annotations.py b/python/packages/core/tests/core/test_tools_future_annotations.py new file mode 100644 index 0000000000..1c9649dcb9 --- /dev/null +++ b/python/packages/core/tests/core/test_tools_future_annotations.py @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Tests for @tool with PEP 563 (from __future__ import annotations). + +When ``from __future__ import annotations`` is active, all annotations +become strings. _resolve_input_model must resolve them via +typing.get_type_hints() before passing them to Pydantic's create_model. +""" + +from __future__ import annotations + +from pydantic import BaseModel + +from agent_framework import tool +from agent_framework._middleware import FunctionInvocationContext + + +class SearchConfig(BaseModel): + max_results: int = 10 + + +def test_tool_with_context_parameter(): + """FunctionInvocationContext parameter is excluded from schema under PEP 563.""" + + @tool + def get_weather(location: str, ctx: FunctionInvocationContext) -> str: + """Get the weather for a given location.""" + return f"Weather in {location}" + + params = get_weather.parameters() + assert "ctx" not in params.get("properties", {}) + assert "location" in params["properties"] + + +def test_tool_with_context_parameter_first(): + """FunctionInvocationContext as the first parameter is excluded under PEP 563.""" + + @tool + def get_weather(ctx: FunctionInvocationContext, location: str) -> str: + """Get the weather for a given location.""" + return f"Weather in {location}" + + params = get_weather.parameters() + assert "ctx" not in params.get("properties", {}) + assert "location" in params["properties"] + + +def test_tool_with_optional_param(): + """Optional[int] is resolved to the actual type, not left as a string.""" + + @tool + def search(query: str, limit: int | None = None) -> str: + """Search for something.""" + return query + + params = search.parameters() + assert params["properties"]["query"]["type"] == "string" + limit_schema = params["properties"]["limit"] + limit_types = {t["type"] for t in limit_schema["anyOf"]} + assert limit_types == {"integer", "null"} + + +def test_tool_with_optional_param_and_context(): + """Optional param + FunctionInvocationContext both work under PEP 563.""" + + @tool + def search(query: str, limit: int | None = None, ctx: FunctionInvocationContext | None = None) -> str: + """Search for something.""" + return query + + params = search.parameters() + assert params["properties"]["query"]["type"] == "string" + limit_schema = params["properties"]["limit"] + limit_types = {t["type"] for t in limit_schema["anyOf"]} + assert limit_types == {"integer", "null"} + assert "ctx" not in params.get("properties", {}) + + +def test_tool_with_optional_custom_type(): + """Optional[CustomType] is resolved under PEP 563 (original bug pattern).""" + + @tool + def search(query: str, config: SearchConfig | None = None) -> str: + """Search for something.""" + return query + + params = search.parameters() + assert params["properties"]["query"]["type"] == "string" + config_schema = params["properties"]["config"] + config_types = [t.get("type") for t in config_schema["anyOf"]] + assert "null" in config_types + + +def test_tool_with_unresolvable_forward_ref(): + """Fallback to raw annotations when get_type_hints() fails.""" + import types + + # Build a function in an isolated namespace so get_type_hints() cannot resolve + # the forward reference, exercising the except-branch fallback. + ns: dict = {} + exec( + "def greet(name: str = 'world') -> str:\n '''Greet someone.'''\n return f'Hello {name}'\n", + ns, + ) + func = ns["greet"] + # Place the function in a throwaway module so get_type_hints() will fail on + # any non-builtin forward ref while still having a valid __module__. + mod = types.ModuleType("_phantom") + func.__module__ = mod.__name__ + + t = tool(func) + params = t.parameters() + assert params["properties"]["name"]["type"] == "string" + + +async def test_tool_invoke_with_context(): + """Full invocation with FunctionInvocationContext under PEP 563.""" + + @tool + def get_weather(location: str, ctx: FunctionInvocationContext) -> str: + """Get the weather for a given location.""" + user = ctx.kwargs.get("user", "anon") + return f"Weather in {location} for {user}" + + params = get_weather.parameters() + assert "ctx" not in params.get("properties", {}) + + context = FunctionInvocationContext( + function=get_weather, + arguments=get_weather.input_model(location="Seattle"), + kwargs={"user": "test_user"}, + ) + result = await get_weather.invoke(context=context) + assert result[0].text == "Weather in Seattle for test_user"