diff --git a/.github/upgrades/prompts/SemanticKernelToAgentFramework.md b/.github/upgrades/prompts/SemanticKernelToAgentFramework.md index 2f2a6886c7..99308e84b4 100644 --- a/.github/upgrades/prompts/SemanticKernelToAgentFramework.md +++ b/.github/upgrades/prompts/SemanticKernelToAgentFramework.md @@ -1,1639 +1,5 @@ -# Instructions for migrating from Semantic Kernel Agents to Agent Framework in .NET projects. +# [Instructions for migrating from Semantic Kernel Agents to Agent Framework in .NET projects.](https://github.com/microsoft/semantic-kernel/blob/main/.github/upgrades/prompts/SemanticKernelToAgentFramework.md) -## Scope +See the Semantic Kernel repository for instructions: -When you are asked to migrate a project from `Microsoft.SemanticKernel.Agents` to `Microsoft.Agents.AI` you need to determine for which projects you need to do it. -If a single project is specified - do it for that project only. If you are asked to do it for a solution, migrate all projects in the solution -that reference `Microsoft.SemanticKernel.Agents` or related Semantic Kernel agent packages. If you don't know which projects to migrate, ask the user. - -## Things to consider while doing migration - -- NuGet package names, assembly names, projects names or other dependencies names are case insensitive(!). You ***must take it into account*** when doing something - with project dependencies, like searching for dependencies or when removing them from projects etc. -- Agent Framework uses different namespace patterns and API structures compared to Semantic Kernel Agents -- Text-based heuristics should be avoided in favor of proper content type inspection when available. - -## Planning - -For each project that needs to be migrated, you need to do the following: - - -- Find projects depending on `Microsoft.SemanticKernel.Agents` or related Semantic Kernel agent packages (when searching for projects, if some projects are not part of the - solution or you could not find the project, notify user and continue with other projects). -- Identify the specific Semantic Kernel agent types being used: - - `ChatCompletionAgent` → `ChatClientAgent` - - `OpenAIAssistantAgent` → `assistantsClient.CreateAIAgent()` (via OpenAI Assistants client extension) - - `AzureAIAgent` → `persistentAgentsClient.CreateAIAgent()` (via Azure AI Foundry client extension) - - `OpenAIResponseAgent` → `responsesClient.CreateAIAgent()` (via OpenAI Responses client extension) - - `A2AAgent` → `AIAgent` (via A2A card resolver) - - `BedrockAgent` → Custom implementation required (not supported) -- Determine if agents are being created new or retrieved from hosted services: - - **New agents**: Use `CreateAIAgent()` methods - - **Existing hosted agents**: Use `GetAIAgent(agentId)` methods for OpenAI Assistants and Azure AI Foundry - - -- Determine the AI provider being used (OpenAI, Azure OpenAI, Azure AI Foundry, etc.) -- Analyze tool/function registration patterns -- Review thread management and invocation patterns - -## Execution - -***Important***: when running steps in this section you must not pause, you must continue until you are done with all steps or you are truly unable to -continue and need user's interaction (you will be penalized if you stop unnecessarily). - -Keep in mind information in the next section about differences and follow these steps in the order they are specified (you will be penalized if you do steps -below in wrong order or skip any of them): - -1. For each project that has an explicit package dependency to Semantic Kernel agent packages in the project file or some imported MSBuild targets (some - project could receive package dependencies transitively, so avoid adding new package dependencies for such projects), do the following: - -- Remove the Semantic Kernel agent package references from the project file: - - `Microsoft.SemanticKernel.Agents.Core` - - `Microsoft.SemanticKernel.Agents.OpenAI` - - `Microsoft.SemanticKernel.Agents.AzureAI` - - `Microsoft.SemanticKernel` (if only used for agents) -- Add the appropriate Agent Framework package references based on the provider being used: - - `Microsoft.Agents.AI.Abstractions` (always required) - - `Microsoft.Agents.AI.OpenAI` (for OpenAI and Azure OpenAI providers) - - For unsupported providers (Bedrock, CopilotStudio), note in the report that custom implementation is required -- If projects use Central Package Management, update the `Directory.Packages.props` file to remove the Semantic Kernel agent package versions in addition to - removing package reference from projects. - When adding the Agent Framework PackageReferences, add them to affected project files without a version and add PackageVersion elements to the - Directory.Packages.props file with the version that supports the project's target framework. - -2. Update code files using Semantic Kernel Agents in the selected projects (and in projects that depend on them since they could receive Semantic Kernel transitively): - -- Find ***all*** code files in the selected projects (and in projects that depend on them since they could receive Semantic Kernel transitively). - When doing search of code files that need changes, prefer calling search tools with `upgrade_` prefix if available. Also do pass project's root folder for all - selected projects or projects that depend on them. -- Update the code files that use Semantic Kernel Agents to use Agent Framework instead. You never should add placeholders when updating code, or remove any comments in the code files, - you must keep the business logic as close as possible to the original code but use new API. When checking if code file needs to be updated, you should check for - using statements, types and API from `Microsoft.SemanticKernel.Agents` namespace (skip comments and string literal constants). -- Ensure that you replace all Semantic Kernel agent using statements with Agent Framework using statements (always check if there are any other Semantic Kernel agent - API used in the file having any of the Semantic Kernel agent using statements; if no other API detected, Semantic Kernel agent using statements should be just removed - instead of replaced). If there were no Semantic Kernel agent using statements in the file, do not add Agent Framework using statements. -- When replacing types you must ensure that you add using statements for them, since some types that lived in main `Microsoft.SemanticKernel.Agents` namespace live in other namespaces - under `Microsoft.Agents.AI`. For example, `Microsoft.SemanticKernel.Agents.ChatCompletionAgent` is replaced with `Microsoft.Agents.AI.ChatClientAgent`, when that - happens using statement with `Microsoft.Agents.AI` needs to be added (unless you use fully qualified type name) -- If you see some code that really cannot be converted or will have potential behavior changes at runtime, remember files and code lines where it - happens at the end of the migration process you will generate a report markdown file and list all follow up steps user would have to do. - -3. Validate that all places where Semantic Kernel Agents were used are migrated. To do that search for `Microsoft.SemanticKernel.Agents` in all affected projects and projects that depend - on them again and if still see any Semantic Kernel agent presence go back to step 2. Steps 2 and 3 should be repeated until you see no Semantic Kernel agent references. - -4. Build all modified projects to ensure that they compile without errors. If there are any build errors, you must fix them all yourself one by one and - don't stop until all errors are fixed without breaking any of the migration guidance. - -5. **Validate Migration**: Use the validation checklist below to ensure complete migration. - -6. Generate the report file under `\.github folder`, the file name should be `SemanticKernelToAgentFrameworkReport.md`, it is highly important that - you generate report when migration complete. Report should contain: - - all project dependencies changes (mention what was changed, added or removed, including provider-specific packages) - - all code files that were changed (mention what was changed in the file, if it was not changed, just mention that the file was not changed) - - provider-specific migration patterns used (OpenAI, Azure OpenAI, Azure AI Foundry, A2A, ONNX, etc.) - - all cases where you could not convert the code because of unsupported features and you were unable to find a workaround - - unsupported providers that require custom implementation (Bedrock, CopilotStudio) - - breaking glass pattern migrations (InnerContent → RawRepresentation) and any CodeInterpreter or advanced tool usage - - all behavioral changes that have to be verified at runtime - - provider-specific configuration changes that may affect behavior - - all follow up steps that user would have to do in the report markdown file - -## Migration Validation Checklist - -After completing migration, verify these specific items: - -1. **Compilation**: Execute `dotnet build` on all modified projects - zero errors required -2. **Namespace Updates**: Confirm all `using Microsoft.SemanticKernel.Agents` statements are replaced -3. **Method Calls**: Verify all `InvokeAsync` calls are changed to `RunAsync` -4. **Return Types**: Confirm handling of `AgentRunResponse` instead of `IAsyncEnumerable>` -5. **Thread Creation**: Validate all thread creation uses `agent.GetNewThread()` pattern -6. **Tool Registration**: Ensure `[KernelFunction]` attributes are removed and `AIFunctionFactory.Create()` is used -7. **Options Configuration**: Verify `AgentRunOptions` or `ChatClientAgentRunOptions` replaces `AgentInvokeOptions` -8. **Breaking Glass**: Test `RawRepresentation` access replaces `InnerContent` access - -## Detailed information about differences in Semantic Kernel Agents and Agent Framework - - -Agent Framework provides functionality for creating and managing AI agents through the Microsoft.Extensions.AI package ecosystem. The framework uses different APIs and patterns compared to Semantic Kernel Agents. - -Key API differences: -- Agent creation: Remove Kernel dependency, use direct client-based creation -- Method names: `InvokeAsync` → `RunAsync`, `InvokeStreamingAsync` → `RunStreamingAsync` -- Return types: `IAsyncEnumerable>` → `AgentRunResponse` -- Thread creation: Provider-specific constructors → `agent.GetNewThread()` -- Tool registration: `KernelPlugin` system → Direct `AIFunction` registration -- Options: `AgentInvokeOptions` → Provider-specific run options (e.g., `ChatClientAgentRunOptions`) - - - -Configuration patterns have changed from Kernel-based to direct client configuration: -- Remove `Kernel.CreateBuilder()` patterns -- Replace with provider-specific client creation -- Update namespace imports from `Microsoft.SemanticKernel.Agents` to `Microsoft.Agents.AI` -- Change tool registration from attribute-based to factory-based - - -### Exact API Mappings - - -Replace these Semantic Kernel agent classes with their Agent Framework equivalents: - -| Semantic Kernel Class | Agent Framework Replacement | Constructor Changes | -|----------------------|----------------------------|-------------------| -| `IChatCompletionService` | `IChatClient` | Convert to `IChatClient` using `chatService.AsChatClient()` extensions | -| `ChatCompletionAgent` | `ChatClientAgent` | Remove `Kernel` parameter, add `IChatClient` parameter | -| `OpenAIAssistantAgent` | `AIAgent` (via extension) | **New**: `OpenAIClient.GetAssistantClient().CreateAIAgent()`
**Existing**: `OpenAIClient.GetAssistantClient().GetAIAgent(assistantId)` | -| `AzureAIAgent` | `AIAgent` (via extension) | **New**: `PersistentAgentsClient.CreateAIAgent()`
**Existing**: `PersistentAgentsClient.GetAIAgent(agentId)` | -| `OpenAIResponseAgent` | `AIAgent` (via extension) | Replace with `OpenAIClient.GetOpenAIResponseClient().CreateAIAgent()` | -| `A2AAgent` | `AIAgent` (via extension) | Replace with `A2ACardResolver.GetAIAgentAsync()` | -| `BedrockAgent` | Not supported | Custom implementation required | - -**Important distinction:** -- **CreateAIAgent()**: Use when creating new agents in the hosted service -- **GetAIAgent(agentId)**: Use when retrieving existing agents from the hosted service -
- - -Replace these method calls: - -| Semantic Kernel Method | Agent Framework Method | Parameter Changes | -|----------------------|----------------------|------------------| -| `agent.InvokeAsync(message, thread, options)` | `agent.RunAsync(message, thread, options)` | Same parameters, different return type | -| `agent.InvokeStreamingAsync(message, thread, options)` | `agent.RunStreamingAsync(message, thread, options)` | Same parameters, different return type | -| `new ChatHistoryAgentThread()` | `agent.GetNewThread()` | No parameters needed | -| `new OpenAIAssistantAgentThread(client)` | `agent.GetNewThread()` | No parameters needed | -| `new AzureAIAgentThread(client)` | `agent.GetNewThread()` | No parameters needed | -| `thread.DeleteAsync()` | Provider-specific cleanup | Use provider client directly | - -Return type changes: -- `IAsyncEnumerable>` → `AgentRunResponse` -- `IAsyncEnumerable` → `IAsyncEnumerable` - - - -Replace these configuration patterns: - -| Semantic Kernel Pattern | Agent Framework Pattern | -|------------------------|------------------------| -| `AgentInvokeOptions` | `AgentRunOptions`
**ChatClientAgent**: `ChatClientAgentRunOptions` | -| `KernelArguments` | If no arguments are provided, do nothing. If arguments are provided, template is not supported and the prompt must be rendered before calling agent | -| `[KernelFunction]` attribute | Remove attribute, use `AIFunctionFactory.Create()` | -| `KernelPlugin` registration | Direct function list in agent creation | -| `InnerContent` property | `RawRepresentation` property | -| `content.Metadata` property | `AdditionalProperties` property | -
- - -### Functional Differences - -Agent Framework changes these behaviors compared to Semantic Kernel Agents: - -1. **Thread Management**: Agent Framework automatically manages thread state. Semantic Kernel required manual thread updates in some scenarios (e.g., OpenAI Responses). - -2. **Return Types**: - - Non-streaming: Returns single `AgentRunResponse` instead of `IAsyncEnumerable>` - - Streaming: Returns `IAsyncEnumerable` instead of `IAsyncEnumerable` - -3. **Tool Registration**: Agent Framework uses direct function registration without requiring `[KernelFunction]` attributes. - -4. **Usage Metadata**: Agent Framework provides unified `UsageDetails` access via `response.Usage` and `update.Contents.OfType()`. - -5. **Breaking Glass**: Access underlying SDK objects via `RawRepresentation` instead of `InnerContent`. - - -### Namespace Updates - - -Replace these exact namespace imports: - -**Remove these Semantic Kernel namespaces:** -```csharp -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.OpenAI; -using Microsoft.SemanticKernel.Agents.AzureAI; -using Microsoft.SemanticKernel.Agents.A2A; -using Microsoft.SemanticKernel.Connectors.OpenAI; -``` - -**Add these Agent Framework namespaces:** -```csharp -using Microsoft.Extensions.AI; -using Microsoft.Agents.AI; -// Provider-specific namespaces (add only if needed): -using OpenAI; // For OpenAI provider -using Azure.AI.OpenAI; // For Azure OpenAI provider -using Azure.AI.Agents.Persistent; // For Azure AI Foundry provider -using Azure.Identity; // For Azure authentication -``` - - -### Chat Completion Abstractions - - - -**Replace this Semantic Kernel pattern:** -```csharp -Kernel kernel = Kernel.CreateBuilder() - .AddOpenAIChatCompletion(modelId, apiKey) - .Build(); - -ChatCompletionAgent agent = new() -{ - Instructions = "You are a helpful assistant", - Kernel = kernel -}; -``` - -**With this Agent Framework pattern:** -```csharp -// Method 1: Direct constructor -IChatClient chatClient = new OpenAIClient(apiKey).GetChatClient(modelId).AsIChatClient(); -AIAgent agent = new ChatClientAgent(chatClient, instructions: "You are a helpful assistant"); - -// Method 2: Extension method (recommended) -AIAgent agent = new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent(instructions: "You are a helpful assistant"); -``` - - -### Chat Completion Service - - - -**Replace this Semantic Kernel pattern:** - -```csharp -IChatCompletionService completionService = kernel.GetService(); - -ChatCompletionAgent agent = new() -{ - Instructions = "You are a helpful assistant", - Kernel = kernel -}; -``` - -**With this Agent Framework pattern:** - -Agent Framework does not support `IChatCompletionService` directly. Instead, use `IChatClient` as the common abstraction -converting from `IChatCompletionService` to `IChatClient` via `AsChatClient()` extension method or creating a new `IChatClient` - instance directly using the provider package dedicated extensions. - -```csharp -IChatCompletionService completionService = kernel.GetService(); -IChatClient chatClient = completionService.AsChatClient(); - -var agent = new ChatClientAgent(chatClient, instructions: "You are a helpful assistant"); -``` - - -### Agent Creation Transformation - - - -**Replace this Semantic Kernel pattern:** -```csharp -Kernel kernel = Kernel.CreateBuilder() - .AddOpenAIChatClient(modelId, apiKey) - .Build(); - -ChatCompletionAgent agent = new() -{ - Instructions = "You are a helpful assistant", - Kernel = kernel -}; -``` - -**With this Agent Framework pattern:** -```csharp -// Method 1: Direct constructor (OpenAI/AzureOpenAI Package specific) -IChatClient chatClient = new OpenAIClient(apiKey).GetChatClient(modelId).AsIChatClient(); -AIAgent agent = new ChatClientAgent(chatClient, instructions: "You are a helpful assistant"); - -// Method 2: Extension method (recommended) -AIAgent agent = new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent(instructions: "You are a helpful assistant"); -``` - -**Required changes:** -1. Remove `Kernel.CreateBuilder()` and `.Build()` calls -2. Replace `ChatCompletionAgent` with `ChatClientAgent` or use extension methods -3. Remove `Kernel` property assignment -4. Pass `IChatClient` directly to constructor or use extension methods - - -### Thread Management Transformation - - -**Replace these Semantic Kernel thread creation patterns:** -```csharp -// Remove these provider-specific thread constructors: -AgentThread thread = new ChatHistoryAgentThread(); -AgentThread thread = new OpenAIAssistantAgentThread(assistantClient); -AgentThread thread = new AzureAIAgentThread(azureClient); -``` - -**With this unified Agent Framework pattern:** -```csharp -// Use this single pattern for all agent types: -AgentThread thread = agent.GetNewThread(); -``` - -**Required changes:** -1. Remove all `new [Provider]AgentThread()` constructor calls -2. Replace with `agent.GetNewThread()` method call -3. Remove provider client parameters from thread creation -4. Use the same pattern regardless of agent provider type - - -### Tool Registration Transformation - - -**Replace this Semantic Kernel tool registration pattern:** -```csharp -[KernelFunction] // Remove this attribute -[Description("Get the weather for a location")] -static string GetWeather(string location) => $"Weather in {location}"; - -KernelFunction kernelFunction = KernelFunctionFactory.CreateFromMethod(GetWeather); -KernelPlugin kernelPlugin = KernelPluginFactory.CreateFromFunctions("WeatherPlugin", [kernelFunction]); -kernel.Plugins.Add(kernelPlugin); - -ChatCompletionAgent agent = new() { Kernel = kernel }; -``` - -**With this Agent Framework pattern:** -```csharp -[Description("Get the weather for a location")] // Keep Description attribute -static string GetWeather(string location) => $"Weather in {location}"; - -AIAgent agent = chatClient.CreateAIAgent( - instructions: "You are a helpful assistant", - tools: [AIFunctionFactory.Create(GetWeather)]); -``` - -**Required changes:** -1. Remove `[KernelFunction]` attributes from methods -2. Keep `[Description]` attributes for function descriptions -3. Remove `KernelFunctionFactory.CreateFromMethod()` calls -4. Remove `KernelPluginFactory.CreateFromFunctions()` calls -5. Remove `kernel.Plugins.Add()` calls -6. Replace with `AIFunctionFactory.Create()` in tools parameter -7. Pass tools directly to agent creation method - - -### Invocation Method Transformation - - -**Replace this Semantic Kernel non-streaming pattern:** -```csharp -await foreach (AgentResponseItem item in agent.InvokeAsync(userInput, thread, options)) -{ - Console.WriteLine(item.Message); -} -``` - -**With this Agent Framework non-streaming pattern:** -```csharp -AgentRunResponse result = await agent.RunAsync(userInput, thread, options); -Console.WriteLine(result); -``` - -**Replace this Semantic Kernel streaming pattern:** -```csharp -await foreach (StreamingChatMessageContent update in agent.InvokeStreamingAsync(userInput, thread, options)) -{ - Console.Write(update.Message); -} -``` - -**With this Agent Framework streaming pattern:** -```csharp -await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(userInput, thread, options)) -{ - Console.Write(update); -} -``` - -**Required changes:** -1. Replace `agent.InvokeAsync()` with `agent.RunAsync()` -2. Replace `agent.InvokeStreamingAsync()` with `agent.RunStreamingAsync()` -3. Change return type handling from `IAsyncEnumerable>` to `AgentRunResponse` -4. Change streaming type from `StreamingChatMessageContent` to `AgentRunResponseUpdate` -5. Remove `await foreach` for non-streaming calls -6. Access message content directly from result object instead of iterating - - -### Options and Configuration Transformation - - -**Replace this Semantic Kernel options pattern:** -```csharp -OpenAIPromptExecutionSettings settings = new() { MaxTokens = 1000 }; -AgentInvokeOptions options = new() { KernelArguments = new(settings) }; -``` - -**With this Agent Framework options pattern:** -```csharp -ChatClientAgentRunOptions options = new(new ChatOptions { MaxOutputTokens = 1000 }); -``` - -**Required changes:** -1. Remove `OpenAIPromptExecutionSettings` (or other provider-specific settings) -2. Remove `AgentInvokeOptions` wrapper -3. Remove `KernelArguments` wrapper -4. Replace with `ChatClientAgentRunOptions` containing `ChatOptions` -5. Update property names: `MaxTokens` → `MaxOutputTokens` -6. Pass options directly to `RunAsync()` or `RunStreamingAsync()` methods - - -### Dependency Injection Transformation - - -**Replace this Semantic Kernel DI pattern:** - -Different providers require different kernel extensions: - -```csharp -services.AddKernel().AddOpenAIChatClient(modelId, apiKey); -services.AddTransient(sp => new() -{ - Kernel = sp.GetRequiredService(), - Instructions = "You are helpful" -}); -``` - -**With this Agent Framework DI pattern:** -```csharp -services.AddTransient(sp => - new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent(instructions: "You are helpful")); -``` - -**Required changes:** -1. Remove `services.AddKernel()` registration -2. Remove provider-specific kernel extensions (e.g., `.AddOpenAIChatClient()`) -3. Replace `ChatCompletionAgent` with `AIAgent` in service registration -4. Remove `Kernel` dependency from constructor -5. Use direct client creation and extension methods -6. Remove `sp.GetRequiredService()` calls - - -### Thread Cleanup Transformation - - -**Replace this Semantic Kernel cleanup pattern:** -```csharp -await thread.DeleteAsync(); // For hosted threads -``` - -**With these Agent Framework cleanup patterns:** - -For every thread created if there's intent to cleanup, the caller should track all the created threads for the provider that support hosted threads for cleanup purposes. - -```csharp -// For OpenAI Assistants (when cleanup is needed): -var assistantClient = new OpenAIClient(apiKey).GetAssistantClient(); -await assistantClient.DeleteThreadAsync(thread.ConversationId); - -// For Azure AI Foundry (when cleanup is needed): -var persistentClient = new PersistentAgentsClient(endpoint, credential); -await persistentClient.Threads.DeleteThreadAsync(thread.ConversationId); - -// No thread and agent cleanup is needed for non-hosted agent providers like -// - Azure OpenAI Chat Completion -// - OpenAI Chat Completion -// - Azure OpenAI Responses -// - OpenAI Responses -``` - -**Required changes:** -1. Remove `thread.DeleteAsync()` calls -2. Use provider-specific client for cleanup when required -3. Access thread ID via `thread.ConversationId` property -4. Only implement cleanup for providers that require it (Assistants, Azure AI Foundry) - - -### Provider-Specific Creation Patterns - - -Use these exact patterns for each provider: - -**OpenAI Chat Completion:** -```csharp -AIAgent agent = new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent(instructions: instructions); -``` - -**OpenAI Assistants (New):** -```csharp -AIAgent agent = new OpenAIClient(apiKey) - .GetAssistantClient() - .CreateAIAgent(modelId, instructions: instructions); -``` - -**OpenAI Assistants (Existing):** -```csharp -AIAgent agent = new OpenAIClient(apiKey) - .GetAssistantClient() - .GetAIAgent(assistantId); -``` - -**Azure OpenAI:** -```csharp -AIAgent agent = new AzureOpenAIClient(endpoint, credential) - .GetChatClient(deploymentName) - .CreateAIAgent(instructions: instructions); -``` - -**Azure AI Foundry (New):** -```csharp -AIAgent agent = new PersistentAgentsClient(endpoint, credential) - .CreateAIAgent(model: deploymentName, instructions: instructions); -``` - -**Azure AI Foundry (Existing):** -```csharp -AIAgent agent = await new PersistentAgentsClient(endpoint, credential) - .GetAIAgentAsync(agentId); -``` - -**A2A:** -```csharp -A2ACardResolver resolver = new(new Uri(agentHost)); -AIAgent agent = await resolver.GetAIAgentAsync(); -``` - - -### Complete Migration Examples - -#### Basic Agent Creation Transformation - -**Replace this complete Semantic Kernel pattern:** -```csharp -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; - -Kernel kernel = Kernel.CreateBuilder() - .AddOpenAIChatClient(modelId, apiKey) - .Build(); - -ChatCompletionAgent agent = new() -{ - Instructions = "You are helpful", - Kernel = kernel -}; - -AgentThread thread = new ChatHistoryAgentThread(); -``` - -**With this complete Agent Framework pattern:** -```csharp -using Microsoft.Agents.AI; -using OpenAI; - -AIAgent agent = new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent(instructions: "You are helpful"); - -AgentThread thread = agent.GetNewThread(); -``` - - -#### Tool Registration Transformation - -**Replace this complete Semantic Kernel tool pattern:** -```csharp -[KernelFunction] // Remove this attribute -[Description("Get weather information")] -static string GetWeather([Description("Location")] string location) - => $"Weather in {location}"; - -KernelFunction function = KernelFunctionFactory.CreateFromMethod(GetWeather); -KernelPlugin plugin = KernelPluginFactory.CreateFromFunctions("Weather", [function]); -kernel.Plugins.Add(plugin); -``` - -**With this complete Agent Framework tool pattern:** -```csharp -[Description("Get weather information")] // Keep this attribute -static string GetWeather([Description("Location")] string location) - => $"Weather in {location}"; - -AIAgent agent = chatClient.CreateAIAgent( - instructions: "You are a helpful assistant", - tools: [AIFunctionFactory.Create(GetWeather)]); -``` - - -#### Agent Invocation Transformation - -**Replace this complete Semantic Kernel invocation pattern:** -```csharp -OpenAIPromptExecutionSettings settings = new() { MaxTokens = 1000 }; -AgentInvokeOptions options = new() { KernelArguments = new(settings) }; - -await foreach (var result in agent.InvokeAsync(input, thread, options)) -{ - Console.WriteLine(result.Message); -} -``` - -**With this complete Agent Framework invocation pattern:** -```csharp -ChatClientAgentRunOptions options = new(new ChatOptions { MaxOutputTokens = 1000 }); - -AgentRunResponse result = await agent.RunAsync(input, thread, options); -Console.WriteLine(result); - -// Access underlying content when needed: -var chatResponse = result.RawRepresentation as ChatResponse; -// Access underlying SDK objects via chatResponse?.RawRepresentation -``` - - -### Usage Metadata Transformation - - -**Replace this Semantic Kernel non-streaming usage pattern:** -```csharp -await foreach (var result in agent.InvokeAsync(input, thread, options)) -{ - if (result.Message.Metadata?.TryGetValue("Usage", out object? usage) ?? false) - { - if (usage is ChatTokenUsage openAIUsage) - { - Console.WriteLine($"Tokens: {openAIUsage.TotalTokenCount}"); - } - } -} -``` - -**With this Agent Framework non-streaming usage pattern:** -```csharp -AgentRunResponse result = await agent.RunAsync(input, thread, options); -Console.WriteLine($"Tokens: {result.Usage.TotalTokenCount}"); -``` - -**Replace this Semantic Kernel streaming usage pattern:** -```csharp -await foreach (StreamingChatMessageContent response in agent.InvokeStreamingAsync(message, agentThread)) -{ - if (response.Metadata?.TryGetValue("Usage", out object? usage) ?? false) - { - if (usage is ChatTokenUsage openAIUsage) - { - Console.WriteLine($"Tokens: {openAIUsage.TotalTokenCount}"); - } - } -} -``` - -**With this Agent Framework streaming usage pattern:** -```csharp -await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync(input, thread, options)) -{ - if (update.Contents.OfType().FirstOrDefault() is { } usageContent) - { - Console.WriteLine($"Tokens: {usageContent.Details.TotalTokenCount}"); - } -} -``` - - - - -### Breaking Glass Pattern Transformation - - -**Replace this Semantic Kernel breaking glass pattern:** -```csharp -await foreach (var content in agent.InvokeAsync(userInput, thread)) -{ - UnderlyingSdkType? underlyingChatMessage = content.Message.InnerContent as UnderlyingSdkType; -} -``` - -**With this Agent Framework breaking glass pattern:** -```csharp -var agentRunResponse = await agent.RunAsync(userInput, thread); - -// If the agent uses a ChatClient the first breaking glass probably will be a Microsoft.Extensions.AI.ChatResponse -ChatResponse? chatResponse = agentRunResponse.RawRepresentation as ChatResponse; - -// If thats the case, to access the underlying SDK types you will need to break glass again. -UnderlyingSdkType? underlyingChatMessage = chatResponse?.RawRepresentation as UnderlyingSdkType; -``` - -**Required changes:** -1. Replace `InnerContent` property access with `RawRepresentation` property access -2. Cast `RawRepresentation` to appropriate type expected -3. If the `RawRepresentation` is a `Microsoft.Extensions.AI` type, break glass again to access the underlying SDK types - - -#### CodeInterpreter Tool Transformation - - -**Replace this Semantic Kernel CodeInterpreter pattern:** -```csharp -await foreach (var content in agent.InvokeAsync(userInput, thread)) -{ - bool isCode = content.Message.Metadata?.ContainsKey(AzureAIAgent.CodeInterpreterMetadataKey) ?? false; - Console.WriteLine($"# {content.Message.Role}{(isCode ? "\n# Generated Code:\n" : ":")}{content.Message.Content}"); - - // Process annotations - foreach (var item in content.Message.Items) - { - if (item is AnnotationContent annotation) - { - Console.WriteLine($"[{item.GetType().Name}] {annotation.Label}: File #{annotation.ReferenceId}"); - } - else if (item is FileReferenceContent fileReference) - { - Console.WriteLine($"[{item.GetType().Name}] File #{fileReference.FileId}"); - } - } -} -``` - -**With this Agent Framework CodeInterpreter pattern:** -```csharp -var result = await agent.RunAsync(userInput, thread); -Console.WriteLine(result); - -// Extract chat response MEAI type via first level breaking glass -var chatResponse = result.RawRepresentation as ChatResponse; - -// Extract underlying SDK updates via second level breaking glass -var underlyingStreamingUpdates = chatResponse?.RawRepresentation as IEnumerable ?? []; - -StringBuilder generatedCode = new(); -foreach (object? underlyingUpdate in underlyingStreamingUpdates ?? []) -{ - if (underlyingUpdate is RunStepDetailsUpdate stepDetailsUpdate && stepDetailsUpdate.CodeInterpreterInput is not null) - { - generatedCode.Append(stepDetailsUpdate.CodeInterpreterInput); - } -} - -if (!string.IsNullOrEmpty(generatedCode.ToString())) -{ - Console.WriteLine($"\n# {chatResponse?.Messages[0].Role}:Generated Code:\n{generatedCode}"); -} -``` - -**Functional differences:** -1. Code interpreter output is separate from text content, not a metadata property -2. Access code via `RunStepDetailsUpdate.CodeInterpreterInput` instead of metadata -3. Use breaking glass pattern to access underlying SDK objects -4. Process text content and code interpreter output independently - - -#### Provider-Specific Options Configuration - - -For advanced model settings not available in `ChatOptions`, use the `RawRepresentationFactory` property: - -```csharp -var agentOptions = new ChatClientAgentRunOptions(new ChatOptions -{ - MaxOutputTokens = 8000, - // Breaking glass to access provider-specific options - RawRepresentationFactory = (_) => new OpenAI.Responses.ResponseCreationOptions() - { - ReasoningOptions = new() - { - ReasoningEffortLevel = OpenAI.Responses.ResponseReasoningEffortLevel.High, - ReasoningSummaryVerbosity = OpenAI.Responses.ResponseReasoningSummaryVerbosity.Detailed - } - } -}); -``` - -**Use this pattern when:** -1. Standard `ChatOptions` properties don't cover required model settings -2. Provider-specific configuration is needed (e.g., reasoning effort level) -3. Advanced SDK features need to be accessed - - -#### Type-Safe Extension Methods - - -Use provider-specific extension methods for safer breaking glass access: - -```csharp -using OpenAI; // Brings in extension methods - -// Type-safe extraction of OpenAI ChatCompletion -var chatCompletion = result.AsChatCompletion(); - -// Access underlying OpenAI objects safely -var openAIResponse = chatCompletion.GetRawResponse(); -``` - -**Available extension methods:** -- `result.AsChatCompletion()` for OpenAI providers -- `result.GetRawResponse()` for accessing underlying SDK responses -- Provider-specific extensions for type-safe casting - - - - -### Common Migration Issues and Solutions - - -**Issue: Missing Using Statements** -- **Problem**: Compilation errors due to missing namespace imports -- **Solution**: Add `using Microsoft.Agents.AI;` and remove `using Microsoft.SemanticKernel.Agents;` - -**Issue: Tool Function Signatures** -- **Problem**: `[KernelFunction]` attributes cause compilation errors -- **Solution**: Remove `[KernelFunction]` attributes, keep `[Description]` attributes - -**Issue: Thread Type Mismatches** -- **Problem**: Provider-specific thread constructors not found -- **Solution**: Replace all thread constructors with `agent.GetNewThread()` - -**Issue: Options Configuration** -- **Problem**: `AgentInvokeOptions` type not found -- **Solution**: Replace with `AgentRunOptions` or `ChatClientAgentRunOptions` containing `ChatOptions` - -**Issue: Dependency Injection** -- **Problem**: `Kernel` service registration not found -- **Solution**: Remove `services.AddKernel()`, use direct client registration - - -### Migration Execution Steps - - -1. **Update Package References**: Remove SK packages, add AF packages per provider -2. **Update Namespaces**: Replace SK namespaces with AF namespaces -3. **Update Agent Creation**: Remove Kernel, use direct client creation -4. **Update Method Calls**: Replace `InvokeAsync` with `RunAsync` -5. **Update Thread Creation**: Replace provider-specific constructors with `GetNewThread()` -6. **Update Tool Registration**: Remove attributes, use `AIFunctionFactory.Create()` -7. **Update Options**: Replace `AgentInvokeOptions` with provider-specific options -8. **Test and Validate**: Compile and test all functionality - - -## Provider-Specific Migration Patterns - - -The following sections provide detailed migration patterns for each supported provider, covering package references, agent creation patterns, and provider-specific configurations. - - -### 1. OpenAI Chat Completion Migration - - -**Remove Semantic Kernel Packages:** -```xml - -``` - -**Add Agent Framework Packages:** -```xml - -``` - - -**Before (Semantic Kernel):** -```csharp -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; - -Kernel kernel = Kernel.CreateBuilder() - .AddOpenAIChatClient(modelId, apiKey) - .Build(); - -ChatCompletionAgent agent = new() -{ - Instructions = "You are a helpful assistant", - Kernel = kernel -}; - -AgentThread thread = new ChatHistoryAgentThread(); -``` - -**After (Agent Framework):** -```csharp -using Microsoft.Agents.AI; -using OpenAI; - -AIAgent agent = new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent(instructions: "You are a helpful assistant"); - -AgentThread thread = agent.GetNewThread(); -``` - -### 2. Azure OpenAI Chat Completion Migration - - -**Remove Semantic Kernel Packages:** -```xml - - - -``` - -**Add Agent Framework Packages:** -```xml - - - -``` - -**Note**: If not using `AzureCliCredential`, you can use `ApiKeyCredential` instead without the `Azure.Identity` package. - - -**Before (Semantic Kernel):** -```csharp -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Azure.Identity; - -Kernel kernel = Kernel.CreateBuilder() - .AddAzureOpenAIChatClient(deploymentName, endpoint, new AzureCliCredential()) - .Build(); - -ChatCompletionAgent agent = new() -{ - Instructions = "You are a helpful assistant", - Kernel = kernel -}; -``` - -**After (Agent Framework):** -```csharp -using Microsoft.Agents.AI; -using Azure.AI.OpenAI; -using Azure.Identity; - -AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) - .GetChatClient(deploymentName) - .CreateAIAgent(instructions: "You are a helpful assistant"); -``` - -### 3. OpenAI Assistants Migration - - -**Remove Semantic Kernel Packages:** -```xml - -``` - -**Add Agent Framework Packages:** -```xml - -``` - - - -**Replace this Semantic Kernel pattern:** -```csharp -using Microsoft.SemanticKernel.Agents.OpenAI; -using OpenAI.Assistants; - -AssistantClient assistantClient = new(apiKey); -Assistant assistant = await assistantClient.CreateAssistantAsync( - modelId, - instructions: "You are a helpful assistant"); - -OpenAIAssistantAgent agent = new(assistant, assistantClient) -{ - Kernel = kernel -}; - -AgentThread thread = new OpenAIAssistantAgentThread(assistantClient); -``` - -**With this Agent Framework pattern:** - -**Creating a new assistant:** -```csharp -using Microsoft.Agents.AI; -using OpenAI; - -AIAgent agent = new OpenAIClient(apiKey) - .GetAssistantClient() - .CreateAIAgent(modelId, instructions: "You are a helpful assistant"); - -AgentThread thread = agent.GetNewThread(); - -// Cleanup when needed -await assistantClient.DeleteThreadAsync(thread.ConversationId); -``` - -**Retrieving an existing assistant:** -```csharp -using Microsoft.Agents.AI; -using OpenAI; - -AIAgent agent = new OpenAIClient(apiKey) - .GetAssistantClient() - .GetAIAgent(assistantId); // Use existing assistant ID - -AgentThread thread = agent.GetNewThread(); -``` - - -### 4. Azure AI Foundry (AzureAIAgent) Migration - - -**Remove Semantic Kernel Packages:** -```xml - - -``` - -**Add Agent Framework Packages:** -```xml - - -``` - - - -**Replace these Semantic Kernel patterns:** - -**Pattern 1: Direct AzureAIAgent creation** -```csharp -using Microsoft.SemanticKernel.Agents.AzureAI; -using Azure.Identity; - -AzureAIAgent agent = new( - endpoint: new Uri(endpoint), - credential: new AzureCliCredential(), - projectId: projectId) -{ - Instructions = "You are a helpful assistant" -}; - -AgentThread thread = new AzureAIAgentThread(agent); -``` - -**Pattern 2: PersistentAgent definition creation** -```csharp -// Define the agent -PersistentAgent definition = await client.Administration.CreateAgentAsync( - deploymentName, - tools: [new CodeInterpreterToolDefinition()]); - -AzureAIAgent agent = new(definition, client); - -// Create a thread for the agent conversation. -AgentThread thread = new AzureAIAgentThread(client); -``` - -**With these Agent Framework patterns:** - -**Creating a new agent:** -```csharp -using Microsoft.Agents.AI; -using Azure.AI.Agents.Persistent; -using Azure.Identity; - -var client = new PersistentAgentsClient(endpoint, new AzureCliCredential()); - -// Create a new AIAgent using Agent Framework -AIAgent agent = client.CreateAIAgent( - model: deploymentName, - instructions: "You are a helpful assistant", - tools: [/* List of specialized Azure.AI.Agents.Persistent.ToolDefinition types */]); - -AgentThread thread = agent.GetNewThread(); -``` - -**Retrieving an existing agent:** -```csharp -using Microsoft.Agents.AI; -using Azure.AI.Agents.Persistent; -using Azure.Identity; - -var client = new PersistentAgentsClient(endpoint, new AzureCliCredential()); - -// Retrieve an existing AIAgent using its ID -AIAgent agent = await client.GetAIAgentAsync(agentId); - -AgentThread thread = agent.GetNewThread(); -``` - - -### 5. A2A Migration - - -**Remove Semantic Kernel Packages:** -```xml - -``` - -**Add Agent Framework Packages:** -```xml - -``` - - - -**Replace this Semantic Kernel pattern:** -```csharp -// Create an A2A agent instance -using var httpClient = CreateHttpClient(); -var client = new A2AClient(url, httpClient); -var cardResolver = new A2ACardResolver(url, httpClient); -var agentCard = await cardResolver.GetAgentCardAsync(); -var agent = new A2AAgent(client, agentCard); -``` - -**With this Agent Framework pattern:** -```csharp -// Initialize an A2ACardResolver to get an A2A agent card. -A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost)); - -// Create an instance of the AIAgent for an existing A2A agent specified by the agent card. -AIAgent agent = await agentCardResolver.GetAIAgentAsync(); -``` - - -### 6. OpenAI Responses Migration - - -**Remove Semantic Kernel Packages:** -```xml - -``` - -**Add Agent Framework Packages:** -```xml - -``` - - - -**Replace this Semantic Kernel pattern:** - -The thread management is done manually with OpenAI Responses in Semantic Kernel, where the thread -needs to be passed to the `InvokeAsync` method and updated with the `item.Thread` from the response. - -```csharp -using Microsoft.SemanticKernel.Agents.OpenAI; - -// Define the agent -OpenAIResponseAgent agent = new(new OpenAIClient(apiKey)) -{ - Name = "ResponseAgent", - Instructions = "Answer all queries in English and French.", -}; - -// Initial thread can be null as it will be automatically created -AgentThread? agentThread = null; - -var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "Input message."), agentThread); -await foreach (AgentResponseItem responseItem in responseItems) -{ - // Update the thread to maintain the conversation for future interaction - agentThread = responseItem.Thread; - - WriteAgentChatMessage(responseItem.Message); -} -``` - -**With this Agent Framework pattern:** - -Agent Framework automatically manages the thread, so there's no need to manually update it. - -```csharp -using Microsoft.Agents.AI.OpenAI; - -AIAgent agent = new OpenAIClient(apiKey) - .GetOpenAIResponseClient(modelId) - .CreateAIAgent( - name: "ResponseAgent", - instructions: "Answer all queries in English and French.", - tools: [/* AITools */]); - -AgentThread thread = agent.GetNewThread(); - -var result = await agent.RunAsync(userInput, thread); - -// The thread will be automatically updated with the new response id from this point -``` - - -### 7. Azure OpenAI Responses Migration - - -**Remove Semantic Kernel Packages:** -```xml - - -``` - -**Add Agent Framework Packages:** -```xml - - -``` - - - -**Replace this Semantic Kernel pattern:** - -Azure OpenAI Responses uses `AzureOpenAIClient` instead of `OpenAIClient`. The thread management is done manually where the thread needs to be passed to the `InvokeAsync` method and updated with the `item.Thread` from the response. - -```csharp -using Microsoft.SemanticKernel.Agents.OpenAI; -using Azure.AI.OpenAI; - -// Define the agent -OpenAIResponseAgent agent = new(new AzureOpenAIClient(endpoint, new AzureCliCredential())) -{ - Name = "ResponseAgent", - Instructions = "Answer all queries in English and French.", -}; - -// Initial thread can be null as it will be automatically created -AgentThread? agentThread = null; - -var responseItems = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "Input message."), agentThread); -await foreach (AgentResponseItem responseItem in responseItems) -{ - // Update the thread to maintain the conversation for future interaction - agentThread = responseItem.Thread; - - WriteAgentChatMessage(responseItem.Message); -} -``` - -**With this Agent Framework pattern:** - -Agent Framework automatically manages the thread, so there's no need to manually update it. - -```csharp -using Microsoft.Agents.AI.OpenAI; -using Azure.AI.OpenAI; - -AIAgent agent = new AzureOpenAIClient(endpoint, new AzureCliCredential()) - .GetOpenAIResponseClient(modelId) - .CreateAIAgent( - name: "ResponseAgent", - instructions: "Answer all queries in English and French.", - tools: [/* AITools */]); - -AgentThread thread = agent.GetNewThread(); - -var result = await agent.RunAsync(userInput, thread); - -// The thread will be automatically updated with the new response id from this point -``` - - -### 8. A2A Migration - - -**Remove Semantic Kernel Packages:** -```xml - -``` - -**Add Agent Framework Packages:** -```xml - -``` - - - -**Replace this Semantic Kernel pattern:** -```csharp -using A2A; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Agents.A2A; - -using var httpClient = CreateHttpClient(); -var client = new A2AClient(agentUrl, httpClient); -var cardResolver = new A2ACardResolver(url, httpClient); -var agentCard = await cardResolver.GetAgentCardAsync(); -Console.WriteLine(JsonSerializer.Serialize(agentCard, s_jsonSerializerOptions)); -var agent = new A2AAgent(client, agentCard); -``` - -**With this Agent Framework pattern:** -```csharp -using System; -using A2A; -using Microsoft.Agents.AI; -using Microsoft.Agents.AI.A2A; - -// Initialize an A2ACardResolver to get an A2A agent card. -A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost)); - -// Create an instance of the AIAgent for an existing A2A agent specified by the agent card. -AIAgent agent = await agentCardResolver.GetAIAgentAsync(); -``` - - -### 9. Unsupported Providers (Require Custom Implementation) - - -#### BedrockAgent Migration - -**Status**: Hosted Agents is not directly supported in Agent Framework - -**Status**: Non-Hosted AI Model Agents supported via `ChatClientAgent` - -**Replace this Semantic Kernel pattern:** -```csharp -using Microsoft.SemanticKernel.Agents.Bedrock; - -// Create a new agent on the Bedrock Agent service and prepare it for use -using var client = new AmazonBedrockAgentClient(); -using var runtimeClient = new AmazonBedrockAgentRuntimeClient(); -var agentModel = await client.CreateAndPrepareAgentAsync(new CreateAgentRequest() - { - AgentName = agentName, - Description = "AgentDescription", - Instruction = "You are a helpful assistant", - AgentResourceRoleArn = TestConfiguration.BedrockAgent.AgentResourceRoleArn, - FoundationModel = TestConfiguration.BedrockAgent.FoundationModel, - }); - -// Create a new BedrockAgent instance with the agent model and the client -// so that we can interact with the agent using Semantic Kernel contents. -var agent = new BedrockAgent(agentModel, client, runtimeClient); -``` - -**With this Agent Framework workaround:** - -Currently there's no support for the Hosted Bedrock Agent service in Agent Framework. - -For providers like AWS Bedrock that have an `IChatClient` implementation available, use the `ChatClientAgent` directly by providing the `IChatClient` instance to the agent. - -_Those agents will be purely backed by the AI chat models behavior and will not store any state in the server._ - -```csharp -using Microsoft.Agents.AI; - -services.TryAddAWSService(); -var serviceProvider = services.BuildServiceProvider(); -IAmazonBedrockRuntime runtime = serviceProvider.GetRequiredService(); - -using var bedrockChatClient = runtime.AsIChatClient(); -AIAgent agent = new ChatClientAgent(bedrockChatClient, instructions: "You are a helpful assistant"); -``` - - -### Unsupported Features that need workarounds - - -The following Semantic Kernel Agents features currently don't have direct equivalents in Agent Framework: - -#### Plugins Migration - -**Problem**: Semantic Kernel plugins allowed multiple functions to be registered under a type or object instance - -**Semantic Kernel pattern** -```csharp -// Create plugin with multiple functions -public class WeatherPlugin -{ - [KernelFunction, Description("Get current weather")] - public string GetCurrentWeather(string location) - => $"Weather in {location}: Sunny"; - - [KernelFunction, Description("Get weather forecast")] - public static Task GetForecastAsync(string location, int days) - => Task.FromResult($"Forecast for {location}: {days} days"); -} - -kernel.Plugins.AddFromType(); -// OR -kernel.Plugins.AddFromObject(new WeatherPlugin()); -``` - -**Agent Framework workaround:** - -```csharp -// Create individual functions (no plugin grouping) -public class WeatherFunctions -{ - [Description("Get current weather")] - public static string GetCurrentWeather(string location) - => $"Weather in {location}: Sunny"; - - [Description("Get weather forecast")] - public Task GetForecastAsync(string location, int days) - => Task.FromResult($"Forecast for {location}: {days} days"); -} - -var weatherService = new WeatherFunctions(); - -// Register functions individually as tools -AITool[] tools = [ - AIFunctionFactory.Create(WeatherFunctions.GetCurrentWeather), // Get from type static method - AIFunctionFactory.Create(weatherService.GetForecastAsync) // Get from instance method -]; - -// OR Iterate over the type or instance if many functions are needed for registration -AITool[] tools = -[ - .. typeof(WeatherFunctions) - .GetMethods(BindingFlags.Static | BindingFlags.Public) - .Select((m) => AIFunctionFactory.Create(m, target: null)), // Get from type static methods - .. weatherService.GetType() - .GetMethods(BindingFlags.Instance | BindingFlags.Public) - .Select((m) => AIFunctionFactory.Create(m, target: weatherService)) // Get from instance methods -]; - -AIAgent agent = new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent( - instructions: "You are a weather assistant", - tools: tools); -``` - -#### Prompt Template Migration - -**Problem**: Agent prompt templating is not yet supported in Agent Framework - -**Semantic Kernel pattern** -```csharp -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; - -var template = "Tell a story about {{$topic}} that is {{$length}} sentences long."; - -ChatCompletionAgent agent = - new(templateFactory: new KernelPromptTemplateFactory(), - templateConfig: new(template) { TemplateFormat = PromptTemplateConfig.SemanticKernelTemplateFormat }) - { - Kernel = kernel, - Name = "StoryTeller", - Arguments = new KernelArguments() - { - { "topic", "Dog" }, - { "length", "3" }, - } - }; -``` - -**Agent Framework workaround** - -```csharp -using Microsoft.Agents.AI; -using Microsoft.SemanticKernel; - -// Manually render template -var template = "Tell a story about {{$topic}} that is {{$length}} sentences long."; - -var renderedTemplate = await new KernelPromptTemplateFactory() - .Create(new PromptTemplateConfig(template)) - .RenderAsync(new Kernel(), new KernelArguments() - { - ["topic"] = "Dog", - ["length"] = "3" - }); - -AIAgent agent = new OpenAIClient(apiKey) - .GetChatClient(modelId) - .CreateAIAgent(instructions: renderedTemplate); - -// No template variables in invocation - use plain string -var result = await agent.RunAsync("What's the weather?", thread); -Console.WriteLine(result); -``` - - -### 10. Function Invocation Filtering - -**Invocation Context** - -Semantic Kernel's `IAutoFunctionInvocationFilter` provides a `AutoFunctionInvocationContext` where Agent Framework provides `FunctionInvocationContext` - -The property mapping guide from a `AutoFunctionInvocationContext` to a `FunctionInvocationContext` is as follows: - -| SK | AF | -| --- | --- | -| RequestSequenceIndex | Iteration | -| FunctionSequenceIndex | FunctionCallIndex | -| ToolCallId | CallContent.CallId | -| ChatMessageContent | Messages[0] | -| ExecutionSettings | Options | -| ChatHistory | Messages | -| Function | Function | -| Kernel | N/A | -| Result | Use `return` from the delegate | -| Terminate | Terminate | -| CancellationToken | provided via argument to middleware delegate | -| Arguments | Arguments | - -#### Semantic Kernel - -```csharp -// Filter specifically for functions calling -public sealed class CustomAutoFunctionInvocationFilter : IAutoFunctionInvocationFilter -{ - public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func next) - { - Console.WriteLine($"[SK Auto Filter] Auto-invoking function: {context.Function.Name}"); - - // Check if function should be auto-invoked - if (context.Function.Name.Contains("Dangerous")) - { - Console.WriteLine($"[SK Auto Filter] Skipping dangerous function: {context.Function.Name}"); - context.Terminate = true; - return; - } - - await next(context); - - Console.WriteLine($"[SK Auto Filter] Auto-invocation completed for: {context.Function.Name}"); - } -} - -var builder = Kernel.CreateBuilder() - .AddOpenAIChatClient(modelId, apiKey); - -// via builder DI -var builder = Kernel.CreateBuilder() - .AddOpenAIChatClient(modelId, apiKey) - .Services - .AddSingleton(); - -// OR via DI -services - .AddKernel() - .AddOpenAIChatClient(modelId, apiKey) - .AddSingleton(); - -// OR register auto function filter directly with the kernel instance -kernel.AutoFunctionInvocationFilters.Add(new CustomAutoFunctionInvocationFilter()); - -// Create agent with filtered kernel -ChatCompletionAgent agent = new() -{ - Instructions = "You are a helpful assistant", - Kernel = kernel -}; -``` - -#### Agent Framework - -Agent Framework provides function calling middleware that offers equivalent capabilities to Semantic Kernel's auto function invocation filters: - -```csharp -// Function calling middleware equivalent to CustomAutoFunctionInvocationFilter -async ValueTask CustomAutoFunctionMiddleware( - AIAgent agent, - FunctionInvocationContext context, - Func> next, - CancellationToken cancellationToken) -{ - Console.WriteLine($"[AF Middleware] Auto-invoking function: {context.Function.Name}"); - - // Check if function should be auto-invoked - if (context.Function.Name.Contains("Dangerous")) - { - Console.WriteLine($"[AF Middleware] Skipping dangerous function: {context.Function.Name}"); - context.Terminate = true; - return "Function execution blocked for security reasons"; - } - - var result = await next(context, cancellationToken); - - Console.WriteLine($"[AF Middleware] Auto-invocation completed for: {context.Function.Name}"); - return result; -} - -// Apply middleware to agent -var filteredAgent = originalAgent - .AsBuilder() - .Use(CustomAutoFunctionMiddleware) - .Build(); -``` - -### 11. Function Invocation Contexts - -**Invocation Context** - -Semantic Kernel's `IAutoFunctionInvocationFilter` provides a `AutoFunctionInvocationContext` where Agent Framework provides `FunctionInvocationContext` - -The property mapping guide from a `AutoFunctionInvocationContext` to a `FunctionInvocationContext` is as follows: - -| Semantic Kernel | Agent Framework | -| --- | --- | -| RequestSequenceIndex | Iteration | -| FunctionSequenceIndex | FunctionCallIndex | -| ToolCallId | CallContent.CallId | -| ChatMessageContent | Messages[0] | -| ExecutionSettings | Options | -| ChatHistory | Messages | -| Function | Function | -| Kernel | N/A | -| Result | Use `return` from the delegate | -| Terminate | Terminate | -| CancellationToken | provided via argument to middleware delegate | -| Arguments | Arguments | \ No newline at end of file +https://github.com/microsoft/semantic-kernel/blob/main/.github/upgrades/prompts/SemanticKernelToAgentFramework.md \ No newline at end of file diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index 9d3b86535c..a369b8f914 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -131,9 +131,6 @@ - - - diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs index b18d8e2d84..ef1849fe02 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs @@ -22,7 +22,7 @@ .GetChatClient(deploymentName); // Create the ChatClientAgent with the specified name and instructions. -ChatClientAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.")); +ChatClientAgent agent = chatClient.CreateAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant."); // Set PersonInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke the agent with some unstructured input. AgentRunResponse response = await agent.RunAsync("Please provide information about John Smith, who is a 35-year-old software engineer."); @@ -34,12 +34,10 @@ Console.WriteLine($"Occupation: {response.Result.Occupation}"); // Create the ChatClientAgent with the specified name, instructions, and expected structured output the agent should produce. -ChatClientAgent agentWithPersonInfo = chatClient.CreateAIAgent(new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.") +ChatClientAgent agentWithPersonInfo = chatClient.CreateAIAgent(new ChatClientAgentOptions() { - ChatOptions = new() - { - ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema() - } + Name = "HelpfulAssistant", + ChatOptions = new() { Instructions = "You are a helpful assistant.", ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema() } }); // Invoke the agent with some unstructured input while streaming, to extract the structured information from. diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs index 8986734972..d1316b6c80 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs @@ -28,7 +28,7 @@ .GetChatClient(deploymentName) .CreateAIAgent(new ChatClientAgentOptions { - Instructions = "You are good at telling jokes.", + ChatOptions = new() { Instructions = "You are good at telling jokes." }, Name = "Joker", ChatMessageStoreFactory = ctx => { diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs index 894c034eb0..d1b75d2fe5 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs @@ -18,8 +18,7 @@ HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); // Add agent options to the service collection. -builder.Services.AddSingleton( - new ChatClientAgentOptions(instructions: "You are good at telling jokes.", name: "Joker")); +builder.Services.AddSingleton(new ChatClientAgentOptions() { Name = "Joker", ChatOptions = new() { Instructions = "You are good at telling jokes." } }); // Add a chat client to the service collection. builder.Services.AddKeyedChatClient("AzureOpenAI", (sp) => new AzureOpenAIClient( diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_Memory/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step13_Memory/Program.cs index ad59deb97f..4b9b1866a9 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_Memory/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_Memory/Program.cs @@ -33,7 +33,7 @@ // and its storage to that user id. AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions() { - Instructions = "You are a friendly assistant. Always address the user by their name.", + ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." }, AIContextProviderFactory = ctx => new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions) }); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs index 590b5308d5..04704b5da0 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs @@ -21,7 +21,7 @@ .GetChatClient(deploymentName) .CreateAIAgent(new ChatClientAgentOptions { - Instructions = "You are good at telling jokes.", + ChatOptions = new() { Instructions = "You are good at telling jokes." }, Name = "Joker", ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions) }); diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs index be345b4656..b1d2547175 100644 --- a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs @@ -118,10 +118,11 @@ internal sealed class SloganWriterExecutor : Executor /// The chat client to use for the AI agent. public SloganWriterExecutor(string id, IChatClient chatClient) : base(id) { - ChatClientAgentOptions agentOptions = new(instructions: "You are a professional slogan writer. You will be given a task to create a slogan.") + ChatClientAgentOptions agentOptions = new() { ChatOptions = new() { + Instructions = "You are a professional slogan writer. You will be given a task to create a slogan.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }; @@ -193,10 +194,11 @@ internal sealed class FeedbackExecutor : Executor /// The chat client to use for the AI agent. public FeedbackExecutor(string id, IChatClient chatClient) : base(id) { - ChatClientAgentOptions agentOptions = new(instructions: "You are a professional editor. You will be given a slogan and the task it is meant to accomplish.") + ChatClientAgentOptions agentOptions = new() { ChatOptions = new() { + Instructions = "You are a professional editor. You will be given a slogan and the task it is meant to accomplish.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }; diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs index b6e3d4d513..0f762ea40d 100644 --- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs @@ -85,10 +85,11 @@ private static async Task Main() /// /// A ChatClientAgent configured for spam detection private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) => - new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails.") + new(chatClient, new ChatClientAgentOptions() { ChatOptions = new() { + Instructions = "You are a spam detection assistant that identifies spam emails.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }); @@ -98,10 +99,11 @@ private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) => /// /// A ChatClientAgent configured for email assistance private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) => - new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.") + new(chatClient, new ChatClientAgentOptions() { ChatOptions = new() { + Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }); diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs index 13f0a75bc2..ccda3fa19e 100644 --- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs @@ -100,10 +100,11 @@ private static async Task Main() /// /// A ChatClientAgent configured for spam detection private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) => - new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails. Be less confident in your assessments.") + new(chatClient, new ChatClientAgentOptions() { ChatOptions = new() { + Instructions = "You are a spam detection assistant that identifies spam emails. Be less confident in your assessments.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }); @@ -113,10 +114,11 @@ private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) => /// /// A ChatClientAgent configured for email assistance private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) => - new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.") + new(chatClient, new ChatClientAgentOptions() { ChatOptions = new() { + Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }); diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs index 15746f727e..e92f2ad589 100644 --- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs @@ -140,10 +140,11 @@ private static async Task Main() /// /// A ChatClientAgent configured for email analysis private static ChatClientAgent GetEmailAnalysisAgent(IChatClient chatClient) => - new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails.") + new(chatClient, new ChatClientAgentOptions() { ChatOptions = new() { + Instructions = "You are a spam detection assistant that identifies spam emails.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }); @@ -153,10 +154,11 @@ private static ChatClientAgent GetEmailAnalysisAgent(IChatClient chatClient) => /// /// A ChatClientAgent configured for email assistance private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) => - new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.") + new(chatClient, new ChatClientAgentOptions() { ChatOptions = new() { + Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }); @@ -166,10 +168,11 @@ private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) => /// /// A ChatClientAgent configured for email summarization private static ChatClientAgent GetEmailSummaryAgent(IChatClient chatClient) => - new(chatClient, new ChatClientAgentOptions(instructions: "You are an assistant that helps users summarize emails.") + new(chatClient, new ChatClientAgentOptions() { ChatOptions = new() { + Instructions = "You are an assistant that helps users summarize emails.", ResponseFormat = ChatResponseFormat.ForJsonSchema() } }); diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs index 1d5f228fcc..246df1f378 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs @@ -55,12 +55,17 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA chatClient = clientFactory(chatClient); } + if (!string.IsNullOrWhiteSpace(persistentAgentMetadata.Instructions) && chatOptions?.Instructions is null) + { + chatOptions ??= new ChatOptions(); + chatOptions.Instructions = persistentAgentMetadata.Instructions; + } + return new ChatClientAgent(chatClient, options: new() { Id = persistentAgentMetadata.Id, Name = persistentAgentMetadata.Name, Description = persistentAgentMetadata.Description, - Instructions = persistentAgentMetadata.Instructions, ChatOptions = chatOptions }); } @@ -179,12 +184,17 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA chatClient = clientFactory(chatClient); } + if (!string.IsNullOrWhiteSpace(persistentAgentMetadata.Instructions) && options.ChatOptions?.Instructions is null) + { + options.ChatOptions ??= new ChatOptions(); + options.ChatOptions.Instructions = persistentAgentMetadata.Instructions; + } + var agentOptions = new ChatClientAgentOptions() { Id = persistentAgentMetadata.Id, Name = options.Name ?? persistentAgentMetadata.Name, Description = options.Description ?? persistentAgentMetadata.Description, - Instructions = options.Instructions ?? persistentAgentMetadata.Instructions, ChatOptions = options.ChatOptions, AIContextProviderFactory = options.AIContextProviderFactory, ChatMessageStoreFactory = options.ChatMessageStoreFactory, @@ -415,7 +425,7 @@ public static ChatClientAgent CreateAIAgent( model: model, name: options.Name, description: options.Description, - instructions: options.Instructions, + instructions: options.ChatOptions?.Instructions, tools: toolDefinitionsAndResources.ToolDefinitions, toolResources: toolDefinitionsAndResources.ToolResources, temperature: null, @@ -473,7 +483,7 @@ public static async Task CreateAIAgentAsync( model: model, name: options.Name, description: options.Description, - instructions: options.Instructions, + instructions: options.ChatOptions?.Instructions, tools: toolDefinitionsAndResources.ToolDefinitions, toolResources: toolDefinitionsAndResources.ToolResources, temperature: null, diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs index 71f9b5436b..74055b596e 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs @@ -73,12 +73,17 @@ public static ChatClientAgent GetAIAgent( chatClient = clientFactory(chatClient); } + if (!string.IsNullOrWhiteSpace(assistantMetadata.Instructions) && chatOptions?.Instructions is null) + { + chatOptions ??= new ChatOptions(); + chatOptions.Instructions = assistantMetadata.Instructions; + } + return new ChatClientAgent(chatClient, options: new() { Id = assistantMetadata.Id, Name = assistantMetadata.Name, Description = assistantMetadata.Description, - Instructions = assistantMetadata.Instructions, ChatOptions = chatOptions }); } @@ -203,12 +208,17 @@ public static ChatClientAgent GetAIAgent( chatClient = clientFactory(chatClient); } + if (string.IsNullOrWhiteSpace(options.ChatOptions?.Instructions) && !string.IsNullOrWhiteSpace(assistantMetadata.Instructions)) + { + options.ChatOptions ??= new ChatOptions(); + options.ChatOptions.Instructions = assistantMetadata.Instructions; + } + var mergedOptions = new ChatClientAgentOptions() { Id = assistantMetadata.Id, Name = options.Name ?? assistantMetadata.Name, Description = options.Description ?? assistantMetadata.Description, - Instructions = options.Instructions ?? assistantMetadata.Instructions, ChatOptions = options.ChatOptions, AIContextProviderFactory = options.AIContextProviderFactory, ChatMessageStoreFactory = options.ChatMessageStoreFactory, @@ -321,10 +331,10 @@ public static ChatClientAgent CreateAIAgent( { Name = name, Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions() + ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions() { Tools = tools, + Instructions = instructions } }, clientFactory, @@ -356,7 +366,7 @@ public static ChatClientAgent CreateAIAgent( { Name = options.Name, Description = options.Description, - Instructions = options.Instructions, + Instructions = options.ChatOptions?.Instructions, }; // Convert AITools to ToolDefinitions and ToolResources @@ -382,12 +392,7 @@ public static ChatClientAgent CreateAIAgent( chatClient = clientFactory(chatClient); } - var agentOptions = options.Clone(); - agentOptions.Id = assistantId; - options.ChatOptions ??= new ChatOptions(); - options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools; - - return new ChatClientAgent(chatClient, agentOptions, loggerFactory); + return new ChatClientAgent(chatClient, options, loggerFactory); } /// @@ -418,10 +423,10 @@ await client.CreateAIAgentAsync(model, { Name = name, Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions() + ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions() { Tools = tools, + Instructions = instructions, } }, clientFactory, @@ -453,7 +458,7 @@ public static async Task CreateAIAgentAsync( { Name = options.Name, Description = options.Description, - Instructions = options.Instructions, + Instructions = options.ChatOptions?.Instructions, }; // Convert AITools to ToolDefinitions and ToolResources diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs index 36114d009c..b51679e42e 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs @@ -47,9 +47,9 @@ public static ChatClientAgent CreateAIAgent( { Name = name, Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions() + ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions() { + Instructions = instructions, Tools = tools, } }, diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs index c9f2743229..27ec4b2506 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs @@ -48,9 +48,9 @@ public static ChatClientAgent CreateAIAgent( { Name = name, Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions() + ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions() { + Instructions = instructions, Tools = tools, } }, diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs index b529e1151b..5870e2fdcc 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs @@ -32,7 +32,7 @@ public OpenAIChatClientAgent( { Name = name, Description = description, - Instructions = instructions, + ChatOptions = new ChatOptions() { Instructions = instructions }, }, loggerFactory) { } diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs index 8c5603fb05..9d554e6a84 100644 --- a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs @@ -32,7 +32,7 @@ public OpenAIResponseClientAgent( { Name = name, Description = description, - Instructions = instructions, + ChatOptions = new ChatOptions() { Instructions = instructions }, }, loggerFactory) { } diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index edca371f5d..992be57189 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -59,13 +59,13 @@ public ChatClientAgent(IChatClient chatClient, string? instructions = null, stri chatClient, new ChatClientAgentOptions { - Name = name, - Description = description, - Instructions = instructions, - ChatOptions = tools is null ? null : new ChatOptions + ChatOptions = (tools is null && string.IsNullOrWhiteSpace(instructions)) ? null : new ChatOptions { Tools = tools, - } + Instructions = instructions + }, + Name = name, + Description = description }, loggerFactory, services) @@ -141,7 +141,7 @@ public ChatClientAgent(IChatClient chatClient, ChatClientAgentOptions? options, /// These instructions are typically provided to the AI model as system messages to establish /// the context and expected behavior for the agent's responses. /// - public string? Instructions => this._agentOptions?.Instructions; + public string? Instructions => this._agentOptions?.ChatOptions?.Instructions; /// /// Gets of the default used by the agent. @@ -451,7 +451,6 @@ await thread.AIContextProvider.InvokedAsync(new(inputMessages) { InvokeException requestChatOptions.AllowMultipleToolCalls ??= this._agentOptions.ChatOptions.AllowMultipleToolCalls; requestChatOptions.ConversationId ??= this._agentOptions.ChatOptions.ConversationId; requestChatOptions.FrequencyPenalty ??= this._agentOptions.ChatOptions.FrequencyPenalty; - requestChatOptions.Instructions ??= this._agentOptions.ChatOptions.Instructions; requestChatOptions.MaxOutputTokens ??= this._agentOptions.ChatOptions.MaxOutputTokens; requestChatOptions.ModelId ??= this._agentOptions.ChatOptions.ModelId; requestChatOptions.PresencePenalty ??= this._agentOptions.ChatOptions.PresencePenalty; @@ -462,6 +461,13 @@ await thread.AIContextProvider.InvokedAsync(new(inputMessages) { InvokeException requestChatOptions.TopK ??= this._agentOptions.ChatOptions.TopK; requestChatOptions.ToolMode ??= this._agentOptions.ChatOptions.ToolMode; + // Merge instructions by concatenating them if both are present. + requestChatOptions.Instructions = !string.IsNullOrWhiteSpace(requestChatOptions.Instructions) && !string.IsNullOrWhiteSpace(this.Instructions) + ? $"{this.Instructions}\n{requestChatOptions.Instructions}" + : (!string.IsNullOrWhiteSpace(requestChatOptions.Instructions) + ? requestChatOptions.Instructions + : this.Instructions); + // Merge only the additional properties from the agent if they are not already set in the request options. if (requestChatOptions.AdditionalProperties is not null && this._agentOptions.ChatOptions.AdditionalProperties is not null) { @@ -606,12 +612,6 @@ await thread.AIContextProvider.InvokedAsync(new(inputMessages) { InvokeException """); } - if (!string.IsNullOrWhiteSpace(this.Instructions)) - { - chatOptions ??= new(); - chatOptions.Instructions = string.IsNullOrWhiteSpace(chatOptions.Instructions) ? this.Instructions : $"{this.Instructions}\n{chatOptions.Instructions}"; - } - // Only create or update ChatOptions if we have an id on the thread and we don't have the same one already in ChatOptions. if (!string.IsNullOrWhiteSpace(typedThread.ConversationId) && typedThread.ConversationId != chatOptions?.ConversationId) { diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs index f83e6912d5..019f4f42e4 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentOptions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Collections.Generic; using System.Text.Json; using Microsoft.Extensions.AI; @@ -17,35 +16,6 @@ namespace Microsoft.Agents.AI; /// public class ChatClientAgentOptions { - /// - /// Initializes a new instance of the class. - /// - public ChatClientAgentOptions() - { - } - - /// - /// Initializes a new instance of the class with the specified parameters. - /// - /// If is provided, a new instance is created - /// with the specified instructions and tools. - /// The instructions or guidelines for the chat client agent. Can be if not specified. - /// The name of the chat client agent. Can be if not specified. - /// The description of the chat client agent. Can be if not specified. - /// A list of instances available to the chat client agent. Can be if no - /// tools are specified. - public ChatClientAgentOptions(string? instructions, string? name = null, string? description = null, IList? tools = null) - { - this.Name = name; - this.Instructions = instructions; - this.Description = description; - - if (tools is not null) - { - (this.ChatOptions ??= new()).Tools = tools; - } - } - /// /// Gets or sets the agent id. /// @@ -56,11 +26,6 @@ public ChatClientAgentOptions(string? instructions, string? name = null, string? /// public string? Name { get; set; } - /// - /// Gets or sets the agent instructions. - /// - public string? Instructions { get; set; } - /// /// Gets or sets the agent description. /// @@ -106,7 +71,6 @@ public ChatClientAgentOptions Clone() { Id = this.Id, Name = this.Name, - Instructions = this.Instructions, Description = this.Description, ChatOptions = this.ChatOptions?.Clone(), ChatMessageStoreFactory = this.ChatMessageStoreFactory, diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs index 32b51b4196..42379c37da 100644 --- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs +++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs @@ -34,16 +34,20 @@ public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync(string create { "CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - name: AgentName, - description: AgentDescription)), + options: new ChatClientAgentOptions() + { + ChatOptions = new() { Instructions = AgentInstructions }, + Name = AgentName, + Description = AgentDescription + }), "CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - name: AgentName, - description: AgentDescription)), + options: new ChatClientAgentOptions() + { + ChatOptions = new() { Instructions = AgentInstructions }, + Name = AgentName, + Description = AgentDescription + }), "CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync( s_config.DeploymentName, instructions: AgentInstructions, @@ -109,14 +113,24 @@ You are a helpful agent that can help fetch data from files you know about. { "CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }] + } + }), "CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }] + } + }), "CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync( s_config.DeploymentName, instructions: AgentInstructions, @@ -179,15 +193,24 @@ and report the SECRET_NUMBER value it prints. Respond only with the number. // Hosted tool path (tools supplied via ChatClientAgentOptions) "CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }] + } + }), "CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])), - // Foundry (definitions + resources provided directly) + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }] + } + }), "CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync( s_config.DeploymentName, instructions: AgentInstructions, @@ -232,14 +255,24 @@ public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync(string create { "CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [weatherFunction])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [weatherFunction] + } + }), "CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent( s_config.DeploymentName, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [weatherFunction])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [weatherFunction] + } + }), _ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}") }; diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs index 2405cd3347..9157544021 100644 --- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs @@ -309,7 +309,7 @@ public void GetAIAgent_WithResponseAndOptions_WorksCorrectly() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act @@ -336,7 +336,7 @@ public void GetAIAgent_WithPersistentAgentAndOptions_WorksCorrectly() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act @@ -385,7 +385,7 @@ public void GetAIAgent_WithAgentIdAndOptions_WorksCorrectly() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act @@ -412,7 +412,7 @@ public async Task GetAIAgentAsync_WithAgentIdAndOptions_WorksCorrectlyAsync() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act @@ -556,7 +556,7 @@ public void CreateAIAgent_WithOptions_WorksCorrectly() { Name = "Test Agent", Description = "Test description", - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }; // Act @@ -583,7 +583,7 @@ public async Task CreateAIAgentAsync_WithOptions_WorksCorrectlyAsync() { Name = "Test Agent", Description = "Test description", - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }; // Act diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs index 61e3f5ef57..f405fde4e4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs @@ -91,7 +91,7 @@ public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly() { Name = "Test Agent", Description = "Test description", - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }; // Act @@ -222,7 +222,7 @@ public void GetAIAgent_WithClientResultAndOptions_WorksCorrectly() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act @@ -249,7 +249,7 @@ public void GetAIAgent_WithAssistantAndOptions_WorksCorrectly() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act @@ -298,7 +298,7 @@ public void GetAIAgent_WithAgentIdAndOptions_WorksCorrectly() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act @@ -325,7 +325,7 @@ public async Task GetAIAgentAsync_WithAgentIdAndOptions_WorksCorrectlyAsync() { Name = "Override Name", Description = "Override Description", - Instructions = "Override Instructions" + ChatOptions = new() { Instructions = "Override Instructions" } }; // Act diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs index 72ea0395e9..ef9f27b01a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs @@ -130,7 +130,7 @@ public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly() { Name = "Test Agent", Description = "Test description", - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }; // Act diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs index dc983ef202..58cf5f718f 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs @@ -19,7 +19,6 @@ public void DefaultConstructor_InitializesWithNullValues() // Assert Assert.Null(options.Name); - Assert.Null(options.Instructions); Assert.Null(options.Description); Assert.Null(options.ChatOptions); Assert.Null(options.ChatMessageStoreFactory); @@ -27,90 +26,44 @@ public void DefaultConstructor_InitializesWithNullValues() } [Fact] - public void ParameterizedConstructor_WithNullValues_SetsPropertiesCorrectly() + public void Constructor_WithNullValues_SetsPropertiesCorrectly() { // Act - var options = new ChatClientAgentOptions( - instructions: null, - name: null, - description: null, - tools: null); + var options = new ChatClientAgentOptions() { Name = null, Description = null, ChatOptions = new() { Tools = null, Instructions = null } }; // Assert Assert.Null(options.Name); - Assert.Null(options.Instructions); Assert.Null(options.Description); - Assert.Null(options.ChatOptions); Assert.Null(options.AIContextProviderFactory); - } - - [Fact] - public void ParameterizedConstructor_WithInstructionsOnly_SetsChatOptionsWithInstructions() - { - // Arrange - const string Instructions = "Test instructions"; - - // Act - var options = new ChatClientAgentOptions( - instructions: Instructions, - name: null, - description: null, - tools: null); - - // Assert - Assert.Null(options.Name); - Assert.Equal(Instructions, options.Instructions); - Assert.Null(options.Description); - Assert.Null(options.ChatOptions); - } - - [Fact] - public void ParameterizedConstructor_WithToolsOnly_SetsChatOptionsWithTools() - { - // Arrange - var tools = new List { AIFunctionFactory.Create(() => "test") }; - - // Act - var options = new ChatClientAgentOptions( - instructions: null, - name: null, - description: null, - tools: tools); - - // Assert - Assert.Null(options.Name); - Assert.Null(options.Instructions); - Assert.Null(options.Description); + Assert.Null(options.ChatMessageStoreFactory); Assert.NotNull(options.ChatOptions); Assert.Null(options.ChatOptions.Instructions); - Assert.Same(tools, options.ChatOptions.Tools); + Assert.Null(options.ChatOptions.Tools); } [Fact] - public void ParameterizedConstructor_WithInstructionsAndTools_SetsChatOptionsWithBoth() + public void Constructor_WithToolsOnly_SetsChatOptionsWithTools() { // Arrange - const string Instructions = "Test instructions"; var tools = new List { AIFunctionFactory.Create(() => "test") }; // Act - var options = new ChatClientAgentOptions( - instructions: Instructions, - name: null, - description: null, - tools: tools); + var options = new ChatClientAgentOptions() + { + Name = null, + Description = null, + ChatOptions = new() { Tools = tools } + }; // Assert Assert.Null(options.Name); - Assert.Equal(Instructions, options.Instructions); Assert.Null(options.Description); Assert.NotNull(options.ChatOptions); - Assert.Null(options.ChatOptions.Instructions); - Assert.Same(tools, options.ChatOptions.Tools); + AssertSameTools(tools, options.ChatOptions.Tools); } [Fact] - public void ParameterizedConstructor_WithAllParameters_SetsAllPropertiesCorrectly() + public void Constructor_WithAllParameters_SetsAllPropertiesCorrectly() { // Arrange const string Instructions = "Test instructions"; @@ -119,38 +72,37 @@ public void ParameterizedConstructor_WithAllParameters_SetsAllPropertiesCorrectl var tools = new List { AIFunctionFactory.Create(() => "test") }; // Act - var options = new ChatClientAgentOptions( - instructions: Instructions, - name: Name, - description: Description, - tools: tools); + var options = new ChatClientAgentOptions() + { + Name = Name, + Description = Description, + ChatOptions = new() { Tools = tools, Instructions = Instructions } + }; // Assert Assert.Equal(Name, options.Name); - Assert.Equal(Instructions, options.Instructions); + Assert.Equal(Instructions, options.ChatOptions.Instructions); Assert.Equal(Description, options.Description); Assert.NotNull(options.ChatOptions); - Assert.Null(options.ChatOptions.Instructions); - Assert.Same(tools, options.ChatOptions.Tools); + AssertSameTools(tools, options.ChatOptions.Tools); } [Fact] - public void ParameterizedConstructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions() + public void Constructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions() { // Arrange const string Name = "Test name"; const string Description = "Test description"; // Act - var options = new ChatClientAgentOptions( - instructions: null, - name: Name, - description: Description, - tools: null); + var options = new ChatClientAgentOptions() + { + Name = Name, + Description = Description, + }; // Assert Assert.Equal(Name, options.Name); - Assert.Null(options.Instructions); Assert.Equal(Description, options.Description); Assert.Null(options.ChatOptions); } @@ -159,7 +111,6 @@ public void ParameterizedConstructor_WithNameAndDescriptionOnly_DoesNotCreateCha public void Clone_CreatesDeepCopyWithSameValues() { // Arrange - const string Instructions = "Test instructions"; const string Name = "Test name"; const string Description = "Test description"; var tools = new List { AIFunctionFactory.Create(() => "test") }; @@ -171,8 +122,11 @@ static AIContextProvider AIContextProviderFactory( ChatClientAgentOptions.AIContextProviderFactoryContext ctx) => new Mock().Object; - var original = new ChatClientAgentOptions(Instructions, Name, Description, tools) + var original = new ChatClientAgentOptions() { + Name = Name, + Description = Description, + ChatOptions = new() { Tools = tools }, Id = "test-id", ChatMessageStoreFactory = ChatMessageStoreFactory, AIContextProviderFactory = AIContextProviderFactory @@ -185,7 +139,6 @@ static AIContextProvider AIContextProviderFactory( Assert.NotSame(original, clone); Assert.Equal(original.Id, clone.Id); Assert.Equal(original.Name, clone.Name); - Assert.Equal(original.Instructions, clone.Instructions); Assert.Equal(original.Description, clone.Description); Assert.Same(original.ChatMessageStoreFactory, clone.ChatMessageStoreFactory); Assert.Same(original.AIContextProviderFactory, clone.AIContextProviderFactory); @@ -197,14 +150,13 @@ static AIContextProvider AIContextProviderFactory( } [Fact] - public void Clone_WithNullChatOptions_ClonesCorrectly() + public void Clone_WithoutProvidingChatOptions_ClonesCorrectly() { // Arrange var original = new ChatClientAgentOptions { Id = "test-id", Name = "Test name", - Instructions = "Test instructions", Description = "Test description" }; @@ -215,10 +167,19 @@ public void Clone_WithNullChatOptions_ClonesCorrectly() Assert.NotSame(original, clone); Assert.Equal(original.Id, clone.Id); Assert.Equal(original.Name, clone.Name); - Assert.Equal(original.Instructions, clone.Instructions); Assert.Equal(original.Description, clone.Description); - Assert.Null(clone.ChatOptions); + Assert.Null(original.ChatOptions); Assert.Null(clone.ChatMessageStoreFactory); Assert.Null(clone.AIContextProviderFactory); } + + private static void AssertSameTools(IList? expected, IList? actual) + { + var index = 0; + foreach (var tool in expected ?? []) + { + Assert.Same(tool, actual?[index]); + index++; + } + } } diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs index f20f7fe082..e6cecc556a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs @@ -31,7 +31,7 @@ public void VerifyChatClientAgentDefinition() Id = "test-agent-id", Name = "test name", Description = "test description", - Instructions = "test instructions", + ChatOptions = new ChatOptions() { Instructions = "test instructions" }, }); // Assert @@ -65,7 +65,7 @@ public async Task VerifyChatClientAgentInvocationAsync() ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions" + ChatOptions = new ChatOptions() { Instructions = "test instructions" }, }); // Act @@ -99,7 +99,7 @@ public async Task RunAsyncThrowsArgumentNullExceptionWhenMessagesIsNullAsync() { // Arrange var chatClient = new Mock().Object; - ChatClientAgent agent = new(chatClient, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(chatClient, options: new() { ChatOptions = new ChatOptions() { Instructions = "test instructions" } }); // Act & Assert await Assert.ThrowsAsync(() => agent.RunAsync((IReadOnlyCollection)null!)); @@ -120,7 +120,7 @@ public async Task RunAsyncPassesChatOptionsWhenUsingChatClientAgentRunOptionsAsy It.Is(opts => opts.MaxOutputTokens == 100), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } }); // Act await agent.RunAsync([new(ChatRole.User, "test")], options: new ChatClientAgentRunOptions(chatOptions)); @@ -181,7 +181,7 @@ public async Task RunAsyncIncludesBaseInstructionsInOptionsAsync() capturedMessages.AddRange(msgs)) .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new ChatOptions() { Instructions = "base instructions" } }); var runOptions = new AgentRunOptions(); // Act @@ -210,7 +210,7 @@ public async Task RunAsyncSetsAuthorNameOnAllResponseMessagesAsync() It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse(responseMessages)); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions", Name = "TestAgent" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" }, Name = "TestAgent" }); // Act var result = await agent.RunAsync([new(ChatRole.User, "test")]); @@ -237,7 +237,7 @@ public async Task RunAsyncRetrievesMessagesFromThreadWhenThreadImplementsIMessag capturedMessages.AddRange(msgs)) .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } }); // Create a thread using the agent's GetNewThread method var thread = agent.GetNewThread(); @@ -268,7 +268,7 @@ public async Task RunAsyncWorksWithoutInstructionsWhenInstructionsAreNullOrEmpty capturedMessages.AddRange(msgs)) .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = null }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = null } }); // Act await agent.RunAsync([new(ChatRole.User, "test message")]); @@ -298,7 +298,7 @@ public async Task RunAsyncWorksWithEmptyMessagesWhenNoMessagesProvidedAsync() capturedMessages.AddRange(msgs)) .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new ChatOptions() { Instructions = "test instructions" } }); // Act await agent.RunAsync([]); @@ -324,7 +324,7 @@ public async Task RunAsyncDoesNotThrowWhenSpecifyingTwoSameThreadIdsAsync() It.Is(opts => opts.ConversationId == "ConvId"), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" }); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } }); ChatClientAgentThread thread = new() { ConversationId = "ConvId" }; @@ -344,7 +344,7 @@ public async Task RunAsyncThrowsWhenSpecifyingTwoDifferentThreadIdsAsync() var chatOptions = new ChatOptions { ConversationId = "ConvId" }; Mock mockService = new(); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new ChatOptions() { Instructions = "test instructions" } }); ChatClientAgentThread thread = new() { ConversationId = "ThreadId" }; @@ -367,7 +367,7 @@ public async Task RunAsyncClonesChatOptionsToAddThreadIdAsync() It.Is(opts => opts.MaxOutputTokens == 100 && opts.ConversationId == "ConvId"), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" }); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new ChatOptions() { Instructions = "test instructions" } }); ChatClientAgentThread thread = new() { ConversationId = "ConvId" }; @@ -392,7 +392,7 @@ public async Task RunAsyncThrowsForMissingConversationIdWithConversationIdThread It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } }); ChatClientAgentThread thread = new() { ConversationId = "ConvId" }; @@ -413,7 +413,7 @@ public async Task RunAsyncSetsConversationIdOnThreadWhenReturnedByChatClientAsyn It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]) { ConversationId = "ConvId" }); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } }); ChatClientAgentThread thread = new(); // Act @@ -440,7 +440,7 @@ public async Task RunAsyncUsesChatMessageStoreWhenNoConversationIdReturnedByChat mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", + ChatOptions = new ChatOptions() { Instructions = "test instructions" }, ChatMessageStoreFactory = mockFactory.Object }); @@ -470,7 +470,7 @@ public async Task RunAsyncIgnoresChatMessageStoreWhenConversationIdReturnedByCha mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", + ChatOptions = new() { Instructions = "test instructions" }, ChatMessageStoreFactory = mockFactory.Object }); @@ -525,7 +525,7 @@ public async Task RunAsyncInvokesAIContextProviderAndUsesResultAsync() .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) .Returns(new ValueTask()); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object }); // Act await agent.RunAsync(requestMessages); @@ -570,7 +570,7 @@ public async Task RunAsyncInvokesAIContextProviderWhenGetResponseFailsAsync() .Setup(p => p.InvokedAsync(It.IsAny(), It.IsAny())) .Returns(new ValueTask()); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object }); // Act await Assert.ThrowsAsync(() => agent.RunAsync(requestMessages)); @@ -612,7 +612,7 @@ public async Task RunAsyncInvokesAIContextProviderAndSucceedsWithEmptyAIContextA .Setup(p => p.InvokingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new AIContext()); - ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions", AIContextProviderFactory = _ => mockProvider.Object, ChatOptions = new() { Tools = [AIFunctionFactory.Create(() => { }, "base function")] } }); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] }, AIContextProviderFactory = _ => mockProvider.Object }); // Act await agent.RunAsync([new(ChatRole.User, "user message")]); @@ -819,7 +819,7 @@ public void InstructionsReturnsMetadataInstructionsWhenMetadataProvided() { // Arrange var chatClient = new Mock().Object; - var metadata = new ChatClientAgentOptions { Instructions = "You are a helpful assistant" }; + var metadata = new ChatClientAgentOptions { ChatOptions = new() { Instructions = "You are a helpful assistant" } }; ChatClientAgent agent = new(chatClient, metadata); // Act & Assert @@ -848,7 +848,7 @@ public void InstructionsReturnsNullWhenMetadataInstructionsIsNull() { // Arrange var chatClient = new Mock().Object; - var metadata = new ChatClientAgentOptions { Instructions = null }; + var metadata = new ChatClientAgentOptions { ChatOptions = new() { Instructions = null } }; ChatClientAgent agent = new(chatClient, metadata); // Act & Assert @@ -882,7 +882,7 @@ public void ConstructorUsesOptionalParams() /// Verify that ChatOptions property returns null when no params are provided that require a ChatOptions instance. /// [Fact] - public void ChatOptionsReturnsNullWhenConstructorToolsNotProvided() + public void ChatOptionsToolsReturnsNullWhenConstructorToolsNotProvided() { // Arrange var chatClient = new Mock().Object; @@ -892,7 +892,7 @@ public void ChatOptionsReturnsNullWhenConstructorToolsNotProvided() Assert.Equal("TestInstructions", agent.Instructions); Assert.Equal("TestName", agent.Name); Assert.Equal("TestDescription", agent.Description); - Assert.Null(agent.ChatOptions); + Assert.Null(agent.ChatOptions?.Tools); } #endregion @@ -983,7 +983,7 @@ public void ChatOptionsReturnsClonedCopyWhenAgentOptionsHaveChatOptions() public async Task ChatOptionsMergingUsesAgentOptionsWhenRequestHasNoneAsync() { // Arrange - var agentChatOptions = new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f }; + var agentChatOptions = new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f, Instructions = "test instructions" }; Mock mockService = new(); ChatOptions? capturedChatOptions = null; mockService.Setup( @@ -997,7 +997,6 @@ public async Task ChatOptionsMergingUsesAgentOptionsWhenRequestHasNoneAsync() ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", ChatOptions = agentChatOptions }); var messages = new List { new(ChatRole.User, "test") }; @@ -1026,7 +1025,7 @@ public async Task ChatOptionsMergingUsesAgentOptionsConstructorWhenRequestHasNon capturedChatOptions = opts) .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - ChatClientAgent agent = new(mockService.Object, options: new("test instructions")); + ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } }); var messages = new List { new(ChatRole.User, "test") }; // Act @@ -1083,6 +1082,7 @@ public async Task ChatOptionsMergingPrioritizesRequestOptionsOverAgentOptionsAsy Temperature = 0.7f, TopP = 0.9f, ModelId = "agent-model", + Instructions = "agent instructions", AdditionalProperties = new AdditionalPropertiesDictionary { ["key"] = "agent-value" } }; var requestChatOptions = new ChatOptions @@ -1100,7 +1100,7 @@ public async Task ChatOptionsMergingPrioritizesRequestOptionsOverAgentOptionsAsy AdditionalProperties = new AdditionalPropertiesDictionary { ["key"] = "request-value" }, // Request value takes priority TopP = 0.9f, // Agent value used when request doesn't specify ModelId = "agent-model", // Agent value used when request doesn't specify - Instructions = "test instructions\nrequest instructions" // Request is in addition to agent instructions + Instructions = "agent instructions\nrequest instructions" // Request is in addition to agent instructions }; Mock mockService = new(); @@ -1116,7 +1116,6 @@ public async Task ChatOptionsMergingPrioritizesRequestOptionsOverAgentOptionsAsy ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", ChatOptions = agentChatOptions }); var messages = new List { new(ChatRole.User, "test") }; @@ -1135,34 +1134,6 @@ public async Task ChatOptionsMergingPrioritizesRequestOptionsOverAgentOptionsAsy Assert.Equal("agent-model", capturedChatOptions.ModelId); // Agent value used when request doesn't specify } - /// - /// Verify that ChatOptions merging returns null when both agent and request have no ChatOptions. - /// - [Fact] - public async Task ChatOptionsMergingReturnsNullWhenBothAgentAndRequestHaveNoneAsync() - { - // Arrange - Mock mockService = new(); - ChatOptions? capturedChatOptions = null; - mockService.Setup( - s => s.GetResponseAsync( - It.IsAny>(), - It.IsAny(), - It.IsAny())) - .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => - capturedChatOptions = opts) - .ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")])); - - ChatClientAgent agent = new(mockService.Object); - var messages = new List { new(ChatRole.User, "test") }; - - // Act - await agent.RunAsync(messages); - - // Assert - Assert.Null(capturedChatOptions); - } - /// /// Verify that ChatOptions merging concatenates Tools from agent and request. /// @@ -1195,7 +1166,6 @@ public async Task ChatOptionsMergingConcatenatesToolsFromAgentAndRequestAsync() ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", ChatOptions = agentChatOptions }); var messages = new List { new(ChatRole.User, "test") }; @@ -1245,7 +1215,6 @@ public async Task ChatOptionsMergingUsesAgentToolsWhenRequestHasNoToolsAsync() ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", ChatOptions = agentChatOptions }); var messages = new List { new(ChatRole.User, "test") }; @@ -1292,7 +1261,6 @@ public async Task ChatOptionsMergingUsesRawRepresentationFactoryWithFallbackAsyn ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", ChatOptions = agentChatOptions }); var messages = new List { new(ChatRole.User, "test") }; @@ -1348,7 +1316,7 @@ public async Task ChatOptionsMergingHandlesAllScalarPropertiesCorrectlyAsync() TopK = 50, PresencePenalty = 0.1f, FrequencyPenalty = 0.2f, - Instructions = "test instructions\nrequest instructions", + Instructions = "agent instructions\nrequest instructions", ModelId = "agent-model", Seed = 12345, ConversationId = "agent-conversation", @@ -1371,7 +1339,6 @@ public async Task ChatOptionsMergingHandlesAllScalarPropertiesCorrectlyAsync() ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", ChatOptions = agentChatOptions }); var messages = new List { new(ChatRole.User, "test") }; @@ -1421,7 +1388,7 @@ public void GetService_RequestingAIAgentMetadata_ReturnsMetadata() { Id = "test-agent-id", Name = "TestAgent", - Instructions = "Test instructions" + ChatOptions = new ChatOptions() { Instructions = "Test instructions" } }); // Act @@ -1444,7 +1411,7 @@ public void GetService_RequestingIChatClient_ReturnsChatClient() var mockChatClient = new Mock(); var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1468,7 +1435,7 @@ public void GetService_RequestingChatClientAgent_ReturnsChatClientAgent() var mockChatClient = new Mock(); var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1494,7 +1461,7 @@ public void GetService_RequestingUnknownServiceType_DelegatesToChatClient() var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1518,7 +1485,7 @@ public void GetService_RequestingUnknownServiceTypeWithNullFromChatClient_Return var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1544,7 +1511,7 @@ public void GetService_WithServiceKey_DelegatesToChatClient() var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1573,7 +1540,7 @@ public void GetService_RequestingAIAgentMetadata_ReturnsMetadataWithCorrectProvi var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1606,7 +1573,7 @@ public void GetService_RequestingAIAgentMetadata_ReturnsCorrectAIAgentMetadataBa { Id = "test-agent-id", Name = "TestAgent", - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1633,7 +1600,7 @@ public void GetService_RequestingAIAgentMetadata_ReturnsConsistentMetadata() var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1668,12 +1635,12 @@ public void GetService_RequestingAIAgentMetadata_StructureIsConsistentAcrossConf var chatClientAgent1 = new ChatClientAgent(mockChatClient1.Object, new ChatClientAgentOptions { - Instructions = "Test instructions 1" + ChatOptions = new() { Instructions = "Test instructions 1" } }); var chatClientAgent2 = new ChatClientAgent(mockChatClient2.Object, new ChatClientAgentOptions { - Instructions = "Test instructions 2" + ChatOptions = new() { Instructions = "Test instructions 2" } }); // Act @@ -1708,7 +1675,7 @@ public void GetService_RequestingChatClientAgentType_ReturnsBaseImplementation() var mockChatClient = new Mock(); var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1732,7 +1699,7 @@ public void GetService_RequestingAIAgentType_ReturnsBaseImplementation() var mockChatClient = new Mock(); var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act @@ -1757,7 +1724,7 @@ public void GetService_RequestingIChatClientWithServiceKey_ReturnsOwnChatClient( var mockChatClient = new Mock(); var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act - Request IChatClient with a service key (base.GetService will return null due to serviceKey) @@ -1782,7 +1749,7 @@ public void GetService_RequestingUnknownServiceWithServiceKey_CallsUnderlyingCha mockChatClient.Setup(c => c.GetService(typeof(string), "some-key")).Returns("test-result"); var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions" + ChatOptions = new() { Instructions = "Test instructions" } }); // Act - Request string with a service key (base.GetService will return null due to serviceKey) @@ -1823,7 +1790,7 @@ public async Task VerifyChatClientAgentStreamingAsync() ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions" + ChatOptions = new() { Instructions = "test instructions" } }); // Act @@ -1870,7 +1837,7 @@ public async Task RunStreamingAsyncUsesChatMessageStoreWhenNoConversationIdRetur mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", + ChatOptions = new() { Instructions = "test instructions" }, ChatMessageStoreFactory = mockFactory.Object }); @@ -1905,7 +1872,7 @@ public async Task RunStreamingAsyncIgnoresChatMessageStoreWhenConversationIdRetu mockFactory.Setup(f => f(It.IsAny())).Returns(new InMemoryChatMessageStore()); ChatClientAgent agent = new(mockService.Object, options: new() { - Instructions = "test instructions", + ChatOptions = new() { Instructions = "test instructions" }, ChatMessageStoreFactory = mockFactory.Object }); @@ -1931,7 +1898,7 @@ public void GetNewThreadUsesAIContextProviderFactoryIfProvided() var factoryCalled = false; var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions { - Instructions = "Test instructions", + ChatOptions = new() { Instructions = "Test instructions" }, AIContextProviderFactory = _ => { factoryCalled = true; diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientBuilderExtensionsTests.cs index 3877358644..3407f172a2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientBuilderExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientBuilderExtensionsTests.cs @@ -90,7 +90,7 @@ public void BuildAIAgent_WithOptions_CreatesAgentWithOptions() { Name = "AgentWithOptions", Description = "Desc", - Instructions = "Instr", + ChatOptions = new() { Instructions = "Instr" }, UseProvidedChatClientAsIs = true }; @@ -115,7 +115,7 @@ public void BuildAIAgent_WithOptionsAndServices_CreatesAgentCorrectly() var options = new ChatClientAgentOptions { Name = "ServiceAgent", - Instructions = "Service instructions" + ChatOptions = new() { Instructions = "Service instructions" } }; // Act @@ -148,7 +148,7 @@ public void BuildAIAgent_WithNullBuilderAndOptions_Throws() ChatClientBuilder builder = null!; // Act & Assert - Assert.Throws(() => builder.BuildAIAgent(options: new() { Instructions = "instructions" })); + Assert.Throws(() => builder.BuildAIAgent(options: new() { ChatOptions = new() { Instructions = "instructions" } })); } [Fact] @@ -166,7 +166,7 @@ public void BuildAIAgent_WithMiddleware_BuildsCorrectPipeline() var agent = builder.BuildAIAgent( new ChatClientAgentOptions { - Instructions = "Middleware test", + ChatOptions = new() { Instructions = "Middleware test" }, UseProvidedChatClientAsIs = true } ); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientExtensionsTests.cs index 182de0be5b..51beb6aa2e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientExtensionsTests.cs @@ -57,7 +57,7 @@ public void CreateAIAgent_WithOptions_CreatesAgentWithOptions() { Name = "AgentWithOptions", Description = "Desc", - Instructions = "Instr", + ChatOptions = new() { Instructions = "Instr" }, UseProvidedChatClientAsIs = true }; @@ -89,6 +89,6 @@ public void CreateAIAgent_WithNullClientAndOptions_Throws() IChatClient chatClient = null!; // Act & Assert - Assert.Throws(() => chatClient.CreateAIAgent(options: new() { Instructions = "instructions" })); + Assert.Throws(() => chatClient.CreateAIAgent(options: new() { ChatOptions = new() { Instructions = "instructions" } })); } } diff --git a/dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs b/dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs index c5a683ec2d..273244fc24 100644 --- a/dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs +++ b/dotnet/tests/OpenAIAssistant.IntegrationTests/OpenAIAssistantClientExtensionsTests.cs @@ -37,14 +37,24 @@ public async Task CreateAIAgentAsync_WithAIFunctionTool_InvokesFunctionAsync(str { "CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync( model: s_config.ChatModelId!, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [weatherFunction])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [weatherFunction] + } + }), "CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent( model: s_config.ChatModelId!, - options: new ChatClientAgentOptions( - instructions: AgentInstructions, - tools: [weatherFunction])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = AgentInstructions, + Tools = [weatherFunction] + } + }), "CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync( model: s_config.ChatModelId!, instructions: AgentInstructions, @@ -94,14 +104,24 @@ public async Task CreateAIAgentAsync_WithHostedCodeInterpreter_RunsCodeAsync(str { "CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync( model: s_config.ChatModelId!, - options: new ChatClientAgentOptions( - instructions: Instructions, - tools: [codeInterpreterTool])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = Instructions, + Tools = [codeInterpreterTool] + } + }), "CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent( model: s_config.ChatModelId!, - options: new ChatClientAgentOptions( - instructions: Instructions, - tools: [codeInterpreterTool])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = Instructions, + Tools = [codeInterpreterTool] + } + }), "CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync( model: s_config.ChatModelId!, instructions: Instructions, @@ -159,14 +179,24 @@ You are a helpful agent that can help fetch data from files you know about. { "CreateWithChatClientAgentOptionsAsync" => await this._assistantClient.CreateAIAgentAsync( model: s_config.ChatModelId!, - options: new ChatClientAgentOptions( - instructions: Instructions, - tools: [fileSearchTool])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = Instructions, + Tools = [fileSearchTool] + } + }), "CreateWithChatClientAgentOptionsSync" => this._assistantClient.CreateAIAgent( model: s_config.ChatModelId!, - options: new ChatClientAgentOptions( - instructions: Instructions, - tools: [fileSearchTool])), + options: new ChatClientAgentOptions() + { + ChatOptions = new() + { + Instructions = Instructions, + Tools = [fileSearchTool] + } + }), "CreateWithParamsAsync" => await this._assistantClient.CreateAIAgentAsync( model: s_config.ChatModelId!, instructions: Instructions, diff --git a/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs b/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs index f98540d8cc..656d310ddf 100644 --- a/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs +++ b/dotnet/tests/OpenAIChatCompletion.IntegrationTests/OpenAIChatCompletionFixture.cs @@ -47,8 +47,7 @@ public Task CreateChatClientAgentAsync( return Task.FromResult(new ChatClientAgent(chatClient, options: new() { Name = name, - Instructions = instructions, - ChatOptions = new() { Tools = aiTools } + ChatOptions = new() { Instructions = instructions, Tools = aiTools } })); } diff --git a/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs b/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs index d223a65e28..c0013cb952 100644 --- a/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs +++ b/dotnet/tests/OpenAIResponse.IntegrationTests/OpenAIResponseFixture.cs @@ -73,9 +73,9 @@ public async Task CreateChatClientAgentAsync( options: new() { Name = name, - Instructions = instructions, ChatOptions = new ChatOptions { + Instructions = instructions, Tools = aiTools, RawRepresentationFactory = new Func(_ => new ResponseCreationOptions() { StoredOutputEnabled = store }) },