mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
* 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>
936 lines
50 KiB
C#
936 lines
50 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System.ClientModel;
|
|
using System.ClientModel.Primitives;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using System.Text.Json.Serialization;
|
|
using System.Text.RegularExpressions;
|
|
using Azure.AI.Extensions.OpenAI;
|
|
using Azure.AI.Projects.Agents;
|
|
using Microsoft.Agents.AI;
|
|
using Microsoft.Agents.AI.AzureAI;
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Shared.DiagnosticIds;
|
|
using Microsoft.Shared.Diagnostics;
|
|
using OpenAI;
|
|
using OpenAI.Responses;
|
|
|
|
namespace Azure.AI.Projects;
|
|
|
|
/// <summary>
|
|
/// Provides extension methods for <see cref="AIProjectClient"/>.
|
|
/// </summary>
|
|
[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
|
|
public static partial class AzureAIProjectChatClientExtensions
|
|
{
|
|
/// <summary>
|
|
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="AgentReference"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The <see cref="AIProjectClient"/> to create the <see cref="ChatClientAgent"/> with. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="agentReference">The <see cref="AgentReference"/> representing the name and version of the server side agent to create a <see cref="ChatClientAgent"/> for. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentReference"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
|
|
/// <remarks>
|
|
/// When instantiating a <see cref="ChatClientAgent"/> by using an <see cref="AgentReference"/>, minimal information will be available about the agent in the instance level, and any logic that relies
|
|
/// on <see cref="AIAgent.GetService{TService}(object?)"/> to retrieve information about the agent like <see cref="AgentVersion" /> will receive <see langword="null"/> as the result.
|
|
/// </remarks>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
AgentReference agentReference,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentReference);
|
|
ThrowIfInvalidAgentName(agentReference.Name);
|
|
|
|
var innerAgent = AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentReference,
|
|
new ChatClientAgentOptions()
|
|
{
|
|
Id = $"{agentReference.Name}:{agentReference.Version}",
|
|
Name = agentReference.Name,
|
|
ChatOptions = new() { Tools = tools },
|
|
},
|
|
clientFactory,
|
|
services);
|
|
|
|
return new FoundryAgent(aiProjectClient, innerAgent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The <see cref="AIProjectClient"/> to create the <see cref="ChatClientAgent"/> with. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="name">The name of the server side agent to create a <see cref="ChatClientAgent"/> for. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the named Azure AI Agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="name"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is empty or whitespace, or when the agent with the specified name was not found.</exception>
|
|
/// <exception cref="InvalidOperationException">The agent with the specified name was not found.</exception>
|
|
[Obsolete("Use native AIProjectClient agent APIs and AsAIAgent(AgentRecord/AgentVersion) instead.")]
|
|
public static async Task<FoundryAgent> GetAIAgentAsync(
|
|
this AIProjectClient aiProjectClient,
|
|
string name,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
ThrowIfInvalidAgentName(name);
|
|
|
|
AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, name, cancellationToken).ConfigureAwait(false);
|
|
|
|
return AsAIAgent(
|
|
aiProjectClient,
|
|
agentRecord,
|
|
tools,
|
|
clientFactory,
|
|
services);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="AgentRecord"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="agentRecord">The agent record to be converted. The latest version will be used. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="tools">The tools to use when interacting with the agent. This is required when using prompt agent definitions with tools.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the latest version of the Azure AI Agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentRecord"/> is <see langword="null"/>.</exception>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
AgentRecord agentRecord,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentRecord);
|
|
|
|
var allowDeclarativeMode = tools is not { Count: > 0 };
|
|
|
|
var innerAgent = AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentRecord,
|
|
tools,
|
|
clientFactory,
|
|
!allowDeclarativeMode,
|
|
services);
|
|
|
|
return new FoundryAgent(aiProjectClient, innerAgent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/> and <see cref="AgentVersion"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to interact with Azure AI Agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="agentVersion">The agent version to be converted. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="tools">In-process invocable tools to be provided. If no tools are provided manual handling will be necessary to invoke in-process tools.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations based on the provided version of the Azure AI Agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="agentVersion"/> is <see langword="null"/>.</exception>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
AgentVersion agentVersion,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentVersion);
|
|
|
|
var allowDeclarativeMode = tools is not { Count: > 0 };
|
|
|
|
var innerAgent = AsChatClientAgent(
|
|
aiProjectClient,
|
|
agentVersion,
|
|
tools,
|
|
clientFactory,
|
|
!allowDeclarativeMode,
|
|
services);
|
|
|
|
return new FoundryAgent(aiProjectClient, innerAgent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously retrieves an existing server side agent, wrapped as a <see cref="ChatClientAgent"/> using the provided <see cref="AIProjectClient"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
|
|
[Obsolete("Use native AIProjectClient agent APIs and AsAIAgent(AgentRecord/AgentVersion) instead.")]
|
|
public static async Task<FoundryAgent> GetAIAgentAsync(
|
|
this AIProjectClient aiProjectClient,
|
|
ChatClientAgentOptions options,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(options);
|
|
|
|
if (string.IsNullOrWhiteSpace(options.Name))
|
|
{
|
|
throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
|
|
}
|
|
|
|
ThrowIfInvalidAgentName(options.Name);
|
|
|
|
AgentRecord agentRecord = await GetAgentRecordByNameAsync(aiProjectClient, options.Name, cancellationToken).ConfigureAwait(false);
|
|
var agentVersion = agentRecord.GetLatestVersion();
|
|
|
|
var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: !options.UseProvidedChatClientAsIs);
|
|
|
|
return new FoundryAgent(
|
|
aiProjectClient,
|
|
AsChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a <see cref="ChatClientAgent"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="name">The name for the agent.</param>
|
|
/// <param name="model">The name of the model to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="instructions">The instructions that guide the agent's behavior. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="description">The description for the agent.</param>
|
|
/// <param name="tools">The tools to use when interacting with the agent, this is required when using prompt agent definitions with tools.</param>
|
|
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/>, <paramref name="model"/>, or <paramref name="instructions"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> or <paramref name="instructions"/> is empty or whitespace.</exception>
|
|
/// <remarks>When using prompt agent definitions with tools the parameter <paramref name="tools"/> needs to be provided.</remarks>
|
|
[Obsolete("Use native AIProjectClient.Agents APIs instead.")]
|
|
public static Task<FoundryAgent> CreateAIAgentAsync(
|
|
this AIProjectClient aiProjectClient,
|
|
string name,
|
|
string model,
|
|
string instructions,
|
|
string? description = null,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
ThrowIfInvalidAgentName(name);
|
|
Throw.IfNullOrWhitespace(model);
|
|
Throw.IfNullOrWhitespace(instructions);
|
|
|
|
return CreateAIAgentAsync(
|
|
aiProjectClient,
|
|
name,
|
|
tools,
|
|
new AgentVersionCreationOptions(new PromptAgentDefinition(model) { Instructions = instructions }) { Description = description },
|
|
clientFactory,
|
|
services,
|
|
cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a <see cref="ChatClientAgent"/>.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="model">The name of the model to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="options">The options for creating the agent. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the operation if needed.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> is empty or whitespace, or when the agent name is not provided in the options.</exception>
|
|
[Obsolete("Use native AIProjectClient.Agents APIs instead.")]
|
|
public static async Task<FoundryAgent> CreateAIAgentAsync(
|
|
this AIProjectClient aiProjectClient,
|
|
string model,
|
|
ChatClientAgentOptions options,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
IServiceProvider? services = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(options);
|
|
Throw.IfNullOrWhitespace(model);
|
|
|
|
if (string.IsNullOrWhiteSpace(options.Name))
|
|
{
|
|
throw new ArgumentException("Agent name must be provided in the options.Name property", nameof(options));
|
|
}
|
|
|
|
ThrowIfInvalidAgentName(options.Name);
|
|
|
|
AgentVersion agentVersion = await CreateAgentVersionFromOptionsAsync(aiProjectClient, model, options, cancellationToken).ConfigureAwait(false);
|
|
|
|
var agentOptions = CreateChatClientAgentOptions(agentVersion, options, requireInvocableTools: true);
|
|
|
|
return new FoundryAgent(
|
|
aiProjectClient,
|
|
AsChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new Prompt AI agent in the Foundry service using the specified configuration parameters, and exposes it as a <see cref="ChatClientAgent"/>.
|
|
/// parameters.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The client used to manage and interact with AI agents. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="name">The name for the agent.</param>
|
|
/// <param name="creationOptions">Settings that control the creation of the agent.</param>
|
|
/// <param name="clientFactory">A factory function to customize the creation of the chat client used by the agent.</param>
|
|
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> instance that can be used to perform operations on the newly created agent.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="creationOptions"/> is <see langword="null"/>.</exception>
|
|
/// <remarks>
|
|
/// When using this extension method with a <see cref="PromptAgentDefinition"/> the tools are only declarative and not invocable.
|
|
/// Invocation of any in-process tools will need to be handled manually.
|
|
/// </remarks>
|
|
[Obsolete("Use native AIProjectClient.Agents APIs instead.")]
|
|
public static Task<FoundryAgent> CreateAIAgentAsync(
|
|
this AIProjectClient aiProjectClient,
|
|
string name,
|
|
AgentVersionCreationOptions creationOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
ThrowIfInvalidAgentName(name);
|
|
Throw.IfNull(creationOptions);
|
|
|
|
return CreateAIAgentAsync(
|
|
aiProjectClient,
|
|
name,
|
|
tools: null,
|
|
creationOptions,
|
|
clientFactory,
|
|
services: null,
|
|
cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a non-versioned <see cref="ChatClientAgent"/> backed by the project's Responses API using the specified model and instructions.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The <see cref="AIProjectClient"/> to use for Responses API calls. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="model">The model deployment name to use for the agent. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="instructions">The instructions that guide the agent's behavior. Cannot be <see langword="null"/> or whitespace.</param>
|
|
/// <param name="name">Optional name for the agent.</param>
|
|
/// <param name="description">Optional human-readable description for the agent.</param>
|
|
/// <param name="tools">Optional collection of tools that the agent can invoke during conversations.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="loggerFactory">Optional logger factory for creating loggers used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> backed by the project's Responses API.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="ArgumentException">Thrown when <paramref name="model"/> or <paramref name="instructions"/> is empty or whitespace.</exception>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
string model,
|
|
string instructions,
|
|
string? name = null,
|
|
string? description = null,
|
|
IList<AITool>? tools = null,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
ILoggerFactory? loggerFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNullOrWhitespace(model);
|
|
Throw.IfNullOrWhitespace(instructions);
|
|
|
|
ChatClientAgentOptions options = new()
|
|
{
|
|
Name = name,
|
|
Description = description,
|
|
ChatOptions = new ChatOptions
|
|
{
|
|
ModelId = model,
|
|
Instructions = instructions,
|
|
Tools = tools,
|
|
},
|
|
};
|
|
|
|
return new FoundryAgent(aiProjectClient, CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a non-versioned <see cref="ChatClientAgent"/> backed by the project's Responses API using the specified options.
|
|
/// </summary>
|
|
/// <param name="aiProjectClient">The <see cref="AIProjectClient"/> to use for Responses API calls. Cannot be <see langword="null"/>.</param>
|
|
/// <param name="options">Configuration options that control the agent's behavior. <see cref="ChatOptions.ModelId"/> is required.</param>
|
|
/// <param name="clientFactory">Provides a way to customize the creation of the underlying <see cref="IChatClient"/> used by the agent.</param>
|
|
/// <param name="loggerFactory">Optional logger factory for creating loggers used by the agent.</param>
|
|
/// <param name="services">An optional <see cref="IServiceProvider"/> to use for resolving services required by the <see cref="AIFunction"/> instances being invoked.</param>
|
|
/// <returns>A <see cref="ChatClientAgent"/> backed by the project's Responses API.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when <paramref name="aiProjectClient"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
|
|
/// <exception cref="ArgumentException">Thrown when <paramref name="options"/> does not specify <see cref="ChatOptions.ModelId"/>.</exception>
|
|
public static FoundryAgent AsAIAgent(
|
|
this AIProjectClient aiProjectClient,
|
|
ChatClientAgentOptions options,
|
|
Func<IChatClient, IChatClient>? clientFactory = null,
|
|
ILoggerFactory? loggerFactory = null,
|
|
IServiceProvider? services = null)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(options);
|
|
|
|
return new FoundryAgent(aiProjectClient, CreateResponsesChatClientAgent(aiProjectClient, options, clientFactory, loggerFactory, services));
|
|
}
|
|
|
|
#region Private
|
|
|
|
private static readonly ModelReaderWriterOptions s_modelWriterOptionsWire = new("W");
|
|
|
|
/// <summary>
|
|
/// Asynchronously retrieves an agent record by name using the protocol method to inject user-agent headers.
|
|
/// </summary>
|
|
internal static async Task<AgentRecord> GetAgentRecordByNameAsync(AIProjectClient aiProjectClient, string agentName, CancellationToken cancellationToken)
|
|
{
|
|
ClientResult protocolResponse = await aiProjectClient.Agents.GetAgentAsync(agentName, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
|
|
var rawResponse = protocolResponse.GetRawResponse();
|
|
AgentRecord? result = ModelReaderWriter.Read<AgentRecord>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
|
|
return result ?? throw new InvalidOperationException($"Agent with name '{agentName}' not found.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously creates an agent version using the protocol method to inject user-agent headers.
|
|
/// </summary>
|
|
internal static async Task<AgentVersion> CreateAgentVersionWithProtocolAsync(AIProjectClient aiProjectClient, string agentName, AgentVersionCreationOptions creationOptions, CancellationToken cancellationToken)
|
|
{
|
|
BinaryData serializedOptions = ModelReaderWriter.Write(creationOptions, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
|
|
BinaryContent content = BinaryContent.Create(serializedOptions);
|
|
ClientResult protocolResponse = await aiProjectClient.Agents.CreateAgentVersionAsync(agentName, content, foundryFeatures: null, cancellationToken.ToRequestOptions(false)).ConfigureAwait(false);
|
|
var rawResponse = protocolResponse.GetRawResponse();
|
|
AgentVersion? result = ModelReaderWriter.Read<AgentVersion>(rawResponse.Content, s_modelWriterOptionsWire, AzureAIProjectsAgentsContext.Default);
|
|
return result ?? throw new InvalidOperationException($"Failed to create agent version for agent '{agentName}'.");
|
|
}
|
|
|
|
private static async Task<FoundryAgent> CreateAIAgentAsync(
|
|
this AIProjectClient aiProjectClient,
|
|
string name,
|
|
IList<AITool>? tools,
|
|
AgentVersionCreationOptions creationOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var allowDeclarativeMode = tools is not { Count: > 0 };
|
|
|
|
if (!allowDeclarativeMode)
|
|
{
|
|
ApplyToolsToAgentDefinition(creationOptions.Definition, tools);
|
|
}
|
|
|
|
AgentVersion agentVersion = await CreateAgentVersionWithProtocolAsync(aiProjectClient, name, creationOptions, cancellationToken).ConfigureAwait(false);
|
|
|
|
return new FoundryAgent(aiProjectClient, AsChatClientAgent(aiProjectClient, agentVersion, tools, clientFactory, !allowDeclarativeMode, services));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an agent version with optional tool application, using the protocol method to inject user-agent headers.
|
|
/// </summary>
|
|
internal static async Task<AgentVersion> CreateAgentVersionWithProtocolAsync(AIProjectClient aiProjectClient, string agentName, AgentVersionCreationOptions creationOptions, IList<AITool>? tools, CancellationToken cancellationToken)
|
|
{
|
|
if (tools is { Count: > 0 })
|
|
{
|
|
ApplyToolsToAgentDefinition(creationOptions.Definition, tools);
|
|
}
|
|
|
|
return await CreateAgentVersionWithProtocolAsync(aiProjectClient, agentName, creationOptions, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an agent version from <see cref="ChatClientAgentOptions"/>, mapping options to a <see cref="PromptAgentDefinition"/>.
|
|
/// </summary>
|
|
internal static async Task<AgentVersion> CreateAgentVersionFromOptionsAsync(
|
|
AIProjectClient aiProjectClient,
|
|
string model,
|
|
ChatClientAgentOptions options,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
PromptAgentDefinition agentDefinition = new(model)
|
|
{
|
|
Instructions = options.ChatOptions?.Instructions,
|
|
Temperature = options.ChatOptions?.Temperature,
|
|
TopP = options.ChatOptions?.TopP,
|
|
TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) }
|
|
};
|
|
|
|
if (options.ChatOptions?.Reasoning is { } reasoning)
|
|
{
|
|
agentDefinition.ReasoningOptions = ToResponseReasoningOptions(reasoning);
|
|
}
|
|
else if (options.ChatOptions?.RawRepresentationFactory?.Invoke(new NoOpChatClient()) is CreateResponseOptions respCreationOptions)
|
|
{
|
|
agentDefinition.ReasoningOptions = respCreationOptions.ReasoningOptions;
|
|
}
|
|
|
|
ApplyToolsToAgentDefinition(agentDefinition, options.ChatOptions?.Tools);
|
|
|
|
AgentVersionCreationOptions creationOptions = new(agentDefinition);
|
|
if (!string.IsNullOrWhiteSpace(options.Description))
|
|
{
|
|
creationOptions.Description = options.Description;
|
|
}
|
|
|
|
return await CreateAgentVersionWithProtocolAsync(aiProjectClient, options.Name!, creationOptions, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>Creates a <see cref="ChatClientAgent"/> with the specified options.</summary>
|
|
internal static ChatClientAgent CreateChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
AgentVersion agentVersion,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentVersion, agentOptions.ChatOptions);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
|
}
|
|
|
|
internal static ChatClientAgent CreateResponsesChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
ILoggerFactory? loggerFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
Throw.IfNull(aiProjectClient);
|
|
Throw.IfNull(agentOptions);
|
|
Throw.IfNull(agentOptions.ChatOptions);
|
|
Throw.IfNullOrWhitespace(agentOptions.ChatOptions.ModelId);
|
|
|
|
IChatClient chatClient = new AzureAIProjectResponsesChatClient(aiProjectClient, agentOptions.ChatOptions.ModelId);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
|
|
}
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
AgentVersion agentVersion,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
=> CreateChatClientAgent(aiProjectClient, agentVersion, agentOptions, clientFactory, services);
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
AgentRecord agentRecord,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentRecord, agentOptions.ChatOptions);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
|
}
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with the specified ChatClientAgentOptions.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient aiProjectClient,
|
|
AgentReference agentReference,
|
|
ChatClientAgentOptions agentOptions,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
IServiceProvider? services)
|
|
{
|
|
IChatClient chatClient = new AzureAIProjectChatClient(aiProjectClient, agentReference, defaultModelId: null, agentOptions.ChatOptions);
|
|
|
|
if (clientFactory is not null)
|
|
{
|
|
chatClient = clientFactory(chatClient);
|
|
}
|
|
|
|
return new ChatClientAgent(chatClient, agentOptions, services: services);
|
|
}
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient AIProjectClient,
|
|
AgentVersion agentVersion,
|
|
IList<AITool>? tools,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
bool requireInvocableTools,
|
|
IServiceProvider? services)
|
|
=> AsChatClientAgent(
|
|
AIProjectClient,
|
|
agentVersion,
|
|
CreateChatClientAgentOptions(agentVersion, new ChatOptions() { Tools = tools }, requireInvocableTools),
|
|
clientFactory,
|
|
services);
|
|
|
|
/// <summary>This method creates an <see cref="ChatClientAgent"/> with a auto-generated ChatClientAgentOptions from the specified configuration parameters.</summary>
|
|
private static ChatClientAgent AsChatClientAgent(
|
|
AIProjectClient AIProjectClient,
|
|
AgentRecord agentRecord,
|
|
IList<AITool>? tools,
|
|
Func<IChatClient, IChatClient>? clientFactory,
|
|
bool requireInvocableTools,
|
|
IServiceProvider? services)
|
|
=> AsChatClientAgent(
|
|
AIProjectClient,
|
|
agentRecord,
|
|
CreateChatClientAgentOptions(agentRecord.GetLatestVersion(), new ChatOptions() { Tools = tools }, requireInvocableTools),
|
|
clientFactory,
|
|
services);
|
|
|
|
/// <summary>
|
|
/// This method creates <see cref="ChatClientAgentOptions"/> for the specified <see cref="AgentVersion"/> and the provided tools.
|
|
/// </summary>
|
|
/// <param name="agentVersion">The agent version.</param>
|
|
/// <param name="chatOptions">The <see cref="ChatOptions"/> to use when interacting with the agent.</param>
|
|
/// <param name="requireInvocableTools">Indicates whether to enforce the presence of invocable tools when the AIAgent is created with an agent definition that uses them.</param>
|
|
/// <returns>The created <see cref="ChatClientAgentOptions"/>.</returns>
|
|
/// <exception cref="InvalidOperationException">Thrown when the agent definition requires in-process tools but none were provided.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown when the agent definition required tools were not provided.</exception>
|
|
/// <remarks>
|
|
/// This method rebuilds the agent options from the agent definition returned by the version and combine with the in-proc tools when provided
|
|
/// this ensures that all required tools are provided and the definition of the agent options are consistent with the agent definition coming from the server.
|
|
/// </remarks>
|
|
internal static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatOptions? chatOptions, bool requireInvocableTools)
|
|
{
|
|
var agentDefinition = agentVersion.Definition;
|
|
|
|
List<AITool>? agentTools = null;
|
|
if (agentDefinition is PromptAgentDefinition { Tools: { Count: > 0 } definitionTools })
|
|
{
|
|
// Check if no tools were provided while the agent definition requires in-proc tools.
|
|
if (requireInvocableTools && chatOptions?.Tools is not { Count: > 0 } && definitionTools.Any(t => t is FunctionTool))
|
|
{
|
|
throw new ArgumentException("The agent definition in-process tools must be provided in the extension method tools parameter.");
|
|
}
|
|
|
|
// Agregate all missing tools for a single error message.
|
|
List<string>? missingTools = null;
|
|
|
|
// Check function tools
|
|
foreach (ResponseTool responseTool in definitionTools)
|
|
{
|
|
if (responseTool is FunctionTool functionTool)
|
|
{
|
|
// Check if a tool with the same type and name exists in the provided tools.
|
|
// Always prefer matching AIFunction when available, regardless of requireInvocableTools.
|
|
var matchingTool = chatOptions?.Tools?.FirstOrDefault(t => t is AIFunction tf && functionTool.FunctionName == tf.Name);
|
|
|
|
if (matchingTool is not null)
|
|
{
|
|
(agentTools ??= []).Add(matchingTool!);
|
|
continue;
|
|
}
|
|
|
|
if (requireInvocableTools)
|
|
{
|
|
(missingTools ??= []).Add($"Function tool: {functionTool.FunctionName}");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
(agentTools ??= []).Add(responseTool.AsAITool());
|
|
}
|
|
|
|
if (requireInvocableTools && missingTools is { Count: > 0 })
|
|
{
|
|
throw new InvalidOperationException($"The following prompt agent definition required tools were not provided: {string.Join(", ", missingTools)}");
|
|
}
|
|
}
|
|
|
|
// Use the agent version's ID if available, otherwise generate one from name and version.
|
|
// This handles cases where hosted agents (like MCP agents) may not have an ID assigned.
|
|
var version = string.IsNullOrWhiteSpace(agentVersion.Version) ? "latest" : agentVersion.Version;
|
|
var agentId = string.IsNullOrWhiteSpace(agentVersion.Id)
|
|
? $"{agentVersion.Name}:{version}"
|
|
: agentVersion.Id;
|
|
|
|
var agentOptions = new ChatClientAgentOptions()
|
|
{
|
|
Id = agentId,
|
|
Name = agentVersion.Name,
|
|
Description = agentVersion.Description,
|
|
};
|
|
|
|
if (agentDefinition is PromptAgentDefinition promptAgentDefinition)
|
|
{
|
|
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
|
|
agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
|
|
agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature;
|
|
agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP;
|
|
}
|
|
|
|
if (agentTools is { Count: > 0 })
|
|
{
|
|
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
|
|
agentOptions.ChatOptions.Tools = agentTools;
|
|
}
|
|
|
|
return agentOptions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of <see cref="ChatClientAgentOptions"/> configured for the specified agent version and
|
|
/// optional base options.
|
|
/// </summary>
|
|
/// <param name="agentVersion">The agent version to use when configuring the chat client agent options.</param>
|
|
/// <param name="options">An optional <see cref="ChatClientAgentOptions"/> instance whose relevant properties will be copied to the
|
|
/// returned options. If <see langword="null"/>, only default values are used.</param>
|
|
/// <param name="requireInvocableTools">Specifies whether the returned options must include invocable tools. Set to <see langword="true"/> to require
|
|
/// invocable tools; otherwise, <see langword="false"/>.</param>
|
|
/// <returns>A <see cref="ChatClientAgentOptions"/> instance configured according to the specified parameters.</returns>
|
|
internal static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion agentVersion, ChatClientAgentOptions? options, bool requireInvocableTools)
|
|
{
|
|
var agentOptions = CreateChatClientAgentOptions(agentVersion, options?.ChatOptions, requireInvocableTools);
|
|
if (options is not null)
|
|
{
|
|
agentOptions.AIContextProviders = options.AIContextProviders;
|
|
agentOptions.ChatHistoryProvider = options.ChatHistoryProvider;
|
|
agentOptions.UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs;
|
|
}
|
|
|
|
return agentOptions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the specified AI tools to a prompt agent definition, while also ensuring that all invocable tools are provided.
|
|
/// </summary>
|
|
/// <param name="agentDefinition">The agent definition to which the tools will be applied. Must be a PromptAgentDefinition to support tools.</param>
|
|
/// <param name="tools">A list of AI tools to add to the agent definition. If null or empty, no tools are added.</param>
|
|
/// <exception cref="ArgumentException">Thrown if tools were provided but <paramref name="agentDefinition"/> is not a <see cref="PromptAgentDefinition"/>.</exception>
|
|
/// <exception cref="InvalidOperationException">When providing functions, they need to be invokable AIFunctions.</exception>
|
|
private static void ApplyToolsToAgentDefinition(AgentDefinition agentDefinition, IList<AITool>? tools)
|
|
{
|
|
if (tools is { Count: > 0 })
|
|
{
|
|
if (agentDefinition is not PromptAgentDefinition promptAgentDefinition)
|
|
{
|
|
throw new ArgumentException("Only prompt agent definitions support tools.", nameof(agentDefinition));
|
|
}
|
|
|
|
// When tools are provided, those should represent the complete set of tools for the agent definition.
|
|
// This is particularly important for existing agents so no duplication happens for what was already defined.
|
|
promptAgentDefinition.Tools.Clear();
|
|
|
|
foreach (var tool in tools)
|
|
{
|
|
// Ensure that any AIFunctions provided are In-Proc, not just the declarations.
|
|
if (tool is not AIFunction && (
|
|
tool.GetService<FunctionTool>() is not null // Declarative FunctionTool converted as AsAITool()
|
|
|| tool is AIFunctionDeclaration)) // AIFunctionDeclaration type
|
|
{
|
|
throw new InvalidOperationException("When providing functions, they need to be invokable AIFunctions. AIFunctions can be created correctly using AIFunctionFactory.Create");
|
|
}
|
|
|
|
promptAgentDefinition.Tools.Add(
|
|
// If this is a converted ResponseTool as AITool, we can directly retrieve the ResponseTool instance from GetService.
|
|
tool.GetService<ResponseTool>()
|
|
// Otherwise we should be able to convert existing MEAI Tool abstractions into OpenAI ResponseTools
|
|
?? tool.AsOpenAIResponseTool()
|
|
?? throw new InvalidOperationException("The provided AITool could not be converted to a ResponseTool, ensure that the AITool was created using responseTool.AsAITool() extension."));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static ResponseTextFormat? ToOpenAIResponseTextFormat(ChatResponseFormat? format, ChatOptions? options = null) =>
|
|
format switch
|
|
{
|
|
ChatResponseFormatText => ResponseTextFormat.CreateTextFormat(),
|
|
|
|
ChatResponseFormatJson jsonFormat when StrictSchemaTransformCache.GetOrCreateTransformedSchema(jsonFormat) is { } jsonSchema =>
|
|
ResponseTextFormat.CreateJsonSchemaFormat(
|
|
jsonFormat.SchemaName ?? "json_schema",
|
|
BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(jsonSchema, AgentClientJsonContext.Default.JsonElement)),
|
|
jsonFormat.SchemaDescription,
|
|
HasStrict(options?.AdditionalProperties)),
|
|
|
|
ChatResponseFormatJson => ResponseTextFormat.CreateJsonObjectFormat(),
|
|
|
|
_ => null,
|
|
};
|
|
|
|
/// <summary>Key into AdditionalProperties used to store a strict option.</summary>
|
|
private const string StrictKey = "strictJsonSchema";
|
|
|
|
/// <summary>Gets whether the properties specify that strict schema handling is desired.</summary>
|
|
private static bool? HasStrict(IReadOnlyDictionary<string, object?>? additionalProperties) =>
|
|
additionalProperties?.TryGetValue(StrictKey, out object? strictObj) is true &&
|
|
strictObj is bool strictValue ?
|
|
strictValue : null;
|
|
|
|
/// <summary>
|
|
/// Gets the JSON schema transformer cache conforming to OpenAI <b>strict</b> / structured output restrictions per
|
|
/// https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#supported-schemas.
|
|
/// </summary>
|
|
private static AIJsonSchemaTransformCache StrictSchemaTransformCache { get; } = new(new()
|
|
{
|
|
DisallowAdditionalProperties = true,
|
|
ConvertBooleanSchemas = true,
|
|
MoveDefaultKeywordToDescription = true,
|
|
RequireAllProperties = true,
|
|
TransformSchemaNode = (ctx, node) =>
|
|
{
|
|
// Move content from common but unsupported properties to description. In particular, we focus on properties that
|
|
// the AIJsonUtilities schema generator might produce and/or that are explicitly mentioned in the OpenAI documentation.
|
|
|
|
if (node is JsonObject schemaObj)
|
|
{
|
|
StringBuilder? additionalDescription = null;
|
|
|
|
ReadOnlySpan<string> unsupportedProperties =
|
|
[
|
|
// Produced by AIJsonUtilities but not in allow list at https://platform.openai.com/docs/guides/structured-outputs#supported-properties:
|
|
"contentEncoding", "contentMediaType", "not",
|
|
|
|
// Explicitly mentioned at https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#key-ordering as being unsupported with some models:
|
|
"minLength", "maxLength", "pattern", "format",
|
|
"minimum", "maximum", "multipleOf",
|
|
"patternProperties",
|
|
"minItems", "maxItems",
|
|
|
|
// Explicitly mentioned at https://learn.microsoft.com/azure/ai-services/openai/how-to/structured-outputs?pivots=programming-language-csharp&tabs=python-secure%2Cdotnet-entra-id#unsupported-type-specific-keywords
|
|
// as being unsupported with Azure OpenAI:
|
|
"unevaluatedProperties", "propertyNames", "minProperties", "maxProperties",
|
|
"unevaluatedItems", "contains", "minContains", "maxContains", "uniqueItems",
|
|
];
|
|
|
|
foreach (string propName in unsupportedProperties)
|
|
{
|
|
if (schemaObj[propName] is { } propNode)
|
|
{
|
|
_ = schemaObj.Remove(propName);
|
|
AppendLine(ref additionalDescription, propName, propNode);
|
|
}
|
|
}
|
|
|
|
if (additionalDescription is not null)
|
|
{
|
|
schemaObj["description"] = schemaObj["description"] is { } descriptionNode && descriptionNode.GetValueKind() == JsonValueKind.String ?
|
|
$"{descriptionNode.GetValue<string>()}{Environment.NewLine}{additionalDescription}" :
|
|
additionalDescription.ToString();
|
|
}
|
|
|
|
return node;
|
|
|
|
static void AppendLine(ref StringBuilder? sb, string propName, JsonNode propNode)
|
|
{
|
|
sb ??= new();
|
|
|
|
if (sb.Length > 0)
|
|
{
|
|
_ = sb.AppendLine();
|
|
}
|
|
|
|
_ = sb.Append(propName).Append(": ").Append(propNode);
|
|
}
|
|
}
|
|
|
|
return node;
|
|
},
|
|
});
|
|
|
|
/// <summary>
|
|
/// This class is a no-op implementation of <see cref="IChatClient"/> to be used to honor the argument passed
|
|
/// while triggering <see cref="ChatOptions.RawRepresentationFactory"/> avoiding any unexpected exception on the caller implementation.
|
|
/// </summary>
|
|
private sealed class NoOpChatClient : IChatClient
|
|
{
|
|
public void Dispose() { }
|
|
|
|
public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new ChatResponse());
|
|
|
|
public object? GetService(Type serviceType, object? serviceKey = null) => null;
|
|
|
|
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
|
{
|
|
yield return new ChatResponseUpdate();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#if NET
|
|
[GeneratedRegex("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$")]
|
|
private static partial Regex AgentNameValidationRegex();
|
|
#else
|
|
private static Regex AgentNameValidationRegex() => new("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$");
|
|
#endif
|
|
|
|
internal static string ThrowIfInvalidAgentName(string? name)
|
|
{
|
|
Throw.IfNullOrWhitespace(name);
|
|
if (!AgentNameValidationRegex().IsMatch(name))
|
|
{
|
|
throw new ArgumentException("Agent name must be 1-63 characters long, start and end with an alphanumeric character, and can only contain alphanumeric characters or hyphens.", nameof(name));
|
|
}
|
|
return name;
|
|
}
|
|
|
|
private static ResponseReasoningOptions? ToResponseReasoningOptions(ReasoningOptions reasoning)
|
|
{
|
|
ResponseReasoningEffortLevel? effortLevel = reasoning.Effort switch
|
|
{
|
|
ReasoningEffort.Low => ResponseReasoningEffortLevel.Low,
|
|
ReasoningEffort.Medium => ResponseReasoningEffortLevel.Medium,
|
|
ReasoningEffort.High => ResponseReasoningEffortLevel.High,
|
|
ReasoningEffort.ExtraHigh => ResponseReasoningEffortLevel.High,
|
|
_ => null,
|
|
};
|
|
|
|
ResponseReasoningSummaryVerbosity? summary = reasoning.Output switch
|
|
{
|
|
ReasoningOutput.Summary => ResponseReasoningSummaryVerbosity.Concise,
|
|
ReasoningOutput.Full => ResponseReasoningSummaryVerbosity.Detailed,
|
|
_ => null,
|
|
};
|
|
|
|
if (effortLevel is null && summary is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new ResponseReasoningOptions
|
|
{
|
|
ReasoningEffortLevel = effortLevel,
|
|
ReasoningSummaryVerbosity = summary,
|
|
};
|
|
}
|
|
}
|
|
|
|
[JsonSerializable(typeof(JsonElement))]
|
|
internal sealed partial class AgentClientJsonContext : JsonSerializerContext;
|