mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
* Add Azure AI Foundry Responses hosting adapter Implement Microsoft.Agents.AI.Hosting.AzureAIResponses to host agent-framework AIAgents and workflows within Azure Foundry as hosted agents via the Azure.AI.AgentServer.Responses SDK. - AgentFrameworkResponseHandler: bridges ResponseHandler to AIAgent execution - InputConverter: converts Responses API inputs/history to MEAI ChatMessage - OutputConverter: converts agent response updates to SSE event stream - ServiceCollectionExtensions: DI registration helpers - 336 unit tests across net8.0/net9.0/net10.0 (112 per TFM) - ResponseStreamValidator: SSE protocol validation tool for samples - FoundryResponsesHosting sample app Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump System.ClientModel to 1.10.0 for Azure.Core 1.52.0 compat Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clean up tests and sample formatting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update Azure.AI.AgentServer packages to 1.0.0-alpha.20260401.5 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add hosted package version suffix (0.9.0-hosted) to distinguish from mainline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move Foundry Responses hosting into Microsoft.Agents.AI.Foundry package Move source and test files from the standalone Hosting.AzureAIResponses project into the Foundry package under a Hosting/ subfolder. This consolidates the Foundry-specific hosting adapter into the main Foundry package. - Source: Microsoft.Agents.AI.Foundry.Hosting namespace - Tests: merged into Foundry.UnitTests/Hosting/ - Conditionally compiled for .NETCoreApp TFMs only (net8.0+) - Deleted standalone Hosting.AzureAIResponses project and test project - Updated sample and solution references Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump package version to 0.9.0-hosted.260402.2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump OpenTelemetry packages to fix NU1109 downgrade errors - OpenTelemetry/Api/Exporter.Console/Exporter.InMemory: 1.13.1 -> 1.15.0 - OpenTelemetry.Exporter.OpenTelemetryProtocol: already 1.15.0 - OpenTelemetry.Extensions.Hosting: already 1.14.0 - OpenTelemetry.Instrumentation.AspNetCore/Http: already 1.14.0 - OpenTelemetry.Instrumentation.Runtime: 1.13.0 -> 1.14.0 - Azure.Monitor.OpenTelemetry.Exporter: 1.4.0 -> 1.5.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CA1873: guard LogWarning with IsEnabled check Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix model override bug and add client REPL sample - InputConverter: stop propagating request.Model to ChatOptions.ModelId Hosted agents use their own model; client-provided model values like 'hosted-agent' were being passed through and causing server errors. - Add FoundryResponsesRepl sample: interactive CLI client that connects to a Foundry Responses endpoint using ResponsesClient.AsAIAgent() - Bump package version to 0.9.0-hosted.260403.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Catch agent errors and emit response.failed with real error message Previously, unhandled exceptions from agent execution would bubble up to the SDK orchestrator, which emits a generic 'An internal server error occurred.' message — hiding the actual cause (e.g., 401 auth failures, model not found, etc.). Now AgentFrameworkResponseHandler catches non-cancellation exceptions and emits a proper response.failed event containing the real error message, making it visible to clients and in logs. OperationCanceledException still propagates for proper cancellation handling by the SDK. Also bumps package version to 0.9.0-hosted.260403.2. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Renaming and merging hosting extensions. (#5091) * Rename AddAgentFrameworkHandler to AddFoundryResponses and add MapFoundryResponses - Rename extension methods: AddAgentFrameworkHandler -> AddFoundryResponses, MapAgentFrameworkHandler -> MapFoundryResponses - AddFoundryResponses now calls AddResponsesServer() internally - Add MapFoundryResponses() extension on IEndpointRouteBuilder - Update sample and tests to use new API names - Remove redundant AddResponsesServer() and /ready endpoint from sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fixing numbering in sample. --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address breaking changes in 260408 * Bump hosted internal package version * Add UserAgent middleware tests for Foundry hosting * Hosting Samples update * Hosting Samples update * Hosting Samples update * Hosting Samples update * ChatClientAgent working * Adding SessionStorage and SessionManagement, improving samples to align Consumption vs Hosting * Using updates * Update chat client agent for contributor and devs * Foundry Agent Hosting * Address text rag sample working * Version bump * Adding LocalTools + Workflow samples * Removing extra using samples * Add Hosted-McpTools sample with dual MCP pattern Demonstrates two MCP integration layers in a single hosted agent: - Client-side MCP: McpClient connects to Microsoft Learn, agent handles tool invocations locally (docs_search, code_sample_search, docs_fetch) - Server-side MCP: HostedMcpServerTool delegates tool discovery and invocation to the LLM provider (Responses API), no local connection Includes DevTemporaryTokenCredential for Docker local debugging, Dockerfile.contributor for ProjectReference builds, and the openai/v1 route mapping for AIProjectClient compatibility in Development mode. * .NET: Bump Azure.AI.AgentServer packages to 1.0.0-beta.1/beta.21 and fix br… (#5287) * Bump Azure.AI.AgentServer packages to 1.0.0-beta.1/beta.21 and fix breaking API changes - Azure.AI.AgentServer.Core: 1.0.0-beta.11 -> 1.0.0-beta.21 - Azure.AI.AgentServer.Invocations: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1 - Azure.AI.AgentServer.Responses: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1 - Azure.Identity: 1.20.0 -> 1.21.0 (transitive requirement) - Azure.Core: 1.52.0 -> 1.53.0 (transitive requirement) - Remove azure-sdk-for-net dev feed (packages now on nuget.org) - Fix OutputConverter for new builder API (auto-tracked children, split EmitTextDone/EmitDone) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fixing small issues. --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Azure AI Foundry Responses hosting adapter Implement Microsoft.Agents.AI.Hosting.AzureAIResponses to host agent-framework AIAgents and workflows within Azure Foundry as hosted agents via the Azure.AI.AgentServer.Responses SDK. - AgentFrameworkResponseHandler: bridges ResponseHandler to AIAgent execution - InputConverter: converts Responses API inputs/history to MEAI ChatMessage - OutputConverter: converts agent response updates to SSE event stream - ServiceCollectionExtensions: DI registration helpers - 336 unit tests across net8.0/net9.0/net10.0 (112 per TFM) - ResponseStreamValidator: SSE protocol validation tool for samples - FoundryResponsesHosting sample app Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump System.ClientModel to 1.10.0 for Azure.Core 1.52.0 compat Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clean up tests and sample formatting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update Azure.AI.AgentServer packages to 1.0.0-alpha.20260401.5 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add hosted package version suffix (0.9.0-hosted) to distinguish from mainline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move Foundry Responses hosting into Microsoft.Agents.AI.Foundry package Move source and test files from the standalone Hosting.AzureAIResponses project into the Foundry package under a Hosting/ subfolder. This consolidates the Foundry-specific hosting adapter into the main Foundry package. - Source: Microsoft.Agents.AI.Foundry.Hosting namespace - Tests: merged into Foundry.UnitTests/Hosting/ - Conditionally compiled for .NETCoreApp TFMs only (net8.0+) - Deleted standalone Hosting.AzureAIResponses project and test project - Updated sample and solution references Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump package version to 0.9.0-hosted.260402.2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump OpenTelemetry packages to fix NU1109 downgrade errors - OpenTelemetry/Api/Exporter.Console/Exporter.InMemory: 1.13.1 -> 1.15.0 - OpenTelemetry.Exporter.OpenTelemetryProtocol: already 1.15.0 - OpenTelemetry.Extensions.Hosting: already 1.14.0 - OpenTelemetry.Instrumentation.AspNetCore/Http: already 1.14.0 - OpenTelemetry.Instrumentation.Runtime: 1.13.0 -> 1.14.0 - Azure.Monitor.OpenTelemetry.Exporter: 1.4.0 -> 1.5.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CA1873: guard LogWarning with IsEnabled check Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix model override bug and add client REPL sample - InputConverter: stop propagating request.Model to ChatOptions.ModelId Hosted agents use their own model; client-provided model values like 'hosted-agent' were being passed through and causing server errors. - Add FoundryResponsesRepl sample: interactive CLI client that connects to a Foundry Responses endpoint using ResponsesClient.AsAIAgent() - Bump package version to 0.9.0-hosted.260403.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Catch agent errors and emit response.failed with real error message Previously, unhandled exceptions from agent execution would bubble up to the SDK orchestrator, which emits a generic 'An internal server error occurred.' message — hiding the actual cause (e.g., 401 auth failures, model not found, etc.). Now AgentFrameworkResponseHandler catches non-cancellation exceptions and emits a proper response.failed event containing the real error message, making it visible to clients and in logs. OperationCanceledException still propagates for proper cancellation handling by the SDK. Also bumps package version to 0.9.0-hosted.260403.2. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Renaming and merging hosting extensions. (#5091) * Rename AddAgentFrameworkHandler to AddFoundryResponses and add MapFoundryResponses - Rename extension methods: AddAgentFrameworkHandler -> AddFoundryResponses, MapAgentFrameworkHandler -> MapFoundryResponses - AddFoundryResponses now calls AddResponsesServer() internally - Add MapFoundryResponses() extension on IEndpointRouteBuilder - Update sample and tests to use new API names - Remove redundant AddResponsesServer() and /ready endpoint from sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fixing numbering in sample. --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address breaking changes in 260408 * Bump hosted internal package version * Add UserAgent middleware tests for Foundry hosting * Hosting Samples update * Hosting Samples update * Hosting Samples update * Hosting Samples update * ChatClientAgent working * Adding SessionStorage and SessionManagement, improving samples to align Consumption vs Hosting * Using updates * Update chat client agent for contributor and devs * Foundry Agent Hosting * Address text rag sample working * Version bump * Adding LocalTools + Workflow samples * Removing extra using samples * Add Hosted-McpTools sample with dual MCP pattern Demonstrates two MCP integration layers in a single hosted agent: - Client-side MCP: McpClient connects to Microsoft Learn, agent handles tool invocations locally (docs_search, code_sample_search, docs_fetch) - Server-side MCP: HostedMcpServerTool delegates tool discovery and invocation to the LLM provider (Responses API), no local connection Includes DevTemporaryTokenCredential for Docker local debugging, Dockerfile.contributor for ProjectReference builds, and the openai/v1 route mapping for AIProjectClient compatibility in Development mode. * Bump Azure.AI.AgentServer packages to 1.0.0-beta.1/beta.21 and fix breaking API changes - Azure.AI.AgentServer.Core: 1.0.0-beta.11 -> 1.0.0-beta.21 - Azure.AI.AgentServer.Invocations: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1 - Azure.AI.AgentServer.Responses: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1 - Azure.Identity: 1.20.0 -> 1.21.0 (transitive requirement) - Azure.Core: 1.52.0 -> 1.53.0 (transitive requirement) - Remove azure-sdk-for-net dev feed (packages now on nuget.org) - Fix OutputConverter for new builder API (auto-tracked children, split EmitTextDone/EmitDone) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fixing small issues. * Fix IDE0009: add 'this' qualification in DevTemporaryTokenCredential Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix IDE0009: add 'this' qualification in all HostedAgentsV2 samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CHARSET: add UTF-8 BOM to Hosted-LocalTools and Hosted-Workflows Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix dotnet format: add Async suffix to test methods (IDE1006), fix encoding and style Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Register AgentSessionStore in test DI setups Add InMemoryAgentSessionStore registration to all ServiceCollection setups in AgentFrameworkResponseHandlerTests and WorkflowIntegrationTests. This is needed after the AgentSessionStore infrastructure was introduced in the responses-hosting feature. Tests still have NotImplementedException stubs for CreateSessionCoreAsync which will be fixed when the session infrastructure is fully available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Invocations protocol samples (hosted echo agent + client) (#5278) Add Hosted-Invocations-EchoAgent: a minimal echo agent hosted via the Invocations protocol (POST /invocations) using AddInvocationsServer and MapInvocationsServer, bridged to an Agent Framework AIAgent through a custom InvocationHandler. Add SimpleInvocationsAgent: a console REPL client that wraps HttpClient calls to the /invocations endpoint in a custom InvocationsAIAgent, demonstrating programmatic consumption of the Invocations protocol. Both samples default to port 8088 for consistency with other hosted agent samples. * Restructure FoundryHostedAgents samples into invocations/ and responses/ Align dotnet hosted agent samples with the Python side (PR #5281) by reorganizing the directory structure: - Remove HostedAgentsV1 entirely (old API pattern) - Split HostedAgentsV2 into invocations/ and responses/ based on protocol - Move Using-Samples accordingly (SimpleAgent to responses, SimpleInvocationsAgent to invocations) - Update slnx with new project paths and add previously missing invocations projects - Update README cd paths from HostedAgentsV2 to invocations or responses - Rename .env.local to .env.example to match Python naming convention - Fix format violations in newly included invocations projects * Remove launchSettings, use .env for port configuration - Delete all launchSettings.json files (port 8088 now comes from ASPNETCORE_URLS in .env) - Add DotNetEnv to Hosted-Invocations-EchoAgent so it loads .env like the responses samples - Create .env.example for EchoAgent with ASPNETCORE_URLS and ASPNETCORE_ENVIRONMENT - Add AGENT_NAME to ChatClientAgent and FoundryAgent .env.example (required by those samples) - Add AZURE_BEARER_TOKEN=DefaultAzureCredential to all .env.example files - Update DevTemporaryTokenCredential in all 6 samples to treat the sentinel value as unavailable, allowing ChainedTokenCredential to fall through to DefaultAzureCredential - Update EchoAgent README with Configuration section * Use placeholder for AGENT_NAME in Hosted-FoundryAgent .env.example * Move FoundryResponsesHosting to responses/Hosted-WorkflowHandoff, use GetResponsesClient * Rename Hosted-Workflows to Hosted-Workflow-Simple, Hosted-WorkflowHandoff to Hosted-Workflow-Handoff * Remove FoundryResponsesRepl and empty FoundryResponsesHosting directory * Add Dockerfiles, README, agent yamls and bearer token support to Hosted-Workflow-Handoff - Add Dockerfile and Dockerfile.contributor for Docker-based testing - Add agent.yaml and agent.manifest.yaml with triage-workflow as primary agent - Add README.md following sibling pattern, noting Azure OpenAI vs Foundry endpoint - Add DevTemporaryTokenCredential and ChainedTokenCredential for Docker auth - Register triage-workflow as non-keyed default so azd invoke works without model - Update .env.example with AZURE_BEARER_TOKEN sentinel - Add .gitignore to 04-hosting to suppress VS-generated launchSettings.json - Fix docker run image name in Hosted-Workflow-Simple README * Fix AgentFrameworkResponseHandlerTests: implement session methods in test mock agents * .NET: Auto-instrument resolved AIAgents with OpenTelemetry for Foundry Hosted Agents (#5316) * Auto-instrument resolved AIAgents with OpenTelemetry using Core ResponsesSourceName * Add OTel telemetry capture tests for Foundry hosted agent handler * Net: Prepare Foundry Preview Release (#5336) * Prepare Foundry preview release 1.2.0-preview.* Bump VersionPrefix to 1.2.0 and update the preview stamp date. Invert packaging opt-in so only the Foundry preview set produces NuGet packages: - Microsoft.Agents.AI.Abstractions - Microsoft.Agents.AI - Microsoft.Agents.AI.Workflows - Microsoft.Agents.AI.Workflows.Generators - Microsoft.Agents.AI.Foundry Flip IsReleased=false on the preview set so they pick up the -preview.YYMMDD.N suffix. Gate GeneratePackageOnBuild on IsPackable=true. Remove the global IsPackable=true from nuget-package.props so the repo-level default (false) applies to everything else. * Lower preview VersionPrefix to 0.0.1 Retroactive preview publish: bump VersionPrefix and GitTag from 1.2.0 to 0.0.1 so the 5 Foundry preview packages emit as 0.0.1-preview.260417.1. * Net: Publish all packages as 0.0.1-preview.260417.2 (#5341) Revises the Foundry pre-release approach to publish ALL normally packable src projects as preview packages stamped 0.0.1-preview.260417.2, including projects previously flagged IsReleased=true or with a non-default VersionSuffix (rc/alpha). nuget-package.props: - Collapse the four conditional PackageVersion expressions (IsReleaseCandidate, VersionSuffix, default preview, IsReleased stable) into a single unconditional 0.0.1-preview.260417.2. On this preview-only branch every package ships with the same pre-release stamp regardless of per-project flags. - Restore the global IsPackable=true default (offsetting the repo-wide IsPackable=false in Directory.Build.props). Projects that opt out (Mem0, Declarative) already set IsPackable=false AFTER importing this file so they remain non-packable. - Remove the IsReleased-gated EnablePackageValidation line. Package validation does not apply to a 0.0.1 preview. csproj reverts (Abstractions, Agents.AI, Workflows, Workflows.Generators, Foundry): - Revert the IsPackable=true opt-in block introduced in #5336 (now redundant since the props default is true again). - Restore IsReleased=true to its pre-PR value. The setting is now a no-op because the props no longer branches on it. * Bump preview version to 260420.1 and fix AgentServer package deps (#5367) - Bump PackageVersion to 0.0.1-preview.260420.1 - Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by Azure.AI.AgentServer.Responses beta.3) - Replace AgentHostTelemetry.ResponsesSourceName with local constant (type made internal in AgentServer.Core beta.22) Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Hosted agents toolbox support (#5368) * feat: Add Foundry Toolbox (MCP) support to AgentFrameworkResponseHandler Adds support for Foundry Toolsets MCP proxy integration in the hosted agent response handler. Toolsets connect at startup via IHostedService, gating the readiness probe per spec §3.1. MCP tools are injected into every request's ChatOptions and OAuth consent errors (-32006) are intercepted and surfaced as mcp_approval_request + incomplete SSE events. New files: - FoundryToolboxOptions.cs: configuration POCO for toolset names and API version - FoundryToolboxBearerTokenHandler.cs: DelegatingHandler with Azure Bearer token auth, Foundry-Features header injection, and 3x exponential backoff on 429/5xx - McpConsentContext.cs: AsyncLocal-based per-request consent state shared between the tool wrapper and the response handler - ConsentAwareMcpClientTool.cs: AIFunction wrapper that catches -32006 errors and signals consent via shared state and linked CancellationTokenSource - FoundryToolboxService.cs: IHostedService that creates McpClient per toolset at startup and exposes cached tools Modified files: - AgentFrameworkResponseHandler.cs: injects toolbox tools into ChatOptions, sets up linked CTS consent interception, emits mcp_approval_request on -32006 - ServiceCollectionExtensions.cs: adds AddFoundryToolboxes(params string[]) extension - Microsoft.Agents.AI.Foundry.csproj: adds ModelContextProtocol and Azure.Identity dependencies under NETCoreApp condition Sample: - Hosted-Toolbox: minimal hosted agent sample using AddFoundryToolboxes * Rename toolset to toolbox in user-facing API; rename ConsentAwareMcpClientTool to ConsentAwareMcpClientAIFunction * Add HostedMcpToolboxAITool for client-selectable Foundry toolboxes Introduces HostedMcpToolboxAITool, a marker tool subclassing HostedMcpServerTool that rides the OpenAI Responses 'mcp' wire format to let clients request a specific Foundry toolbox per request. - New FoundryAITool.CreateHostedMcpToolbox(name, version?) factory. - FoundryToolboxOptions.StrictMode (default true) rejects unregistered toolboxes; set to false to allow lazy-open on first use. - FoundryToolboxService.GetToolboxToolsAsync(name, version?) resolves cached or lazy-opened MCP tools. - AgentFrameworkResponseHandler parses request.Tools for foundry-toolbox://name[?version=v] markers and injects resolved tools per request, merging with pre-registered ones. - Unit tests for marker parsing and strict-mode resolution. * Bump Azure.AI.Projects to 2.1.0-alpha; add ToolboxRecord/ToolboxVersion factory overloads + tests * Fix PR review issues: retry off-by-one, URI encoding, docs, tests, build - Fix off-by-one in FoundryToolboxBearerTokenHandler retry loop (4 attempts → 3) - URI-encode version parameter in HostedMcpToolboxAITool.BuildAddress - Add XML doc clarifying version pinning is reserved for future use - Add comment clarifying AddHostedService deduplication safety - Fix DevTemporaryTokenCredential expiry to use DateTimeOffset.MaxValue - Fix AgentCard ambiguity in A2AServer sample with using alias - Add 18 new unit tests for retry handler and ReadMcpToolboxMarkers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Hosted agent adapter (#5371) * Bump preview version to 260420.1 and fix AgentServer package deps - Bump PackageVersion to 0.0.1-preview.260420.1 - Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by Azure.AI.AgentServer.Responses beta.3) - Replace AgentHostTelemetry.ResponsesSourceName with local constant (type made internal in AgentServer.Core beta.22) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy the CA1873 analyzer rule which flags potentially expensive argument evaluation when logging is disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Hosted agent adapter (#5374) * Bump preview version to 260420.1 and fix AgentServer package deps - Bump PackageVersion to 0.0.1-preview.260420.1 - Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by Azure.AI.AgentServer.Responses beta.3) - Replace AgentHostTelemetry.ResponsesSourceName with local constant (type made internal in AgentServer.Core beta.22) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy the CA1873 analyzer rule which flags potentially expensive argument evaluation when logging is disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bumping NuGet version --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Hosted agent adapter (#5406) * Bump preview version to 260420.1 and fix AgentServer package deps - Bump PackageVersion to 0.0.1-preview.260420.1 - Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by Azure.AI.AgentServer.Responses beta.3) - Replace AgentHostTelemetry.ResponsesSourceName with local constant (type made internal in AgentServer.Core beta.22) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy the CA1873 analyzer rule which flags potentially expensive argument evaluation when logging is disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bumping NuGet version * Restore conditional versioning, remove dev feed, bump Azure.AI.Projects to beta.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Hosted agent adapter (#5408) * Bump preview version to 260420.1 and fix AgentServer package deps - Bump PackageVersion to 0.0.1-preview.260420.1 - Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by Azure.AI.AgentServer.Responses beta.3) - Replace AgentHostTelemetry.ResponsesSourceName with local constant (type made internal in AgentServer.Core beta.22) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy the CA1873 analyzer rule which flags potentially expensive argument evaluation when logging is disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bumping NuGet version * Restore conditional versioning, remove dev feed, bump Azure.AI.Projects to beta.1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR #5312 review comments - Add comment explaining NU1903 suppression (Microsoft.Bcl.Memory transitive vuln) - Remove NU1903 from sample/test projects where not needed - Fix Dockerfile ENTRYPOINT mismatch in Hosted-Workflow-Simple - Align agent name to 'hosted-workflow-simple' in agent.yaml and README - Fix Hosted-McpTools README: replace GitHub PAT refs with Microsoft Learn - Fix session persistence: only persist when client provides conversation ID - Upgrade IsNullOrEmpty to IsNullOrWhiteSpace for session ID checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Split Foundry into stable V1 and preview Hosting package Extract hosted agent functionality from Microsoft.Agents.AI.Foundry into a new Microsoft.Agents.AI.Foundry.Hosting preview package. This resolves NU5104 build errors caused by the stable Foundry package depending on prerelease Azure SDK packages (Azure.AI.AgentServer.Responses, Azure.AI.Projects beta). Changes: - Create Microsoft.Agents.AI.Foundry.Hosting with VersionSuffix=preview, targeting .NET Core only (net8.0/9.0/10.0) - Move all Hosting/ source files to the new project - Move ToolboxRecord/ToolboxVersion overloads to FoundryAIToolExtensions - Revert Azure.AI.Projects to 2.0.0 in Directory.Packages.props; Hosting uses VersionOverride for 2.1.0-beta.1 - Clean V1 Foundry csproj: remove beta deps, ASP.NET Core ref, hosting conditionals - Update 8 hosted agent sample projects to reference Foundry.Hosting - Split unit tests: ToolboxRecord/ToolboxVersion tests moved to Hosting/ - Add Foundry.Hosting to solution file Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review comments: experimental attrs, doc fixes, token propagation - Add [Experimental(OPENAI001)] to all 7 public Hosting types per reviewer request - Fix McpConsentContext XML doc: 'Thread-static' -> 'Async-local' (AsyncLocal flows with ExecutionContext, not thread-static) - Expand UserAgentMiddleware test regex to match prerelease versions (e.g. 1.0.0-rc.4) - Propagate CancellationToken in AgentFrameworkResponseHandler session save Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unnecessary MEAI001 suppression from stable Foundry package MEAI001 was a leftover from when Hosting code lived in the same project. The stable V1 Foundry package builds clean without it, and suppressing experimental diagnostics in a released package can hide unintentional exposure of experimental APIs to consumers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Foundry.Hosting to release solution filter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Ben Thomas <ben.thomas@microsoft.com>
436 lines
22 KiB
C#
436 lines
22 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Text.RegularExpressions;
|
|
using Azure.AI.Extensions.OpenAI;
|
|
using Azure.AI.Projects.Agents;
|
|
using Microsoft.Agents.AI;
|
|
using Microsoft.Agents.AI.Foundry;
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Shared.DiagnosticIds;
|
|
using Microsoft.Shared.Diagnostics;
|
|
using OpenAI.Responses;
|
|
|
|
namespace Azure.AI.Projects;
|
|
|
|
/// <summary>
|
|
/// Provides extension methods for <see cref="AIProjectClient"/>.
|
|
/// </summary>
|
|
[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
|
|
public static partial class AzureAIProjectChatClientExtensions
|
|
{
|
|
/// <summary>
|
|
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="AgentReference"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The <see cref="AIProjectClient"/> to create the <see cref="ChatClientAgent"/> with. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="agentReference">The <see cref="AgentReference"/> representing the name and version of the server side agent to create a <see cref="ChatClientAgent"/> for. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentReference"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
|
|
/// <remarks>
|
|
/// When instantiating a <see cref="ChatClientAgent"/> by using an <see cref="AgentReference"/>, minimal information will be available about the agent in the instance level, and any logic that relies
|
|
/// on <see cref="AIAgent.GetService{TService}(object?)"/> to retrieve information about the agent like <see cref="ProjectsAgentVersion" /> will receive <see langword="null"/> as the result.
|
|
/// </remarks>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
AgentReference agentReference,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentReference);
|
|
ThrowIfInvalidAgentName(agentReference.Name);
|
|
|
|
var innerAgent = AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentReference,
|
|
new ChatClientAgentOptions()
|
|
{
|
|
Id = $"{agentReference.Name}:{agentReference.Version}",
|
|
Name = agentReference.Name,
|
|
ChatOptions = new() { Tools = tools },
|
|
},
|
|
clientFactory,
|
|
services);
|
|
|
|
return new FoundryAgent(aiProjectClient, innerAgent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="ProjectsAgentRecord"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="agentRecord">The agent record to be converted. The latest version will be used. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the Azure AI Agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentRecord"/> is <see langword="null"/>.</exception>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
ProjectsAgentRecord agentRecord,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentRecord);
|
|
|
|
var allowDeclarativeMode = tools is not { Count: > 0 };
|
|
|
|
var innerAgent = AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentRecord,
|
|
tools,
|
|
clientFactory,
|
|
!allowDeclarativeMode,
|
|
services);
|
|
|
|
return new FoundryAgent(aiProjectClient, innerAgent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="ProjectsAgentVersion"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="agentVersion">The agent version to be converted. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="tools">In-process invocable tools to be provided. If no tools are provided manual handling will be necessary to invoke in-process tools.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the provided version of the Azure AI Agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentVersion"/> is <see langword="null"/>.</exception>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
ProjectsAgentVersion agentVersion,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentVersion);
|
|
|
|
var allowDeclarativeMode = tools is not { Count: > 0 };
|
|
|
|
var innerAgent = AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentVersion,
|
|
tools,
|
|
clientFactory,
|
|
!allowDeclarativeMode,
|
|
services);
|
|
|
|
return new FoundryAgent(aiProjectClient, innerAgent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a non-versioned <see cref="ChatClientAgent"/> backed by the project's Responses API using the specified model and instructions.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The <see cref="AIProjectClient"/> to use for Responses API calls. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="model">The model deployment name to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="instructions">The instructions that guide the agent's behavior. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="name">Optional name for the agent.</param>
|
|
/// <param name="description">Optional human-readable description for the agent.</param>
|
|
/// <param name="tools">Optional collection of tools that the agent can invoke during conversations.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="loggerFactory">Optional logger factory for creating loggers used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> backed by the project's Responses API.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> or <paramref name="instructions"/> is empty or whitespace.</exception>
|
|
public static ChatClientAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
string model,
|
|
string instructions,
|
|
string? name = null,
|
|
string? description = null,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
ILoggerFactory? loggerFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNullOrWhitespace(model);
|
|
Throw.IfNullOrWhitespace(instructions);
|
|
|
|
ChatClientAgentOptions options = new()
|
|
{
|
|
Name = name,
|
|
Description = description,
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
ModelId = model,
|
|
Instructions = instructions,
|
|
Tools = tools,
|
|
},
|
|
};
|
|
|
|
return CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a non-versioned <see cref="ChatClientAgent"/> backed by the project's Responses API using the specified options.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The <see cref="AIProjectClient"/> to use for Responses API calls. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="options">Optional configuration options that control the agent's behavior.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="loggerFactory">Optional logger factory for creating loggers used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> backed by the project's Responses API.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="ArgumentException">Thrown when <paramref name="options"/> does not specify <see cref="ChatOptions.ModelId"/>.</exception>
|
|
public static ChatClientAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
ChatClientAgentOptions? options = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
ILoggerFactory? loggerFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
|
|
return CreateResponsesChatClientAgent(aiProjectClient, options ?? new(), clientFactory, loggerFactory, services);
|
|
}
|
|
|
|
#region Private
|
|
|
|
/// <summary>Creates a <see cref="ChatClientAgent"/> with the specified options.</summary>
|
|
private static ChatClientAgent CreateChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
ProjectsAgentVersion agentVersion,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentVersion, agentOptions.ChatOptions);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
|
}
|
|
|
|
private static ChatClientAgent CreateResponsesChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
ILoggerFactory? loggerFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentOptions);
|
|
Throw.IfNull(agentOptions.ChatOptions);
|
|
Throw.IfNullOrWhitespace(agentOptions.ChatOptions.ModelId);
|
|
|
|
IChatClient chatClient = aiProjectClient
|
|
.GetProjectOpenAIClient()
|
|
.GetResponsesClient()
|
|
.AsIChatClient(agentOptions.ChatOptions.ModelId);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
|
|
}
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
ProjectsAgentVersion agentVersion,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
=> CreateChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services);
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
ProjectsAgentRecord agentRecord,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentRecord, agentOptions.ChatOptions);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
|
}
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
AgentReference agentReference,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentReference, defaultModelId: null, agentOptions.ChatOptions);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
|
}
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
ProjectsAgentVersion agentVersion,
|
|
IList<AITool>? tools,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
bool requireInvocableTools,
|
|
IServiceProvider? services)
|
|
=> AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentVersion,
|
|
CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
|
|
clientFactory,
|
|
services);
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
ProjectsAgentRecord agentRecord,
|
|
IList<AITool>? tools,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
bool requireInvocableTools,
|
|
IServiceProvider? services)
|
|
=> AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentRecord,
|
|
CreateChatClientAgentOptions(agentRecord.GetLatestVersion(), new ChatOptions() { Tools = tools }, requireInvocableTools),
|
|
clientFactory,
|
|
services);
|
|
|
|
/// <summary>
|
|
/// This method creates <see cref="ChatClientAgentOptions"/> for the specified <see cref="ProjectsAgentVersion"/> and the provided tools.
|
|
/// </summary>
|
|
/// <param name="agentVersion">The agent version.</param>
|
|
/// <param name="chatOptions">The <see cref="ChatOptions"/> to use when interacting with the agent.</param>
|
|
/// <param name="requireInvocableTools">Indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.</param>
|
|
/// <returns>The created <see cref="ChatClientAgentOptions"/>.</returns>
|
|
/// <exception cref="InvalidOperationException">Thrown when the agent definition requires in-process tools but none were provided.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when the agent definition required tools were not provided.</exception>
|
|
/// <remarks>
|
|
/// This method rebuilds the agent options from the agent definition returned by the version and combine with the in-proc tools when provided
|
|
/// this ensures that all required tools are provided and the definition of the agent options are consistent with the agent definition coming from the server.
|
|
/// </remarks>
|
|
private static ChatClientAgentOptions CreateChatClientAgentOptions(ProjectsAgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools)
|
|
{
|
|
var agentDefinition = agentVersion.Definition;
|
|
|
|
List<AITool>? agentTools = null;
|
|
if (agentDefinition is DeclarativeAgentDefinition { Tools: { Count: > 0 } definitionTools })
|
|
{
|
|
// Check if no tools were provided while the agent definition requires in-proc tools.
|
|
if (requireInvocableTools && chatOptions?.Tools is not { Count: > 0 } && definitionTools.Any(t => t is FunctionTool))
|
|
{
|
|
throw new ArgumentException("The agent definition in-process tools must be provided in the extension method tools parameter.");
|
|
}
|
|
|
|
// Agregate all missing tools for a single error message.
|
|
List<string>? missingTools = null;
|
|
|
|
// Check function tools
|
|
foreach (ResponseTool responseTool in definitionTools)
|
|
{
|
|
if (responseTool is FunctionTool functionTool)
|
|
{
|
|
// Check if a tool with the same type and name exists in the provided tools.
|
|
// Always prefer matching AIFunction when available, regardless of requireInvocableTools.
|
|
var matchingTool = chatOptions?.Tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name);
|
|
|
|
if (matchingTool is not null)
|
|
{
|
|
(agentTools ??= []).Add(matchingTool!);
|
|
continue;
|
|
}
|
|
|
|
if (requireInvocableTools)
|
|
{
|
|
(missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
(agentTools ??= []).Add(responseTool.AsAITool());
|
|
}
|
|
|
|
if (requireInvocableTools && missingTools is { Count: > 0 })
|
|
{
|
|
throw new InvalidOperationException($"The following prompt agent definition required tools were not provided: {string.Join(", ", missingTools)}");
|
|
}
|
|
}
|
|
|
|
// Use the agent version's ID if available, otherwise generate one from name and version.
|
|
// This handles cases where hosted agents (like MCP agents) may not have an ID assigned.
|
|
var version = string.IsNullOrWhiteSpace(agentVersion.Version) ? "latest" : agentVersion.Version;
|
|
var agentId = string.IsNullOrWhiteSpace(agentVersion.Id)
|
|
? $"{agentVersion.Name}:{version}"
|
|
: agentVersion.Id;
|
|
|
|
var agentOptions = new ChatClientAgentOptions()
|
|
{
|
|
Id = agentId,
|
|
Name = agentVersion.Name,
|
|
Description = agentVersion.Description,
|
|
};
|
|
|
|
if (agentDefinition is DeclarativeAgentDefinition promptAgentDefinition)
|
|
{
|
|
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
|
|
agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
|
|
agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature;
|
|
agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP;
|
|
}
|
|
|
|
if (agentTools is { Count: > 0 })
|
|
{
|
|
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
|
|
agentOptions.ChatOptions.Tools = agentTools;
|
|
}
|
|
|
|
return agentOptions;
|
|
}
|
|
|
|
#if NET
|
|
[GeneratedRegex("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$")]
|
|
private static partial Regex AgentNameValidationRegex();
|
|
#else
|
|
private static Regex AgentNameValidationRegex() => new("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$");
|
|
#endif
|
|
|
|
internal static string ThrowIfInvalidAgentName(string? name)
|
|
{
|
|
Throw.IfNullOrWhitespace(name);
|
|
if (!AgentNameValidationRegex().IsMatch(name))
|
|
{
|
|
throw new ArgumentException("Agent name must be 1-63 characters long, start and end with an alphanumeric character, and can only contain alphanumeric characters or hyphens.", nameof(name));
|
|
}
|
|
return name;
|
|
}
|
|
}
|
|
|
|
[JsonSerializable(typeof(JsonElement))]
|
|
internal sealed partial class AgentClientJsonContext : JsonSerializerContext;
|
|
|
|
#endregion
|