mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
f45fc7d402
* Stage * Add FoundryAgentClient, model param, chatClientFactory, and RAPI samples - Add model parameter to FoundryAgentClient simple constructor - Add chatClientFactory parameter to both constructors - Switch to OpenAI.GetProjectResponsesClientForModel for direct Responses API usage - Add FoundryAgents-RAPI samples (Step01 Basics, Step02 Multiturn, Step03 FunctionTools) - Add solution folder entry for FoundryAgents-RAPI samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add auto-discovery constructor and simplify RAPI samples - Add FoundryAgentClient constructor that reads AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME from environment variables with DefaultAzureCredential - Simplify RAPI samples to use auto-discovery (no env var or credential code) - Remove Azure.Identity direct references from sample csproj files - Update READMEs to document environment variable requirements Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add remaining RAPI samples (Step04-Step12) - Step04: Function tools with human-in-the-loop approvals - Step05: Structured output with typed responses - Step06: Persisted conversations with session serialization - Step07: Observability with OpenTelemetry - Step08: Dependency injection with hosted service - Step10: Image multi-modality - Step11: Agent as function tool (agent composition) - Step12: Middleware (PII, guardrails, function logging, HITL approval) - Update solution file and folder README with all new samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add all RAPI samples (Step09-Step23) and switch to AzureCliCredential - Step09: MCP client as tools (GitHub server via stdio) - Step13: Plugins with dependency injection - Step14: Code Interpreter tool - Step15: Computer Use tool with screenshot simulation - Step16: File Search with vector stores - Step17: OpenAPI tools (REST Countries API) - Step18: Bing Custom Search - Step19: SharePoint grounding - Step20: Microsoft Fabric - Step21: Web Search with citations - Step22: Memory Search with multi-turn conversations - Step23: Local MCP via HTTP (Microsoft Learn) - Switch all samples (Step04-Step12) to use AzureCliCredential with env vars - Update solution file and README with all 23 samples - All 23 samples build successfully, tested Step05/06/11/13/21 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Switch Step01-03 samples to AzureCliCredential for consistency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarify connection ID format in SharePoint and Fabric READMEs Document that SHAREPOINT_PROJECT_CONNECTION_ID and FABRIC_PROJECT_CONNECTION_ID should use the connection name (e.g., 'SharepointTestTool'), not the full ARM resource URI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Normalize env vars, fix structured output, update READMEs with connection ID formats - Normalize AZURE_FOUNDRY_PROJECT_* env vars to AZURE_AI_PROJECT_ENDPOINT / AZURE_AI_MODEL_DEPLOYMENT_NAME across all samples (Steps 18-22 READMEs + Steps 19-20 Program.cs) - Fix RAPI Step05 StructuredOutput to use full constructor with ResponseFormat for streaming JSON - Update Deep Research sample to use AzureCliCredential - Enrich Bing Grounding README with full ARM resource URI format - Fix Bing Custom Search README env var mismatch (BING_CUSTOM_SEARCH_* -> AZURE_AI_CUSTOM_SEARCH_*) - Add finding instructions for connection ID and instance name in Bing Custom Search READMEs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refactor memory samples and switch to DefaultAzureCredential - Refactor RAPI Step22 MemorySearch: extract store setup to EnsureMemoryStoreAsync local function - Refactor non-RAPI Step22 MemorySearch: same pattern with explicit memory lifecycle - Set UpdateDelay=0 on MemoryUpdateOptions and MemorySearchPreviewTool for faster ingestion - Use WaitForMemoriesUpdateAsync with 500ms polling interval - Switch Step19 SharePoint, Step20 Fabric, Step22 MemorySearch (both) to DefaultAzureCredential - Remove SearchOptions from MemorySearchPreviewTool (causes unknown parameter error) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Switch all RAPI samples to DefaultAzureCredential and format - Replace AzureCliCredential with DefaultAzureCredential across all 20 RAPI samples - Run dotnet format on all RAPI and non-RAPI Foundry samples - AzureAI unit tests: 341 passed (net10.0 + net472) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename to Microsoft Foundry, add metadata, rename RAPI folder - Replace 'Azure AI Foundry' / 'Azure Foundry' with 'Microsoft Foundry' in all docs, comments, and XML docs - Update FoundryAgentClient metadata provider name to 'microsoft.foundry' - Rename FoundryAgents-RAPI folder to FoundryResponseAgents - Rewrite FoundryResponseAgents README with comparison table vs Foundry Agents - Update slnx and parent README with new folder references Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: simplify sample comments and fix DeepResearch credential - Remove 'no server-side agent' and 'Responses API directly' phrasing from comments - Simplify to 'Create a FoundryAgentClient' per review feedback - Switch Agent_Step15_DeepResearch to DefaultAzureCredential Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restore full DefaultAzureCredential warning comment in DeepResearch sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add ADR 0020: Foundry agent type naming convention Proposes naming options for a new MAF type wrapping versioned Foundry agents (Prompt, ContainerApp, Hosted, Workflow) to distinguish from the existing FoundryResponsesAgent (RAPI path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Simplify FoundryResponsesAgent samples with env-var constructors and rename folders - Add env-var constructors to FoundryResponsesAgent (simple + options-based) - Fix Constructor 1 model optionality (no longer throws on missing AZURE_AI_MODEL_DEPLOYMENT_NAME) - Add ApplyModelDeploymentFallback helper for options-based constructor - Update all 23 FoundryResponseAgents samples to remove Environment.GetEnvironmentVariable boilerplate - Condense 6 simple samples to one-liner constructor calls - Add XML doc remarks about auto-resolved parameters on all constructors - Rename FoundryAgents -> FoundryVersionedAgents (server-side, versioned) - Rename FoundryResponseAgents -> FoundryAgents (now the default path forward) - Update .slnx and README cross-references for new folder names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add FoundryAITool factory, rename RAPI folders, and clean up references - Create FoundryAITool static factory class with 17 methods wrapping AgentTool.Create* and ResponseTool.Create* into AITool returns - Rename 23 FoundryAgentsRAPI_* subfolders to FoundryAgents_* (drop RAPI prefix) - Rename .csproj files and update .slnx references accordingly - Update 12 samples (6 FoundryAgents + 6 FoundryVersionedAgents) to use FoundryAITool - Replace all FoundryResponsesAgent references with FoundryAgent in comments and READMEs - Update sample READMEs to reference FoundryAITool methods Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename FoundryVersionedAgents subfolders from FoundryAgents_* to FoundryVersionedAgents_* Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add FoundryVersionedAgent class and refactor extension method internals - Create FoundryVersionedAgent with private ctor and async static factory methods (CreateAIAgentAsync/GetAIAgentAsync) with env-var and explicit endpoint tiers - Extract shared internal helpers from AzureAIProjectChatClientExtensions: CreateChatClientAgent, CreateAgentVersionFromOptionsAsync, CreateAgentVersionWithProtocolAsync (tools overload), CreateChatClientAgentOptions, GetAgentRecordByNameAsync, ThrowIfInvalidAgentName - Extension methods now delegate to shared internal helpers - All 49 existing samples continue to build successfully Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add CreateConversationSessionAsync, DeleteAIAgentAsync, auto-resolve model, simplify samples - Add CreateConversationSessionAsync to FoundryAgent and FoundryVersionedAgent (returns ChatClientAgentSession, creates server-side conversation + session in one call) - Add DeleteAIAgentAsync static method to FoundryVersionedAgent - Make model parameter optional in env-var factory overloads (auto-resolves from AZURE_AI_MODEL_DEPLOYMENT_NAME) - Update all FoundryVersionedAgents samples to use DeleteAIAgentAsync - Remove deploymentName env var from samples where only used for model parameter - Use CreateConversationSessionAsync in Step02_MultiturnConversation - Use explicit types instead of var for agent/session variables - All 49 samples build successfully Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove manual AIProjectClient construction from FoundryVersionedAgents samples - Replace manual AIProjectClient construction with GetService<AIProjectClient>() from the FoundryVersionedAgent in all dual-option and tool-specific samples - Remove AZURE_AI_PROJECT_ENDPOINT env var reads from updated samples - Remove Azure.Identity usings where no longer needed - Only Step01.1, Step01.2, Eval_Step01 retain manual construction (pedagogical samples) - All 49 samples build successfully Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Replace aiProjectClient extension calls with FoundryVersionedAgent factories in all samples - Replace aiProjectClient.CreateAIAgentAsync with FoundryVersionedAgent.CreateAIAgentAsync in Option 2 (Native SDK) paths across Steps 14-21 - Replace aiProjectClient.Agents.DeleteAgentAsync with FoundryVersionedAgent.DeleteAIAgentAsync - Remove unused AIProjectClient variables and using directives - Only Step01.1, Step01.2, Eval_Step01 retain direct AIProjectClient usage (pedagogical) - Step16, Step22 use GetService<AIProjectClient>() for file/memory operations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unused using directives from Step01.2, Step09, Eval_Step02 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update ADR 0020 with accepted decision: Option 6 - Add Option 6 detailing FoundryAgent, FoundryVersionedAgent, FoundryAITool, env-var auto-discovery, and self-contained factory patterns - Mark decision as accepted with rationale - Update current state and metadata sections Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update Step01 basics samples to use FoundryVersionedAgent factories - Step01.1: Replace manual AIProjectClient/AsAIAgent with FoundryVersionedAgent.CreateAIAgentAsync/GetAIAgentAsync/DeleteAIAgentAsync - Step01.2: Replace manual AIProjectClient with FoundryVersionedAgent.CreateAIAgentAsync/DeleteAIAgentAsync - Remove env var boilerplate and Azure.Identity dependency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add DeleteAIAgentVersionAsync to FoundryVersionedAgent - DeleteAIAgentAsync: deletes the agent and all its versions (existing) - DeleteAIAgentVersionAsync: deletes only the specific version associated with the agent instance - Internally delegates to Agents.DeleteAgentAsync vs Agents.DeleteAgentVersionAsync Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix cleanup comments: DeleteAIAgentAsync deletes the agent and all its versions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update all FoundryVersionedAgents READMEs for FoundryVersionedAgent and auto-discovery - Rewrite main README with FoundryVersionedAgent usage, auto-discovery table, code example - Fix sample table links from FoundryAgents_Step* to FoundryVersionedAgents_Step* - Add FoundryAITool references in tool-specific sample descriptions - Update individual READMEs: fix stale paths, add auto-discovery note after env var blocks - Update tool references: AgentTool/ResponseTool -> FoundryAITool - Update parent 02-agents/README.md with FoundryVersionedAgent description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert unrelated AGUI and Hosting.OpenAI formatting changes to main Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove env-var auto-discovery, add AsAIAgent, mark extensions Obsolete - Remove 2 env-var constructors from FoundryAgent (keep explicit endpoint ctors) - Remove 5 env-var factory methods from FoundryVersionedAgent (keep explicit ones) - Add 3 AsAIAgent static methods to FoundryVersionedAgent (AgentVersion/AgentRecord/AgentReference) - Mark all 8 AIProjectClient extension methods as [Obsolete] pointing to FoundryVersionedAgent - Remove ApplyModelDeploymentFallback, env var constants, Azure.Identity usings from source Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update all samples to use explicit endpoint, credential, and model parameters - Add explicit Environment.GetEnvironmentVariable reads for AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME to all 48 sample files - Pass new Uri(endpoint), new DefaultAzureCredential(), deploymentName to FoundryAgent constructors and FoundryVersionedAgent factory methods - Add using Azure.Identity where missing - Matches repo-wide pattern used by other non-Foundry samples - All 49 samples build successfully Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate remaining samples and source from obsoleted extension methods - Migrate AgentProviders, AgentWithRAG, AgentWithMemory, HostedWorkflow samples to FoundryVersionedAgent - Migrate AzureAgentProvider.cs to FoundryVersionedAgent.AsAIAgent - Migrate AzureAIProjectChatClientTests.cs to FoundryVersionedAgent.GetAIAgentAsync - Remove pragma suppressions from migrated files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add unit tests for FoundryAgent and FoundryVersionedAgent - FoundryAgentTests.cs: 14 tests covering constructors, validation, properties, metadata, GetService, chat client factory, user-agent header - FoundryVersionedAgentTests.cs: 31 tests covering CreateAIAgentAsync, GetAIAgentAsync, AsAIAgent (3 overloads), DeleteAIAgentAsync, DeleteAIAgentVersionAsync, validation, invalid names, metadata, GetService Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Finalize Foundry agent migration Align FoundryAgent and FoundryVersionedAgent samples, docs, and tests with the explicit configuration model, clean up stale README guidance, and fix AzureAI unit test validation/build issues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply formatter cleanup after validation Capture the dotnet format follow-up changes produced during branch validation so the committed state matches the successfully built and tested branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add integration tests for FoundryAgent and FoundryVersionedAgent Mark old AIProjectClient extension-method integration tests as obsolete and add new integration test suites for both FoundryAgent (Responses API) and FoundryVersionedAgent (versioned agents). All 71 non-skipped tests pass against the live Foundry service. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update ADR 0020 with test coverage details Add integration test coverage note to the Current State section of ADR 0020. * Simplify Foundry agents and validate moved samples * Rename FoundryAgent integration tests to ResponsesAgent The test classes exercise the non-versioned Responses path via AIProjectClient.AsAIAgent(), not the removed FoundryAgent wrapper type. Rename files and class names to reflect the actual test surface. * Update documentation for ChatClientAgent usage Added example usage of ChatClientAgent with JokerAgent. * Refactor ChatClientAgent instantiation for clarity * Revise agent type naming and usage examples Updated documentation to reflect changes in agent creation methods and added examples for using `ChatClientAgent`. * Fix Azure SDK namespace migration after rebase Update Azure.AI.Projects.OpenAI references to Azure.AI.Projects.Agents and Azure.AI.Extensions.OpenAI to match Azure.AI.Projects 2.0.0-beta.2. - Replace deprecated namespace across samples, tests, and src - Fix renamed types: OpenAPIFunctionDefinition -> OpenApiFunctionDefinition, BingCustomSearchToolParameters -> BingCustomSearchToolOptions, BrowserAutomationToolParameters -> BrowserAutomationToolOptions - Fix API changes: AgentRecord.Versions -> GetLatestVersion(), ResponsesClient constructor, FunctionApprovalRequestContent -> ToolApprovalRequestContent - Apply dotnet format Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address merge markers * Replace obsolete GetAIAgentAsync with AsAIAgent in samples Switch Agent_Step07_AsMcpTool and A2AServer to use the non-obsolete PersistentAgentsClient.AsAIAgent(PersistentAgent) extension instead of the deprecated GetAIAgentAsync, fixing CS0618 build errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix broken markdown links in Responses sample READMEs Replace stale ChatClientAgents_Step* folder references with the correct Agent_Step* names across all Responses sample READMEs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix format errors and address PR review comments - Fix charset and remove unused using in AzureAIProjectResponsesChatClient - Fix doc comment tags (code -> c) in FoundryAITool - Fix stray period in LocalMCP sample comment - Fix grammar in FoundryMemoryProvider xmldoc - Fix AIProjectClientAgentRunStreamingConversationTests base class Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply dotnet format fixes to PR-changed files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix build errors from format pass and apply naming conventions - Fix static call to CreateSessionAsync in Step02 samples and extension tests - Use expression-bodied lambda in FoundryMemoryProvider (RCS1021) - Apply PascalCase naming to const fields in ResponsesAgentExtensionCreateTests (IDE1006) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Introduce FoundryAgent sealed type and update AsAIAgent extensions - Add FoundryAgent sealed class wrapping ChatClientAgent with: - Public ctors: (projectEndpoint, credential, model, instructions) and (agentEndpoint, credential) - Internal ctor: (AIProjectClient, ChatClientAgent) for extension use - CreateConversationSessionAsync() for server-side conversations - GetService<ChatClientAgent>() and GetService<AIProjectClient>() - MEAI user-agent policy on internally-created AIProjectClient - Change all AsAIAgent extension return types from ChatClientAgent to FoundryAgent - Update all samples and tests to use FoundryAgent type - Add 16 FoundryAgentTests covering ctors, GetService, UserAgent, RunAsync - Fix pre-existing Agent_Step12_Plugins build error Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Collapse sample folders and add FoundryAgent_Step01 sample - Move all Responses/* samples up to AgentsWithFoundry/ (flat structure) - Remove entire Versioned/ folder (26 samples) - Add FoundryAgent_Step01 sample showing direct FoundryAgent ctor usage - Update slnx to reflect flat folder structure - Fix csproj ProjectReference paths for new depth Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update READMEs for flat AgentsWithFoundry structure - Rewrite AgentsWithFoundry/README.md with FoundryAgent quick start - Fix cd commands and paths in 11 sample READMEs - Update 02-agents/README.md to single Foundry link - Update AGENTS.md tree to flat structure - Fix AgentWithMemory cross-reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix FoundryAgent_Step01 sample with full create/run/delete lifecycle Show the complete server-side agent lifecycle: create version with native SDK, wrap as FoundryAgent via AsAIAgent, run, then delete. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert RAPI samples to use AIAgent instead of FoundryAgent RAPI samples should not reference FoundryAgent directly. Restored original sample code with only ChatClientAgent -> AIAgent type change to accommodate the AsAIAgent return type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Convert versioned-pattern samples to pure RAPI Step09, Step13, Step17, Step22 were using CreateAgentVersionAsync + PromptAgentDefinition which is the versioned pattern. Converted to use AsAIAgent(model, instructions, tools) which is the RAPI path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix format issues from Docker CI check - FoundryAgent_Step01: CRLF -> LF - Agent_Step09: missing final newline - Agent_Step11_Middleware: add internal modifier, final newline - Agent_Step02: remove redundant cast (IDE0004) - Agent_Step08: simplify name (IDE0001) - FoundryAgentTests: s_ prefix, Async suffix naming conventions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Switch Step09 MCP sample to Microsoft Learn HTTP endpoint Replace npx stdio GitHub MCP server with the public Microsoft Learn MCP endpoint (https://learn.microsoft.com/api/mcp) using HTTP transport. No external tooling required to run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix missing final newline in Step09 MCP sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: use DelegatingAIAgent, clean up Step01 sample - FoundryAgent now inherits DelegatingAIAgent instead of AIAgent, removing manual delegation boilerplate (westey-m feedback) - Simplified Agent_Step01_Basics to single agent creation path, moved composable IChatClient approach to README (westey-m feedback) - Fixed FoundryAgentTests param name assertion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update sample using Project specialized type instead * Address PR review feedback: DefaultAzureCredential warnings, sample simplifications, format fixes - Add DefaultAzureCredential production warning comments to ~25 samples - Simplify Anthropic and OpenAI Step01 samples to single agent - Convert Step11 Middleware regex patterns to [GeneratedRegex] - Remove unnecessary cleanup comment from Step06 - Fix Step09 README MCP transport description - Enhance FoundryAgent xmldoc with non-persistent agent comparison Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Split Step02, simplify RAG Step04, sharpen Step23 differentiation - Split Step02 into 02.1 (simple multi-turn via sessions) and 02.2 (server-side conversations via CreateConversationSessionAsync) - RAG Step04: replace HostedFileSearchTool + MEAI wrapping with native OpenAI FileSearchTool - Step23: clarify DelegatingAIFunction wrapping pattern vs Step09 basic MCP Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Hosted MCP sample: use ResponseTool.CreateMcpTool and move tool to PromptAgentDefinition Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix broken README link after Step02 split Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Sergey round 3 feedback: branding, README nav, sample rename - Replace 'Azure AI Foundry' with 'Microsoft Foundry' in ADR 0020 - Fix 3 READMEs: 'ChatClientAgents' → 'AgentsWithFoundry' sample directory - Rename FoundryAgent_Step01 → Agent_Step00_FoundryAgentLifecycle for naming consistency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3473 lines
128 KiB
C#
3473 lines
128 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.ClientModel;
|
|
using System.ClientModel.Primitives;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Azure.AI.Extensions.OpenAI;
|
|
using Azure.AI.Projects;
|
|
using Azure.AI.Projects.Agents;
|
|
using Microsoft.Extensions.AI;
|
|
using Moq;
|
|
using OpenAI.Responses;
|
|
|
|
namespace Microsoft.Agents.AI.AzureAI.UnitTests;
|
|
|
|
#pragma warning disable CS0618
|
|
/// <summary>
|
|
/// Unit tests for the <see cref="AzureAIProjectChatClientExtensions"/> class.
|
|
/// </summary>
|
|
[Obsolete("Includes coverage for obsolete AIProjectClient compatibility extension methods.")]
|
|
public sealed class AzureAIProjectChatClientExtensionsTests
|
|
{
|
|
#region AsAIAgent(AIProjectClient, model, instructions) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that the non-versioned AsAIAgent overload throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithModelAndInstructions_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent("gpt-4o-mini", "You are helpful."));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the non-versioned AsAIAgent overload creates a valid ChatClientAgent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithModelAndInstructions_CreatesChatClientAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
List<AITool> tools =
|
|
[
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
];
|
|
|
|
// Act
|
|
FoundryAgent agent = client.AsAIAgent(
|
|
"gpt-4o-mini",
|
|
"You are helpful.",
|
|
name: "test-agent",
|
|
description: "A test agent",
|
|
tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-agent", agent.Name);
|
|
Assert.Equal("A test agent", agent.Description);
|
|
Assert.Same(client, agent.GetService<AIProjectClient>());
|
|
Assert.NotNull(agent.GetService<IChatClient>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the non-versioned AsAIAgent overload applies the clientFactory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithModelAndInstructions_WithClientFactory_AppliesFactoryCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
FoundryAgent agent = client.AsAIAgent(
|
|
"gpt-4o-mini",
|
|
"You are helpful.",
|
|
clientFactory: innerClient => testChatClient = new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
TestChatClient? retrievedTestClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(retrievedTestClient);
|
|
Assert.Same(testChatClient, retrievedTestClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the options-based non-versioned AsAIAgent overload creates a valid ChatClientAgent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithOptions_CreatesChatClientAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
ChatClientAgentOptions options = new()
|
|
{
|
|
Name = "options-agent",
|
|
Description = "Agent from options",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
ModelId = "gpt-4o-mini",
|
|
Instructions = "You are helpful.",
|
|
},
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = client.AsAIAgent(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("options-agent", agent.Name);
|
|
Assert.Equal("Agent from options", agent.Description);
|
|
Assert.Same(client, agent.GetService<AIProjectClient>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the non-versioned AsAIAgent overload adds the MEAI user-agent header to Responses API requests.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task AsAIAgent_WithModelAndInstructions_UserAgentHeaderAddedToResponsesRequestsAsync()
|
|
{
|
|
// Arrange
|
|
bool userAgentFound = false;
|
|
using HttpHandlerAssert httpHandler = new(request =>
|
|
{
|
|
if (request.Headers.TryGetValues("User-Agent", out IEnumerable<string>? values))
|
|
{
|
|
foreach (string value in values)
|
|
{
|
|
if (value.Contains("MEAI"))
|
|
{
|
|
userAgentFound = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (request.Method == HttpMethod.Post && request.RequestUri!.PathAndQuery.Contains("/responses"))
|
|
{
|
|
return new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(
|
|
TestDataUtil.GetOpenAIDefaultResponseJson(),
|
|
Encoding.UTF8,
|
|
"application/json")
|
|
};
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent("{}", Encoding.UTF8, "application/json")
|
|
};
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using HttpClient httpClient = new(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
AIProjectClient aiProjectClient = new(
|
|
new Uri("https://test.openai.azure.com/"),
|
|
new FakeAuthenticationTokenProvider(),
|
|
new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
FoundryAgent agent = aiProjectClient.AsAIAgent(
|
|
"gpt-4o-mini",
|
|
"You are helpful.");
|
|
|
|
// Act
|
|
AgentSession session = await agent.CreateSessionAsync();
|
|
await agent.RunAsync("Hello", session);
|
|
|
|
// Assert
|
|
Assert.True(userAgentFound, "MEAI user-agent header was not found in any request");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AsAIAgent(AIProjectClient, AgentRecord) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent(agentRecord));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when agentRecord is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_WithNullAgentRecord_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((AgentRecord)null!));
|
|
|
|
Assert.Equal("agentRecord", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_CreatesValidAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("agent_abc123", agent.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecord_WithClientFactory_AppliesFactoryCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var retrievedTestClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(retrievedTestClient);
|
|
Assert.Same(testChatClient, retrievedTestClient);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AsAIAgent(AIProjectClient, AgentVersion) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent(agentVersion));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when agentVersion is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithNullAgentVersion_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((AgentVersion)null!));
|
|
|
|
Assert.Equal("agentVersion", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_CreatesValidAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("agent_abc123", agent.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithClientFactory_AppliesFactoryCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
agentVersion,
|
|
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var retrievedTestClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(retrievedTestClient);
|
|
Assert.Same(testChatClient, retrievedTestClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with requireInvocableTools=true enforces invocable tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithRequireInvocableToolsTrue_EnforcesInvocableTools()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion, tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with requireInvocableTools=false allows declarative functions.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersion_WithRequireInvocableToolsFalse_AllowsDeclarativeFunctions()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act - should not throw even without tools when requireInvocableTools is false
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync(AIProjectClient, ChatClientAgentOptions) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when client is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithNullClient_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
var options = new ChatClientAgentOptions { Name = "test-agent" };
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
client!.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentNullException when options is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithNullOptions_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
mockClient.Object.GetAIAgentAsync((ChatClientAgentOptions)null!));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_CreatesValidAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent");
|
|
var options = new ChatClientAgentOptions { Name = "test-agent" };
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-agent", agent.Name);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AsAIAgent(AIProjectClient, string) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_ByName_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent("test-agent"));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_ByName_WithNullName_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((string)null!));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentException when name is empty.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_ByName_WithEmptyName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentException>(() =>
|
|
mockClient.Object.AsAIAgent(string.Empty));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync(AIProjectClient, string) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_ByName_WithNullClient_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
client!.GetAIAgentAsync("test-agent"));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws ArgumentNullException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_ByName_WithNullName_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
mockClient.Object.GetAIAgentAsync(name: null!));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws InvalidOperationException when agent is not found.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_ByName_WithNonExistentAgent_ThrowsInvalidOperationExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockAgentOperations = new Mock<AgentsClient>();
|
|
mockAgentOperations
|
|
.Setup(c => c.GetAgentAsync(It.IsAny<string>(), It.IsAny<RequestOptions>()))
|
|
.ReturnsAsync(ClientResult.FromOptionalValue((AgentRecord)null!, new MockPipelineResponse(200, BinaryData.FromString("null"))));
|
|
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
mockClient.SetupGet(c => c.Agents).Returns(mockAgentOperations.Object);
|
|
mockClient.Setup(x => x.GetConnection(It.IsAny<string>())).Returns(new ClientConnection("fake-connection-id", "http://localhost", ClientPipeline.Create(), CredentialKind.None));
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
mockClient.Object.GetAIAgentAsync("non-existent-agent"));
|
|
|
|
Assert.Contains("not found", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AsAIAgent(AIProjectClient, AgentRecord) with tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with additional tools when the definition has no tools does not throw and results in an agent with no tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordAndAdditionalTools_WhenDefinitionHasNoTools_ShouldNotThrow()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord, tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
var chatClient = agent.GetService<IChatClient>();
|
|
Assert.NotNull(chatClient);
|
|
var agentVersion = chatClient.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
var definition = Assert.IsType<PromptAgentDefinition>(agentVersion.Definition);
|
|
Assert.Empty(definition.Tools);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with null tools works correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordAndNullTools_WorksCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord, tools: null);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("agent_abc123", agent.Name);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync(AIProjectClient, string) with tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with tools parameter creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithNameAndTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync("test-agent", tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_CreatesValidAgentAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", instructions: "Test instructions");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions" }
|
|
};
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-agent", agent.Name);
|
|
Assert.Equal("Test instructions", agent.GetService<ChatClientAgent>()!.Instructions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithClientFactory_AppliesFactoryCorrectlyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", instructions: "Test instructions");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new() { Instructions = "Test instructions" }
|
|
};
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync(
|
|
"test-model",
|
|
options,
|
|
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var retrievedTestClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(retrievedTestClient);
|
|
Assert.Same(testChatClient, retrievedTestClient);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync(AIProjectClient, string, AgentDefinition) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithAgentDefinition_WithNullClient_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
var definition = new PromptAgentDefinition("test-model");
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
client!.CreateAIAgentAsync("agent-name", options));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync throws ArgumentNullException when creationOptions is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithAgentDefinition_WithNullDefinition_ThrowsArgumentNullExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
|
mockClient.Object.CreateAIAgentAsync(name: "agent-name", null!));
|
|
|
|
Assert.Equal("creationOptions", exception.ParamName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Tool Validation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent creates an agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent without tools parameter creates an agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithoutToolsParameter_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent without tools in definition creates an agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithoutToolsInDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definition);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent uses tools from the definition when no separate tools parameter is provided.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDefinitionTools_UsesDefinitionToolsAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
// Add a function tool to the definition
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
// Create a response definition with the same tool
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Single(promptDef.Tools);
|
|
Assert.Equal("required_tool", (promptDef.Tools.First() as FunctionTool)?.FunctionName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent creates an agent successfully when definition has a mix of custom and hosted tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithMixedToolsInDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
|
|
definition.Tools.Add(new HostedFileSearchTool().GetService<ResponseTool>() ?? new HostedFileSearchTool().AsOpenAIResponseTool());
|
|
|
|
// Simulate agent definition response with the tools
|
|
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
foreach (var tool in definition.Tools)
|
|
{
|
|
definitionResponse.Tools.Add(tool);
|
|
}
|
|
|
|
using var testClient = CreateTestAgentClientWithHandler(agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Equal(3, promptDef.Tools.Count);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync when AI Tools are provided, uses them for the definition via http request.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithNameAndAITools_SendsToolDefinitionViaHttpAsync()
|
|
{
|
|
// Arrange
|
|
using var httpHandler = new HttpHandlerAssert(async (request) =>
|
|
{
|
|
if (request.Content is not null)
|
|
{
|
|
var requestBody = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
|
|
Assert.Contains("required_tool", requestBody);
|
|
}
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
var client = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
// Act
|
|
var agent = await client.CreateAIAgentAsync(
|
|
name: "test-agent",
|
|
model: "test-model",
|
|
instructions: "Test",
|
|
tools: [AIFunctionFactory.Create(() => true, "required_tool")]);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
Assert.IsType<PromptAgentDefinition>(agentVersion.Definition);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when providing AITools with AsAIAgent, any additional tool that doesn't match the tools in agent definition are ignored.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_AdditionalAITools_WhenNotInTheDefinitionAreIgnored()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Manually add tools to the definition to simulate inline tools
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
promptDef.Tools.Add(ResponseTool.CreateFunctionTool("inline_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
}
|
|
|
|
var invocableInlineAITool = AIFunctionFactory.Create(() => "test", "inline_tool", "An invocable AIFunction for the inline function");
|
|
var shouldBeIgnoredTool = AIFunctionFactory.Create(() => "test", "additional_tool", "An additional test function that should be ignored");
|
|
|
|
// Act & Assert
|
|
var agent = client.AsAIAgent(agentVersion, tools: [invocableInlineAITool, shouldBeIgnoredTool]);
|
|
Assert.NotNull(agent);
|
|
var version = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(version);
|
|
var definition = Assert.IsType<PromptAgentDefinition>(version.Definition);
|
|
Assert.NotEmpty(definition.Tools);
|
|
Assert.NotNull(GetAgentChatOptions(agent));
|
|
Assert.NotNull(GetAgentChatOptions(agent)!.Tools);
|
|
Assert.Single(GetAgentChatOptions(agent)!.Tools!);
|
|
Assert.Equal("inline_tool", (definition.Tools.First() as FunctionTool)?.FunctionName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Inline Tools vs Parameter Tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that tools passed as parameters are accepted by AsAIAgent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithParameterTools_AcceptsTools()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "tool1", "param_tool_1", "First parameter tool"),
|
|
AIFunctionFactory.Create(() => "tool2", "param_tool_2", "Second parameter tool")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord, tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
var chatClient = agent.GetService<IChatClient>();
|
|
Assert.NotNull(chatClient);
|
|
var agentVersion = chatClient.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgent with string parameters and tools creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithStringParamsAndTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "weather", "string_param_tool", "Tool from string params")
|
|
};
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(new PromptAgentDefinition("test-model") { Instructions = "Test instructions" }, tools);
|
|
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync(
|
|
"test-agent",
|
|
"test-model",
|
|
"Test instructions",
|
|
tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Single(promptDef.Tools);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with tools in definition creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDefinitionTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("async_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with tools parameter creates an agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithToolsParameter_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "async_get_result", "async_get_tool", "An async get tool")
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync("test-agent", tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Declarative Function Handling Tests
|
|
|
|
/// <summary>
|
|
/// Verifies that CreateAIAgent uses tools from definition when they are ResponseTool instances, resulting in successful agent creation.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithResponseToolsInDefinition_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
|
|
var fabricToolOptions = new FabricDataAgentToolOptions();
|
|
fabricToolOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
|
|
|
|
var sharepointOptions = new SharePointGroundingToolOptions();
|
|
sharepointOptions.ProjectConnections.Add(new ToolProjectConnection("connection-id"));
|
|
|
|
var structuredOutputs = new StructuredOutputDefinition("name", "description", new Dictionary<string, BinaryData> { ["schema"] = BinaryData.FromString(AIJsonUtilities.CreateJsonSchema(new { id = "test" }.GetType()).ToString()) }, false);
|
|
|
|
// Add tools to the definition
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("create_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateBingCustomSearchTool(new BingCustomSearchToolOptions([new BingCustomSearchConfiguration("connection-id", "instance-name")])));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateBrowserAutomationTool(new BrowserAutomationToolOptions(new BrowserAutomationToolConnectionParameters("id"))));
|
|
definition.Tools.Add(AgentTool.CreateA2ATool(new Uri("https://test-uri.microsoft.com")));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateBingGroundingTool(new BingGroundingSearchToolOptions([new BingGroundingSearchConfiguration("connection-id")])));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateMicrosoftFabricTool(fabricToolOptions));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateOpenApiTool(new OpenApiFunctionDefinition("name", BinaryData.FromString(OpenAPISpec), new OpenAPIAnonymousAuthenticationDetails())));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateSharepointTool(sharepointOptions));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateStructuredOutputsTool(structuredOutputs));
|
|
definition.Tools.Add((ResponseTool)AgentTool.CreateAzureAISearchTool(new AzureAISearchToolOptions([new AzureAISearchToolIndex() { IndexName = "name" }])));
|
|
|
|
// Generate agent definition response with the tools
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, definition.Tools.Select(t => t.AsAITool()).ToList());
|
|
|
|
using var testClient = CreateTestAgentClientWithHandler(agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Equal(10, promptDef.Tools.Count);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync accepts FunctionTools from definition.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithFunctionToolsInDefinition_AcceptsDeclarativeFunctionAsync()
|
|
{
|
|
// Arrange
|
|
var functionTool = ResponseTool.CreateFunctionTool(
|
|
functionName: "get_user_name",
|
|
functionParameters: BinaryData.FromString("{}"),
|
|
strictModeEnabled: false,
|
|
functionDescription: "Gets the user's name, as used for friendly address."
|
|
);
|
|
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(functionTool);
|
|
|
|
// Generate response with the declarative function
|
|
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
definitionResponse.Tools.Add(functionTool);
|
|
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync accepts declarative functions from definition.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDeclarativeFunctionFromDefinition_AcceptsDeclarativeFunctionAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
// Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
|
|
using var doc = JsonDocument.Parse("{}");
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
|
|
|
|
// Add to definition
|
|
definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync accepts declarative functions from definition.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDeclarativeFunctionInDefinition_AcceptsDeclarativeFunctionAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
// Create a declarative function (not invocable) using AIFunctionFactory.CreateDeclaration
|
|
using var doc = JsonDocument.Parse("{}");
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
|
|
|
|
// Add to definition
|
|
definition.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
|
|
|
// Generate response with the declarative function
|
|
var definitionResponse = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
definitionResponse.Tools.Add(declarativeFunction.AsOpenAIResponseTool() ?? throw new InvalidOperationException());
|
|
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Options Generation Validation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that ChatClientAgentOptions are generated correctly without tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_GeneratesCorrectChatClientAgentOptionsAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test instructions" };
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(definition, null);
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-agent", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
Assert.Equal("test-agent", agentVersion.Name);
|
|
Assert.Equal("Test instructions", (agentVersion.Definition as PromptAgentDefinition)?.Instructions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with options preserves custom properties from input options.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_PreservesCustomPropertiesAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient(agentName: "test-agent", instructions: "Custom instructions", description: "Custom description");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
Description = "Custom description",
|
|
ChatOptions = new ChatOptions { Instructions = "Custom instructions" }
|
|
};
|
|
|
|
// Act
|
|
var agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-agent", agent.Name);
|
|
Assert.Equal("Custom instructions", agent.GetService<ChatClientAgent>()!.Instructions);
|
|
Assert.Equal("Custom description", agent.Description);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with options and tools generates correct ChatClientAgentOptions.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithOptionsAndTools_GeneratesCorrectOptionsAsync()
|
|
{
|
|
// Arrange
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "result", "option_tool", "A tool from options")
|
|
};
|
|
|
|
var definitionResponse = GeneratePromptDefinitionResponse(
|
|
new PromptAgentDefinition("test-model") { Instructions = "Test" },
|
|
tools);
|
|
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: definitionResponse);
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test", Tools = tools }
|
|
};
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
if (agentVersion.Definition is PromptAgentDefinition promptDef)
|
|
{
|
|
Assert.NotEmpty(promptDef.Tools);
|
|
Assert.Single(promptDef.Tools);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AgentName Validation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public void AsAIAgent_ByName_WithInvalidAgentName_ThrowsArgumentException(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentException>(() =>
|
|
mockClient.Object.AsAIAgent(invalidName));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task GetAIAgentAsync_ByName_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
mockClient.Object.GetAIAgentAsync(invalidName));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task GetAIAgentAsync_WithOptions_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = invalidName };
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task CreateAIAgentAsync_WithBasicParams_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
mockClient.Object.CreateAIAgentAsync(invalidName, "model", "instructions"));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with AgentVersionCreationOptions throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task CreateAIAgentAsync_WithAgentDefinition_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
var definition = new PromptAgentDefinition("test-model");
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
mockClient.Object.CreateAIAgentAsync(invalidName, options));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatClientAgentOptions throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public async Task CreateAIAgentAsync_WithOptions_WithInvalidAgentName_ThrowsArgumentExceptionAsync(string invalidName)
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = invalidName };
|
|
|
|
// Act & Assert
|
|
var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference throws ArgumentException when agent name is invalid.
|
|
/// </summary>
|
|
[Theory]
|
|
[MemberData(nameof(InvalidAgentNameTestData.GetInvalidAgentNames), MemberType = typeof(InvalidAgentNameTestData))]
|
|
public void AsAIAgent_WithAgentReference_WithInvalidAgentName_ThrowsArgumentException(string invalidName)
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
var agentReference = new AgentReference(invalidName, "1");
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentException>(() =>
|
|
mockClient.Object.AsAIAgent(agentReference));
|
|
|
|
Assert.Equal("name", exception.ParamName);
|
|
Assert.Contains("Agent name must be 1-63 characters long", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AzureAIChatClient Behavior Tests
|
|
|
|
/// <summary>
|
|
/// Verify that the underlying chat client created by extension methods can be wrapped with clientFactory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithClientFactory_WrapsUnderlyingChatClient()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
int factoryCallCount = 0;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) =>
|
|
{
|
|
factoryCallCount++;
|
|
return new TestChatClient(innerClient);
|
|
});
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal(1, factoryCallCount);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that clientFactory is called with the correct underlying chat client.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithClientFactory_ReceivesCorrectUnderlyingClientAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
IChatClient? receivedClient = null;
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync(
|
|
"test-agent",
|
|
options,
|
|
clientFactory: (innerClient) =>
|
|
{
|
|
receivedClient = innerClient;
|
|
return new TestChatClient(innerClient);
|
|
});
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.NotNull(receivedClient);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that multiple clientFactory calls create independent wrapped clients.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_MultipleCallsWithClientFactory_CreatesIndependentClients()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent1 = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
var agent2 = client.AsAIAgent(
|
|
agentRecord,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent1);
|
|
Assert.NotNull(agent2);
|
|
var client1 = agent1.GetService<TestChatClient>();
|
|
var client2 = agent2.GetService<TestChatClient>();
|
|
Assert.NotNull(client1);
|
|
Assert.NotNull(client2);
|
|
Assert.NotSame(client1, client2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that agent created with clientFactory maintains agent properties.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithClientFactory_PreservesAgentPropertiesAsync()
|
|
{
|
|
// Arrange
|
|
const string AgentName = "test-agent";
|
|
const string Model = "test-model";
|
|
const string Instructions = "Test instructions";
|
|
using var testClient = CreateTestAgentClientWithHandler(AgentName, Instructions);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync(
|
|
AgentName,
|
|
Model,
|
|
Instructions,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal(AgentName, agent.Name);
|
|
Assert.Equal(Instructions, agent.GetService<ChatClientAgent>()!.Instructions);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that agent created with clientFactory is created successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithClientFactory_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
var definition = new PromptAgentDefinition("test-model") { Instructions = "Test" };
|
|
|
|
var agentDefinitionResponse = GeneratePromptDefinitionResponse(definition, null);
|
|
using var testClient = CreateTestAgentClientWithHandler(agentName: "test-agent", agentDefinitionResponse: agentDefinitionResponse);
|
|
|
|
var options = new AgentVersionCreationOptions(definition);
|
|
|
|
// Act
|
|
var agent = await testClient.Client.CreateAIAgentAsync(
|
|
"test-agent",
|
|
options,
|
|
clientFactory: (innerClient) => new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var wrappedClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(wrappedClient);
|
|
var agentVersion = agent.GetService<AgentVersion>();
|
|
Assert.NotNull(agentVersion);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region User-Agent Header Tests
|
|
|
|
/// <summary>
|
|
/// Verifies that the MEAI user-agent header is added to CreateAIAgentAsync POST requests
|
|
/// via the protocol method's RequestOptions pipeline policy.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_UserAgentHeaderAddedToRequestsAsync()
|
|
{
|
|
using var httpHandler = new HttpHandlerAssert(request =>
|
|
{
|
|
Assert.Equal("POST", request.Method.Method);
|
|
|
|
// Verify MEAI user-agent header is present on CreateAgentVersion POST request
|
|
Assert.True(request.Headers.TryGetValues("User-Agent", out var userAgentValues));
|
|
Assert.Contains(userAgentValues, v => v.Contains("MEAI"));
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
// Arrange
|
|
var aiProjectClient = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
var agentOptions = new ChatClientAgentOptions { Name = "test-agent" };
|
|
|
|
// Act
|
|
var agent = await aiProjectClient.CreateAIAgentAsync("test", agentOptions);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the user-agent header is added to asynchronous GetAIAgentAsync requests.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgent_UserAgentHeaderAddedToRequestsAsync()
|
|
{
|
|
using var httpHandler = new HttpHandlerAssert(request =>
|
|
{
|
|
Assert.Equal("GET", request.Method.Method);
|
|
Assert.Contains("MEAI", request.Headers.UserAgent.ToString());
|
|
|
|
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(TestDataUtil.GetAgentResponseJson(), Encoding.UTF8, "application/json") };
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
// Arrange
|
|
var aiProjectClient = new AIProjectClient(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
// Act
|
|
var agent = await aiProjectClient.GetAIAgentAsync("test");
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgent(AIProjectClient, AgentReference) Tests
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when AIProjectClient is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithNullClient_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AIProjectClient? client = null;
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
client!.AsAIAgent(agentReference));
|
|
|
|
Assert.Equal("aiProjectClient", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent throws ArgumentNullException when agentReference is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithNullAgentReference_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
var mockClient = new Mock<AIProjectClient>();
|
|
|
|
// Act & Assert
|
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
|
mockClient.Object.AsAIAgent((AgentReference)null!));
|
|
|
|
Assert.Equal("agentReference", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference creates a valid agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_CreatesValidAgent()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-name", agent.Name);
|
|
Assert.Equal("test-name:1", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference and clientFactory applies the factory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithClientFactory_AppliesFactoryCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
TestChatClient? testChatClient = null;
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(
|
|
agentReference,
|
|
clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient));
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var retrievedTestClient = agent.GetService<TestChatClient>();
|
|
Assert.NotNull(retrievedTestClient);
|
|
Assert.Same(testChatClient, retrievedTestClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference sets the agent ID correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_SetsAgentIdCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "2");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("test-name:2", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentReference and tools includes the tools in ChatOptions.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentReference_WithTools_IncludesToolsInChatOptions()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference, tools: tools);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
var chatOptions = GetAgentChatOptions(agent);
|
|
Assert.NotNull(chatOptions);
|
|
Assert.NotNull(chatOptions.Tools);
|
|
Assert.Single(chatOptions.Tools);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetService<AgentRecord> Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns AgentRecord for agents created from AgentRecord.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentRecord_ReturnsAgentRecord()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
var retrievedRecord = agent.GetService<AgentRecord>();
|
|
|
|
// Assert
|
|
Assert.NotNull(retrievedRecord);
|
|
Assert.Equal(agentRecord.Id, retrievedRecord.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns null for AgentRecord when agent is created from AgentReference.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentReference_ReturnsNullForAgentRecord()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
var retrievedRecord = agent.GetService<AgentRecord>();
|
|
|
|
// Assert
|
|
Assert.Null(retrievedRecord);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetService<AgentVersion> Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns AgentVersion for agents created from AgentVersion.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentVersion_ReturnsAgentVersion()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
var retrievedVersion = agent.GetService<AgentVersion>();
|
|
|
|
// Assert
|
|
Assert.NotNull(retrievedVersion);
|
|
Assert.Equal(agentVersion.Id, retrievedVersion.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns null for AgentVersion when agent is created from AgentReference.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentReference_ReturnsNullForAgentVersion()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-name", "1");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
var retrievedVersion = agent.GetService<AgentVersion>();
|
|
|
|
// Assert
|
|
Assert.Null(retrievedVersion);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ChatClientMetadata Tests
|
|
|
|
/// <summary>
|
|
/// Verify that ChatClientMetadata is properly populated for agents created from AgentRecord.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ChatClientMetadata_WithAgentRecord_IsPopulatedCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
var metadata = agent.GetService<ChatClientMetadata>();
|
|
|
|
// Assert
|
|
Assert.NotNull(metadata);
|
|
Assert.NotNull(metadata.DefaultModelId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that ChatClientMetadata.DefaultModelId is set from PromptAgentDefinition model property.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ChatClientMetadata_WithPromptAgentDefinition_SetsDefaultModelIdFromModel()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var definition = new PromptAgentDefinition("gpt-4-turbo")
|
|
{
|
|
Instructions = "Test instructions"
|
|
};
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord(definition);
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
var metadata = agent.GetService<ChatClientMetadata>();
|
|
|
|
// Assert
|
|
Assert.NotNull(metadata);
|
|
// The metadata should contain the model information from the agent definition
|
|
Assert.NotNull(metadata.DefaultModelId);
|
|
Assert.Equal("gpt-4-turbo", metadata.DefaultModelId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that ChatClientMetadata is properly populated for agents created from AgentVersion.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ChatClientMetadata_WithAgentVersion_IsPopulatedCorrectly()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
var metadata = agent.GetService<ChatClientMetadata>();
|
|
|
|
// Assert
|
|
Assert.NotNull(metadata);
|
|
Assert.NotNull(metadata.DefaultModelId);
|
|
Assert.Equal((agentVersion.Definition as PromptAgentDefinition)!.Model, metadata.DefaultModelId);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AgentReference Availability Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns AgentReference for agents created from AgentReference.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentReference_ReturnsAgentReference()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("test-agent", "1.0");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
var retrievedReference = agent.GetService<AgentReference>();
|
|
|
|
// Assert
|
|
Assert.NotNull(retrievedReference);
|
|
Assert.Equal("test-agent", retrievedReference.Name);
|
|
Assert.Equal("1.0", retrievedReference.Version);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns null for AgentReference when agent is created from AgentRecord.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentRecord_ReturnsAlsoAgentReference()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecord();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
var retrievedReference = agent.GetService<AgentReference>();
|
|
|
|
// Assert
|
|
Assert.NotNull(retrievedReference);
|
|
Assert.Equal(agentRecord.Name, retrievedReference.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns null for AgentReference when agent is created from AgentVersion.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentVersion_ReturnsAlsoAgentReference()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
var retrievedReference = agent.GetService<AgentReference>();
|
|
|
|
// Assert
|
|
Assert.NotNull(retrievedReference);
|
|
Assert.Equal(agentVersion.Name, retrievedReference.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetService returns AgentReference with correct version information.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetService_WithAgentReference_ReturnsCorrectVersionInformation()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var agentReference = new AgentReference("versioned-agent", "3.5");
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentReference);
|
|
var retrievedReference = agent.GetService<AgentReference>();
|
|
|
|
// Assert
|
|
Assert.NotNull(retrievedReference);
|
|
Assert.Equal("versioned-agent", retrievedReference.Name);
|
|
Assert.Equal("3.5", retrievedReference.Version);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetAIAgentAsync - Empty Name Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithNullName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = null };
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is empty.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = string.Empty };
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is whitespace.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions { Name = " " };
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - Empty Name Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is null.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithNullName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = null,
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is empty.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = string.Empty,
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is whitespace.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithModelAndOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = " ",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.CreateAIAgentAsync("test-model", options));
|
|
|
|
Assert.Equal("options", exception.ParamName);
|
|
Assert.Contains("Agent name must be provided", exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - Response Format Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatText response format creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithTextResponseFormat_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = ChatResponseFormat.Text
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson response format without schema creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithoutSchema_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = ChatResponseFormat.Json
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchema_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
|
|
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = jsonFormat
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema and strict mode creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictMode_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
|
|
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
|
|
var additionalProps = new AdditionalPropertiesDictionary
|
|
{
|
|
["strictJsonSchema"] = true
|
|
};
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = jsonFormat,
|
|
AdditionalProperties = additionalProps
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema and strict mode false creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictModeFalse_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
|
|
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
|
|
var additionalProps = new AdditionalPropertiesDictionary
|
|
{
|
|
["strictJsonSchema"] = false
|
|
};
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
ResponseFormat = jsonFormat,
|
|
AdditionalProperties = additionalProps
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - RawRepresentationFactory Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns CreateResponseOptions creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithRawRepresentationFactory_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
RawRepresentationFactory = _ => new CreateResponseOptions()
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns null does not fail.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNull_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
RawRepresentationFactory = _ => null
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns non-CreateResponseOptions does not fail.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNonCreateResponseOptions_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
RawRepresentationFactory = _ => new object()
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAIAgentAsync - Description Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with description sets description on the agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithDescription_SetsDescriptionAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler(description: "Test description");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
Description = "Test description",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.Equal("Test description", agent.Description);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync without description still creates agent successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithoutDescription_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateChatClientAgentOptions - Missing Tools Tests
|
|
|
|
/// <summary>
|
|
/// Verify that when invocable tools are required but not provided, an exception is thrown.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithToolsRequiredButNotProvided_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Contains("in-process tools must be provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when specific invocable tools are required but wrong ones are provided, InvalidOperationException is thrown.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithWrongToolsProvided_ThrowsInvalidOperationExceptionAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "wrong_function", "Wrong function")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Contains("required_function", exception.Message);
|
|
Assert.Contains("were not provided", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when tools are provided that match the definition, agent is created successfully.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithMatchingToolsProvided_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "required_function", "Required function")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateChatClientAgentOptions - Options Preservation Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateChatClientAgentOptions preserves AIContextProviders.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithAIContextProviders_PreservesProviderAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" },
|
|
AIContextProviders = [new TestAIContextProvider()]
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateChatClientAgentOptions preserves ChatHistoryProvider.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithChatHistoryProvider_PreservesProviderAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" },
|
|
ChatHistoryProvider = new TestChatHistoryProvider()
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateChatClientAgentOptions preserves UseProvidedChatClientAsIs.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_PreservesSettingAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" },
|
|
UseProvidedChatClientAsIs = true
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with UseProvidedChatClientAsIs=true skips tool validation
|
|
/// and does not throw even when server-side function tools exist without matching invocable tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_SkipsToolValidationAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" },
|
|
UseProvidedChatClientAsIs = true
|
|
};
|
|
|
|
// Act - should not throw even without tools when UseProvidedChatClientAsIs is true
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync with UseProvidedChatClientAsIs=true still matches provided AIFunction tools
|
|
/// to server-side function definitions, instead of falling back to the ResponseToolAITool wrapper.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_PreservesProvidedToolsAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("my_function", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
var providedTool = AIFunctionFactory.Create(() => "test", "my_function", "A test function");
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
UseProvidedChatClientAsIs = true,
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = [providedTool]
|
|
},
|
|
};
|
|
|
|
// Act - UseProvidedChatClientAsIs is true, but provided AIFunctions should still be matched and preserved
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
|
|
// Verify the provided AIFunction was matched and preserved in ChatOptions.Tools (not replaced by AsAITool wrapper)
|
|
var chatOptions = agent.GetService<ChatOptions>();
|
|
Assert.NotNull(chatOptions);
|
|
Assert.NotNull(chatOptions!.Tools);
|
|
Assert.Contains(chatOptions.Tools, t => t is AIFunction af && af.Name == "my_function");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Empty Version and ID Handling Tests
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync handles an agent with empty version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithEmptyVersion_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
// Verify the agent ID is generated from server-returned name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord handles empty version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordEmptyVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecordWithEmptyVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent record name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion handles empty version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersionEmptyVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithEmptyVersion();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersionWithEmptyVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent version name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that GetAIAgentAsync handles an agent with whitespace-only version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithWhitespaceVersion_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions { Instructions = "Test" }
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
// Verify the agent ID is generated from server-returned name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentRecord handles whitespace-only version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentRecordWhitespaceVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
|
AgentRecord agentRecord = this.CreateTestAgentRecordWithWhitespaceVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentRecord);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent record name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that AsAIAgent with AgentVersion handles whitespace-only version by using "latest" as fallback.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithAgentVersionWhitespaceVersion_CreatesAgentWithGeneratedId()
|
|
{
|
|
// Arrange
|
|
AIProjectClient client = this.CreateTestAgentClientWithWhitespaceVersion();
|
|
AgentVersion agentVersion = this.CreateTestAgentVersionWithWhitespaceVersion();
|
|
|
|
// Act
|
|
var agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
// Verify the agent ID is generated from agent version name ("agent_abc123") and "latest"
|
|
Assert.Equal("agent_abc123:latest", agent.Id);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ApplyToolsToAgentDefinition Tests
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with non-PromptAgentDefinition and tools throws ArgumentException.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithNonPromptAgentDefinitionAndTools_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
|
|
};
|
|
|
|
using HttpHandlerAssert httpHandler = new(_ => new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json")
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using HttpClient httpClient = new(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
AIProjectClient client = new(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
// Create a mock AgentDefinition that is not PromptAgentDefinition
|
|
// Since we can't easily create a non-PromptAgentDefinition in the public API, we test this path via the CreateAIAgentAsync that builds a PromptAgentDefinition
|
|
// The ApplyToolsToAgentDefinition is only called when tools.Count > 0, and we provide tools
|
|
// But PromptAgentDefinition is always created by CreateAIAgentAsync(name, model, instructions, tools)
|
|
// So this path is hard to hit without mocking. Let's test the declarative function rejection instead.
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", JsonDocument.Parse("{}").RootElement);
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.CreateAIAgentAsync(
|
|
name: "test-agent",
|
|
model: "test-model",
|
|
instructions: "Test",
|
|
tools: [declarativeFunction]));
|
|
|
|
Assert.Contains("invokable AIFunctions", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with AIFunctionDeclaration tools throws InvalidOperationException.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithAIFunctionDeclarationTool_ThrowsInvalidOperationExceptionAsync()
|
|
{
|
|
// Arrange
|
|
using var doc = JsonDocument.Parse("{}");
|
|
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
|
|
|
|
using HttpHandlerAssert httpHandler = new(_ => new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json")
|
|
});
|
|
|
|
#pragma warning disable CA5399
|
|
using HttpClient httpClient = new(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
AIProjectClient client = new(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.CreateAIAgentAsync(
|
|
name: "test-agent",
|
|
model: "test-model",
|
|
instructions: "Test",
|
|
tools: [declarativeFunction]));
|
|
|
|
Assert.Contains("invokable AIFunctions", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with ResponseTool converted via AsAITool works.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithResponseToolAsAITool_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
ResponseTool responseTool = ResponseTool.CreateFunctionTool("response_tool", BinaryData.FromString("{}"), strictModeEnabled: false);
|
|
AITool convertedTool = responseTool.AsAITool();
|
|
|
|
// Create a definition with the function tool already in it
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(responseTool);
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
// Matching invokable tool must be provided
|
|
var invokableTool = AIFunctionFactory.Create(() => "test", "response_tool", "Invokable version of the tool");
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = [invokableTool]
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that CreateAIAgentAsync with hosted tool types works correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task CreateAIAgentAsync_WithHostedToolTypes_CreatesAgentSuccessfullyAsync()
|
|
{
|
|
// Arrange
|
|
using var testClient = CreateTestAgentClientWithHandler();
|
|
var webSearchTool = new HostedWebSearchTool();
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = [webSearchTool]
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await testClient.Client.CreateAIAgentAsync("test-model", options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when the server returns tools but matching tools are provided, the agent is created.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithServerDefinedToolsAndMatchingProvidedTools_CreatesAgentAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
// Add multiple function tools
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("tool_one", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("tool_two", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "one", "tool_one", "Tool one"),
|
|
AIFunctionFactory.Create(() => "two", "tool_two", "Tool two")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when the server returns mixed tools (function and hosted), the agent handles them correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithMixedServerTools_MatchesFunctionToolsOnlyAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
// Add a function tool
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("function_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
// Add a hosted tool
|
|
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
var tools = new List<AITool>
|
|
{
|
|
AIFunctionFactory.Create(() => "result", "function_tool", "The function tool")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act
|
|
FoundryAgent agent = await client.GetAIAgentAsync(options);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when partial tools are provided (some missing), InvalidOperationException is thrown listing missing tools.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task GetAIAgentAsync_WithPartialToolsProvided_ThrowsInvalidOperationWithMissingToolNamesAsync()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("provided_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
definition.Tools.Add(ResponseTool.CreateFunctionTool("missing_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
|
|
|
|
var tools = new List<AITool>
|
|
{
|
|
// Only providing one of two required tools
|
|
AIFunctionFactory.Create(() => "result", "provided_tool", "The provided tool")
|
|
};
|
|
|
|
var options = new ChatClientAgentOptions
|
|
{
|
|
Name = "test-agent",
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
Instructions = "Test",
|
|
Tools = tools
|
|
}
|
|
};
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
|
client.GetAIAgentAsync(options));
|
|
|
|
Assert.Contains("missing_tool", exception.Message);
|
|
Assert.DoesNotContain("provided_tool", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when AsAIAgent is called without requireInvocableTools, hosted tools are correctly added.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AsAIAgent_WithServerHostedTools_AddsToolsToAgentOptions()
|
|
{
|
|
// Arrange
|
|
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
|
|
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
|
|
|
|
AIProjectClient client = this.CreateTestAgentClient();
|
|
AgentVersion agentVersion = ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson(agentDefinition: definition)))!;
|
|
|
|
// Act - no tools provided, but requireInvocableTools is false when no tools param is passed
|
|
FoundryAgent agent = client.AsAIAgent(agentVersion);
|
|
|
|
// Assert
|
|
Assert.NotNull(agent);
|
|
Assert.IsType<FoundryAgent>(agent);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
/// <summary>
|
|
/// Creates a test AIProjectClient with fake behavior.
|
|
/// </summary>
|
|
private FakeAgentClient CreateTestAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
|
{
|
|
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AIProjectClient backed by an HTTP handler that returns canned responses.
|
|
/// Used for tests that exercise the protocol-method code path (CreateAgentVersion).
|
|
/// The returned client must be disposed to clean up the underlying HttpClient/handler.
|
|
/// </summary>
|
|
private static DisposableTestClient CreateTestAgentClientWithHandler(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
|
{
|
|
var responseJson = TestDataUtil.GetAgentVersionResponseJson(agentName, agentDefinitionResponse, instructions, description);
|
|
|
|
var httpHandler = new HttpHandlerAssert(_ =>
|
|
new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(responseJson, Encoding.UTF8, "application/json") });
|
|
|
|
#pragma warning disable CA5399
|
|
var httpClient = new HttpClient(httpHandler);
|
|
#pragma warning restore CA5399
|
|
|
|
var client = new AIProjectClient(
|
|
new Uri("https://test.openai.azure.com/"),
|
|
new FakeAuthenticationTokenProvider(),
|
|
new() { Transport = new HttpClientPipelineTransport(httpClient) });
|
|
|
|
return new DisposableTestClient(client, httpClient, httpHandler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wraps an AIProjectClient and its disposable dependencies for deterministic cleanup.
|
|
/// </summary>
|
|
private sealed class DisposableTestClient : IDisposable
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly HttpHandlerAssert _httpHandler;
|
|
|
|
public DisposableTestClient(AIProjectClient client, HttpClient httpClient, HttpHandlerAssert httpHandler)
|
|
{
|
|
this.Client = client;
|
|
this._httpClient = httpClient;
|
|
this._httpHandler = httpHandler;
|
|
}
|
|
|
|
public AIProjectClient Client { get; }
|
|
|
|
public void Dispose()
|
|
{
|
|
this._httpClient.Dispose();
|
|
this._httpHandler.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentRecord for testing.
|
|
/// </summary>
|
|
private AgentRecord CreateTestAgentRecord(AgentDefinition? agentDefinition = null)
|
|
{
|
|
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJson(agentDefinition: agentDefinition)))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AIProjectClient with empty version fields for testing hosted MCP agents.
|
|
/// </summary>
|
|
private FakeAgentClient CreateTestAgentClientWithEmptyVersion(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
|
{
|
|
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse, useEmptyVersion: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentRecord with empty version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentRecord CreateTestAgentRecordWithEmptyVersion(AgentDefinition? agentDefinition = null)
|
|
{
|
|
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithEmptyVersion(agentDefinition: agentDefinition)))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentVersion with empty version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentVersion CreateTestAgentVersionWithEmptyVersion()
|
|
{
|
|
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion()))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AIProjectClient with whitespace-only version fields for testing hosted MCP agents.
|
|
/// </summary>
|
|
private FakeAgentClient CreateTestAgentClientWithWhitespaceVersion(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null)
|
|
{
|
|
return new FakeAgentClient(agentName, instructions, description, agentDefinitionResponse, versionMode: VersionMode.Whitespace);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentRecord with whitespace-only version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentRecord CreateTestAgentRecordWithWhitespaceVersion(AgentDefinition? agentDefinition = null)
|
|
{
|
|
return ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(TestDataUtil.GetAgentResponseJsonWithWhitespaceVersion(agentDefinition: agentDefinition)))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentVersion with whitespace-only version for testing hosted MCP agents.
|
|
/// </summary>
|
|
private AgentVersion CreateTestAgentVersionWithWhitespaceVersion()
|
|
{
|
|
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJsonWithWhitespaceVersion()))!;
|
|
}
|
|
|
|
private const string OpenAPISpec = """
|
|
{
|
|
"openapi": "3.0.3",
|
|
"info": { "title": "Tiny Test API", "version": "1.0.0" },
|
|
"paths": {
|
|
"/ping": {
|
|
"get": {
|
|
"summary": "Health check",
|
|
"operationId": "getPing",
|
|
"responses": {
|
|
"200": {
|
|
"description": "OK",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": { "message": { "type": "string" } },
|
|
"required": ["message"]
|
|
},
|
|
"example": { "message": "pong" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
/// <summary>
|
|
/// Creates a test AgentVersion for testing.
|
|
/// </summary>
|
|
private AgentVersion CreateTestAgentVersion()
|
|
{
|
|
return ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson()))!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the version mode for test data generation.
|
|
/// </summary>
|
|
private enum VersionMode
|
|
{
|
|
Normal,
|
|
Empty,
|
|
Whitespace
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fake AIProjectClient for testing.
|
|
/// </summary>
|
|
private sealed class FakeAgentClient : AIProjectClient
|
|
{
|
|
public FakeAgentClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, bool useEmptyVersion = false, VersionMode versionMode = VersionMode.Normal)
|
|
{
|
|
// Handle backward compatibility with bool parameter
|
|
var effectiveVersionMode = useEmptyVersion ? VersionMode.Empty : versionMode;
|
|
this.Agents = new FakeAgentsClient(agentName, instructions, description, agentDefinitionResponse, effectiveVersionMode);
|
|
}
|
|
|
|
public override ClientConnection GetConnection(string connectionId)
|
|
{
|
|
return new ClientConnection("fake-connection-id", "http://localhost", ClientPipeline.Create(), CredentialKind.None);
|
|
}
|
|
|
|
public override AgentsClient Agents { get; }
|
|
|
|
private sealed class FakeAgentsClient : AgentsClient
|
|
{
|
|
private readonly string? _agentName;
|
|
private readonly string? _instructions;
|
|
private readonly string? _description;
|
|
private readonly AgentDefinition? _agentDefinition;
|
|
private readonly VersionMode _versionMode;
|
|
|
|
public FakeAgentsClient(string? agentName = null, string? instructions = null, string? description = null, AgentDefinition? agentDefinitionResponse = null, VersionMode versionMode = VersionMode.Normal)
|
|
{
|
|
this._agentName = agentName;
|
|
this._instructions = instructions;
|
|
this._description = description;
|
|
this._agentDefinition = agentDefinitionResponse;
|
|
this._versionMode = versionMode;
|
|
}
|
|
|
|
private string GetAgentResponseJson()
|
|
{
|
|
return this._versionMode switch
|
|
{
|
|
VersionMode.Empty => TestDataUtil.GetAgentResponseJsonWithEmptyVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
VersionMode.Whitespace => TestDataUtil.GetAgentResponseJsonWithWhitespaceVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
_ => TestDataUtil.GetAgentResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description)
|
|
};
|
|
}
|
|
|
|
private string GetAgentVersionResponseJson()
|
|
{
|
|
return this._versionMode switch
|
|
{
|
|
VersionMode.Empty => TestDataUtil.GetAgentVersionResponseJsonWithEmptyVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
VersionMode.Whitespace => TestDataUtil.GetAgentVersionResponseJsonWithWhitespaceVersion(this._agentName, this._agentDefinition, this._instructions, this._description),
|
|
_ => TestDataUtil.GetAgentVersionResponseJson(this._agentName, this._agentDefinition, this._instructions, this._description)
|
|
};
|
|
}
|
|
|
|
public override ClientResult GetAgent(string agentName, RequestOptions options)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson)));
|
|
}
|
|
|
|
public override ClientResult<AgentRecord> GetAgent(string agentName, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
|
}
|
|
|
|
public override Task<ClientResult> GetAgentAsync(string agentName, RequestOptions options)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return Task.FromResult<ClientResult>(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200, BinaryData.FromString(responseJson))));
|
|
}
|
|
|
|
public override Task<ClientResult<AgentRecord>> GetAgentAsync(string agentName, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentResponseJson();
|
|
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentRecord>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
|
}
|
|
|
|
public override ClientResult<AgentVersion> CreateAgentVersion(string agentName, AgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentVersionResponseJson();
|
|
return ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200));
|
|
}
|
|
|
|
public override Task<ClientResult<AgentVersion>> CreateAgentVersionAsync(string agentName, AgentVersionCreationOptions? options = null, string? foundryFeatures = null, CancellationToken cancellationToken = default)
|
|
{
|
|
var responseJson = this.GetAgentVersionResponseJson();
|
|
return Task.FromResult(ClientResult.FromValue(ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(responseJson))!, new MockPipelineResponse(200)));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static PromptAgentDefinition GeneratePromptDefinitionResponse(PromptAgentDefinition inputDefinition, List<AITool>? tools)
|
|
{
|
|
var definitionResponse = new PromptAgentDefinition(inputDefinition.Model) { Instructions = inputDefinition.Instructions };
|
|
if (tools is not null)
|
|
{
|
|
foreach (var tool in tools)
|
|
{
|
|
definitionResponse.Tools.Add(tool.GetService<ResponseTool>() ?? tool.AsOpenAIResponseTool());
|
|
}
|
|
}
|
|
|
|
return definitionResponse;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test custom chat client that can be used to verify clientFactory functionality.
|
|
/// </summary>
|
|
private sealed class TestChatClient : DelegatingChatClient
|
|
{
|
|
public TestChatClient(IChatClient innerClient) : base(innerClient)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mock pipeline response for testing ClientResult wrapping.
|
|
/// </summary>
|
|
private sealed class MockPipelineResponse : PipelineResponse
|
|
{
|
|
private readonly MockPipelineResponseHeaders _headers;
|
|
|
|
public MockPipelineResponse(int status, BinaryData? content = null)
|
|
{
|
|
this.Status = status;
|
|
this.Content = content ?? BinaryData.Empty;
|
|
this._headers = new MockPipelineResponseHeaders();
|
|
}
|
|
|
|
public override int Status { get; }
|
|
|
|
public override string ReasonPhrase => "OK";
|
|
|
|
public override Stream? ContentStream
|
|
{
|
|
get => null;
|
|
set { }
|
|
}
|
|
|
|
public override BinaryData Content { get; }
|
|
|
|
protected override PipelineResponseHeaders HeadersCore => this._headers;
|
|
|
|
public override BinaryData BufferContent(CancellationToken cancellationToken = default) =>
|
|
throw new NotSupportedException("Buffering content is not supported for mock responses.");
|
|
|
|
public override ValueTask<BinaryData> BufferContentAsync(CancellationToken cancellationToken = default) =>
|
|
throw new NotSupportedException("Buffering content asynchronously is not supported for mock responses.");
|
|
|
|
public override void Dispose()
|
|
{
|
|
}
|
|
|
|
private sealed class MockPipelineResponseHeaders : PipelineResponseHeaders
|
|
{
|
|
private readonly Dictionary<string, string> _headers = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
{ "Content-Type", "application/json" },
|
|
{ "x-ms-request-id", "test-request-id" }
|
|
};
|
|
|
|
public override bool TryGetValue(string name, out string? value)
|
|
{
|
|
return this._headers.TryGetValue(name, out value);
|
|
}
|
|
|
|
public override bool TryGetValues(string name, out IEnumerable<string>? values)
|
|
{
|
|
if (this._headers.TryGetValue(name, out var value))
|
|
{
|
|
values = [value];
|
|
return true;
|
|
}
|
|
|
|
values = null;
|
|
return false;
|
|
}
|
|
|
|
public override IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
|
{
|
|
return this._headers.GetEnumerator();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Helper method to access internal ChatOptions property via reflection.
|
|
/// </summary>
|
|
private static ChatOptions? GetAgentChatOptions(AIAgent agent)
|
|
{
|
|
ChatClientAgent? chatClientAgent = agent as ChatClientAgent ?? agent.GetService<ChatClientAgent>();
|
|
if (chatClientAgent is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var chatOptionsProperty = typeof(ChatClientAgent).GetProperty(
|
|
"ChatOptions",
|
|
System.Reflection.BindingFlags.Public |
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
System.Reflection.BindingFlags.Instance);
|
|
|
|
return chatOptionsProperty?.GetValue(chatClientAgent) as ChatOptions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test schema for JSON response format tests.
|
|
/// </summary>
|
|
#pragma warning disable CA1812 // Avoid uninstantiated internal classes - used via reflection by AIJsonUtilities
|
|
private sealed class TestSchema
|
|
{
|
|
public string? Name { get; set; }
|
|
public int Value { get; set; }
|
|
}
|
|
#pragma warning restore CA1812
|
|
|
|
/// <summary>
|
|
/// Test AIContextProvider for options preservation tests.
|
|
/// </summary>
|
|
private sealed class TestAIContextProvider : AIContextProvider
|
|
{
|
|
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
return new ValueTask<AIContext>(context.AIContext);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test ChatHistoryProvider for options preservation tests.
|
|
/// </summary>
|
|
private sealed class TestChatHistoryProvider : ChatHistoryProvider
|
|
{
|
|
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
return new ValueTask<IEnumerable<ChatMessage>>(context.RequestMessages);
|
|
}
|
|
|
|
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
#pragma warning restore CS0618
|
|
|
|
/// <summary>
|
|
/// Provides test data for invalid agent name validation tests.
|
|
/// </summary>
|
|
internal static class InvalidAgentNameTestData
|
|
{
|
|
/// <summary>
|
|
/// Gets a collection of invalid agent names for theory-based testing.
|
|
/// </summary>
|
|
/// <returns>Collection of invalid agent name test cases.</returns>
|
|
public static IEnumerable<object[]> GetInvalidAgentNames()
|
|
{
|
|
yield return new object[] { "-agent" };
|
|
yield return new object[] { "agent-" };
|
|
yield return new object[] { "agent_name" };
|
|
yield return new object[] { "agent name" };
|
|
yield return new object[] { "agent@name" };
|
|
yield return new object[] { "agent#name" };
|
|
yield return new object[] { "agent$name" };
|
|
yield return new object[] { "agent%name" };
|
|
yield return new object[] { "agent&name" };
|
|
yield return new object[] { "agent*name" };
|
|
yield return new object[] { "agent.name" };
|
|
yield return new object[] { "agent/name" };
|
|
yield return new object[] { "agent\\name" };
|
|
yield return new object[] { "agent:name" };
|
|
yield return new object[] { "agent;name" };
|
|
yield return new object[] { "agent,name" };
|
|
yield return new object[] { "agent<name" };
|
|
yield return new object[] { "agent>name" };
|
|
yield return new object[] { "agent?name" };
|
|
yield return new object[] { "agent!name" };
|
|
yield return new object[] { "agent~name" };
|
|
yield return new object[] { "agent`name" };
|
|
yield return new object[] { "agent^name" };
|
|
yield return new object[] { "agent|name" };
|
|
yield return new object[] { "agent[name" };
|
|
yield return new object[] { "agent]name" };
|
|
yield return new object[] { "agent{name" };
|
|
yield return new object[] { "agent}name" };
|
|
yield return new object[] { "agent(name" };
|
|
yield return new object[] { "agent)name" };
|
|
yield return new object[] { "agent+name" };
|
|
yield return new object[] { "agent=name" };
|
|
yield return new object[] { "a" + new string('b', 63) };
|
|
}
|
|
}
|