From 16c8b74780055a60009d226fae0989ec756d5bbb Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:31:29 +0000 Subject: [PATCH 01/30] Initial draft of the declarative agent schema for review --- .../0018-custom-prompt-template-formats.md | 285 --------------- .../NNNN-declarative-agent-schema.md | 332 ++++++++++++++++++ 2 files changed, 332 insertions(+), 285 deletions(-) delete mode 100644 docs/decisions/0018-custom-prompt-template-formats.md create mode 100644 docs/decisions/NNNN-declarative-agent-schema.md diff --git a/docs/decisions/0018-custom-prompt-template-formats.md b/docs/decisions/0018-custom-prompt-template-formats.md deleted file mode 100644 index 5cd1f7f90cb4..000000000000 --- a/docs/decisions/0018-custom-prompt-template-formats.md +++ /dev/null @@ -1,285 +0,0 @@ ---- -status: approved -contact: markwallace-microsoft -date: 2023-10-26 -deciders: matthewbolanos, markwallace-microsoft, SergeyMenshykh, RogerBarreto -consulted: dmytrostruk -informed: ---- - -# Custom Prompt Template Formats - -## Context and Problem Statement - -Semantic Kernel currently supports a custom prompt template language that allows for variable interpolation and function execution. -Semantic Kernel allows for custom prompt template formats to be integrated e.g., prompt templates using [Handlebars](https://handlebarsjs.com/) syntax. - -The purpose of this ADR is to describe how a custom prompt template formats will be supported in the Semantic Kernel. - -### Current Design - -By default the `Kernel` uses the `BasicPromptTemplateEngine` which supports the Semantic Kernel specific template format. - -#### Code Patterns - -Below is an expanded example of how to create a semantic function from a prompt template string which uses the built-in Semantic Kernel format: - -```csharp -IKernel kernel = Kernel.Builder - .WithPromptTemplateEngine(new BasicPromptTemplateEngine()) - .WithOpenAIChatCompletionService( - modelId: openAIModelId, - apiKey: openAIApiKey) - .Build(); - -kernel.ImportFunctions(new TimePlugin(), "time"); - -string templateString = "Today is: {{time.Date}} Is it weekend time (weekend/not weekend)?"; -var promptTemplateConfig = new PromptTemplateConfig(); -var promptTemplate = new PromptTemplate(templateString, promptTemplateConfig, kernel.PromptTemplateEngine); -var kindOfDay = kernel.RegisterSemanticFunction("KindOfDay", promptTemplateConfig, promptTemplate); - -var result = await kernel.RunAsync(kindOfDay); -Console.WriteLine(result.GetValue()); -``` - -We have an extension method `var kindOfDay = kernel.CreateSemanticFunction(promptTemplate);` to simplify the process to create and register a semantic function but the expanded format is shown above to highlight the dependency on `kernel.PromptTemplateEngine`. -Also the `BasicPromptTemplateEngine` is the default prompt template engine and will be loaded automatically if the package is available and no other prompt template engine is specified. - -Some issues with this: - -1. You need to have a `Kernel` instance to create a semantic function, which is contrary to one of the goals of allow semantic functions to be created once and reused across multiple `Kernel` instances. -1. `Kernel` only supports a single `IPromptTemplateEngine` so we cannot support using multiple prompt templates at the same time. -1. `IPromptTemplateEngine` is stateless and must perform a parse of the template for each render -1. Our semantic function extension methods rely on our implementation of `IPromptTemplate` (i.e., `PromptTemplate`) which stores the template string and uses the `IPromptTemplateEngine` to render it every time. Note implementations of `IPromptTemplate` are currently stateful as they also store the parameters. - -#### Performance - -The `BasicPromptTemplateEngine` uses the `TemplateTokenizer` to parse the template i.e. extract the blocks. -Then it renders the template i.e. inserts variables and executes functions. Some sample timings for these operations: - -| Operation | Ticks | Milliseconds | -| ---------------- | ------- | ------------ | -| Extract blocks | 1044427 | 103 | -| Render variables | 168 | 0 | - -Sample template used was: `"{{variable1}} {{variable2}} {{variable3}} {{variable4}} {{variable5}}"` - -**Note: We will use the sample implementation to support the f-string template format.** - -Using `HandlebarsDotNet` for the same use case results in the following timings: - -| Operation | Ticks | Milliseconds | -| ---------------- | ----- | ------------ | -| Compile template | 66277 | 6 | -| Render variables | 4173 | 0 | - -**By separating the extract blocks/compile from the render variables operation it will be possible to optimise performance by compiling templates just once.** - -#### Implementing a Custom Prompt Template Engine - -There are two interfaces provided: - -```csharp -public interface IPromptTemplateEngine -{ - Task RenderAsync(string templateText, SKContext context, CancellationToken cancellationToken = default); -} - -public interface IPromptTemplate -{ - IReadOnlyList Parameters { get; } - - public Task RenderAsync(SKContext executionContext, CancellationToken cancellationToken = default); -} -``` - -A prototype implementation of a handlebars prompt template engine could look something like this: - -```csharp -public class HandlebarsTemplateEngine : IPromptTemplateEngine -{ - private readonly ILoggerFactory _loggerFactory; - - public HandlebarsTemplateEngine(ILoggerFactory? loggerFactory = null) - { - this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; - } - - public async Task RenderAsync(string templateText, SKContext context, CancellationToken cancellationToken = default) - { - var handlebars = HandlebarsDotNet.Handlebars.Create(); - - var functionViews = context.Functions.GetFunctionViews(); - foreach (FunctionView functionView in functionViews) - { - var skfunction = context.Functions.GetFunction(functionView.PluginName, functionView.Name); - handlebars.RegisterHelper($"{functionView.PluginName}_{functionView.Name}", async (writer, hcontext, parameters) => - { - var result = await skfunction.InvokeAsync(context).ConfigureAwait(true); - writer.WriteSafeString(result.GetValue()); - }); - } - - var template = handlebars.Compile(templateText); - - var prompt = template(context.Variables); - - return await Task.FromResult(prompt).ConfigureAwait(true); - } -} -``` - -**Note: This is just a prototype implementation for illustration purposes only.** - -Some issues: - -1. The `IPromptTemplate` interface is not used and causes confusion. -1. There is no way to allow developers to support multiple prompt template formats at the same time. - -There is one implementation of `IPromptTemplate` provided in the Semantic Kernel core package. -The `RenderAsync` implementation just delegates to the `IPromptTemplateEngine`. -The `Parameters` list get's populated with the parameters defined in the `PromptTemplateConfig` and any missing variables defined in the template. - -#### Handlebars Considerations - -Handlebars does not support dynamic binding of helpers. Consider the following snippet: - -```csharp -HandlebarsHelper link_to = (writer, context, parameters) => -{ - writer.WriteSafeString($"{context["text"]}"); -}; - -string source = @"Click here: {{link_to}}"; - -var data = new -{ - url = "https://github.com/rexm/handlebars.net", - text = "Handlebars.Net" -}; - -// Act -var handlebars = HandlebarsDotNet.Handlebars.Create(); -handlebars.RegisterHelper("link_to", link_to); -var template = handlebars1.Compile(source); -// handlebars.RegisterHelper("link_to", link_to); This also works -var result = template1(data); -``` - -Handlebars allows the helpers to be registered with the `Handlebars` instance either before or after a template is compiled. -The optimum would be to have a shared `Handlebars` instance for a specific collection of functions and register the helpers just once. -For use cases where the Kernel function collection may have been mutated we will be forced to create a `Handlebars` instance at render time -and then register the helpers. This means we cannot take advantage of the performance improvement provided by compiling the template. - -## Decision Drivers - -In no particular order: - -- Support creating a semantic function without a `IKernel`instance. -- Support late binding of functions i.e., having functions resolved when the prompt is rendered. -- Support allowing the prompt template to be parsed (compiled) just once to optimize performance if needed. -- Support using multiple prompt template formats with a single `Kernel` instance. -- Provide simple abstractions which allow third parties to implement support for custom prompt template formats. - -## Considered Options - -- Obsolete `IPromptTemplateEngine` and replace with `IPromptTemplateFactory`. - -### Obsolete `IPromptTemplateEngine` and replace with `IPromptTemplateFactory` - -ISKFunction class relationships - -Below is an expanded example of how to create a semantic function from a prompt template string which uses the built-in Semantic Kernel format: - -```csharp -// Semantic function can be created once -var promptTemplateFactory = new BasicPromptTemplateFactory(); -string templateString = "Today is: {{time.Date}} Is it weekend time (weekend/not weekend)?"; -var promptTemplate = promptTemplateFactory.CreatePromptTemplate(templateString, new PromptTemplateConfig()); -var kindOfDay = ISKFunction.CreateSemanticFunction("KindOfDay", promptTemplateConfig, promptTemplate) - -// Create Kernel after creating the semantic function -// Later we will support passing a function collection to the KernelBuilder -IKernel kernel = Kernel.Builder - .WithOpenAIChatCompletionService( - modelId: openAIModelId, - apiKey: openAIApiKey) - .Build(); - -kernel.ImportFunctions(new TimePlugin(), "time"); -// Optionally register the semantic function with the Kernel -// kernel.RegisterCustomFunction(kindOfDay); - -var result = await kernel.RunAsync(kindOfDay); -Console.WriteLine(result.GetValue()); -``` - -**Notes:** - -- `BasicPromptTemplateFactory` will be the default implementation and will be automatically provided in `KernelSemanticFunctionExtensions`. Developers will also be able to provide their own implementation. -- The factory uses the new `PromptTemplateConfig.TemplateFormat` to create the appropriate `IPromptTemplate` instance. -- We should look to remove `promptTemplateConfig` as a parameter to `CreateSemanticFunction`. That change is outside of the scope of this ADR. - -The `BasicPromptTemplateFactory` and `BasicPromptTemplate` implementations look as follows: - -```csharp -public sealed class BasicPromptTemplateFactory : IPromptTemplateFactory -{ - private readonly IPromptTemplateFactory _promptTemplateFactory; - private readonly ILoggerFactory _loggerFactory; - - public BasicPromptTemplateFactory(IPromptTemplateFactory promptTemplateFactory, ILoggerFactory? loggerFactory = null) - { - this._promptTemplateFactory = promptTemplateFactory; - this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; - } - - public IPromptTemplate? CreatePromptTemplate(string templateString, PromptTemplateConfig promptTemplateConfig) - { - if (promptTemplateConfig.TemplateFormat.Equals(PromptTemplateConfig.SEMANTICKERNEL, System.StringComparison.Ordinal)) - { - return new BasicPromptTemplate(templateString, promptTemplateConfig, this._loggerFactory); - } - else if (this._promptTemplateFactory is not null) - { - return this._promptTemplateFactory.CreatePromptTemplate(templateString, promptTemplateConfig); - } - - throw new SKException($"Invalid prompt template format {promptTemplateConfig.TemplateFormat}"); - } -} - -public sealed class BasicPromptTemplate : IPromptTemplate -{ - public BasicPromptTemplate(string templateString, PromptTemplateConfig promptTemplateConfig, ILoggerFactory? loggerFactory = null) - { - this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; - this._logger = this._loggerFactory.CreateLogger(typeof(BasicPromptTemplate)); - this._templateString = templateString; - this._promptTemplateConfig = promptTemplateConfig; - this._parameters = new(() => this.InitParameters()); - this._blocks = new(() => this.ExtractBlocks(this._templateString)); - this._tokenizer = new TemplateTokenizer(this._loggerFactory); - } - - public IReadOnlyList Parameters => this._parameters.Value; - - public async Task RenderAsync(SKContext executionContext, CancellationToken cancellationToken = default) - { - return await this.RenderAsync(this._blocks.Value, executionContext, cancellationToken).ConfigureAwait(false); - } - - // Not showing the implementation details -} -``` - -**Note:** - -- The call to `ExtractBlocks` is called lazily once for each prompt template -- The `RenderAsync` doesn't need to extract the blocks every time - -## Decision Outcome - -Chosen option: "Obsolete `IPromptTemplateEngine` and replace with `IPromptTemplateFactory`", because -addresses the requirements and provides good flexibility for the future. diff --git a/docs/decisions/NNNN-declarative-agent-schema.md b/docs/decisions/NNNN-declarative-agent-schema.md new file mode 100644 index 000000000000..34ad09d1826a --- /dev/null +++ b/docs/decisions/NNNN-declarative-agent-schema.md @@ -0,0 +1,332 @@ +--- +# These are optional elements. Feel free to remove any of them. +status: proposed +contact: markwallace-microsoft +date: 2025-01-17 +deciders: markwallace-microsoft, bentho, crickman +consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication} +informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication} +--- + +# Schema for Declarative Agent Format + +## Context and Problem Statement + +This ADR describes a schema which can be used to define an Agent which can be loaded and executed using the Semantic Kernel Agent Framework. + +Currently the Agent Framework uses a code first approach to allow Agents to be defined and executed. +Using the schema defined by this ADR developers will be able to declaratively define an Agent and have the Semantic Kernel instantiate and execute the Agent. + +Here is some pseudo code to illustrate what we need to be able to do: + +```csharp +Kernel kernel = ... +string agentYaml = EmbeddedResource.Read("MyAgent.yaml"); +AgentFactory agentFactory = new AggregatorAgentFactory( + new ChatCompletionFactory(), + new OpenAIAssistantAgentFactory(), + new XXXAgentFactory()); +Agent agent = kernel.LoadAgentFromYaml(agentYaml); + +ChatHistory chatHistory = new(); +chatHistory.AddUserMessage(input); +await foreach (ChatMessageContent content in agent.InvokeAsync(chatHistory)) +{ + chatHistory.Add(content); +} +``` + +**Note:** + +1. The above pattern does is not supported at present. +2. We need to decide if the Agent Framework should define an abstraction to allow any Agent to be invoked. +3. If we are supporting an abstraction to allow any Agent to be invoked, what should the pattern look like. +4. We will support JSON also as an out-of-the-box option. + +Currently Semantic Kernel supports two Agent types and these have the following properties: + +1. [`ChatCompletionAgent`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.semantickernel.agents.chatcompletionagent?view=semantic-kernel-dotnet): + - `Arguments`: Optional arguments for the agent. (Inherited from ChatHistoryKernelAgent) + - `Description`: The description of the agent (optional). (Inherited from Agent) + - `HistoryReducer`: (Inherited from ChatHistoryKernelAgent) + - `Id`: The identifier of the agent (optional). (Inherited from Agent) + - `Instructions`: The instructions of the agent (optional). (Inherited from KernelAgent) + - `Kernel`: The Kernel containing services, plugins, and filters for use throughout the agent lifetime. (Inherited from KernelAgent) + - `Logger`: The ILogger associated with this Agent. (Inherited from Agent) + - `LoggerFactory`: A ILoggerFactory for this Agent. (Inherited from Agent) + - `Name`: The name of the agent (optional). (Inherited from Agent) +2. ['OpenAIAssistantAgent'](https://learn.microsoft.com/en-us/dotnet/api/microsoft.semantickernel.agents.agent.description?view=semantic-kernel-dotnet#microsoft-semantickernel-agents-agent-description): + - `Arguments`: Optional arguments for the agent. + - `Definition`: The assistant definition. + - `Description`: The description of the agent (optional). (Inherited from Agent) + - `Id`: The identifier of the agent (optional). (Inherited from Agent) + - `Instructions`: The instructions of the agent (optional). (Inherited from KernelAgent) + - `IsDeleted`: Set when the assistant has been deleted via DeleteAsync(CancellationToken). An assistant removed by other means will result in an exception when invoked. + - `Kernel`: The Kernel containing services, plugins, and filters for use throughout the agent lifetime. (Inherited from KernelAgent) + - `Logger`: The ILogger associated with this Agent. (Inherited from Agent) + - `LoggerFactory`: A ILoggerFactory for this Agent. (Inherited from Agent) + - `Name`: The name of the agent (optional). (Inherited from Agent) + - `PollingOptions`: Defines polling behavior + +When executing an Agent that was declaratively defined some of the properties will be determined by the runtime: + +- `Kernel`: The runtime will be responsible for create the `Kernel` instance to be used by the Agent. This `Kernel` instance must be configured with the models and tools that the Agent requires. +- `Logger` or `LoggerFactory`: The runtime will be responsible for providing a correctly configured `Logger` or `LoggerFactory`. +- **Functions**: The runtime must be able to resolve any functions required by the Agent. E.g. the VSCode extension will provide a very basic runtime to allow developers to test Agents and it should eb able to resolve `KernelFunctions` defined in the current project. See later in the ADR for an example of this. + +For Agent properties that define behaviors e.g. `HistoryReducer` the Semantic Kernel **SHOULD**: + +- Provide implementations that can be configured declaratively i.e., for the most common scenarios we expect developers to encounter. +- Allow implementations to be resolved from the `Kernel` e.g., as required services or possibly `KernelFunction`'s. + +## Decision Drivers + +- Schema **MUST** allow model settings to be assigned to an Agent +- Schema **MUST** allow functions to be assigned to an Agent +- Schema **MUST** allow a Semantic Kernel prompt to be used to define the Agent instructions +- Schema **MUST** be extensible so that support for new Agent types can be added to Semantic Kernel +- Schema **MUST** allow third parties to contribute new Agent types to Semantic Kernel +- … + +## Considered Options + +- Use same semantics as the [Semantic Kernel Prompt Schema](https://learn.microsoft.com/en-us/semantic-kernel/concepts/prompts/yaml-schema#sample-yaml-prompt) +- {title of option 2} + +### Use Same Semantics as the Semantic Kernel Prompt Schema + +Consider the following use cases: + +1. `ChatCompletionAgent` +2. `ChatCompletionAgent` using Prompt Template +3. `ChatCompletionAgent` with Function Calling +4. `OpenAIAssistantAgent` with Function Calling +5. `OpenAIAssistantAgent` with Tools + +#### `ChatCompletionAgent` + +Code first approach: + +```csharp +ChatCompletionAgent agent = + new() + { + Name = "Parrot", + Instructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.", + Kernel = kernel, + }; +``` + +Declarative: + +```yml +name: Parrot +instructions: Repeat the user message in the voice of a pirate and then end with a parrot sound. +``` + +**Note**: `ChatCompletionAgent` could be the default agent type hence no explicit `type` property is required. + +#### `ChatCompletionAgent` using Prompt Template + +Code first approach: + +```csharp +string generateStoryYaml = EmbeddedResource.Read("GenerateStory.yaml"); +PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(generateStoryYaml); + +ChatCompletionAgent agent = + new(templateConfig, new KernelPromptTemplateFactory()) + { + Kernel = this.CreateKernelWithChatCompletion(), + Arguments = new KernelArguments() + { + { "topic", "Dog" }, + { "length", "3" }, + } + }; +``` + +Declarative: + +```yml +name: GenerateStory +template: | + Tell a story about {{$topic}} that is {{$length}} sentences long. +template_format: semantic-kernel +description: A function that generates a story about a topic. +input_variables: + - name: topic + description: The topic of the story. + is_required: true + default: dog + - name: length + description: The number of sentences in the story. + is_required: true + default: 3 +``` + +**Note**: Only elements from the prompt template schema are needed. + +#### `ChatCompletionAgent` with Function Calling + +Code first approach: + +```csharp +ChatCompletionAgent agent = + new() + { + Instructions = "Answer questions about the menu.", + Name = "RestaurantHost", + Description = "This agent answers questions about the menu.", + Kernel = kernel, + Arguments = new KernelArguments(new OpenAIPromptExecutionSettings() { Temperature = 0.4, FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), + }; + +KernelPlugin plugin = KernelPluginFactory.CreateFromType(); +agent.Kernel.Plugins.Add(plugin); +``` + +Declarative: + +```yml +name: RestaurantHost +instructions: Answer questions about the menu. +description: This agent answers questions about the menu. +execution_settings: + default: + temperature: 0.4 + function_choice_behavior: + type: auto + functions: + - MenuPlugin.GetSpecials + - MenuPlugin.GetItemPrice +``` + +#### `OpenAIAssistantAgent` with Function Calling + +Code first approach: + +```csharp +OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + clientProvider: this.GetClientProvider(), + definition: new OpenAIAssistantDefinition("gpt_4o") + { + Instructions = "Answer questions about the menu.", + Name = "RestaurantHost", + Metadata = new Dictionary { { AssistantSampleMetadataKey, bool.TrueString } }, + }, + kernel: new Kernel()); + +KernelPlugin plugin = KernelPluginFactory.CreateFromType(); +agent.Kernel.Plugins.Add(plugin); +``` + +Declarative: + +```yml +name: RestaurantHost +type: openai_assistant +instructions: Answer questions about the menu. +description: This agent answers questions about the menu. +execution_settings: + gpt_4o: + function_choice_behavior: + type: auto + functions: + - MenuPlugin.GetSpecials + - MenuPlugin.GetItemPrice + metadata: + sksample: true +``` + +**Note**: The `Kernel` instance used to create the Agent must have an instance of `OpenAIClientProvider` registered as a service. + +#### `OpenAIAssistantAgent` with Tools + +Code first approach: + +```csharp +OpenAIAssistantAgent agent = + await OpenAIAssistantAgent.CreateAsync( + clientProvider: this.GetClientProvider(), + definition: new(this.Model) + { + Instructions = "You are an Agent that can write and execute code to answer questions.", + Name = "Coder", + EnableCodeInterpreter = true, + EnableFileSearch = true, + Metadata = new Dictionary { { AssistantSampleMetadataKey, bool.TrueString } }, + }, + kernel: new Kernel()); +``` + +Declarative: + +```yml +name: Coder +type: openai_assistant +instructions: You are an Agent that can write and execute code to answer questions. +execution_settings: + default: + enable_code_interpreter: true + enable_file_search: true + metadata: + sksample: true +``` + +## Decision Outcome + +Chosen option: "{title of option 1}", because +{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. + + + +### Consequences + +- Good, because {positive consequence, e.g., improvement of one or more desired qualities, …} +- Bad, because {negative consequence, e.g., compromising one or more desired qualities, …} +- … + + + +## Validation + +{describe how the implementation of/compliance with the ADR is validated. E.g., by a review or an ArchUnit test} + + + +## Pros and Cons of the Options + +### {title of option 1} + + + +{example | description | pointer to more information | …} + +- Good, because {argument a} +- Good, because {argument b} + +- Neutral, because {argument c} +- Bad, because {argument d} +- … + +### {title of other option} + +{example | description | pointer to more information | …} + +- Good, because {argument a} +- Good, because {argument b} +- Neutral, because {argument c} +- Bad, because {argument d} +- … + + + +## More Information + +{You might want to provide additional evidence/confidence for the decision outcome here and/or +document the team agreement on the decision and/or +define when this decision when and how the decision should be realized and if/when it should be re-visited and/or +how the decision is validated. +Links to other decisions and resources might appear here as well.} From d4b6ee14c3a6fd5cb113c28ae3cdbdfa0de8b3ed Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:21:59 +0000 Subject: [PATCH 02/30] Add M365 Declarative Agents as an option --- .../NNNN-declarative-agent-schema.md | 227 ++++++++++++------ 1 file changed, 151 insertions(+), 76 deletions(-) diff --git a/docs/decisions/NNNN-declarative-agent-schema.md b/docs/decisions/NNNN-declarative-agent-schema.md index 34ad09d1826a..1c27de7d36af 100644 --- a/docs/decisions/NNNN-declarative-agent-schema.md +++ b/docs/decisions/NNNN-declarative-agent-schema.md @@ -19,23 +19,35 @@ Using the schema defined by this ADR developers will be able to declaratively de Here is some pseudo code to illustrate what we need to be able to do: +```csharp +Kernel kernel = ... +string agentYaml = EmbeddedResource.Read("MyAgent.yaml"); +Agent agent = kernel.CreateAgentFromYaml(agentYaml); + +agent.InvokeAsync(input); +``` + +The above code represents the simplest case would work as follows: + +1. The Agent runtime is responsible for creating a `Kernel` instance which comes fully configured with all services, functions, etc. +2. The `CreateAgentFromYaml` will create one of the built-in Agent instances (currently just `ChatCompletionAgent`). +3. The new Agent instance is initialized with it's own `Kernel` instance configured the services and tools it requires and a default initial state. +4. The `Agent` abstraction contains a method to allow the Agent instance to be invoked with user input. + ```csharp Kernel kernel = ... string agentYaml = EmbeddedResource.Read("MyAgent.yaml"); AgentFactory agentFactory = new AggregatorAgentFactory( - new ChatCompletionFactory(), + new ChatCompletionAgentFactory(), new OpenAIAssistantAgentFactory(), new XXXAgentFactory()); -Agent agent = kernel.LoadAgentFromYaml(agentYaml); - -ChatHistory chatHistory = new(); -chatHistory.AddUserMessage(input); -await foreach (ChatMessageContent content in agent.InvokeAsync(chatHistory)) -{ - chatHistory.Add(content); -} +Agent agent = kernel.CreateAgentFromYaml(agentYaml, agentFactory, agentState); + +agent.InvokeAsync(input); ``` +The above example shows how different Agent types are supported and also how the initial Agent state can be specified. + **Note:** 1. The above pattern does is not supported at present. @@ -90,10 +102,69 @@ For Agent properties that define behaviors e.g. `HistoryReducer` the Semantic Ke ## Considered Options -- Use same semantics as the [Semantic Kernel Prompt Schema](https://learn.microsoft.com/en-us/semantic-kernel/concepts/prompts/yaml-schema#sample-yaml-prompt) -- {title of option 2} +- Use the [Declarative agent schema 1.2 for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/declarative-agent-manifest-1.2) +- Extend the Declarative agent schema 1.2 for Microsoft 365 Copilot +- Extend the [Semantic Kernel prompt schema](https://learn.microsoft.com/en-us/semantic-kernel/concepts/prompts/yaml-schema#sample-yaml-prompt) + +## Pros and Cons of the Options + +### Use the Declarative agent schema 1.2 for Microsoft 365 Copilot + +Semantic Kernel already has support this, see the [declarative Agent concept sample](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/Agents/DeclarativeAgents.cs). + +- Good, this is an existing standard adopted by the Microsoft 365 Copilot. +- Neutral, the schema splits tools into two properties i.e. `capabilities` which includes code interpreter and `actions` which specifies an API plugin manifest. +- Bad, because it does support different types of Agents. +- Bad, because it doesn't provide a way to specific and configure the AI Model to associate with the Agent. +- Bad, because it doesn't provide a way to use a Prompt Template for the Agent instructions. +- Bad, because `actions` property is focussed on calling REST API's and cater for native and semantic functions. + +### Extend the Declarative agent schema 1.2 for Microsoft 365 Copilot + +Some of the possible extensions include: -### Use Same Semantics as the Semantic Kernel Prompt Schema +1. Agent instructions can be created using a Prompt Template. +2. Agent Model settings can be specified including fallbacks based on the available models. +3. Better definition of functions e.g. support for native and semantic. + +- Good, because {argument a} +- Good, because {argument b} +- Neutral, because {argument c} +- Bad, because {argument d} +- … + +### Extend the Semantic Kernel Prompt Schema + +- Good, because {argument a} +- Good, because {argument b} +- Neutral, because {argument c} +- Bad, because {argument d} +- … + +## Decision Outcome + +Chosen option: "{title of option 1}", because +{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. + + + +### Consequences + +- Good, because {positive consequence, e.g., improvement of one or more desired qualities, …} +- Bad, because {negative consequence, e.g., compromising one or more desired qualities, …} +- … + + + +## Validation + +{describe how the implementation of/compliance with the ADR is validated. E.g., by a review or an ArchUnit test} + + + +## More Information + +Below are examples showing the code first and equivalent declarative syntax fot creating different types of Agents. Consider the following use cases: @@ -117,14 +188,17 @@ ChatCompletionAgent agent = }; ``` -Declarative: +Declarative using M365 or Semantic Kernel schema: ```yml name: Parrot instructions: Repeat the user message in the voice of a pirate and then end with a parrot sound. ``` -**Note**: `ChatCompletionAgent` could be the default agent type hence no explicit `type` property is required. +**Note**: + +- Both M365 and Semantic Kernel schema would be identical +- `ChatCompletionAgent` could be the default agent type hence no explicit `type` property is required. #### `ChatCompletionAgent` using Prompt Template @@ -146,8 +220,16 @@ ChatCompletionAgent agent = }; ``` -Declarative: +Declarative using M365 Agent schema: + +```yml +name: GenerateStory +instructions: ${file['./GenerateStory.yaml']} +``` + +Agent YAML points to another file, the Declarative Agent implementation in Semantic Kernel already uses this technique to load a separate instructions file. +Prompt template which is used to define the instructions. ```yml name: GenerateStory template: | @@ -165,7 +247,7 @@ input_variables: default: 3 ``` -**Note**: Only elements from the prompt template schema are needed. +**Note**: Semantic Kernel could load this file directly. #### `ChatCompletionAgent` with Function Calling @@ -186,7 +268,23 @@ KernelPlugin plugin = KernelPluginFactory.CreateFromType(); agent.Kernel.Plugins.Add(plugin); ``` -Declarative: +Declarative using M365 Agent schema: + +```yml +name: RestaurantHost +instructions: Answer questions about the menu. +description: This agent answers questions about the menu. +actions: + - id: MenuPlugin +``` + +**Note**: + +- There is no way to specify `Temperature` +- There is no way to specify the function choice behavior (e.g. could not specify required) +- All functions in the Plugin would be used. + +Declarative using Semantic Kernel schema: ```yml name: RestaurantHost @@ -222,7 +320,22 @@ KernelPlugin plugin = KernelPluginFactory.CreateFromType(); agent.Kernel.Plugins.Add(plugin); ``` -Declarative: +Declarative using M365 Agent schema: + +```yml +name: RestaurantHost +type: openai_assistant +instructions: Answer questions about the menu. +description: This agent answers questions about the menu. +metadata: + sksample: true +actions: + - id: MenuPlugin +``` + +**Note**: `type` and `metadata` properties need to be added somehow + +Declarative using Semantic Kernel schema: ```yml name: RestaurantHost @@ -230,7 +343,7 @@ type: openai_assistant instructions: Answer questions about the menu. description: This agent answers questions about the menu. execution_settings: - gpt_4o: + default: function_choice_behavior: type: auto functions: @@ -261,7 +374,25 @@ OpenAIAssistantAgent agent = kernel: new Kernel()); ``` -Declarative: +Declarative using M365 Agent schema: + +```yml +name: RestaurantHost +type: openai_assistant +instructions: Answer questions about the menu. +description: This agent answers questions about the menu. +metadata: + sksample: true +capabilities: + - name: CodeInterpreter + - name: FileSearch +actions: + - id: MenuPlugin +``` + +**Note**: `FileSearch` capability needs to be added + +Declarative using Semantic Kernel: ```yml name: Coder @@ -274,59 +405,3 @@ execution_settings: metadata: sksample: true ``` - -## Decision Outcome - -Chosen option: "{title of option 1}", because -{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. - - - -### Consequences - -- Good, because {positive consequence, e.g., improvement of one or more desired qualities, …} -- Bad, because {negative consequence, e.g., compromising one or more desired qualities, …} -- … - - - -## Validation - -{describe how the implementation of/compliance with the ADR is validated. E.g., by a review or an ArchUnit test} - - - -## Pros and Cons of the Options - -### {title of option 1} - - - -{example | description | pointer to more information | …} - -- Good, because {argument a} -- Good, because {argument b} - -- Neutral, because {argument c} -- Bad, because {argument d} -- … - -### {title of other option} - -{example | description | pointer to more information | …} - -- Good, because {argument a} -- Good, because {argument b} -- Neutral, because {argument c} -- Bad, because {argument d} -- … - - - -## More Information - -{You might want to provide additional evidence/confidence for the decision outcome here and/or -document the team agreement on the decision and/or -define when this decision when and how the decision should be realized and if/when it should be re-visited and/or -how the decision is validated. -Links to other decisions and resources might appear here as well.} From e800600344ba3c5083d40f045332754e6d599ebe Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:17:50 +0000 Subject: [PATCH 03/30] Fix type and update decision drivers --- docs/decisions/NNNN-declarative-agent-schema.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/decisions/NNNN-declarative-agent-schema.md b/docs/decisions/NNNN-declarative-agent-schema.md index 1c27de7d36af..28fe6ab30632 100644 --- a/docs/decisions/NNNN-declarative-agent-schema.md +++ b/docs/decisions/NNNN-declarative-agent-schema.md @@ -93,11 +93,12 @@ For Agent properties that define behaviors e.g. `HistoryReducer` the Semantic Ke ## Decision Drivers -- Schema **MUST** allow model settings to be assigned to an Agent -- Schema **MUST** allow functions to be assigned to an Agent -- Schema **MUST** allow a Semantic Kernel prompt to be used to define the Agent instructions -- Schema **MUST** be extensible so that support for new Agent types can be added to Semantic Kernel -- Schema **MUST** allow third parties to contribute new Agent types to Semantic Kernel +- Schema **MUST** be Agent Service agnostic i.e., will work with Agents targeting Azure, Open AI, Mistral AI, ... +- Schema **MUST** allow model settings to be assigned to an Agent. +- Schema **MUST** allow tools (e.g. functions, code interpreter, file search, ...) to be assigned to an Agent. +- Schema **MUST** allow a Semantic Kernel prompt (including Prompty format) to be used to define the Agent instructions. +- Schema **MUST** be extensible so that support for new Agent types with their own settings and tools, can be added to Semantic Kernel. +- Schema **MUST** allow third parties to contribute new Agent types to Semantic Kernel. - … ## Considered Options @@ -164,7 +165,7 @@ Chosen option: "{title of option 1}", because ## More Information -Below are examples showing the code first and equivalent declarative syntax fot creating different types of Agents. +Below are examples showing the code first and equivalent declarative syntax for creating different types of Agents. Consider the following use cases: From 59f2df5e47dc35daa59976d0a871388ba60509b2 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:13:22 +0000 Subject: [PATCH 04/30] Update decision drivers --- .../NNNN-declarative-agent-schema.md | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/decisions/NNNN-declarative-agent-schema.md b/docs/decisions/NNNN-declarative-agent-schema.md index 28fe6ab30632..6e35df0b6306 100644 --- a/docs/decisions/NNNN-declarative-agent-schema.md +++ b/docs/decisions/NNNN-declarative-agent-schema.md @@ -50,12 +50,11 @@ The above example shows how different Agent types are supported and also how the **Note:** -1. The above pattern does is not supported at present. +1. Providing Agent state is not supported in the Agent Framework at present. 2. We need to decide if the Agent Framework should define an abstraction to allow any Agent to be invoked. -3. If we are supporting an abstraction to allow any Agent to be invoked, what should the pattern look like. -4. We will support JSON also as an out-of-the-box option. +3. We will support JSON also as an out-of-the-box option. -Currently Semantic Kernel supports two Agent types and these have the following properties: +Currently Semantic Kernel supports three Agent types and these have the following properties: 1. [`ChatCompletionAgent`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.semantickernel.agents.chatcompletionagent?view=semantic-kernel-dotnet): - `Arguments`: Optional arguments for the agent. (Inherited from ChatHistoryKernelAgent) @@ -67,7 +66,7 @@ Currently Semantic Kernel supports two Agent types and these have the following - `Logger`: The ILogger associated with this Agent. (Inherited from Agent) - `LoggerFactory`: A ILoggerFactory for this Agent. (Inherited from Agent) - `Name`: The name of the agent (optional). (Inherited from Agent) -2. ['OpenAIAssistantAgent'](https://learn.microsoft.com/en-us/dotnet/api/microsoft.semantickernel.agents.agent.description?view=semantic-kernel-dotnet#microsoft-semantickernel-agents-agent-description): +2. [`OpenAIAssistantAgent`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.semantickernel.agents.agent.description?view=semantic-kernel-dotnet#microsoft-semantickernel-agents-agent-description): - `Arguments`: Optional arguments for the agent. - `Definition`: The assistant definition. - `Description`: The description of the agent (optional). (Inherited from Agent) @@ -79,8 +78,19 @@ Currently Semantic Kernel supports two Agent types and these have the following - `LoggerFactory`: A ILoggerFactory for this Agent. (Inherited from Agent) - `Name`: The name of the agent (optional). (Inherited from Agent) - `PollingOptions`: Defines polling behavior +3. [`AzureAIAgent`](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/Agents/AzureAI/AzureAIAgent.cs) + - `Definition`: The assistant definition. + - `PollingOptions`: Defines polling behavior for run processing. + - `Description`: The description of the agent (optional). (Inherited from Agent) + - `Id`: The identifier of the agent (optional). (Inherited from Agent) + - `Instructions`: The instructions of the agent (optional). (Inherited from KernelAgent) + - `IsDeleted`: Set when the assistant has been deleted via DeleteAsync(CancellationToken). An assistant removed by other means will result in an exception when invoked. + - `Kernel`: The Kernel containing services, plugins, and filters for use throughout the agent lifetime. (Inherited from KernelAgent) + - `Logger`: The ILogger associated with this Agent. (Inherited from Agent) + - `LoggerFactory`: A ILoggerFactory for this Agent. (Inherited from Agent) + - `Name`: The name of the agent (optional). (Inherited from Agent) -When executing an Agent that was declaratively defined some of the properties will be determined by the runtime: +When executing an Agent that was defined declaratively some of the properties will be determined by the runtime: - `Kernel`: The runtime will be responsible for create the `Kernel` instance to be used by the Agent. This `Kernel` instance must be configured with the models and tools that the Agent requires. - `Logger` or `LoggerFactory`: The runtime will be responsible for providing a correctly configured `Logger` or `LoggerFactory`. @@ -96,11 +106,16 @@ For Agent properties that define behaviors e.g. `HistoryReducer` the Semantic Ke - Schema **MUST** be Agent Service agnostic i.e., will work with Agents targeting Azure, Open AI, Mistral AI, ... - Schema **MUST** allow model settings to be assigned to an Agent. - Schema **MUST** allow tools (e.g. functions, code interpreter, file search, ...) to be assigned to an Agent. +- Schema **MUST** allow new types of tools to be defined for an Agent to use. - Schema **MUST** allow a Semantic Kernel prompt (including Prompty format) to be used to define the Agent instructions. - Schema **MUST** be extensible so that support for new Agent types with their own settings and tools, can be added to Semantic Kernel. - Schema **MUST** allow third parties to contribute new Agent types to Semantic Kernel. - … +### Out of Scope + +- This ADR does not cover the multi-agent declarative format or the process declarative format + ## Considered Options - Use the [Declarative agent schema 1.2 for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/declarative-agent-manifest-1.2) From a462b15afa88760fd1fa9a01e02d950a0c7c8e85 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:09:09 +0000 Subject: [PATCH 05/30] Add use cases --- .../NNNN-declarative-agent-schema.md | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/docs/decisions/NNNN-declarative-agent-schema.md b/docs/decisions/NNNN-declarative-agent-schema.md index 6e35df0b6306..fa9d0e9cb7c8 100644 --- a/docs/decisions/NNNN-declarative-agent-schema.md +++ b/docs/decisions/NNNN-declarative-agent-schema.md @@ -112,6 +112,17 @@ For Agent properties that define behaviors e.g. `HistoryReducer` the Semantic Ke - Schema **MUST** allow third parties to contribute new Agent types to Semantic Kernel. - … +The document will describe the following use cases: + +1. Metadata about the agent and the file. +2. Creating an Agent with access to function tools and a set of instructions to guide it's behavior. +3. Allow templating of Agent instructions (and other properties). +4. Configuring the model and providing multiple model configurations. +5. Configuring data sources (context/knowledge) for the Agent to use. +6. Configuring additional tools for the Agent to use e.g. code interpreter, OpenAPI endpoints, . +7. Enabling additional modalities for the Agent e.g. speech. +8. Error conditions e.g. models or function tools not being available. + ### Out of Scope - This ADR does not cover the multi-agent declarative format or the process declarative format @@ -180,6 +191,8 @@ Chosen option: "{title of option 1}", because ## More Information +### Code First versus Declarative Format + Below are examples showing the code first and equivalent declarative syntax for creating different types of Agents. Consider the following use cases: @@ -353,6 +366,9 @@ actions: Declarative using Semantic Kernel schema: +Using the syntax below the assistant does not have the functions included in it's definition. +The functions must be added to the `Kernel` instance associated with the Agent and will be passed when the Agent is invoked. + ```yml name: RestaurantHost type: openai_assistant @@ -367,8 +383,28 @@ execution_settings: - MenuPlugin.GetItemPrice metadata: sksample: true +`` + +or the +```yml +name: RestaurantHost +type: openai_assistant +instructions: Answer questions about the menu. +description: This agent answers questions about the menu. +execution_settings: + default: + temperature: 0.4 +tools: + - type: function + name: MenuPlugin-GetSpecials + description: Provides a list of specials from the menu. + - type: function + name: MenuPlugin-GetItemPrice + description: Provides the price of the requested menu item. + parameters: '{"type":"object","properties":{"menuItem":{"type":"string","description":"The name of the menu item."}},"required":["menuItem"]}' ``` + **Note**: The `Kernel` instance used to create the Agent must have an instance of `OpenAIClientProvider` registered as a service. #### `OpenAIAssistantAgent` with Tools @@ -421,3 +457,112 @@ execution_settings: metadata: sksample: true ``` + +### Declarative Format Use Cases + +#### Metadata about the agent and the file + +```yaml +name: RestaurantHost +type: azureai_agent +description: This agent answers questions about the menu. +version: 0.0.1 +``` + +#### Creating an Agent with access to function tools and a set of instructions to guide it's behavior + +```yaml +name: RestaurantHost +type: azureai_agent +description: This agent answers questions about the menu. +version: 0.0.1 +instructions: Answer questions about the menu. +execution_settings: + default: + temperature: 0.4 + function_choice_behavior: + type: auto + functions: + - MenuPlugin.GetSpecials + - MenuPlugin.GetItemPrice +``` + +or + +```yml +name: RestaurantHost +type: azureai_agent +description: This agent answers questions about the menu. +instructions: Answer questions about the menu. +execution_settings: + default: + temperature: 0.4 +tools: + - type: function + name: MenuPlugin-GetSpecials + description: Provides a list of specials from the menu. + - type: function + name: MenuPlugin-GetItemPrice + description: Provides the price of the requested menu item. + parameters: '{"type":"object","properties":{"menuItem":{"type":"string","description":"The name of the menu item."}},"required":["menuItem"]}' +``` + +#### Allow templating of Agent instructions (and other properties) + +```yaml +``` + +#### Configuring the model and providing multiple model configurations + +```yaml +name: RestaurantHost +type: azureai_agent +description: This agent answers questions about the menu. +instructions: Answer questions about the menu. +execution_settings: + default: + temperature: 0.4 + gpt-4o: + temperature: 0.5 + gpt-4o-mini: + temperature: 0.5 +tools: + - type: function + name: MenuPlugin-GetSpecials + description: Provides a list of specials from the menu. + - type: function + name: MenuPlugin-GetItemPrice + description: Provides the price of the requested menu item. + parameters: '{"type":"object","properties":{"menuItem":{"type":"string","description":"The name of the menu item."}},"required":["menuItem"]}' +``` + +#### Configuring data sources (context/knowledge) for the Agent to use + +```yaml +``` + +#### Configuring additional tools for the Agent to use e.g. code interpreter, OpenAPI endpoints + +```yaml +name: Coder Agent +type: azureai_agent +description: This agent uses code to answer questions. +instructions: Use code to answer questions. +execution_settings: + default: + metadata: + sksample: true +tools: + - type: code_interpreter +``` + +```yaml +``` + +#### Enabling additional modalities for the Agent e.g. speech + +```yaml +``` + +#### Error conditions e.g. models or function tools not being available + From e15236a230bee6910effe14af97f3c0d0b5c8243 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:26:24 +0000 Subject: [PATCH 06/30] Add some use case samples --- .../NNNN-declarative-agent-schema.md | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/decisions/NNNN-declarative-agent-schema.md b/docs/decisions/NNNN-declarative-agent-schema.md index fa9d0e9cb7c8..e49e884f6a66 100644 --- a/docs/decisions/NNNN-declarative-agent-schema.md +++ b/docs/decisions/NNNN-declarative-agent-schema.md @@ -475,7 +475,6 @@ version: 0.0.1 name: RestaurantHost type: azureai_agent description: This agent answers questions about the menu. -version: 0.0.1 instructions: Answer questions about the menu. execution_settings: default: @@ -510,6 +509,21 @@ tools: #### Allow templating of Agent instructions (and other properties) ```yaml +name: GenerateStory +description: An agent that generates a story about a topic. +template: | + Tell a story about {{$topic}} that is {{$length}} sentences long. +template_format: semantic-kernel +input_variables: + - name: topic + description: The topic of the story. + is_required: true + - name: length + description: The number of sentences in the story. + is_required: true +execution_settings: + default: + temperature: 0.6 ``` #### Configuring the model and providing multiple model configurations @@ -539,8 +553,20 @@ tools: #### Configuring data sources (context/knowledge) for the Agent to use ```yaml +name: Document FAQ Agent +type: azureai_agent +instructions: Use provide documents to answer questions. Politely decline to answer if the provided documents don't include an answer to the question. +description: This agent uses documents to answer questions. +execution_settings: + default: + metadata: + sksample: true +tools: + - type: file_search ``` +**TODO: How do we configure the documents to include?** + #### Configuring additional tools for the Agent to use e.g. code interpreter, OpenAPI endpoints ```yaml @@ -557,6 +583,19 @@ tools: ``` ```yaml +name: Country FAQ Agent +type: azureai_agent +instructions: Answer questions about countries. For all other questions politely decline to answer. +description: This agent answers question about different countries. +execution_settings: + default: + metadata: + sksample: true +tools: + - type: openapi + name: RestCountriesAPI + description: Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com. + schema: '{"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}' ``` #### Enabling additional modalities for the Agent e.g. speech From cd536b8803d6c21585216ae6650ac524e03fd20c Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:29:13 +0000 Subject: [PATCH 07/30] Start working on the Agent model definition classes --- .../Definition/AgentDefinition.cs | 128 ++++++++++++++++++ .../Definition/ModelConfiguration.cs | 50 +++++++ .../Definition/ModelDefinition.cs | 73 ++++++++++ .../Abstractions/Definition/ModelOptions.cs | 44 ++++++ .../Definition/TemplateOptions.cs | 12 ++ .../Abstractions/Definition/ToolOptions.cs | 12 ++ .../PromptTemplate/InputVariable.cs | 6 + 7 files changed, 325 insertions(+) create mode 100644 dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs create mode 100644 dotnet/src/Agents/Abstractions/Definition/ModelConfiguration.cs create mode 100644 dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs create mode 100644 dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs create mode 100644 dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs create mode 100644 dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs new file mode 100644 index 000000000000..0a5204b9f78d --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using Microsoft.SemanticKernel.Agents.Definition; + +namespace Microsoft.SemanticKernel; + +/// +/// Defines an agent. +/// +public sealed class AgentDefinition +{ + /// + /// Gets or sets the id of the deployed agent. + /// + public string? Id + { + get => this._id; + set + { + Verify.NotNull(value); + this._id = value; + } + } + + /// + /// Gets or sets the name of the agent. + /// + public string? Name + { + get => this._name; + set + { + Verify.NotNull(value); + this._name = value; + } + } + + /// + /// Gets or sets the description of the agent. + /// + public string? Description + { + get => this._description; + set + { + Verify.NotNull(value); + this._description = value; + } + } + + /// + /// Gets or sets the system instructions for the agent to use. + /// + public string? Instructions + { + get => this._instructions; + set + { + Verify.NotNull(value); + this._instructions = value; + } + } + + /// + /// Gets or sets the metadata associated with the agent. + /// + public IDictionary? Metadata + { + get => this._metadata; + set + { + Verify.NotNull(value); + this._metadata = value; + } + } + + /// + /// Gets or sets the model used by the agent. + /// + public ModelDefinition? Model + { + get => this._model; + set + { + Verify.NotNull(value); + this._model = value; + } + } + + /// + /// Gets or sets the collection of input variables used by the agent. + /// + public IList Inputs + { + get => this._inputs ??= []; + set + { + Verify.NotNull(value); + this._inputs = value; + } + } + + /// + /// Gets or sets the collection of output variables supported by the agent. + /// + public IList Outputs + { + get => this._outputs ??= []; + set + { + Verify.NotNull(value); + this._outputs = value; + } + } + + #region + private string? _id; + private string? _name; + private string? _description; + private string? _instructions; + private IDictionary? _metadata; + private ModelDefinition? _model; + private IList? _inputs; + private IList? _outputs; + #endregion + +} diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelConfiguration.cs b/dotnet/src/Agents/Abstractions/Definition/ModelConfiguration.cs new file mode 100644 index 000000000000..ce24cf952b47 --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/ModelConfiguration.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.SemanticKernel; + +/// +/// Defines the configuration for a model. +/// +public sealed class ModelConfiguration +{ + /// + /// The type of the model configuration. + /// + /// + /// Used to identify where the model is deployed e.g., "azure_openai" or "openai". + /// + public string? Type + { + get => this._type; + set + { + Verify.NotNull(value); + this._type = value; + } + } + + /// + /// Extra properties that may be included in the serialized model configuration. + /// + /// + /// Used to store model specific configuration e.g., the deployment name, endpoint, etc. + /// + [JsonExtensionData] + public IDictionary? ExtensionData + { + get => this._extensionData; + set + { + Verify.NotNull(value); + this._extensionData = value; + } + } + + #region private + private string? _type; + private IDictionary? _extensionData; + #endregion +} diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs new file mode 100644 index 000000000000..fbc5106dca53 --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.SemanticKernel.Agents.Definition; + +/// +/// Defines the model to be used by an agent. +/// +public sealed class ModelDefinition +{ + /// + /// Gets or sets the default API type. + /// + public static readonly string DefaultApi = "chat"; + + /// + /// Gets or sets the ID of the model. + /// + public string? Id + { + get => this._id; + set + { + Verify.NotNull(value); + this._id = value; + } + } + + /// + /// Gets or sets the type of API supported by the model. + /// + public string Api + { + get => this._api ?? DefaultApi; + set + { + Verify.NotNull(value); + this._api = value; + } + } + + /// + /// Gets or sets the options used by the model. + /// + public ModelOptions? Options + { + get => this._options; + set + { + Verify.NotNull(value); + this._options = value; + } + } + + /// + /// Gets or sets the options used by the model. + /// + public ModelConfiguration? Configuration + { + get => this._configuration; + set + { + Verify.NotNull(value); + this._configuration = value; + } + } + + #region + private string? _id; + private string? _api; + private ModelOptions? _options; + private ModelConfiguration? _configuration; + #endregion +} diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs b/dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs new file mode 100644 index 000000000000..b45a8a6076ea --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.SemanticKernel.Agents.Definition; + +/// +/// Defines the options used by a model. +/// +public sealed class ModelOptions +{ + /// + /// The ID of the model. + /// + public string? ModelId + { + get => this._modelId; + set + { + Verify.NotNull(value); + this._modelId = value; + } + } + + /// + /// Extra properties that may be included in the serialized model options. + /// + [JsonExtensionData] + public IDictionary? ExtensionData + { + get => this._extensionData; + set + { + Verify.NotNull(value); + this._extensionData = value; + } + } + + #region private + private string? _modelId; + private IDictionary? _extensionData; + #endregion +} diff --git a/dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs b/dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs new file mode 100644 index 000000000000..4d17bae4e393 --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Agents.Definition; +internal class TemplateOptions +{ +} diff --git a/dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs b/dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs new file mode 100644 index 000000000000..d4633307d590 --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Agents.Definition; +internal class ToolOptions +{ +} diff --git a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs index c1f1c94e05a9..ad58115ac6b4 100644 --- a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs +++ b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/InputVariable.cs @@ -101,4 +101,10 @@ public string Description /// [JsonPropertyName("allow_dangerously_set_content")] public bool AllowDangerouslySetContent { get; set; } = false; + + /// + /// Gets or sets a sample value for the variable. + /// + [JsonPropertyName("sample")] + public object? Sample { get; set; } } From 009bd980662ba20326b37e8e24581fab77333562 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:17:17 +0000 Subject: [PATCH 08/30] Starting adding implementations --- dotnet/SK-dotnet.sln | 9 ++ .../Definition/AgentDefinition.cs | 45 ++++++- .../Definition/IKernelAgentFactory.cs | 19 +++ .../Abstractions/Definition/ModelOptions.cs | 44 ------- .../Definition/TemplateOptions.cs | 12 -- .../Abstractions/Definition/ToolDefinition.cs | 64 +++++++++ .../Abstractions/Definition/ToolOptions.cs | 12 -- .../AzureAI/Extensions/AgentRunExtensions.cs | 4 +- .../AzureAI/Internal/AgentMessageFactory.cs | 6 +- .../AzureAI/Internal/AgentThreadActions.cs | 4 +- .../AggregatorKernelAgentFactory.cs | 44 +++++++ .../Definition/ChatCompletionAgentFactory.cs | 61 +++++++++ .../Definition/OpenAIAssistantAgentFactory.cs | 66 ++++++++++ .../AssistantCreationOptionsFactory.cs | 4 +- .../OpenAI/Internal/AssistantThreadActions.cs | 4 +- .../src/Agents/OpenAI/OpenAIAssistantAgent.cs | 2 +- .../ChatCompletionAgentFactoryTests.cs | 47 +++++++ dotnet/src/Agents/Yaml/Agents.Yaml.csproj | 34 +++++ dotnet/src/Agents/Yaml/KernelAgentYaml.cs | 38 ++++++ .../PromptExecutionSettingsTypeConverter.cs | 121 ++++++++++++++++++ .../PromptTemplate}/ModelConfiguration.cs | 2 +- .../PromptTemplate}/ModelDefinition.cs | 4 +- .../PromptTemplate/TemplateOptions.cs | 47 +++++++ 23 files changed, 608 insertions(+), 85 deletions(-) create mode 100644 dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs delete mode 100644 dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs delete mode 100644 dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs create mode 100644 dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs delete mode 100644 dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs create mode 100644 dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs create mode 100644 dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs create mode 100644 dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs create mode 100644 dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs create mode 100644 dotnet/src/Agents/Yaml/Agents.Yaml.csproj create mode 100644 dotnet/src/Agents/Yaml/KernelAgentYaml.cs create mode 100644 dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs rename dotnet/src/{Agents/Abstractions/Definition => SemanticKernel.Abstractions/PromptTemplate}/ModelConfiguration.cs (93%) rename dotnet/src/{Agents/Abstractions/Definition => SemanticKernel.Abstractions/PromptTemplate}/ModelDefinition.cs (94%) create mode 100644 dotnet/src/SemanticKernel.Abstractions/PromptTemplate/TemplateOptions.cs diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 21f3cbc1da67..a4d0ff8ec374 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -445,6 +445,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugins.AI.UnitTests", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connectors.Postgres.UnitTests", "src\Connectors\Connectors.Postgres.UnitTests\Connectors.Postgres.UnitTests.csproj", "{2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agents.Yaml", "src\Agents\Yaml\Agents.Yaml.csproj", "{29DF6278-06FC-4F1D-8555-D3E880AF3204}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1196,6 +1198,12 @@ Global {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Publish|Any CPU.Build.0 = Debug|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.Build.0 = Release|Any CPU + {29DF6278-06FC-4F1D-8555-D3E880AF3204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29DF6278-06FC-4F1D-8555-D3E880AF3204}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29DF6278-06FC-4F1D-8555-D3E880AF3204}.Publish|Any CPU.ActiveCfg = Publish|Any CPU + {29DF6278-06FC-4F1D-8555-D3E880AF3204}.Publish|Any CPU.Build.0 = Publish|Any CPU + {29DF6278-06FC-4F1D-8555-D3E880AF3204}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29DF6278-06FC-4F1D-8555-D3E880AF3204}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1360,6 +1368,7 @@ Global {0C64EC81-8116-4388-87AD-BA14D4B59974} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {03ACF9DD-00C9-4F2B-80F1-537E2151AF5F} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89} + {29DF6278-06FC-4F1D-8555-D3E880AF3204} = {6823CD5E-2ABE-41EB-B865-F86EC13F0CF9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs index 0a5204b9f78d..127a21920216 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.SemanticKernel.Agents.Definition; -namespace Microsoft.SemanticKernel; +namespace Microsoft.SemanticKernel.Agents; /// /// Defines an agent. @@ -23,6 +23,19 @@ public string? Id } } + /// + /// Gets or sets the type of the agent. + /// + public string? Type + { + get => this._type; + set + { + Verify.NotNull(value); + this._type = value; + } + } + /// /// Gets or sets the name of the agent. /// @@ -114,7 +127,34 @@ public IList Outputs } } + /// + /// Gets or sets the template options used by the agent. + /// + public TemplateOptions? Template + { + get => this._template; + set + { + Verify.NotNull(value); + this._template = value; + } + } + + /// + /// Gets or sets the collection of tools used by the agent. + /// + public IList Tools + { + get => this._tools ??= []; + set + { + Verify.NotNull(value); + this._tools = value; + } + } + #region + private string? _type; private string? _id; private string? _name; private string? _description; @@ -123,6 +163,7 @@ public IList Outputs private ModelDefinition? _model; private IList? _inputs; private IList? _outputs; + private TemplateOptions? _template; + private IList? _tools; #endregion - } diff --git a/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs b/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs new file mode 100644 index 000000000000..9041ba69b2f4 --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel.Agents.Factory; + +/// +/// Represents a factory for creating instances. +/// +public interface IKernelAgentFactory +{ + /// + /// Tries to create a from the specified . + /// + /// Kernel instance to associate with the agent. + /// Definition of the agent to create. + /// The created agent, if null the agent type is not supported. + bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result); +} diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs b/dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs deleted file mode 100644 index b45a8a6076ea..000000000000 --- a/dotnet/src/Agents/Abstractions/Definition/ModelOptions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Microsoft.SemanticKernel.Agents.Definition; - -/// -/// Defines the options used by a model. -/// -public sealed class ModelOptions -{ - /// - /// The ID of the model. - /// - public string? ModelId - { - get => this._modelId; - set - { - Verify.NotNull(value); - this._modelId = value; - } - } - - /// - /// Extra properties that may be included in the serialized model options. - /// - [JsonExtensionData] - public IDictionary? ExtensionData - { - get => this._extensionData; - set - { - Verify.NotNull(value); - this._extensionData = value; - } - } - - #region private - private string? _modelId; - private IDictionary? _extensionData; - #endregion -} diff --git a/dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs b/dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs deleted file mode 100644 index 4d17bae4e393..000000000000 --- a/dotnet/src/Agents/Abstractions/Definition/TemplateOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.SemanticKernel.Agents.Definition; -internal class TemplateOptions -{ -} diff --git a/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs new file mode 100644 index 000000000000..4667c4cea41c --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.SemanticKernel.Agents; + +/// +/// The options for defining a tool. +/// +public class ToolDefinition +{ + /// + /// The type of the tool. + /// + /// + /// Used to identify which type of tool is being used e.g., code interpreter, openapi, ... + /// + public string? Type + { + get => this._type; + set + { + Verify.NotNull(value); + this._type = value; + } + } + + /// + /// The name of the tool. + /// + public string? Name + { + get => this._name; + set + { + Verify.NotNull(value); + this._name = value; + } + } + + /// + /// Gets or sets the configuration for the tool. + /// + /// + /// Used to store tool specific configuration e.g., files associated with the tool, etc. + /// + [JsonExtensionData] + public IDictionary? Configuration + { + get => this._configuration; + set + { + Verify.NotNull(value); + this._configuration = value; + } + } + + #region private + private string? _type; + private string? _name; + private IDictionary? _configuration; + #endregion +} diff --git a/dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs b/dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs deleted file mode 100644 index d4633307d590..000000000000 --- a/dotnet/src/Agents/Abstractions/Definition/ToolOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.SemanticKernel.Agents.Definition; -internal class ToolOptions -{ -} diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs index 7d4cf718b1e0..b3d2331101f9 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs @@ -40,7 +40,7 @@ public static async Task CreateAsync( string threadId, AzureAIAgent agent, string? instructions, - ToolDefinition[] tools, + Azure.AI.Projects.ToolDefinition[] tools, AzureAIInvocationOptions? invocationOptions, CancellationToken cancellationToken) { @@ -91,7 +91,7 @@ public static IAsyncEnumerable CreateStreamingAsync( string threadId, AzureAIAgent agent, string? instructions, - ToolDefinition[] tools, + Azure.AI.Projects.ToolDefinition[] tools, AzureAIInvocationOptions? invocationOptions, CancellationToken cancellationToken) { diff --git a/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs b/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs index 621e364acf6a..5a37b0b2fac5 100644 --- a/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs +++ b/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs @@ -74,13 +74,13 @@ public static IEnumerable GetThreadMessages(IEnumerable s_toolMetadata = new() + private static readonly Dictionary s_toolMetadata = new() { { AzureAIAgent.Tools.CodeInterpreter, new CodeInterpreterToolDefinition() }, { AzureAIAgent.Tools.FileSearch, new FileSearchToolDefinition() }, }; - private static IEnumerable GetToolDefinition(IEnumerable? tools) + private static IEnumerable GetToolDefinition(IEnumerable? tools) { if (tools is null) { @@ -89,7 +89,7 @@ private static IEnumerable GetToolDefinition(IEnumerable foreach (string tool in tools) { - if (s_toolMetadata.TryGetValue(tool, out ToolDefinition? toolDefinition)) + if (s_toolMetadata.TryGetValue(tool, out Azure.AI.Projects.ToolDefinition? toolDefinition)) { yield return toolDefinition; } diff --git a/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs b/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs index 167349b63d11..f73e31a75720 100644 --- a/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs +++ b/dotnet/src/Agents/AzureAI/Internal/AgentThreadActions.cs @@ -148,7 +148,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Agents { logger.LogAzureAIAgentCreatingRun(nameof(InvokeAsync), threadId); - List tools = new(agent.Definition.Tools); + List tools = new(agent.Definition.Tools); // Add unique functions from the Kernel which are not already present in the agent's tools var functionToolNames = new HashSet(tools.OfType().Select(t => t.Name)); @@ -380,7 +380,7 @@ public static async IAsyncEnumerable InvokeStreamin { logger.LogAzureAIAgentCreatingRun(nameof(InvokeAsync), threadId); - ToolDefinition[]? tools = [.. agent.Definition.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; + Azure.AI.Projects.ToolDefinition[]? tools = [.. agent.Definition.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; string? instructions = await agent.GetInstructionsAsync(kernel, arguments, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs new file mode 100644 index 000000000000..8a988ff0ce6e --- /dev/null +++ b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.SemanticKernel.Agents.Factory; + +namespace Microsoft.SemanticKernel.Agents.Definition; + +/// +/// Provides a which aggregates multiple kernel agent factories. +/// +public sealed class AggregatorKernelAgentFactory : IKernelAgentFactory +{ + private readonly IKernelAgentFactory?[] _kernelAgentFactories; + + /// Initializes the instance. + /// Ordered instances to aggregate. + public AggregatorKernelAgentFactory(params IKernelAgentFactory[] kernelAgentFactories) + { + Verify.NotNullOrEmpty(kernelAgentFactories); + foreach (IPromptTemplateFactory kernelAgentFactory in kernelAgentFactories) + { + Verify.NotNull(kernelAgentFactory, nameof(kernelAgentFactories)); + } + + this._kernelAgentFactories = kernelAgentFactories; + } + + /// + public bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result) + { + Verify.NotNull(agentDefinition); + + foreach (var kernelAgentFactory in this._kernelAgentFactories) + { + if (kernelAgentFactory?.TryCreate(kernel, agentDefinition, out result) is true && result is not null) + { + return true; + } + } + + result = null; + return false; + } +} diff --git a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs new file mode 100644 index 000000000000..b916a1afd608 --- /dev/null +++ b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.SemanticKernel.Agents.Factory; + +namespace Microsoft.SemanticKernel.Agents; + +/// +/// Provides a which creates instances of . +/// +public sealed class ChatCompletionAgentFactory : IKernelAgentFactory +{ + /// + /// Gets the type of the chat completion agent. + /// + public static string ChatCompletionAgentType => "chat_completion_agent"; + + /// + public bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result) + { + Verify.NotNull(agentDefinition); + + if (agentDefinition.Type?.Equals(ChatCompletionAgentType, System.StringComparison.Ordinal) ?? false) + { + result = new ChatCompletionAgent() + { + Name = agentDefinition.Name, + Description = agentDefinition.Description, + Instructions = agentDefinition.Instructions, + Arguments = GetKernelArguments(agentDefinition), + Kernel = kernel, + LoggerFactory = kernel.LoggerFactory, + }; + return true; + } + + result = null; + return false; + } + + #region private + private static KernelArguments GetKernelArguments(AgentDefinition agentDefinition) + { + var arguments = new KernelArguments(agentDefinition?.Model?.Options); + + if (agentDefinition is not null) + { + // Add default arguments for the agent + foreach (var input in agentDefinition.Inputs) + { + if (!input.IsRequired && input.Default is not null) + { + arguments.Add(input.Name, input.Default); + } + } + } + + return arguments; + } + #endregion +} diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs new file mode 100644 index 000000000000..3f79a53dddcd --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.SemanticKernel.Agents.Factory; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Provides a which creates instances of . +/// +public sealed class OpenAIAssistantAgentFactory : IKernelAgentFactory +{ + /// + /// Gets the type of the OpenAI assistant agent. + /// + public static string OpenAIAssistantAgentType => "openai_assistant"; + + /// + public bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result) + { + Verify.NotNull(agentDefinition); + + if (agentDefinition.Type?.Equals(OpenAIAssistantAgentType, System.StringComparison.Ordinal) ?? false) + { + result = new OpenAIAssistantAgent() + { + Name = agentDefinition.Name, + Description = agentDefinition.Description, + Instructions = agentDefinition.Instructions, + Arguments = GetKernelArguments(agentDefinition), + Kernel = kernel, + LoggerFactory = kernel.LoggerFactory, + }; + return true; + } + + result = null; + return false; + } + + #region private + private static OpenAIClientProvider GetClientProvider(this Kernel kernel) + { + + } + + private static KernelArguments GetKernelArguments(AgentDefinition agentDefinition) + { + var arguments = new KernelArguments(agentDefinition?.Model?.Options); + + if (agentDefinition is not null) + { + // Add default arguments for the agent + foreach (var input in agentDefinition.Inputs) + { + if (!input.IsRequired && input.Default is not null) + { + arguments.Add(input.Name, input.Default); + } + } + } + + return arguments; + } + #endregion +} diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs index 532a8433c37c..2bd69d0d7184 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs @@ -64,12 +64,12 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(this Open if (definition.EnableCodeInterpreter) { - assistantCreationOptions.Tools.Add(ToolDefinition.CreateCodeInterpreter()); + assistantCreationOptions.Tools.Add(global::OpenAI.Assistants.ToolDefinition.CreateCodeInterpreter()); } if (definition.EnableFileSearch) { - assistantCreationOptions.Tools.Add(ToolDefinition.CreateFileSearch()); + assistantCreationOptions.Tools.Add(global::OpenAI.Assistants.ToolDefinition.CreateFileSearch()); } return assistantCreationOptions; diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index b8a0a3778745..dda9d9cc7b8a 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -165,7 +165,7 @@ public static async IAsyncEnumerable GetMessagesAsync(Assist logger.LogOpenAIAssistantCreatingRun(nameof(InvokeAsync), threadId); - List tools = new(agent.Tools); + List tools = new(agent.Tools); // Add unique functions from the Kernel which are not already present in the agent's tools var functionToolNames = new HashSet(tools.OfType().Select(t => t.FunctionName)); @@ -404,7 +404,7 @@ public static async IAsyncEnumerable InvokeStreamin logger.LogOpenAIAssistantCreatingRun(nameof(InvokeAsync), threadId); - ToolDefinition[]? tools = [.. agent.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; + global::OpenAI.Assistants.ToolDefinition[]? tools = [.. agent.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; string? instructions = await agent.GetInstructionsAsync(kernel, arguments, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs index 95144b281c14..95b74d044f03 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs @@ -56,7 +56,7 @@ public sealed class OpenAIAssistantAgent : KernelAgent /// /// Gets the predefined tools for run processing. /// - internal IReadOnlyList Tools => this._assistant.Tools; + internal IReadOnlyList Tools => this._assistant.Tools; /// /// Create a new . diff --git a/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs new file mode 100644 index 000000000000..fdb21489c8e1 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Core.Definition; + +/// +/// Unit testing of . +/// +public class ChatCompletionAgentFactoryTests +{ + /// + /// Verify can create an instance of . + /// + [Fact] + public void VerifyCanCreateChatCompletionAgent() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Type = ChatCompletionAgentFactory.ChatCompletionAgentType, + Name = "ChatCompletionAgent", + Description = "ChatCompletionAgent Description", + Instructions = "ChatCompletionAgent Instructions", + Model = new() + { + Id = "gpt-4o-mini", + Options = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() } + } + }; + ChatCompletionAgentFactory factory = new(); + Kernel kernel = new(); + + // Act + var result = factory.TryCreate(kernel, agentDefinition, out var agent); + + // Assert + Assert.True(result); + Assert.NotNull(agent); + Assert.Equal(agentDefinition.Name, agent.Name); + Assert.Equal(agentDefinition.Description, agent.Description); + Assert.Equal(agentDefinition.Instructions, agent.Instructions); + Assert.Equal(kernel, agent.Kernel); + } +} diff --git a/dotnet/src/Agents/Yaml/Agents.Yaml.csproj b/dotnet/src/Agents/Yaml/Agents.Yaml.csproj new file mode 100644 index 000000000000..bcbc837c3d21 --- /dev/null +++ b/dotnet/src/Agents/Yaml/Agents.Yaml.csproj @@ -0,0 +1,34 @@ + + + + + Microsoft.SemanticKernel.Agents.Yaml + Microsoft.SemanticKernel.Agents + net8.0;netstandard2.0 + $(NoWarn);SKEXP0110 + false + alpha + + + + + + + Semantic Kernel Agents - Core + Provides utilities to serialise Agent definitions from YAML. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/src/Agents/Yaml/KernelAgentYaml.cs b/dotnet/src/Agents/Yaml/KernelAgentYaml.cs new file mode 100644 index 000000000000..beebebb68051 --- /dev/null +++ b/dotnet/src/Agents/Yaml/KernelAgentYaml.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Microsoft.SemanticKernel.Agents; + +/// +/// Factory methods for creating instances. +/// +public static class KernelAgentYaml +{ + public static KernelAgent FromAgentYaml( + string text, + ILoggerFactory? loggerFactory = null) + { + var agentDefinition = ToAgentDefinition(text); + + return KernelAgentFactory.CreateFromDefinition( + agentDefinition, + loggerFactory); + } + + /// + /// Convert the given YAML text to a model. + /// + /// YAML representation of the to use to create the prompt function. + public static AgentDefinition ToAgentDefinition(string text) + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .WithTypeConverter(new PromptExecutionSettingsTypeConverter()) + .Build(); + + return deserializer.Deserialize(text); + } +} diff --git a/dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs b/dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs new file mode 100644 index 000000000000..12431752d6e5 --- /dev/null +++ b/dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.BufferedDeserialization; +using YamlDotNet.Serialization.NamingConventions; +using YamlDotNet.Serialization.ObjectFactories; + +namespace Microsoft.SemanticKernel; + +/// +/// Allows custom deserialization for from YAML prompts. +/// +internal sealed class PromptExecutionSettingsTypeConverter : IYamlTypeConverter +{ + /// + public bool Accepts(Type type) + { + return type == typeof(PromptExecutionSettings); + } + + /// + public object? ReadYaml(IParser parser, Type type) + { + s_deserializer ??= new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .IgnoreUnmatchedProperties() // Required to ignore the 'type' property used as type discrimination. Otherwise, the "Property 'type' not found on type '{type.FullName}'" exception is thrown. + .WithObjectFactory(new FunctionChoiceBehaviorsObjectFactory()) + .WithTypeDiscriminatingNodeDeserializer(CreateAndRegisterTypeDiscriminatingNodeDeserializer) + .Build(); + + parser.MoveNext(); // Move to the first property + + var executionSettings = new PromptExecutionSettings(); + while (parser.Current is not MappingEnd) + { + var propertyName = parser.Consume().Value; + switch (propertyName) + { + case "model_id": + executionSettings.ModelId = s_deserializer.Deserialize(parser); + break; + case "function_choice_behavior": + executionSettings.FunctionChoiceBehavior = s_deserializer.Deserialize(parser); + break; + default: + (executionSettings.ExtensionData ??= new Dictionary()).Add(propertyName, s_deserializer.Deserialize(parser)); + break; + } + } + parser.MoveNext(); // Move past the MappingEnd event + return executionSettings; + } + + /// + public void WriteYaml(IEmitter emitter, object? value, Type type) + { + throw new NotImplementedException(); + } + + /// + /// Creates and register a for polymorphic deserialization of . + /// + /// The to configure the . + private static void CreateAndRegisterTypeDiscriminatingNodeDeserializer(ITypeDiscriminatingNodeDeserializerOptions options) + { + var attributes = typeof(FunctionChoiceBehavior).GetCustomAttributes(false); + + // Getting the type discriminator property name - "type" from the JsonPolymorphicAttribute. + var discriminatorKey = attributes.OfType().Single().TypeDiscriminatorPropertyName; + if (string.IsNullOrEmpty(discriminatorKey)) + { + throw new InvalidOperationException("Type discriminator property name is not specified."); + } + + var discriminatorTypeMapping = new Dictionary(); + + // Getting FunctionChoiceBehavior subtypes and their type discriminators registered for polymorphic deserialization. + var derivedTypeAttributes = attributes.OfType(); + foreach (var derivedTypeAttribute in derivedTypeAttributes) + { + var discriminator = derivedTypeAttribute.TypeDiscriminator?.ToString(); + if (string.IsNullOrEmpty(discriminator)) + { + throw new InvalidOperationException($"Type discriminator is not specified for the {derivedTypeAttribute.DerivedType} type."); + } + + discriminatorTypeMapping.Add(discriminator!, derivedTypeAttribute.DerivedType); + } + + options.AddKeyValueTypeDiscriminator(discriminatorKey!, discriminatorTypeMapping); + } + + /// + /// The YamlDotNet deserializer instance. + /// + private static IDeserializer? s_deserializer; + + private sealed class FunctionChoiceBehaviorsObjectFactory : ObjectFactoryBase + { + private static DefaultObjectFactory? s_defaultFactory = null; + + public override object Create(Type type) + { + if (type == typeof(AutoFunctionChoiceBehavior) || + type == typeof(NoneFunctionChoiceBehavior) || + type == typeof(RequiredFunctionChoiceBehavior)) + { + return Activator.CreateInstance(type, nonPublic: true)!; + } + + // Use the default object factory for other types + return (s_defaultFactory ??= new DefaultObjectFactory()).Create(type); + } + } +} diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelConfiguration.cs b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs similarity index 93% rename from dotnet/src/Agents/Abstractions/Definition/ModelConfiguration.cs rename to dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs index ce24cf952b47..6620622964ee 100644 --- a/dotnet/src/Agents/Abstractions/Definition/ModelConfiguration.cs +++ b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs @@ -14,7 +14,7 @@ public sealed class ModelConfiguration /// The type of the model configuration. /// /// - /// Used to identify where the model is deployed e.g., "azure_openai" or "openai". + /// Used to identify where the model is deployed e.g., azure_openai, openai, ... /// public string? Type { diff --git a/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelDefinition.cs similarity index 94% rename from dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs rename to dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelDefinition.cs index fbc5106dca53..2ae366f7a306 100644 --- a/dotnet/src/Agents/Abstractions/Definition/ModelDefinition.cs +++ b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelDefinition.cs @@ -41,7 +41,7 @@ public string Api /// /// Gets or sets the options used by the model. /// - public ModelOptions? Options + public PromptExecutionSettings? Options { get => this._options; set @@ -67,7 +67,7 @@ public ModelConfiguration? Configuration #region private string? _id; private string? _api; - private ModelOptions? _options; + private PromptExecutionSettings? _options; private ModelConfiguration? _configuration; #endregion } diff --git a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/TemplateOptions.cs b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/TemplateOptions.cs new file mode 100644 index 000000000000..c6f8ce8d4633 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/TemplateOptions.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.SemanticKernel; + +/// +/// The options for defining a template format and parser. +/// +public class TemplateOptions +{ + /// + /// The format of the template. + /// + /// + /// Used to identify which templating language is being used e.g., semantic-kernel, handlebars, ... + /// + public string? Format + { + get => this._format; + set + { + Verify.NotNull(value); + this._format = value; + } + } + + /// + /// The parser to use with the template. + /// + /// + /// Used to identify which parser is used with the template e.g., semantic-kernel, prompty, ... + /// This will be combined with the API type to determine the correct parser to use. + /// + public string? Parser + { + get => this._parser; + set + { + Verify.NotNull(value); + this._parser = value; + } + } + + #region + private string? _format; + private string? _parser; + #endregion +} From 6063e98b73812c02d2448ea4656c74a81f931cc4 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:37:13 +0000 Subject: [PATCH 09/30] Work in progress --- .../Definition/IKernelAgentFactory.cs | 8 +- .../Abstractions/Definition/ToolDefinition.cs | 10 ++ .../Extensions/AgentDefinitionExtensions.cs | 69 +++++++++++++ .../AzureAI/Definition/AzureAIAgentFactory.cs | 49 ++++++++++ .../Extensions/AgentDefinitionExtensions.cs | 10 ++ .../AzureAI/Extensions/KernelExtensions.cs | 57 +++++++++++ .../AggregatorKernelAgentFactory.cs | 16 +-- .../Definition/ChatCompletionAgentFactory.cs | 37 ++----- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 1 + .../Definition/OpenAIAssistantAgentFactory.cs | 54 +++------- .../Extensions/AgentDefinitionExtensions.cs | 98 +++++++++++++++++++ .../OpenAI/Extensions/KernelExtensions.cs | 56 +++++++++++ .../Extensions/KernelFunctionExtensions.cs | 2 +- .../PromptExecutionSettingsExtensions.cs | 86 ++++++++++++++++ .../ChatCompletionAgentFactoryTests.cs | 6 +- .../OpenAIAssistantAgentFactoryTests.cs | 90 +++++++++++++++++ .../PromptTemplate/ModelConfiguration.cs | 11 +++ .../SemanticKernel.Abstractions.csproj | 4 + 18 files changed, 582 insertions(+), 82 deletions(-) create mode 100644 dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs create mode 100644 dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs create mode 100644 dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs create mode 100644 dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs create mode 100644 dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs create mode 100644 dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs diff --git a/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs b/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs index 9041ba69b2f4..e33e755bc079 100644 --- a/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs +++ b/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.SemanticKernel.Agents.Factory; @@ -14,6 +15,7 @@ public interface IKernelAgentFactory /// /// Kernel instance to associate with the agent. /// Definition of the agent to create. - /// The created agent, if null the agent type is not supported. - bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result); + /// Optional cancellation token. + /// The created , if null the agent type is not supported. + Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs index 4667c4cea41c..d641b14adf39 100644 --- a/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs @@ -10,6 +10,16 @@ namespace Microsoft.SemanticKernel.Agents; /// public class ToolDefinition { + /// + /// Tool definition type for code interpreter. + /// + public const string CodeInterpreter = "code_interpreter"; + + /// + /// Tool definition type for file search. + /// + public const string FileSearch = "file_search"; + /// /// The type of the tool. /// diff --git a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs new file mode 100644 index 000000000000..048b6bee974a --- /dev/null +++ b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Linq; + +namespace Microsoft.SemanticKernel.Agents; + +/// +/// Provides extension methods for . +/// +public static class AgentDefinitionExtensions +{ + /// + /// Creates default from the . + /// + /// + public static KernelArguments GetDefaultKernelArguments(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + var arguments = new KernelArguments(agentDefinition?.Model?.Options); + if (agentDefinition is not null) + { + // Add default arguments for the agent + foreach (var input in agentDefinition.Inputs) + { + if (!input.IsRequired && input.Default is not null) + { + arguments.Add(input.Name, input.Default); + } + } + } + + return arguments; + } + + /// + /// Get the first tool definition of the specified type. + /// + /// Agent definition + /// Tool type + public static ToolDefinition? GetFirstToolDefinition(this AgentDefinition agentDefinition, string toolType) + { + Verify.NotNull(agentDefinition); + Verify.NotNull(toolType); + return agentDefinition.Tools?.FirstOrDefault(tool => tool.Type == toolType); + } + + /// + /// Determines if the agent definition has a code interpreter tool. + /// + /// Agent definition + public static bool IsEnableCodeInterpreter(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + return agentDefinition.Tools?.Where(tool => tool.Type == ToolDefinition.CodeInterpreter).Any() ?? false; + } + + /// + /// Determines if the agent definition has a file search tool. + /// + /// Agent definition + public static bool IsEnableFileSearch(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + return agentDefinition.Tools?.Where(tool => tool.Type == ToolDefinition.FileSearch).Any() ?? false; + } +} diff --git a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs new file mode 100644 index 000000000000..ad67d36a9e84 --- /dev/null +++ b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Projects; +using Microsoft.SemanticKernel.Agents.Factory; + +namespace Microsoft.SemanticKernel.Agents.AzureAI.Definition; + +/// +/// Provides a which creates instances of . +/// +public sealed class AzureAIAgentFactory : IKernelAgentFactory +{ + /// + /// Gets the type of the Azure AI agent. + /// + public static string AzureAIAgentType => "azureai_agent"; + + /// + public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + { + Verify.NotNull(agentDefinition); + Verify.NotNull(agentDefinition.Model); + Verify.NotNull(agentDefinition.Model.Id); + + KernelAgent? kernelAgent = null; + if (agentDefinition.Type?.Equals(AzureAIAgentType, System.StringComparison.Ordinal) ?? false) + { + var clientProvider = kernel.GetAzureAIClientProvider(agentDefinition); + + AgentsClient client = clientProvider.Client.GetAgentsClient(); + Azure.AI.Projects.Agent agent = await client.CreateAgentAsync( + model: agentDefinition.Model.Id, + name: agentDefinition.Name, + description: agentDefinition.Description, + instructions: agentDefinition.Instructions, + tools: agentDefinition.GetAzureToolDefinitions(), + metadata: agentDefinition.GetMetadata(kernel)); + + kernelAgent = new AzureAIAgent(agent, clientProvider) + { + Kernel = kernel, + }; + } + + return Task.FromResult(kernelAgent).Result; + } +} diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs new file mode 100644 index 000000000000..37fb98ccd894 --- /dev/null +++ b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.SemanticKernel.Agents.AzureAI.Extensions; + +/// +/// Provides extension methods for . +/// +internal static class AgentDefinitionExtensions +{ +} diff --git a/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs new file mode 100644 index 000000000000..05575f1d6777 --- /dev/null +++ b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ClientModel; +using System.Linq; + +namespace Microsoft.SemanticKernel.Agents.AzureAI.Extensions; + +/// +/// Provides extension methods for . +/// +internal static class KernelExtensions +{ + private const string ConfigEndpoint = "endpoint"; + private const string ConfigApiKey = "api_key"; + + /// + /// Return the to be used with the specified . + /// + /// Kernel instance which will be used to resolve a default . + /// Agent definition whih will be used t provide configuration for the . + public static AzureAIClientProvider GetAzureAIClientProvider(this Kernel kernel, AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + // Use the agent configuration as the first option + var configuration = agentDefinition?.Model?.Configuration; + if (configuration is not null) + { + var hasEndpoint = configuration.TryGetValue(ConfigEndpoint, out var endpoint) && endpoint is not null; + var hasApiKey = configuration.TryGetValue(ConfigApiKey, out var apiKey) && apiKey is not null; + if (hasApiKey && hasEndpoint) + { + return OpenAIClientProvider.ForAzureOpenAI(new ApiKeyCredential(apiKey!.ToString()!), new Uri(endpoint!.ToString()!)); + } + else if (hasApiKey && !hasEndpoint) + { + return OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(apiKey!.ToString()!)); + } + /* + else if (!hasApiKey && hasEndpoint) + { + return OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(endpoint!.ToString()!)); + } + */ + } + + // Return the service registered on the kernel + var clientProvider = kernel.GetAllServices().FirstOrDefault(); + if (clientProvider is not null) + { + return clientProvider; + } + + throw new InvalidOperationException("AzureAI client provider not found."); + } +} diff --git a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs index 8a988ff0ce6e..943b2ecfe065 100644 --- a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; using Microsoft.SemanticKernel.Agents.Factory; namespace Microsoft.SemanticKernel.Agents.Definition; @@ -26,19 +27,22 @@ public AggregatorKernelAgentFactory(params IKernelAgentFactory[] kernelAgentFact } /// - public bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result) + public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); foreach (var kernelAgentFactory in this._kernelAgentFactories) { - if (kernelAgentFactory?.TryCreate(kernel, agentDefinition, out result) is true && result is not null) + if (kernelAgentFactory is not null) { - return true; + var kernelAgent = await kernelAgentFactory.CreateAsync(kernel, agentDefinition, cancellationToken).ConfigureAwait(false); + if (kernelAgent is not null) + { + return Task.FromResult(kernelAgent).Result; + } } } - result = null; - return false; + return Task.FromResult(null).Result; } } diff --git a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs index b916a1afd608..eb9aedfe6c1e 100644 --- a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; using Microsoft.SemanticKernel.Agents.Factory; namespace Microsoft.SemanticKernel.Agents; @@ -16,46 +17,26 @@ public sealed class ChatCompletionAgentFactory : IKernelAgentFactory public static string ChatCompletionAgentType => "chat_completion_agent"; /// - public bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result) + public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); + // TODO Implement template handling + + ChatCompletionAgent? kernelAgent = null; if (agentDefinition.Type?.Equals(ChatCompletionAgentType, System.StringComparison.Ordinal) ?? false) { - result = new ChatCompletionAgent() + kernelAgent = new ChatCompletionAgent() { Name = agentDefinition.Name, Description = agentDefinition.Description, Instructions = agentDefinition.Instructions, - Arguments = GetKernelArguments(agentDefinition), + Arguments = agentDefinition.GetDefaultKernelArguments(), Kernel = kernel, LoggerFactory = kernel.LoggerFactory, }; - return true; - } - - result = null; - return false; - } - - #region private - private static KernelArguments GetKernelArguments(AgentDefinition agentDefinition) - { - var arguments = new KernelArguments(agentDefinition?.Model?.Options); - - if (agentDefinition is not null) - { - // Add default arguments for the agent - foreach (var input in agentDefinition.Inputs) - { - if (!input.IsRequired && input.Default is not null) - { - arguments.Add(input.Name, input.Default); - } - } } - return arguments; + return Task.FromResult(kernelAgent).Result; } - #endregion } diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index b40116f411d0..2e9a5bf34933 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -33,6 +33,7 @@ + diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs index 3f79a53dddcd..bad824c0b029 100644 --- a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; using Microsoft.SemanticKernel.Agents.Factory; namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Provides a which creates instances of . +/// Provides a which creates instances of . /// public sealed class OpenAIAssistantAgentFactory : IKernelAgentFactory { @@ -16,51 +17,22 @@ public sealed class OpenAIAssistantAgentFactory : IKernelAgentFactory public static string OpenAIAssistantAgentType => "openai_assistant"; /// - public bool TryCreate(Kernel kernel, AgentDefinition agentDefinition, [NotNullWhen(true)] out KernelAgent? result) + public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); + KernelAgent? kernelAgent = null; if (agentDefinition.Type?.Equals(OpenAIAssistantAgentType, System.StringComparison.Ordinal) ?? false) { - result = new OpenAIAssistantAgent() - { - Name = agentDefinition.Name, - Description = agentDefinition.Description, - Instructions = agentDefinition.Instructions, - Arguments = GetKernelArguments(agentDefinition), - Kernel = kernel, - LoggerFactory = kernel.LoggerFactory, - }; - return true; + kernelAgent = await OpenAIAssistantAgent.CreateAsync( + clientProvider: kernel.GetOpenAIClientProvider(agentDefinition), + definition: agentDefinition.GetOpenAIAssistantDefinition(), + kernel: kernel, + defaultArguments: agentDefinition.GetDefaultKernelArguments(), + cancellationToken: cancellationToken + ).ConfigureAwait(false); } - result = null; - return false; + return Task.FromResult(kernelAgent).Result; } - - #region private - private static OpenAIClientProvider GetClientProvider(this Kernel kernel) - { - - } - - private static KernelArguments GetKernelArguments(AgentDefinition agentDefinition) - { - var arguments = new KernelArguments(agentDefinition?.Model?.Options); - - if (agentDefinition is not null) - { - // Add default arguments for the agent - foreach (var input in agentDefinition.Inputs) - { - if (!input.IsRequired && input.Default is not null) - { - arguments.Add(input.Name, input.Default); - } - } - } - - return arguments; - } - #endregion } diff --git a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs new file mode 100644 index 000000000000..3b981ae82650 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Provides extension methods for . +/// +internal static class AgentDefinitionExtensions +{ + /// + /// Property name for the file ids configuration. + /// + public const string FileIds = "file_ids"; + + /// + /// Return the which corresponds with the provided . + /// + /// Agent definition + public static OpenAIAssistantDefinition GetOpenAIAssistantDefinition(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + Verify.NotNull(agentDefinition.Model); + Verify.NotNull(agentDefinition.Model.Id); + + return new OpenAIAssistantDefinition(agentDefinition.Model.Id) + { + Id = agentDefinition.Id ?? string.Empty, + Name = agentDefinition.Name, + Description = agentDefinition.Description, + Instructions = agentDefinition.Instructions, + EnableCodeInterpreter = agentDefinition.IsEnableCodeInterpreter(), + CodeInterpreterFileIds = agentDefinition.GetCodeInterpreterFileIds(), + EnableFileSearch = agentDefinition.IsEnableFileSearch(), + VectorStoreId = agentDefinition.GetVectorStoreId(), + EnableJsonResponse = agentDefinition?.Model?.Options?.IsEnableJsonResponse() ?? false, + Temperature = agentDefinition?.Model?.Options?.GetTemperature(), + TopP = agentDefinition?.Model?.Options?.GetTopP(), + ExecutionOptions = agentDefinition.GetExecutionOptions(), + Metadata = agentDefinition.GetMetadata(), + }; + } + + /// + /// Retrieve the code interpreter file IDs from the agent definition. + /// + /// Agent definition + public static IReadOnlyList? GetCodeInterpreterFileIds(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + var toolDefinition = agentDefinition.GetFirstToolDefinition(ToolDefinition.CodeInterpreter); + if (toolDefinition?.Configuration?.TryGetValue(FileIds, out var fileIds) ?? false) + { + // TODO: Verify that the fileIds are strings + return (IReadOnlyList)fileIds; + } + + return null; + } + + /// + /// Retrieve the vector store ID from the agent definition. + /// + /// Agent definition + public static string? GetVectorStoreId(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + // TODO: Implement + return null; + } + + /// + /// Retrieve the execution options from the agent definition. + /// + /// Agent definition + public static OpenAIAssistantExecutionOptions? GetExecutionOptions(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + // TODO: Implement + return null; + } + + /// + /// Retrieve the metadata from the agent definition. + /// + /// Agent definition + public static IReadOnlyDictionary? GetMetadata(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + // TODO: Implement + return null; + } +} diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs index d1e7e0059494..456eee01eb7d 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs @@ -1,11 +1,26 @@ // Copyright (c) Microsoft. All rights reserved. +using System; +using System.ClientModel; +using System.Linq; + namespace Microsoft.SemanticKernel.Agents.OpenAI; +/// +/// +/// internal static class KernelExtensions { + private const string ConfigEndpoint = "endpoint"; + private const string ConfigApiKey = "api_key"; + /// /// Retrieve a kernel function based on the tool name. /// + /// + /// + /// + /// + /// public static KernelFunction GetKernelFunction(this Kernel kernel, string functionName, char delimiter) { string[] nameParts = functionName.Split(delimiter); @@ -15,4 +30,45 @@ public static KernelFunction GetKernelFunction(this Kernel kernel, string functi _ => throw new KernelException($"Agent Failure - Unknown tool: {functionName}"), }; } + + /// + /// Return the to be used with the specified . + /// + /// Kernel instance which will be used to resolve a default . + /// Agent definition whih will be used t provide configuration for the . + public static OpenAIClientProvider GetOpenAIClientProvider(this Kernel kernel, AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + // Use the agent configuration as the first option + var configuration = agentDefinition?.Model?.Configuration; + if (configuration is not null) + { + var hasEndpoint = configuration.TryGetValue(ConfigEndpoint, out var endpoint) && endpoint is not null; + var hasApiKey = configuration.TryGetValue(ConfigApiKey, out var apiKey) && apiKey is not null; + if (hasApiKey && hasEndpoint) + { + return OpenAIClientProvider.ForAzureOpenAI(new ApiKeyCredential(apiKey!.ToString()!), new Uri(endpoint!.ToString()!)); + } + else if (hasApiKey && !hasEndpoint) + { + return OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(apiKey!.ToString()!)); + } + /* + else if (!hasApiKey && hasEndpoint) + { + return OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(endpoint!.ToString()!)); + } + */ + } + + // Return the service registered on the kernel + var clientProvider = kernel.GetAllServices().FirstOrDefault(); + if (clientProvider is not null) + { + return clientProvider; + } + + throw new InvalidOperationException("OpenAI client provider not found."); + } } diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs index a3ccf53d33be..20eeae3520bc 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs @@ -7,7 +7,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Extensions for to support OpenAI specific operations. /// -public static class KernelFunctionExtensions +internal static class KernelFunctionExtensions { /// /// Convert to an OpenAI tool model. diff --git a/dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs new file mode 100644 index 000000000000..8159b3a5215b --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.Connectors.OpenAI; + +namespace Microsoft.SemanticKernel; + +/// +/// Provides extension methods for interacting with +/// +public static class PromptExecutionSettingsExtensions +{ + /// + /// Property name for model temperature. + /// + private const string Temperature = "temperature"; + + /// + /// Property name for model TopP. + /// + private const string TopP = "top_p"; + + /// + /// Property name for model response format. + /// + private const string ResponseFormat = "response_format"; + + /// + /// Get the temperature property. + /// + /// Prompt execution settings. + public static float? GetTemperature(this PromptExecutionSettings executionSettings) + { + Verify.NotNull(executionSettings); + + if (executionSettings is OpenAIPromptExecutionSettings openaiSettings) + { + return (float?)openaiSettings.Temperature; + } + + if (executionSettings?.ExtensionData?.TryGetValue(Temperature, out var temperature) ?? false) + { + return (float?)temperature; + } + return null; + } + + /// + /// Get the TopP property. + /// + /// Prompt execution settings. + public static float? GetTopP(this PromptExecutionSettings executionSettings) + { + Verify.NotNull(executionSettings); + + if (executionSettings is OpenAIPromptExecutionSettings openaiSettings) + { + return (float?)openaiSettings.TopP; + } + + if (executionSettings?.ExtensionData?.TryGetValue(TopP, out var topP) ?? false) + { + return (float?)topP; + } + return null; + } + + /// + /// Get the ResponseFormat property. + /// + /// Prompt execution settings. + public static bool IsEnableJsonResponse(this PromptExecutionSettings executionSettings) + { + Verify.NotNull(executionSettings); + + if (executionSettings is OpenAIPromptExecutionSettings openaiSettings) + { + return openaiSettings.ResponseFormat is not null; + } + + if (executionSettings?.ExtensionData?.TryGetValue(ResponseFormat, out var responseFormat) ?? false) + { + return responseFormat is not null; + } + return false; + } +} diff --git a/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs index fdb21489c8e1..5e2ead4d3344 100644 --- a/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Xunit; @@ -15,7 +16,7 @@ public class ChatCompletionAgentFactoryTests /// Verify can create an instance of . /// [Fact] - public void VerifyCanCreateChatCompletionAgent() + public async Task VerifyCanCreateChatCompletionAgentAsync() { // Arrange AgentDefinition agentDefinition = new() @@ -34,10 +35,9 @@ public void VerifyCanCreateChatCompletionAgent() Kernel kernel = new(); // Act - var result = factory.TryCreate(kernel, agentDefinition, out var agent); + var agent = await factory.CreateAsync(kernel, agentDefinition); // Assert - Assert.True(result); Assert.NotNull(agent); Assert.Equal(agentDefinition.Name, agent.Name); Assert.Equal(agentDefinition.Description, agent.Description); diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs new file mode 100644 index 000000000000..52e9a7e8c7f4 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ClientModel; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI.Definition; + +/// +/// Unit testing of . +/// +public class OpenAIAssistantAgentFactoryTests : IDisposable +{ + private readonly HttpMessageHandlerStub _messageHandlerStub; + private readonly HttpClient _httpClient; + private readonly Kernel _kernel; + + /// + /// Initializes a new instance of the class. + /// + public OpenAIAssistantAgentFactoryTests() + { + this._messageHandlerStub = new HttpMessageHandlerStub(); + this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); + + var builder = Kernel.CreateBuilder(); + builder.Services.AddSingleton(OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient)); + this._kernel = builder.Build(); + } + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + this._messageHandlerStub.Dispose(); + this._httpClient.Dispose(); + } + + /// + /// Verify can create an instance of . + /// + [Fact] + public async Task VerifyCanCreateChatCompletionAgentAsync() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Type = OpenAIAssistantAgentFactory.OpenAIAssistantAgentType, + Name = "OpenAIAssistantAgent", + Description = "OpenAIAssistantAgent Description", + Instructions = "OpenAIAssistantAgent Instructions", + Model = new() + { + Id = "gpt-4o-mini" + }, + Tools = [ + new ToolDefinition() + { + Name = "tool1", + Type = ToolDefinition.CodeInterpreter, + }, + ] + }; + OpenAIAssistantAgentFactory factory = new(); + this.SetupResponse(HttpStatusCode.OK, agentDefinition.GetOpenAIAssistantDefinition()); + + // Act + var agent = await factory.CreateAsync(this._kernel, agentDefinition); + + // Assert + Assert.NotNull(agent); + Assert.Equal(agentDefinition.Name, agent.Name); + Assert.Equal(agentDefinition.Description, agent.Description); + Assert.Equal(agentDefinition.Instructions, agent.Instructions); + Assert.Equal(this._kernel, agent.Kernel); + } + + #region private + private void SetupResponse(HttpStatusCode statusCode, OpenAIAssistantDefinition definition) => + this._messageHandlerStub.SetupResponses(statusCode, OpenAIAssistantResponseContent.AssistantDefinition(definition)); + + #endregion +} diff --git a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs index 6620622964ee..4d08367e01c1 100644 --- a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs +++ b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs @@ -43,6 +43,17 @@ public IDictionary? ExtensionData } } + /// + /// Gets the value associated with the specified key. + /// + /// The key whose value to get. + /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized. + public bool TryGetValue(string key, out object? value) + { + value = null; + return this._extensionData?.TryGetValue(key, out value) ?? false; + } + #region private private string? _type; private IDictionary? _extensionData; diff --git a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj index 235c08e4d52b..8cc7e60512b9 100644 --- a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj +++ b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj @@ -58,5 +58,9 @@ + + + + From d7099ee2905ec1b6e67a6f877e60f6eb87a496bd Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:00:49 +0000 Subject: [PATCH 10/30] Add implementation for AzureAI agents --- .../src/Agents/AzureAI/Agents.AzureAI.csproj | 1 + .../AzureAI/Definition/AzureAIAgentFactory.cs | 5 +- .../Extensions/AgentDefinitionExtensions.cs | 35 +++++- .../AzureAI/Extensions/KernelExtensions.cs | 37 +++--- .../Definition/ChatCompletionAgentFactory.cs | 4 +- .../Extensions/AgentDefinitionExtensions.cs | 4 +- .../OpenAI/Extensions/KernelExtensions.cs | 18 +-- .../Definition/AzureAIAgentFactoryTests.cs | 109 ++++++++++++++++++ .../ChatCompletionAgentFactoryTests.cs | 4 +- .../OpenAIAssistantAgentFactoryTests.cs | 7 +- dotnet/src/Agents/Yaml/KernelAgentYaml.cs | 20 +++- .../LiquidPromptTemplate.cs | 2 +- 12 files changed, 191 insertions(+), 55 deletions(-) create mode 100644 dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs diff --git a/dotnet/src/Agents/AzureAI/Agents.AzureAI.csproj b/dotnet/src/Agents/AzureAI/Agents.AzureAI.csproj index fb3f3ef8196b..bf95a036623f 100644 --- a/dotnet/src/Agents/AzureAI/Agents.AzureAI.csproj +++ b/dotnet/src/Agents/AzureAI/Agents.AzureAI.csproj @@ -38,6 +38,7 @@ + diff --git a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs index ad67d36a9e84..edf677a95133 100644 --- a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs +++ b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs @@ -5,7 +5,7 @@ using Azure.AI.Projects; using Microsoft.SemanticKernel.Agents.Factory; -namespace Microsoft.SemanticKernel.Agents.AzureAI.Definition; +namespace Microsoft.SemanticKernel.Agents.AzureAI; /// /// Provides a which creates instances of . @@ -36,7 +36,8 @@ public sealed class AzureAIAgentFactory : IKernelAgentFactory description: agentDefinition.Description, instructions: agentDefinition.Instructions, tools: agentDefinition.GetAzureToolDefinitions(), - metadata: agentDefinition.GetMetadata(kernel)); + metadata: agentDefinition.GetMetadata(), + cancellationToken: cancellationToken).ConfigureAwait(false); kernelAgent = new AzureAIAgent(agent, clientProvider) { diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs index 37fb98ccd894..5d84567310d9 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs @@ -1,10 +1,43 @@ // Copyright (c) Microsoft. All rights reserved. -namespace Microsoft.SemanticKernel.Agents.AzureAI.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.SemanticKernel.Agents.AzureAI; /// /// Provides extension methods for . /// internal static class AgentDefinitionExtensions { + /// + /// Return the Azure AI tool definitions which corresponds with the provided . + /// + /// Agent definition + /// + public static IEnumerable GetAzureToolDefinitions(this AgentDefinition agentDefinition) + { + return agentDefinition.Tools.Select(tool => + { + return tool.Type switch + { + "code_interpreter" => new Azure.AI.Projects.CodeInterpreterToolDefinition(), + "file_search" => new Azure.AI.Projects.FileSearchToolDefinition(), + _ => throw new InvalidOperationException($"Unable to created Azure AI tool definition because of known tool type: {tool.Type}"), + }; + }); + } + + /// + /// Retrieve the metadata from the agent definition. + /// + /// Agent definition + public static IReadOnlyDictionary? GetMetadata(this AgentDefinition agentDefinition) + { + Verify.NotNull(agentDefinition); + + // TODO: Implement + return null; + } } diff --git a/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs index 05575f1d6777..affd5ac3cf77 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs @@ -1,18 +1,18 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.ClientModel; using System.Linq; +using Azure.AI.Projects; +using Azure.Identity; -namespace Microsoft.SemanticKernel.Agents.AzureAI.Extensions; +namespace Microsoft.SemanticKernel.Agents.AzureAI; /// /// Provides extension methods for . /// internal static class KernelExtensions { - private const string ConfigEndpoint = "endpoint"; - private const string ConfigApiKey = "api_key"; + private const string ConnectionString = "connection_string"; /// /// Return the to be used with the specified . @@ -27,31 +27,22 @@ public static AzureAIClientProvider GetAzureAIClientProvider(this Kernel kernel, var configuration = agentDefinition?.Model?.Configuration; if (configuration is not null) { - var hasEndpoint = configuration.TryGetValue(ConfigEndpoint, out var endpoint) && endpoint is not null; - var hasApiKey = configuration.TryGetValue(ConfigApiKey, out var apiKey) && apiKey is not null; - if (hasApiKey && hasEndpoint) + var hasConnectionString = configuration.TryGetValue(ConnectionString, out var connectionString) && connectionString is not null; + if (hasConnectionString) { - return OpenAIClientProvider.ForAzureOpenAI(new ApiKeyCredential(apiKey!.ToString()!), new Uri(endpoint!.ToString()!)); + return AzureAIClientProvider.FromConnectionString(connectionString!.ToString()!, new AzureCliCredential()); } - else if (hasApiKey && !hasEndpoint) - { - return OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(apiKey!.ToString()!)); - } - /* - else if (!hasApiKey && hasEndpoint) - { - return OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(endpoint!.ToString()!)); - } - */ } - // Return the service registered on the kernel - var clientProvider = kernel.GetAllServices().FirstOrDefault(); - if (clientProvider is not null) + // Return the client registered on the kernel + var client = kernel.GetAllServices().FirstOrDefault(); + if (client is not null) { - return clientProvider; + return AzureAIClientProvider.FromClient(client); } - throw new InvalidOperationException("AzureAI client provider not found."); + // Return the service registered on the kernel + var clientProvider = kernel.GetAllServices().FirstOrDefault(); + return (AzureAIClientProvider?)clientProvider ?? throw new InvalidOperationException("AzureAI client provider not found."); } } diff --git a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs index eb9aedfe6c1e..4ee05b8df480 100644 --- a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs @@ -17,7 +17,7 @@ public sealed class ChatCompletionAgentFactory : IKernelAgentFactory public static string ChatCompletionAgentType => "chat_completion_agent"; /// - public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); @@ -37,6 +37,6 @@ public sealed class ChatCompletionAgentFactory : IKernelAgentFactory }; } - return Task.FromResult(kernelAgent).Result; + return Task.FromResult(kernelAgent); } } diff --git a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs index 3b981ae82650..fac4edc08d49 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs @@ -37,8 +37,8 @@ public static OpenAIAssistantDefinition GetOpenAIAssistantDefinition(this AgentD EnableJsonResponse = agentDefinition?.Model?.Options?.IsEnableJsonResponse() ?? false, Temperature = agentDefinition?.Model?.Options?.GetTemperature(), TopP = agentDefinition?.Model?.Options?.GetTopP(), - ExecutionOptions = agentDefinition.GetExecutionOptions(), - Metadata = agentDefinition.GetMetadata(), + ExecutionOptions = agentDefinition?.GetExecutionOptions(), + Metadata = agentDefinition?.GetMetadata(), }; } diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs index 456eee01eb7d..4168fa7ed697 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs @@ -6,12 +6,12 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// +/// Provides extension methods for . /// internal static class KernelExtensions { - private const string ConfigEndpoint = "endpoint"; - private const string ConfigApiKey = "api_key"; + private const string Endpoint = "endpoint"; + private const string ApiKey = "api_key"; /// /// Retrieve a kernel function based on the tool name. @@ -19,7 +19,6 @@ internal static class KernelExtensions /// /// /// - /// /// public static KernelFunction GetKernelFunction(this Kernel kernel, string functionName, char delimiter) { @@ -44,8 +43,8 @@ public static OpenAIClientProvider GetOpenAIClientProvider(this Kernel kernel, A var configuration = agentDefinition?.Model?.Configuration; if (configuration is not null) { - var hasEndpoint = configuration.TryGetValue(ConfigEndpoint, out var endpoint) && endpoint is not null; - var hasApiKey = configuration.TryGetValue(ConfigApiKey, out var apiKey) && apiKey is not null; + var hasEndpoint = configuration.TryGetValue(Endpoint, out var endpoint) && endpoint is not null; + var hasApiKey = configuration.TryGetValue(ApiKey, out var apiKey) && apiKey is not null; if (hasApiKey && hasEndpoint) { return OpenAIClientProvider.ForAzureOpenAI(new ApiKeyCredential(apiKey!.ToString()!), new Uri(endpoint!.ToString()!)); @@ -64,11 +63,6 @@ public static OpenAIClientProvider GetOpenAIClientProvider(this Kernel kernel, A // Return the service registered on the kernel var clientProvider = kernel.GetAllServices().FirstOrDefault(); - if (clientProvider is not null) - { - return clientProvider; - } - - throw new InvalidOperationException("OpenAI client provider not found."); + return (OpenAIClientProvider?)clientProvider ?? throw new InvalidOperationException("OpenAI client provider not found."); } } diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs new file mode 100644 index 000000000000..14adc5e7017c --- /dev/null +++ b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.Projects; +using Azure.Core; +using Azure.Core.Pipeline; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.AzureAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.AzureAI.Definition; + +/// +/// Unit tests for . +/// +public class AzureAIAgentFactoryTests : IDisposable +{ + private readonly HttpMessageHandlerStub _messageHandlerStub; + private readonly HttpClient _httpClient; + private readonly Kernel _kernel; + + /// + /// Initializes a new instance of the class. + /// + public AzureAIAgentFactoryTests() + { + this._messageHandlerStub = new HttpMessageHandlerStub(); + this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); + + var builder = Kernel.CreateBuilder(); + var client = new AIProjectClient( + "endpoint;subscription_id;resource_group_name;project_name", + new FakeTokenCredential(), + new AIProjectClientOptions() + { Transport = new HttpClientTransport(this._httpClient) }); + builder.Services.AddSingleton(AzureAIClientProvider.FromClient(client)); + this._kernel = builder.Build(); + } + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + this._messageHandlerStub.Dispose(); + this._httpClient.Dispose(); + } + + /// + /// Verify can create an instance of using + /// + [Fact] + public async Task VerifyCanCreateOpenAIAssistantAsync() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Type = AzureAIAgentFactory.AzureAIAgentType, + Name = "AzureAIAgent", + Description = "AzureAIAgent Description", + Instructions = "AzureAIAgent Instructions", + Model = new() + { + Id = "gpt-4o-mini" + }, + Tools = [ + new Microsoft.SemanticKernel.Agents.ToolDefinition() + { + Name = "tool1", + Type = Microsoft.SemanticKernel.Agents.ToolDefinition.CodeInterpreter, + }, + ] + }; + AzureAIAgentFactory factory = new(); + //this.SetupResponse(HttpStatusCode.OK, agentDefinition.GetOpenAIAssistantDefinition()); + + // Act + var agent = await factory.CreateAsync(this._kernel, agentDefinition); + + // Assert + Assert.NotNull(agent); + Assert.Equal(agentDefinition.Name, agent.Name); + Assert.Equal(agentDefinition.Description, agent.Description); + Assert.Equal(agentDefinition.Instructions, agent.Instructions); + Assert.Equal(this._kernel, agent.Kernel); + } + + + #region private + private class FakeTokenCredential : TokenCredential + { + /// + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new AccessToken("fakeToken", DateTimeOffset.Now.AddHours(1)); + } + + /// + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new ValueTask(new AccessToken("fakeToken", DateTimeOffset.Now.AddHours(1))); + } + } + #endregion +} diff --git a/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs index 5e2ead4d3344..7f7d7d161c3c 100644 --- a/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Definition/ChatCompletionAgentFactoryTests.cs @@ -8,12 +8,12 @@ namespace SemanticKernel.Agents.UnitTests.Core.Definition; /// -/// Unit testing of . +/// Unit tests for . /// public class ChatCompletionAgentFactoryTests { /// - /// Verify can create an instance of . + /// Verify can create an instance of using /// [Fact] public async Task VerifyCanCreateChatCompletionAgentAsync() diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs index 52e9a7e8c7f4..bc69b8dcc4a6 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs @@ -14,7 +14,7 @@ namespace SemanticKernel.Agents.UnitTests.OpenAI.Definition; /// -/// Unit testing of . +/// Unit tests for . /// public class OpenAIAssistantAgentFactoryTests : IDisposable { @@ -44,10 +44,10 @@ public void Dispose() } /// - /// Verify can create an instance of . + /// Verify can create an instance of using /// [Fact] - public async Task VerifyCanCreateChatCompletionAgentAsync() + public async Task VerifyCanCreateOpenAIAssistantAsync() { // Arrange AgentDefinition agentDefinition = new() @@ -85,6 +85,5 @@ public async Task VerifyCanCreateChatCompletionAgentAsync() #region private private void SetupResponse(HttpStatusCode statusCode, OpenAIAssistantDefinition definition) => this._messageHandlerStub.SetupResponses(statusCode, OpenAIAssistantResponseContent.AssistantDefinition(definition)); - #endregion } diff --git a/dotnet/src/Agents/Yaml/KernelAgentYaml.cs b/dotnet/src/Agents/Yaml/KernelAgentYaml.cs index beebebb68051..b69c449a477e 100644 --- a/dotnet/src/Agents/Yaml/KernelAgentYaml.cs +++ b/dotnet/src/Agents/Yaml/KernelAgentYaml.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.Extensions.Logging; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel.Agents.Factory; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -11,15 +13,21 @@ namespace Microsoft.SemanticKernel.Agents; /// public static class KernelAgentYaml { - public static KernelAgent FromAgentYaml( - string text, - ILoggerFactory? loggerFactory = null) + /// + /// Create a from the given YAML text. + /// + /// + /// + /// + /// + public static async Task FromAgentYamlAsync(Kernel kernel, string text, IKernelAgentFactory kernelAgentFactory, CancellationToken cancellationToken = default) { var agentDefinition = ToAgentDefinition(text); - return KernelAgentFactory.CreateFromDefinition( + return await kernelAgentFactory.CreateAsync( + kernel, agentDefinition, - loggerFactory); + cancellationToken).ConfigureAwait(false); } /// diff --git a/dotnet/src/Extensions/PromptTemplates.Liquid/LiquidPromptTemplate.cs b/dotnet/src/Extensions/PromptTemplates.Liquid/LiquidPromptTemplate.cs index 474cd99cbd56..dcc412fff8a7 100644 --- a/dotnet/src/Extensions/PromptTemplates.Liquid/LiquidPromptTemplate.cs +++ b/dotnet/src/Extensions/PromptTemplates.Liquid/LiquidPromptTemplate.cs @@ -18,7 +18,7 @@ namespace Microsoft.SemanticKernel.PromptTemplates.Liquid; internal sealed partial class LiquidPromptTemplate : IPromptTemplate { private static readonly FluidParser s_parser = new(); - private static readonly TemplateOptions s_templateOptions = new() + private static readonly Fluid.TemplateOptions s_templateOptions = new() { MemberAccessStrategy = new UnsafeMemberAccessStrategy() { MemberNameStrategy = MemberNameStrategies.SnakeCase }, }; From 3cb6c851529f0c06e9ec2beb2e028660021bd785 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 20 Feb 2025 07:28:28 +0000 Subject: [PATCH 11/30] AzureAI Agent unit tests --- .../Extensions/KernelFunctionExtensions.cs | 2 +- .../Agents/UnitTests/Agents.UnitTests.csproj | 1 + .../Definition/AzureAIAgentFactoryTests.cs | 42 +++--- .../UnitTests/Test/FakeTokenCredential.cs | 23 +++ .../UnitTests/Yaml/KernelAgentYamlTests.cs | 132 ++++++++++++++++++ 5 files changed, 183 insertions(+), 17 deletions(-) create mode 100644 dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs create mode 100644 dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs index 20eeae3520bc..a3ccf53d33be 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelFunctionExtensions.cs @@ -7,7 +7,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Extensions for to support OpenAI specific operations. /// -internal static class KernelFunctionExtensions +public static class KernelFunctionExtensions { /// /// Convert to an OpenAI tool model. diff --git a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj index 32d31f65c776..c108e5293bad 100644 --- a/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj +++ b/dotnet/src/Agents/UnitTests/Agents.UnitTests.csproj @@ -34,6 +34,7 @@ + diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs index 14adc5e7017c..7747e1d9100c 100644 --- a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; using Azure.AI.Projects; -using Azure.Core; using Azure.Core.Pipeline; using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; @@ -51,10 +50,10 @@ public void Dispose() } /// - /// Verify can create an instance of using + /// Verify can create an instance of using /// [Fact] - public async Task VerifyCanCreateOpenAIAssistantAsync() + public async Task VerifyCanCreateAzureAIAgentAsync() { // Arrange AgentDefinition agentDefinition = new() @@ -76,7 +75,7 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() ] }; AzureAIAgentFactory factory = new(); - //this.SetupResponse(HttpStatusCode.OK, agentDefinition.GetOpenAIAssistantDefinition()); + this.SetupResponse(HttpStatusCode.OK, AzureAIAgentResponse); // Act var agent = await factory.CreateAsync(this._kernel, agentDefinition); @@ -89,21 +88,32 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() Assert.Equal(this._kernel, agent.Kernel); } - #region private - private class FakeTokenCredential : TokenCredential - { - /// - public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + private const string AzureAIAgentResponse = + """ { - return new AccessToken("fakeToken", DateTimeOffset.Now.AddHours(1)); + "id": "asst_thdyqg4yVC9ffeILVdEWLONT", + "object": "assistant", + "created_at": 1739991984, + "name": "AzureAIAgent", + "description": "AzureAIAgent Description", + "model": "gpt-4o", + "instructions": "AzureAIAgent Instructions", + "tools": [], + "top_p": 1.0, + "temperature": 1.0, + "tool_resources": {}, + "metadata": {}, + "response_format": "auto" } + """; - /// - public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + private void SetupResponse(HttpStatusCode statusCode, string response) => +#pragma warning disable CA2000 // Dispose objects before losing scope + this._messageHandlerStub.ResponseQueue.Enqueue(new(statusCode) { - return new ValueTask(new AccessToken("fakeToken", DateTimeOffset.Now.AddHours(1))); - } - } + Content = new StringContent(response) + }); +#pragma warning restore CA2000 // Dispose objects before losing scope #endregion } diff --git a/dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs b/dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs new file mode 100644 index 000000000000..61dd0deacc2c --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; + +namespace SemanticKernel.Agents.UnitTests; + +internal class FakeTokenCredential : TokenCredential +{ + /// + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new AccessToken("fakeToken", DateTimeOffset.Now.AddHours(1)); + } + + /// + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new ValueTask(new AccessToken("fakeToken", DateTimeOffset.Now.AddHours(1))); + } +} diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs new file mode 100644 index 000000000000..c8bf5e8d075e --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ClientModel; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Azure.AI.Projects; +using Azure.Core.Pipeline; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.AzureAI; +using Microsoft.SemanticKernel.Agents.OpenAI; +using SemanticKernel.Agents.UnitTests.OpenAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Yaml; + +/// +/// Unit tests for . +/// +public class KernelAgentYamlTests : IDisposable +{ + private readonly HttpMessageHandlerStub _messageHandlerStub; + private readonly HttpClient _httpClient; + private readonly Kernel _kernel; + + /// + /// Initializes a new instance of the class. + /// + public KernelAgentYamlTests() + { + this._messageHandlerStub = new HttpMessageHandlerStub(); + this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); + + var builder = Kernel.CreateBuilder(); + + // Add OpenAI client provider + builder.Services.AddSingleton(OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient)); + + // Add Azure AI client provider + var client = new AIProjectClient( + "endpoint;subscription_id;resource_group_name;project_name", + new FakeTokenCredential(), + new AIProjectClientOptions() + { Transport = new HttpClientTransport(this._httpClient) }); + builder.Services.AddSingleton(AzureAIClientProvider.FromClient(client)); + + this._kernel = builder.Build(); + } + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + this._messageHandlerStub.Dispose(); + this._httpClient.Dispose(); + } + + /// + /// Verify can create an instance of using + /// + [Fact] + public async Task VerifyCanCreateChatCompletionAgentAsync() + { + // Arrange + var text = + """ + type: chat_completion_agent + name: ChatCompletionAgent + description: ChatCompletionAgent Description + instructions: ChatCompletionAgent Instructions + model: + id: gpt-4o-mini + options: + temperature: 0.4 + function_choice_behavior: + type: auto + """; + ChatCompletionAgentFactory factory = new(); + Kernel kernel = new(); + + // Act + var agent = await KernelAgentYaml.FromAgentYamlAsync(kernel, text, factory); + + // Assert + Assert.NotNull(agent); + Assert.True(agent is ChatCompletionAgent); + Assert.Equal("ChatCompletionAgent", agent.Name); + Assert.Equal("ChatCompletionAgent Description", agent.Description); + Assert.Equal("ChatCompletionAgent Instructions", agent.Instructions); + Assert.Equal(kernel, agent.Kernel); + } + + /// + /// Verify can create an instance of using + /// + [Fact] + public async Task VerifyCanCreateOpenAIAssistantAsync() + { + // Arrange + var text = + """ + type: openai_assistant + name: OpenAIAssistantAgent + description: OpenAIAssistantAgent Description + instructions: OpenAIAssistantAgent Instructions + model: + id: gpt-4o-mini + tools: + - name: tool1 + type: code_interpreter + """; + OpenAIAssistantAgentFactory factory = new(); + this.SetupResponse(HttpStatusCode.OK, KernelAgentYaml.ToAgentDefinition(text).GetOpenAIAssistantDefinition()); + + // Act + var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); + + // Assert + // Assert + Assert.NotNull(agent); + Assert.True(agent is OpenAIAssistantAgent); + Assert.Equal(this._kernel, agent.Kernel); + } + + #region private + private void SetupResponse(HttpStatusCode statusCode, OpenAIAssistantDefinition definition) => + this._messageHandlerStub.SetupResponses(statusCode, OpenAIAssistantResponseContent.AssistantDefinition(definition)); + #endregion +} From 698ac9b001d4aaeb8bb18e67a55ebd26918d149d Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:58:30 +0000 Subject: [PATCH 12/30] Add new unit tests pass --- .../Definition/AzureAIAgentFactoryTests.cs | 7 ++- .../UnitTests/Yaml/KernelAgentYamlTests.cs | 51 +++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs index 7747e1d9100c..a5b8c52ddb71 100644 --- a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs @@ -88,8 +88,10 @@ public async Task VerifyCanCreateAzureAIAgentAsync() Assert.Equal(this._kernel, agent.Kernel); } - #region private - private const string AzureAIAgentResponse = + /// + /// Azure AI Agent response. + /// + public const string AzureAIAgentResponse = """ { "id": "asst_thdyqg4yVC9ffeILVdEWLONT", @@ -108,6 +110,7 @@ public async Task VerifyCanCreateAzureAIAgentAsync() } """; + #region private private void SetupResponse(HttpStatusCode statusCode, string response) => #pragma warning disable CA2000 // Dispose objects before losing scope this._messageHandlerStub.ResponseQueue.Enqueue(new(statusCode) diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs index c8bf5e8d075e..cc5b355299a6 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs @@ -12,6 +12,7 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.AzureAI; using Microsoft.SemanticKernel.Agents.OpenAI; +using SemanticKernel.Agents.UnitTests.AzureAI.Definition; using SemanticKernel.Agents.UnitTests.OpenAI; using Xunit; @@ -79,10 +80,9 @@ public async Task VerifyCanCreateChatCompletionAgentAsync() type: auto """; ChatCompletionAgentFactory factory = new(); - Kernel kernel = new(); // Act - var agent = await KernelAgentYaml.FromAgentYamlAsync(kernel, text, factory); + var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); // Assert Assert.NotNull(agent); @@ -90,7 +90,7 @@ public async Task VerifyCanCreateChatCompletionAgentAsync() Assert.Equal("ChatCompletionAgent", agent.Name); Assert.Equal("ChatCompletionAgent Description", agent.Description); Assert.Equal("ChatCompletionAgent Instructions", agent.Instructions); - Assert.Equal(kernel, agent.Kernel); + Assert.Equal(this._kernel, agent.Kernel); } /// @@ -118,15 +118,58 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() // Act var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); - // Assert // Assert Assert.NotNull(agent); Assert.True(agent is OpenAIAssistantAgent); + Assert.Equal("OpenAIAssistantAgent", agent.Name); + Assert.Equal("OpenAIAssistantAgent Description", agent.Description); + Assert.Equal("OpenAIAssistantAgent Instructions", agent.Instructions); + Assert.Equal(this._kernel, agent.Kernel); + } + + /// + /// Verify can create an instance of using + /// + [Fact] + public async Task VerifyCanCreateAzureAIAgentAsync() + { + // Arrange + var text = + """ + type: azureai_agent + name: AzureAIAgent + description: AzureAIAgent Description + instructions: AzureAIAgent Instructions + model: + id: gpt-4o-mini + tools: + - name: tool1 + type: code_interpreter + """; + AzureAIAgentFactory factory = new(); + this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse); + + // Act + var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); + + // Assert + Assert.NotNull(agent); + Assert.True(agent is AzureAIAgent); + Assert.Equal("AzureAIAgent", agent.Name); + Assert.Equal("AzureAIAgent Description", agent.Description); + Assert.Equal("AzureAIAgent Instructions", agent.Instructions); Assert.Equal(this._kernel, agent.Kernel); } #region private private void SetupResponse(HttpStatusCode statusCode, OpenAIAssistantDefinition definition) => this._messageHandlerStub.SetupResponses(statusCode, OpenAIAssistantResponseContent.AssistantDefinition(definition)); + + private void SetupResponse(HttpStatusCode statusCode, string response) => +#pragma warning disable CA2000 // Dispose objects before losing scope + this._messageHandlerStub.ResponseQueue.Enqueue(new(statusCode) + { + Content = new StringContent(response) + }); #endregion } From 649cf0f73acbf06fd05761b32b5ae61089b2b0d8 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:28:56 +0000 Subject: [PATCH 13/30] Update ADR to match the code in the PR --- .../NNNN-declarative-agent-schema.md | 287 ++++-------------- .../Definition/AgentDefinition.cs | 14 + 2 files changed, 81 insertions(+), 220 deletions(-) diff --git a/docs/decisions/NNNN-declarative-agent-schema.md b/docs/decisions/NNNN-declarative-agent-schema.md index e49e884f6a66..9b1f1322fccc 100644 --- a/docs/decisions/NNNN-declarative-agent-schema.md +++ b/docs/decisions/NNNN-declarative-agent-schema.md @@ -20,39 +20,53 @@ Using the schema defined by this ADR developers will be able to declaratively de Here is some pseudo code to illustrate what we need to be able to do: ```csharp -Kernel kernel = ... -string agentYaml = EmbeddedResource.Read("MyAgent.yaml"); -Agent agent = kernel.CreateAgentFromYaml(agentYaml); - -agent.InvokeAsync(input); +Kernel kernel = Kernel + .CreateBuilder() + .AddAzureAIClientProvider(...) + .Build(); +var text = + """ + type: azureai_agent + name: AzureAIAgent + description: AzureAIAgent Description + instructions: AzureAIAgent Instructions + model: + id: gpt-4o-mini + tools: + - name: tool1 + type: code_interpreter + """; + +AzureAIAgentFactory factory = new(); +var agent = await KernelAgentYaml.FromAgentYamlAsync(kernel, text, factory); ``` The above code represents the simplest case would work as follows: -1. The Agent runtime is responsible for creating a `Kernel` instance which comes fully configured with all services, functions, etc. -2. The `CreateAgentFromYaml` will create one of the built-in Agent instances (currently just `ChatCompletionAgent`). +1. The `Kernel` instance has the appropriate services e.g. an instance of `AzureAIClientProvider` when creating AzureAI agents. +2. The `KernelAgentYaml.FromAgentYamlAsync` will create one of the built-in Agent instances i.e., one of `ChatCompletionAgent`, `OpenAIAssistantsAgent`, `AzureAIAgent`. 3. The new Agent instance is initialized with it's own `Kernel` instance configured the services and tools it requires and a default initial state. -4. The `Agent` abstraction contains a method to allow the Agent instance to be invoked with user input. + +Note: Consider creating just plain `Agent` instances and extending the `Agent` abstraction to contain a method which allows the Agent instance to be invoked with user input. ```csharp Kernel kernel = ... -string agentYaml = EmbeddedResource.Read("MyAgent.yaml"); +string text = EmbeddedResource.Read("MyAgent.yaml"); AgentFactory agentFactory = new AggregatorAgentFactory( new ChatCompletionAgentFactory(), new OpenAIAssistantAgentFactory(), - new XXXAgentFactory()); -Agent agent = kernel.CreateAgentFromYaml(agentYaml, agentFactory, agentState); - -agent.InvokeAsync(input); + new AzureAIAgentFactory()); +var agent = KernelAgentYaml.FromAgentYamlAsync(kernel, text, factory);; ``` -The above example shows how different Agent types are supported and also how the initial Agent state can be specified. +The above example shows how different Agent types are supported. **Note:** -1. Providing Agent state is not supported in the Agent Framework at present. -2. We need to decide if the Agent Framework should define an abstraction to allow any Agent to be invoked. -3. We will support JSON also as an out-of-the-box option. +1. Markdown with YAML front-matter (i.e. Prompty format) will be the primary serialization format used. +2. Providing Agent state is not supported in the Agent Framework at present. +3. We need to decide if the Agent Framework should define an abstraction to allow any Agent to be invoked. +4. We will support JSON also as an out-of-the-box option. Currently Semantic Kernel supports three Agent types and these have the following properties: @@ -94,7 +108,7 @@ When executing an Agent that was defined declaratively some of the properties wi - `Kernel`: The runtime will be responsible for create the `Kernel` instance to be used by the Agent. This `Kernel` instance must be configured with the models and tools that the Agent requires. - `Logger` or `LoggerFactory`: The runtime will be responsible for providing a correctly configured `Logger` or `LoggerFactory`. -- **Functions**: The runtime must be able to resolve any functions required by the Agent. E.g. the VSCode extension will provide a very basic runtime to allow developers to test Agents and it should eb able to resolve `KernelFunctions` defined in the current project. See later in the ADR for an example of this. +- **Functions**: The runtime must be able to resolve any functions required by the Agent. E.g. the VSCode extension will provide a very basic runtime to allow developers to test Agents and it should be able to resolve `KernelFunctions` defined in the current project. See later in the ADR for an example of this. For Agent properties that define behaviors e.g. `HistoryReducer` the Semantic Kernel **SHOULD**: @@ -217,16 +231,16 @@ ChatCompletionAgent agent = }; ``` -Declarative using M365 or Semantic Kernel schema: +Declarative Semantic Kernel schema: ```yml +type: chat_completion_agent name: Parrot instructions: Repeat the user message in the voice of a pirate and then end with a parrot sound. ``` **Note**: -- Both M365 and Semantic Kernel schema would be identical - `ChatCompletionAgent` could be the default agent type hence no explicit `type` property is required. #### `ChatCompletionAgent` using Prompt Template @@ -249,23 +263,17 @@ ChatCompletionAgent agent = }; ``` -Declarative using M365 Agent schema: - -```yml -name: GenerateStory -instructions: ${file['./GenerateStory.yaml']} -``` - Agent YAML points to another file, the Declarative Agent implementation in Semantic Kernel already uses this technique to load a separate instructions file. Prompt template which is used to define the instructions. ```yml +--- name: GenerateStory -template: | - Tell a story about {{$topic}} that is {{$length}} sentences long. -template_format: semantic-kernel -description: A function that generates a story about a topic. -input_variables: +description: A function that generates a story about a topic. +template: + format: semantic-kernel + parser: semantic-kernel +inputs: - name: topic description: The topic of the story. is_required: true @@ -274,6 +282,8 @@ input_variables: description: The number of sentences in the story. is_required: true default: 3 +--- +Tell a story about {{$topic}} that is {{$length}} sentences long. ``` **Note**: Semantic Kernel could load this file directly. @@ -297,36 +307,24 @@ KernelPlugin plugin = KernelPluginFactory.CreateFromType(); agent.Kernel.Plugins.Add(plugin); ``` -Declarative using M365 Agent schema: - -```yml -name: RestaurantHost -instructions: Answer questions about the menu. -description: This agent answers questions about the menu. -actions: - - id: MenuPlugin -``` - -**Note**: - -- There is no way to specify `Temperature` -- There is no way to specify the function choice behavior (e.g. could not specify required) -- All functions in the Plugin would be used. - Declarative using Semantic Kernel schema: ```yml +--- +name: RestaurantHost name: RestaurantHost -instructions: Answer questions about the menu. description: This agent answers questions about the menu. -execution_settings: - default: +model: + id: gpt-4o-mini + options: temperature: 0.4 function_choice_behavior: type: auto functions: - MenuPlugin.GetSpecials - MenuPlugin.GetItemPrice +--- +Answer questions about the menu. ``` #### `OpenAIAssistantAgent` with Function Calling @@ -349,33 +347,20 @@ KernelPlugin plugin = KernelPluginFactory.CreateFromType(); agent.Kernel.Plugins.Add(plugin); ``` -Declarative using M365 Agent schema: - -```yml -name: RestaurantHost -type: openai_assistant -instructions: Answer questions about the menu. -description: This agent answers questions about the menu. -metadata: - sksample: true -actions: - - id: MenuPlugin -``` - -**Note**: `type` and `metadata` properties need to be added somehow - Declarative using Semantic Kernel schema: Using the syntax below the assistant does not have the functions included in it's definition. The functions must be added to the `Kernel` instance associated with the Agent and will be passed when the Agent is invoked. ```yml +--- name: RestaurantHost type: openai_assistant -instructions: Answer questions about the menu. description: This agent answers questions about the menu. -execution_settings: - default: +model: + id: gpt-4o-mini + options: + temperature: 0.4 function_choice_behavior: type: auto functions: @@ -383,13 +368,16 @@ execution_settings: - MenuPlugin.GetItemPrice metadata: sksample: true +--- +Answer questions about the menu. `` -or the +or + ```yml +--- name: RestaurantHost type: openai_assistant -instructions: Answer questions about the menu. description: This agent answers questions about the menu. execution_settings: default: @@ -402,9 +390,10 @@ tools: name: MenuPlugin-GetItemPrice description: Provides the price of the requested menu item. parameters: '{"type":"object","properties":{"menuItem":{"type":"string","description":"The name of the menu item."}},"required":["menuItem"]}' +--- +Answer questions about the menu. ``` - **Note**: The `Kernel` instance used to create the Agent must have an instance of `OpenAIClientProvider` registered as a service. #### `OpenAIAssistantAgent` with Tools @@ -426,36 +415,17 @@ OpenAIAssistantAgent agent = kernel: new Kernel()); ``` -Declarative using M365 Agent schema: - -```yml -name: RestaurantHost -type: openai_assistant -instructions: Answer questions about the menu. -description: This agent answers questions about the menu. -metadata: - sksample: true -capabilities: - - name: CodeInterpreter - - name: FileSearch -actions: - - id: MenuPlugin -``` - -**Note**: `FileSearch` capability needs to be added - Declarative using Semantic Kernel: ```yml +--- name: Coder type: openai_assistant -instructions: You are an Agent that can write and execute code to answer questions. -execution_settings: - default: - enable_code_interpreter: true - enable_file_search: true - metadata: - sksample: true +tools: + - type: code_interpreter + - type: file_search +--- +You are an Agent that can write and execute code to answer questions. ``` ### Declarative Format Use Cases @@ -471,137 +441,14 @@ version: 0.0.1 #### Creating an Agent with access to function tools and a set of instructions to guide it's behavior -```yaml -name: RestaurantHost -type: azureai_agent -description: This agent answers questions about the menu. -instructions: Answer questions about the menu. -execution_settings: - default: - temperature: 0.4 - function_choice_behavior: - type: auto - functions: - - MenuPlugin.GetSpecials - - MenuPlugin.GetItemPrice -``` - -or - -```yml -name: RestaurantHost -type: azureai_agent -description: This agent answers questions about the menu. -instructions: Answer questions about the menu. -execution_settings: - default: - temperature: 0.4 -tools: - - type: function - name: MenuPlugin-GetSpecials - description: Provides a list of specials from the menu. - - type: function - name: MenuPlugin-GetItemPrice - description: Provides the price of the requested menu item. - parameters: '{"type":"object","properties":{"menuItem":{"type":"string","description":"The name of the menu item."}},"required":["menuItem"]}' -``` - #### Allow templating of Agent instructions (and other properties) -```yaml -name: GenerateStory -description: An agent that generates a story about a topic. -template: | - Tell a story about {{$topic}} that is {{$length}} sentences long. -template_format: semantic-kernel -input_variables: - - name: topic - description: The topic of the story. - is_required: true - - name: length - description: The number of sentences in the story. - is_required: true -execution_settings: - default: - temperature: 0.6 -``` - #### Configuring the model and providing multiple model configurations -```yaml -name: RestaurantHost -type: azureai_agent -description: This agent answers questions about the menu. -instructions: Answer questions about the menu. -execution_settings: - default: - temperature: 0.4 - gpt-4o: - temperature: 0.5 - gpt-4o-mini: - temperature: 0.5 -tools: - - type: function - name: MenuPlugin-GetSpecials - description: Provides a list of specials from the menu. - - type: function - name: MenuPlugin-GetItemPrice - description: Provides the price of the requested menu item. - parameters: '{"type":"object","properties":{"menuItem":{"type":"string","description":"The name of the menu item."}},"required":["menuItem"]}' -``` - #### Configuring data sources (context/knowledge) for the Agent to use -```yaml -name: Document FAQ Agent -type: azureai_agent -instructions: Use provide documents to answer questions. Politely decline to answer if the provided documents don't include an answer to the question. -description: This agent uses documents to answer questions. -execution_settings: - default: - metadata: - sksample: true -tools: - - type: file_search -``` - -**TODO: How do we configure the documents to include?** - #### Configuring additional tools for the Agent to use e.g. code interpreter, OpenAPI endpoints -```yaml -name: Coder Agent -type: azureai_agent -description: This agent uses code to answer questions. -instructions: Use code to answer questions. -execution_settings: - default: - metadata: - sksample: true -tools: - - type: code_interpreter -``` - -```yaml -name: Country FAQ Agent -type: azureai_agent -instructions: Answer questions about countries. For all other questions politely decline to answer. -description: This agent answers question about different countries. -execution_settings: - default: - metadata: - sksample: true -tools: - - type: openapi - name: RestCountriesAPI - description: Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com. - schema: '{"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}' -``` - #### Enabling additional modalities for the Agent e.g. speech -```yaml -``` - #### Error conditions e.g. models or function tools not being available - diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs index 127a21920216..09f6fe0ce091 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs @@ -10,6 +10,19 @@ namespace Microsoft.SemanticKernel.Agents; /// public sealed class AgentDefinition { + /// + /// Gets or sets the version of the schema. + /// + public string? Version + { + get => this._version; + set + { + Verify.NotNull(value); + this._version = value; + } + } + /// /// Gets or sets the id of the deployed agent. /// @@ -154,6 +167,7 @@ public IList Tools } #region + private string? _version; private string? _type; private string? _id; private string? _name; From 6f602c54aa1fbf6f35b111088ddcad61c8110d00 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Thu, 20 Feb 2025 20:14:38 +0000 Subject: [PATCH 14/30] Resolve merge conflicts --- .../Definition/AgentDefinition.cs | 4 ++-- ...olDefinition.cs => AgentToolDefinition.cs} | 2 +- .../Extensions/AgentDefinitionExtensions.cs | 6 +++--- .../AzureAI/Definition/AzureAIAgentFactory.cs | 2 +- .../Extensions/AgentDefinitionExtensions.cs | 2 +- .../Definition/OpenAIAssistantAgentFactory.cs | 21 ++++++++++++------- .../Extensions/AgentDefinitionExtensions.cs | 2 +- .../Definition/AzureAIAgentFactoryTests.cs | 4 ++-- .../OpenAIAssistantAgentFactoryTests.cs | 4 ++-- 9 files changed, 27 insertions(+), 20 deletions(-) rename dotnet/src/Agents/Abstractions/Definition/{ToolDefinition.cs => AgentToolDefinition.cs} (98%) diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs index 09f6fe0ce091..ca6dc2602d1c 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs @@ -156,7 +156,7 @@ public TemplateOptions? Template /// /// Gets or sets the collection of tools used by the agent. /// - public IList Tools + public IList Tools { get => this._tools ??= []; set @@ -178,6 +178,6 @@ public IList Tools private IList? _inputs; private IList? _outputs; private TemplateOptions? _template; - private IList? _tools; + private IList? _tools; #endregion } diff --git a/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs similarity index 98% rename from dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs rename to dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs index d641b14adf39..695ad310d9ad 100644 --- a/dotnet/src/Agents/Abstractions/Definition/ToolDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs @@ -8,7 +8,7 @@ namespace Microsoft.SemanticKernel.Agents; /// /// The options for defining a tool. /// -public class ToolDefinition +public class AgentToolDefinition { /// /// Tool definition type for code interpreter. diff --git a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs index 048b6bee974a..73a33dfebc20 100644 --- a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs @@ -38,7 +38,7 @@ public static KernelArguments GetDefaultKernelArguments(this AgentDefinition age /// /// Agent definition /// Tool type - public static ToolDefinition? GetFirstToolDefinition(this AgentDefinition agentDefinition, string toolType) + public static AgentToolDefinition? GetFirstToolDefinition(this AgentDefinition agentDefinition, string toolType) { Verify.NotNull(agentDefinition); Verify.NotNull(toolType); @@ -53,7 +53,7 @@ public static bool IsEnableCodeInterpreter(this AgentDefinition agentDefinition) { Verify.NotNull(agentDefinition); - return agentDefinition.Tools?.Where(tool => tool.Type == ToolDefinition.CodeInterpreter).Any() ?? false; + return agentDefinition.Tools?.Where(tool => tool.Type == AgentToolDefinition.CodeInterpreter).Any() ?? false; } /// @@ -64,6 +64,6 @@ public static bool IsEnableFileSearch(this AgentDefinition agentDefinition) { Verify.NotNull(agentDefinition); - return agentDefinition.Tools?.Where(tool => tool.Type == ToolDefinition.FileSearch).Any() ?? false; + return agentDefinition.Tools?.Where(tool => tool.Type == AgentToolDefinition.FileSearch).Any() ?? false; } } diff --git a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs index edf677a95133..bdfc677cf5fe 100644 --- a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs +++ b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs @@ -39,7 +39,7 @@ public sealed class AzureAIAgentFactory : IKernelAgentFactory metadata: agentDefinition.GetMetadata(), cancellationToken: cancellationToken).ConfigureAwait(false); - kernelAgent = new AzureAIAgent(agent, clientProvider) + kernelAgent = new AzureAIAgent(agent, client) { Kernel = kernel, }; diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs index 5d84567310d9..ba62dd66623f 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs @@ -18,7 +18,7 @@ internal static class AgentDefinitionExtensions /// public static IEnumerable GetAzureToolDefinitions(this AgentDefinition agentDefinition) { - return agentDefinition.Tools.Select(tool => + return agentDefinition.Tools.Select(tool => { return tool.Type switch { diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs index bad824c0b029..84df37206334 100644 --- a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -3,6 +3,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.SemanticKernel.Agents.Factory; +using Microsoft.SemanticKernel.Agents.OpenAI.Internal; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -24,13 +26,18 @@ public sealed class OpenAIAssistantAgentFactory : IKernelAgentFactory KernelAgent? kernelAgent = null; if (agentDefinition.Type?.Equals(OpenAIAssistantAgentType, System.StringComparison.Ordinal) ?? false) { - kernelAgent = await OpenAIAssistantAgent.CreateAsync( - clientProvider: kernel.GetOpenAIClientProvider(agentDefinition), - definition: agentDefinition.GetOpenAIAssistantDefinition(), - kernel: kernel, - defaultArguments: agentDefinition.GetDefaultKernelArguments(), - cancellationToken: cancellationToken - ).ConfigureAwait(false); + var clientProvider = kernel.GetOpenAIClientProvider(agentDefinition); + AssistantClient client = clientProvider.Client.GetAssistantClient(); + + var definition = agentDefinition.GetOpenAIAssistantDefinition(); + AssistantCreationOptions assistantCreationOptions = definition.CreateAssistantOptions(); + Assistant model = await client.CreateAssistantAsync(definition.ModelId, assistantCreationOptions, cancellationToken).ConfigureAwait(false); + + kernelAgent = new OpenAIAssistantAgent(model, clientProvider.AssistantClient) + { + Kernel = kernel, + Arguments = agentDefinition.GetDefaultKernelArguments() ?? [], + }; } return Task.FromResult(kernelAgent).Result; diff --git a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs index fac4edc08d49..84ec43ec8100 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs @@ -50,7 +50,7 @@ public static OpenAIAssistantDefinition GetOpenAIAssistantDefinition(this AgentD { Verify.NotNull(agentDefinition); - var toolDefinition = agentDefinition.GetFirstToolDefinition(ToolDefinition.CodeInterpreter); + var toolDefinition = agentDefinition.GetFirstToolDefinition(AgentToolDefinition.CodeInterpreter); if (toolDefinition?.Configuration?.TryGetValue(FileIds, out var fileIds) ?? false) { // TODO: Verify that the fileIds are strings diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs index a5b8c52ddb71..b6b264bea2c1 100644 --- a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs @@ -67,10 +67,10 @@ public async Task VerifyCanCreateAzureAIAgentAsync() Id = "gpt-4o-mini" }, Tools = [ - new Microsoft.SemanticKernel.Agents.ToolDefinition() + new Microsoft.SemanticKernel.Agents.AgentToolDefinition() { Name = "tool1", - Type = Microsoft.SemanticKernel.Agents.ToolDefinition.CodeInterpreter, + Type = Microsoft.SemanticKernel.Agents.AgentToolDefinition.CodeInterpreter, }, ] }; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs index bc69b8dcc4a6..3930d0aec829 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs @@ -61,10 +61,10 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() Id = "gpt-4o-mini" }, Tools = [ - new ToolDefinition() + new AgentToolDefinition() { Name = "tool1", - Type = ToolDefinition.CodeInterpreter, + Type = AgentToolDefinition.CodeInterpreter, }, ] }; From 12e65ae5bb638f80eee7d7abaa2e5a1f3cba4ee4 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:08:33 +0000 Subject: [PATCH 15/30] Undo some namespace related changes --- .../AzureAI/Extensions/AgentRunExtensions.cs | 4 +- .../AzureAI/Internal/AgentMessageFactory.cs | 6 +- .../AzureAI/Internal/AgentThreadActions.cs | 4 +- .../AssistantCreationOptionsFactory.cs | 4 +- dotnet/src/Agents/Yaml/Agents.Yaml.csproj | 1 + .../PromptExecutionSettingsTypeConverter.cs | 121 ------------------ .../Functions.Yaml/Functions.Yaml.csproj | 1 + 7 files changed, 11 insertions(+), 130 deletions(-) delete mode 100644 dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs index b3d2331101f9..7d4cf718b1e0 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/AgentRunExtensions.cs @@ -40,7 +40,7 @@ public static async Task CreateAsync( string threadId, AzureAIAgent agent, string? instructions, - Azure.AI.Projects.ToolDefinition[] tools, + ToolDefinition[] tools, AzureAIInvocationOptions? invocationOptions, CancellationToken cancellationToken) { @@ -91,7 +91,7 @@ public static IAsyncEnumerable CreateStreamingAsync( string threadId, AzureAIAgent agent, string? instructions, - Azure.AI.Projects.ToolDefinition[] tools, + ToolDefinition[] tools, AzureAIInvocationOptions? invocationOptions, CancellationToken cancellationToken) { diff --git a/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs b/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs index 5a37b0b2fac5..621e364acf6a 100644 --- a/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs +++ b/dotnet/src/Agents/AzureAI/Internal/AgentMessageFactory.cs @@ -74,13 +74,13 @@ public static IEnumerable GetThreadMessages(IEnumerable s_toolMetadata = new() + private static readonly Dictionary s_toolMetadata = new() { { AzureAIAgent.Tools.CodeInterpreter, new CodeInterpreterToolDefinition() }, { AzureAIAgent.Tools.FileSearch, new FileSearchToolDefinition() }, }; - private static IEnumerable GetToolDefinition(IEnumerable? tools) + private static IEnumerable GetToolDefinition(IEnumerable? tools) { if (tools is null) { @@ -89,7 +89,7 @@ public static IEnumerable GetThreadMessages(IEnumerable GetMessagesAsync(Agents { logger.LogAzureAIAgentCreatingRun(nameof(InvokeAsync), threadId); - List tools = new(agent.Definition.Tools); + List tools = new(agent.Definition.Tools); // Add unique functions from the Kernel which are not already present in the agent's tools var functionToolNames = new HashSet(tools.OfType().Select(t => t.Name)); @@ -380,7 +380,7 @@ public static async IAsyncEnumerable InvokeStreamin { logger.LogAzureAIAgentCreatingRun(nameof(InvokeAsync), threadId); - Azure.AI.Projects.ToolDefinition[]? tools = [.. agent.Definition.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; + ToolDefinition[]? tools = [.. agent.Definition.Tools, .. kernel.Plugins.SelectMany(p => p.Select(f => f.ToToolDefinition(p.Name)))]; string? instructions = await agent.GetInstructionsAsync(kernel, arguments, cancellationToken).ConfigureAwait(false); diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs index b83ed7182e55..cdcfdadf93ef 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantCreationOptionsFactory.cs @@ -66,12 +66,12 @@ private static AssistantCreationOptions CreateAssistantCreationOptions(this Open if (definition.EnableCodeInterpreter) { - assistantCreationOptions.Tools.Add(global::OpenAI.Assistants.ToolDefinition.CreateCodeInterpreter()); + assistantCreationOptions.Tools.Add(ToolDefinition.CreateCodeInterpreter()); } if (definition.EnableFileSearch) { - assistantCreationOptions.Tools.Add(global::OpenAI.Assistants.ToolDefinition.CreateFileSearch()); + assistantCreationOptions.Tools.Add(ToolDefinition.CreateFileSearch()); } return assistantCreationOptions; diff --git a/dotnet/src/Agents/Yaml/Agents.Yaml.csproj b/dotnet/src/Agents/Yaml/Agents.Yaml.csproj index bcbc837c3d21..3d200b5a0577 100644 --- a/dotnet/src/Agents/Yaml/Agents.Yaml.csproj +++ b/dotnet/src/Agents/Yaml/Agents.Yaml.csproj @@ -19,6 +19,7 @@ + diff --git a/dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs b/dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs deleted file mode 100644 index 12431752d6e5..000000000000 --- a/dotnet/src/Agents/Yaml/PromptExecutionSettingsTypeConverter.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json.Serialization; -using YamlDotNet.Core; -using YamlDotNet.Core.Events; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.BufferedDeserialization; -using YamlDotNet.Serialization.NamingConventions; -using YamlDotNet.Serialization.ObjectFactories; - -namespace Microsoft.SemanticKernel; - -/// -/// Allows custom deserialization for from YAML prompts. -/// -internal sealed class PromptExecutionSettingsTypeConverter : IYamlTypeConverter -{ - /// - public bool Accepts(Type type) - { - return type == typeof(PromptExecutionSettings); - } - - /// - public object? ReadYaml(IParser parser, Type type) - { - s_deserializer ??= new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .IgnoreUnmatchedProperties() // Required to ignore the 'type' property used as type discrimination. Otherwise, the "Property 'type' not found on type '{type.FullName}'" exception is thrown. - .WithObjectFactory(new FunctionChoiceBehaviorsObjectFactory()) - .WithTypeDiscriminatingNodeDeserializer(CreateAndRegisterTypeDiscriminatingNodeDeserializer) - .Build(); - - parser.MoveNext(); // Move to the first property - - var executionSettings = new PromptExecutionSettings(); - while (parser.Current is not MappingEnd) - { - var propertyName = parser.Consume().Value; - switch (propertyName) - { - case "model_id": - executionSettings.ModelId = s_deserializer.Deserialize(parser); - break; - case "function_choice_behavior": - executionSettings.FunctionChoiceBehavior = s_deserializer.Deserialize(parser); - break; - default: - (executionSettings.ExtensionData ??= new Dictionary()).Add(propertyName, s_deserializer.Deserialize(parser)); - break; - } - } - parser.MoveNext(); // Move past the MappingEnd event - return executionSettings; - } - - /// - public void WriteYaml(IEmitter emitter, object? value, Type type) - { - throw new NotImplementedException(); - } - - /// - /// Creates and register a for polymorphic deserialization of . - /// - /// The to configure the . - private static void CreateAndRegisterTypeDiscriminatingNodeDeserializer(ITypeDiscriminatingNodeDeserializerOptions options) - { - var attributes = typeof(FunctionChoiceBehavior).GetCustomAttributes(false); - - // Getting the type discriminator property name - "type" from the JsonPolymorphicAttribute. - var discriminatorKey = attributes.OfType().Single().TypeDiscriminatorPropertyName; - if (string.IsNullOrEmpty(discriminatorKey)) - { - throw new InvalidOperationException("Type discriminator property name is not specified."); - } - - var discriminatorTypeMapping = new Dictionary(); - - // Getting FunctionChoiceBehavior subtypes and their type discriminators registered for polymorphic deserialization. - var derivedTypeAttributes = attributes.OfType(); - foreach (var derivedTypeAttribute in derivedTypeAttributes) - { - var discriminator = derivedTypeAttribute.TypeDiscriminator?.ToString(); - if (string.IsNullOrEmpty(discriminator)) - { - throw new InvalidOperationException($"Type discriminator is not specified for the {derivedTypeAttribute.DerivedType} type."); - } - - discriminatorTypeMapping.Add(discriminator!, derivedTypeAttribute.DerivedType); - } - - options.AddKeyValueTypeDiscriminator(discriminatorKey!, discriminatorTypeMapping); - } - - /// - /// The YamlDotNet deserializer instance. - /// - private static IDeserializer? s_deserializer; - - private sealed class FunctionChoiceBehaviorsObjectFactory : ObjectFactoryBase - { - private static DefaultObjectFactory? s_defaultFactory = null; - - public override object Create(Type type) - { - if (type == typeof(AutoFunctionChoiceBehavior) || - type == typeof(NoneFunctionChoiceBehavior) || - type == typeof(RequiredFunctionChoiceBehavior)) - { - return Activator.CreateInstance(type, nonPublic: true)!; - } - - // Use the default object factory for other types - return (s_defaultFactory ??= new DefaultObjectFactory()).Create(type); - } - } -} diff --git a/dotnet/src/Functions/Functions.Yaml/Functions.Yaml.csproj b/dotnet/src/Functions/Functions.Yaml/Functions.Yaml.csproj index f08cb186dac5..a94f7c789b43 100644 --- a/dotnet/src/Functions/Functions.Yaml/Functions.Yaml.csproj +++ b/dotnet/src/Functions/Functions.Yaml/Functions.Yaml.csproj @@ -27,6 +27,7 @@ + From 9de4e33b5e3d6be975be1d67c69032901bec08bd Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:30:21 +0000 Subject: [PATCH 16/30] Fix typo --- dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs index f9f8057745bd..b1cc867e4717 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs @@ -17,7 +17,7 @@ internal static class KernelExtensions /// Return the to be used with the specified . /// /// Kernel instance which will be used to resolve a default . - /// Agent definition whih will be used t provide configuration for the . + /// Agent definition which will be used to provide configuration for the . public static OpenAIClientProvider GetOpenAIClientProvider(this Kernel kernel, AgentDefinition agentDefinition) { Verify.NotNull(agentDefinition); From 1f06912b01c35d6f20ac666577bad9aa7d70df24 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:39:15 +0000 Subject: [PATCH 17/30] Remove usage of obsolete methods --- .../Definition/OpenAIAssistantAgentFactory.cs | 8 ++-- .../Extensions/AgentDefinitionExtensions.cs | 47 +++++++++---------- .../OpenAIAssistantAgentFactoryTests.cs | 37 +++++++++++++-- .../UnitTests/Yaml/KernelAgentYamlTests.cs | 6 +-- 4 files changed, 63 insertions(+), 35 deletions(-) diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs index 84df37206334..adc8342ae35d 100644 --- a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.SemanticKernel.Agents.Factory; -using Microsoft.SemanticKernel.Agents.OpenAI.Internal; using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -22,6 +21,8 @@ public sealed class OpenAIAssistantAgentFactory : IKernelAgentFactory public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); + Verify.NotNull(agentDefinition.Model); + Verify.NotNull(agentDefinition.Model.Id); KernelAgent? kernelAgent = null; if (agentDefinition.Type?.Equals(OpenAIAssistantAgentType, System.StringComparison.Ordinal) ?? false) @@ -29,9 +30,8 @@ public sealed class OpenAIAssistantAgentFactory : IKernelAgentFactory var clientProvider = kernel.GetOpenAIClientProvider(agentDefinition); AssistantClient client = clientProvider.Client.GetAssistantClient(); - var definition = agentDefinition.GetOpenAIAssistantDefinition(); - AssistantCreationOptions assistantCreationOptions = definition.CreateAssistantOptions(); - Assistant model = await client.CreateAssistantAsync(definition.ModelId, assistantCreationOptions, cancellationToken).ConfigureAwait(false); + var assistantCreationOptions = agentDefinition.CreateAssistantCreationOptions(); + Assistant model = await client.CreateAssistantAsync(agentDefinition.Model.Id, assistantCreationOptions, cancellationToken).ConfigureAwait(false); kernelAgent = new OpenAIAssistantAgent(model, clientProvider.AssistantClient) { diff --git a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs index 84ec43ec8100..06afa479f987 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -15,31 +16,41 @@ internal static class AgentDefinitionExtensions public const string FileIds = "file_ids"; /// - /// Return the which corresponds with the provided . + /// Create the which corresponds with the provided . /// /// Agent definition - public static OpenAIAssistantDefinition GetOpenAIAssistantDefinition(this AgentDefinition agentDefinition) + public static AssistantCreationOptions CreateAssistantCreationOptions(this AgentDefinition agentDefinition) { Verify.NotNull(agentDefinition); Verify.NotNull(agentDefinition.Model); Verify.NotNull(agentDefinition.Model.Id); - return new OpenAIAssistantDefinition(agentDefinition.Model.Id) + var assistantCreationOptions = new AssistantCreationOptions() { - Id = agentDefinition.Id ?? string.Empty, Name = agentDefinition.Name, Description = agentDefinition.Description, Instructions = agentDefinition.Instructions, - EnableCodeInterpreter = agentDefinition.IsEnableCodeInterpreter(), - CodeInterpreterFileIds = agentDefinition.GetCodeInterpreterFileIds(), - EnableFileSearch = agentDefinition.IsEnableFileSearch(), - VectorStoreId = agentDefinition.GetVectorStoreId(), - EnableJsonResponse = agentDefinition?.Model?.Options?.IsEnableJsonResponse() ?? false, Temperature = agentDefinition?.Model?.Options?.GetTemperature(), - TopP = agentDefinition?.Model?.Options?.GetTopP(), - ExecutionOptions = agentDefinition?.GetExecutionOptions(), - Metadata = agentDefinition?.GetMetadata(), + NucleusSamplingFactor = agentDefinition?.Model?.Options?.GetTopP(), + ResponseFormat = agentDefinition?.Model?.Options?.IsEnableJsonResponse() ?? false ? AssistantResponseFormat.JsonObject : AssistantResponseFormat.Auto }; + + // TODO: Implement + // ToolResources + // Metadata + // ExecutionOptions + + if (agentDefinition?.IsEnableCodeInterpreter() ?? false) + { + assistantCreationOptions.Tools.Add(ToolDefinition.CreateCodeInterpreter()); + } + + if (agentDefinition?.IsEnableFileSearch() ?? false) + { + assistantCreationOptions.Tools.Add(ToolDefinition.CreateFileSearch()); + } + + return assistantCreationOptions; } /// @@ -72,18 +83,6 @@ public static OpenAIAssistantDefinition GetOpenAIAssistantDefinition(this AgentD return null; } - /// - /// Retrieve the execution options from the agent definition. - /// - /// Agent definition - public static OpenAIAssistantExecutionOptions? GetExecutionOptions(this AgentDefinition agentDefinition) - { - Verify.NotNull(agentDefinition); - - // TODO: Implement - return null; - } - /// /// Retrieve the metadata from the agent definition. /// diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs index 3930d0aec829..736a265ebc2c 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs @@ -69,7 +69,7 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() ] }; OpenAIAssistantAgentFactory factory = new(); - this.SetupResponse(HttpStatusCode.OK, agentDefinition.GetOpenAIAssistantDefinition()); + this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponse); // Act var agent = await factory.CreateAsync(this._kernel, agentDefinition); @@ -82,8 +82,39 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() Assert.Equal(this._kernel, agent.Kernel); } + /// + /// OpenAI Assistant response. + /// + public const string OpenAIAssistantResponse = + """ + { + "id": "asst_z2BnUzSnnZ4QimeUCsVSdAug", + "object": "assistant", + "created_at": 1740137107, + "name": "OpenAIAssistantAgent", + "description": "OpenAIAssistantAgent Description", + "model": "gpt-4o", + "instructions": "OpenAIAssistantAgent Instructions", + "tools": [ + { + "type": "code_interpreter" + } + ], + "top_p": 1.0, + "temperature": 1.0, + "reasoning_effort": null, + "tool_resources": { + "code_interpreter": { + "file_ids": [] + } + }, + "metadata": {}, + "response_format": "auto" + } + """; + #region private - private void SetupResponse(HttpStatusCode statusCode, OpenAIAssistantDefinition definition) => - this._messageHandlerStub.SetupResponses(statusCode, OpenAIAssistantResponseContent.AssistantDefinition(definition)); + private void SetupResponse(HttpStatusCode statusCode, string response) => + this._messageHandlerStub.SetupResponses(statusCode, [response]); #endregion } diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs index cc5b355299a6..d074100e7b7b 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs @@ -14,6 +14,7 @@ using Microsoft.SemanticKernel.Agents.OpenAI; using SemanticKernel.Agents.UnitTests.AzureAI.Definition; using SemanticKernel.Agents.UnitTests.OpenAI; +using SemanticKernel.Agents.UnitTests.OpenAI.Definition; using Xunit; namespace SemanticKernel.Agents.UnitTests.Yaml; @@ -113,7 +114,7 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() type: code_interpreter """; OpenAIAssistantAgentFactory factory = new(); - this.SetupResponse(HttpStatusCode.OK, KernelAgentYaml.ToAgentDefinition(text).GetOpenAIAssistantDefinition()); + this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantAgentFactoryTests.OpenAIAssistantResponse); // Act var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); @@ -162,9 +163,6 @@ public async Task VerifyCanCreateAzureAIAgentAsync() } #region private - private void SetupResponse(HttpStatusCode statusCode, OpenAIAssistantDefinition definition) => - this._messageHandlerStub.SetupResponses(statusCode, OpenAIAssistantResponseContent.AssistantDefinition(definition)); - private void SetupResponse(HttpStatusCode statusCode, string response) => #pragma warning disable CA2000 // Dispose objects before losing scope this._messageHandlerStub.ResponseQueue.Enqueue(new(statusCode) From 4a9665ea18a98991e05ef106f2ea6abde9614126 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:40:51 +0000 Subject: [PATCH 18/30] Fix typo --- dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs index affd5ac3cf77..a68ce0a7daef 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs @@ -18,7 +18,7 @@ internal static class KernelExtensions /// Return the to be used with the specified . /// /// Kernel instance which will be used to resolve a default . - /// Agent definition whih will be used t provide configuration for the . + /// Agent definition which will be used to provide configuration for the . public static AzureAIClientProvider GetAzureAIClientProvider(this Kernel kernel, AgentDefinition agentDefinition) { Verify.NotNull(agentDefinition); From 1824b5a4cb0b9b69fdd417f5b2f7667a5a2a611f Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:55:03 +0000 Subject: [PATCH 19/30] Make FakeTokenCredential sealed --- dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs b/dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs index 61dd0deacc2c..ff1b41b518ea 100644 --- a/dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs +++ b/dotnet/src/Agents/UnitTests/Test/FakeTokenCredential.cs @@ -7,7 +7,7 @@ namespace SemanticKernel.Agents.UnitTests; -internal class FakeTokenCredential : TokenCredential +internal sealed class FakeTokenCredential : TokenCredential { /// public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) From 852a87359dcdd27025887af25ae742c523e77e74 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 14:00:28 +0000 Subject: [PATCH 20/30] Increase unit test coverage --- .../Definition/IKernelAgentFactory.cs | 2 +- .../Extensions/AgentDefinitionExtensions.cs | 2 +- .../AzureAI/Definition/AzureAIAgentFactory.cs | 1 - .../Extensions/AgentDefinitionExtensions.cs | 10 +- .../AggregatorKernelAgentFactory.cs | 5 +- .../Definition/ChatCompletionAgentFactory.cs | 1 - .../Definition/OpenAIAssistantAgentFactory.cs | 1 - .../AgentDefinitionExtensionsTests.cs | 62 ++++++++ .../AgentDefinitionExtensionsTests.cs | 98 +++++++++++++ .../AggregatorKernelAgentFactoryTests.cs | 136 ++++++++++++++++++ .../AgentDefinitionExtensionsTests.cs | 118 +++++++++++++++ dotnet/src/Agents/Yaml/KernelAgentYaml.cs | 1 - 12 files changed, 423 insertions(+), 14 deletions(-) create mode 100644 dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs create mode 100644 dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs create mode 100644 dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs diff --git a/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs b/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs index e33e755bc079..65a30570892e 100644 --- a/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs +++ b/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.SemanticKernel.Agents.Factory; +namespace Microsoft.SemanticKernel.Agents; /// /// Represents a factory for creating instances. diff --git a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs index 73a33dfebc20..c9b20d63278c 100644 --- a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs @@ -23,7 +23,7 @@ public static KernelArguments GetDefaultKernelArguments(this AgentDefinition age // Add default arguments for the agent foreach (var input in agentDefinition.Inputs) { - if (!input.IsRequired && input.Default is not null) + if (input.Default is not null) { arguments.Add(input.Name, input.Default); } diff --git a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs index bdfc677cf5fe..ca43122aee94 100644 --- a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs +++ b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using Azure.AI.Projects; -using Microsoft.SemanticKernel.Agents.Factory; namespace Microsoft.SemanticKernel.Agents.AzureAI; diff --git a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs index ba62dd66623f..362c103a16b7 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/AgentDefinitionExtensions.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. - using System; using System.Collections.Generic; using System.Linq; +using Azure.AI.Projects; namespace Microsoft.SemanticKernel.Agents.AzureAI; @@ -16,14 +16,14 @@ internal static class AgentDefinitionExtensions /// /// Agent definition /// - public static IEnumerable GetAzureToolDefinitions(this AgentDefinition agentDefinition) + public static IEnumerable GetAzureToolDefinitions(this AgentDefinition agentDefinition) { - return agentDefinition.Tools.Select(tool => + return agentDefinition.Tools.Select(tool => { return tool.Type switch { - "code_interpreter" => new Azure.AI.Projects.CodeInterpreterToolDefinition(), - "file_search" => new Azure.AI.Projects.FileSearchToolDefinition(), + "code_interpreter" => new CodeInterpreterToolDefinition(), + "file_search" => new FileSearchToolDefinition(), _ => throw new InvalidOperationException($"Unable to created Azure AI tool definition because of known tool type: {tool.Type}"), }; }); diff --git a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs index 943b2ecfe065..b7b3a501a9f5 100644 --- a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs @@ -2,9 +2,8 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents.Factory; -namespace Microsoft.SemanticKernel.Agents.Definition; +namespace Microsoft.SemanticKernel.Agents; /// /// Provides a which aggregates multiple kernel agent factories. @@ -18,7 +17,7 @@ public sealed class AggregatorKernelAgentFactory : IKernelAgentFactory public AggregatorKernelAgentFactory(params IKernelAgentFactory[] kernelAgentFactories) { Verify.NotNullOrEmpty(kernelAgentFactories); - foreach (IPromptTemplateFactory kernelAgentFactory in kernelAgentFactories) + foreach (IKernelAgentFactory kernelAgentFactory in kernelAgentFactories) { Verify.NotNull(kernelAgentFactory, nameof(kernelAgentFactories)); } diff --git a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs index 4ee05b8df480..22671a5453c6 100644 --- a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents.Factory; namespace Microsoft.SemanticKernel.Agents; diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs index adc8342ae35d..faeeb63a74e2 100644 --- a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents.Factory; using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs new file mode 100644 index 000000000000..0a053cf5babd --- /dev/null +++ b/dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Linq; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.AzureAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.AzureAI.Extensions; + +/// +/// Unit tests for AgentDefinitionExtensions +/// +public class AgentDefinitionExtensionsTests +{ + /// + /// Verify GetAzureToolDefinitions + /// + [Fact] + public void VerifyGetAzureToolDefinitions() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Tools = [ + new AgentToolDefinition() + { + Name = "tool1", + Type = AgentToolDefinition.CodeInterpreter, + }, + new AgentToolDefinition() + { + Name = "tool2", + Type = AgentToolDefinition.FileSearch, + }, + ] + }; + + // Act + var toolDefinitions = agentDefinition.GetAzureToolDefinitions(); + + // Assert + Assert.NotNull(toolDefinitions); + Assert.Equal(2, toolDefinitions.Count()); + } + + /// + /// Verify GetMetadata + /// + [Fact] + public void VerifyGetMetadata() + { + // Arrange + AgentDefinition agentDefinition = new() + { + }; + + // Act + var metadata = agentDefinition.GetMetadata(); + + // Assert + Assert.Null(metadata); + } +} diff --git a/dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs new file mode 100644 index 000000000000..9d02bc03dfd2 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.Agents; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Core.Extensions; + +/// +/// Unit tests for . +/// +public class AgentDefinitionExtensionsTests +{ + /// + /// Verify GetDefaultKernelArguments + /// + [Fact] + public void VerifyGetDefaultKernelArguments() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Inputs = + [ + new() { Name = "Input1", IsRequired = false, Default = "Default1" }, + new() { Name = "Input2", IsRequired = true, Default = "Default2" } + ], + }; + + // Act + var defaultArgs = agentDefinition.GetDefaultKernelArguments(); + + // Assert + Assert.NotNull(defaultArgs); + Assert.Equal(2, defaultArgs.Count); + Assert.Equal("Default1", defaultArgs["Input1"]); + Assert.Equal("Default2", defaultArgs["Input2"]); + } + + /// + /// Verify GetFirstToolDefinition + /// + [Fact] + public void VerifyGetFirstToolDefinition() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Tools = + [ + new AgentToolDefinition { Type = "code_interpreter", Name = "Tool1" }, + new AgentToolDefinition { Type = "file_search", Name = "Tool2" }, + ], + }; + + // Act & Assert + Assert.NotNull(agentDefinition.GetFirstToolDefinition("code_interpreter")); + Assert.NotNull(agentDefinition.GetFirstToolDefinition("file_search")); + Assert.Null(agentDefinition.GetFirstToolDefinition("openai")); + } + + /// + /// Verify IsEnableCodeInterpreter + /// + [Fact] + public void VerifyIsEnableCodeInterpreter() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Tools = + [ + new AgentToolDefinition { Type = "code_interpreter", Name = "Tool1" }, + ], + }; + + // Act & Assert + Assert.True(agentDefinition.IsEnableCodeInterpreter()); + } + + /// + /// Verify IsEnableFileSearch + /// + [Fact] + public void VerifyIsEnableFileSearch() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Tools = + [ + new AgentToolDefinition { Type = "file_search", Name = "Tool2" }, + ], + }; + + // Act & Assert + Assert.True(agentDefinition.IsEnableFileSearch()); + } +} diff --git a/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs new file mode 100644 index 000000000000..54c42de597d9 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.Core.Factory; + +/// +/// Tests for . +/// +public class AggregatorKernelAgentFactoryTests +{ + /// + /// Verifies that the can create different types of agents. + /// + [Fact] + public async Task ItCreatesKernelAgentsAsync() + { + // Arrange + var agentDefinition1 = new AgentDefinition() { Type = "my-type-1", Name = "my-name-1", Description = "my-description-1", Instructions = "my-instructions-1" }; + var agentDefinition2 = new AgentDefinition() { Type = "my-type-2", Name = "my-name-2", Description = "my-description-2", Instructions = "my-instructions-2" }; + var kernel = new Kernel(); + var target = new AggregatorKernelAgentFactory(new MyKernelAgentFactory1(), new MyKernelAgentFactory2()); + + // Act + var result1 = await target.CreateAsync(kernel, agentDefinition1); + var result2 = await target.CreateAsync(kernel, agentDefinition2); + + // Assert + Assert.NotNull(result1); + Assert.True(result1 is MyKernelAgent1); + Assert.NotNull(result2); + Assert.True(result2 is MyKernelAgent2); + } + + /// + /// Verifies that the returns null for an unknown agent type. + /// + [Fact] + public async Task ItReturnsNullForUnknowAgentTypeAsync() + { + // Arrange + var agentDefinition = new AgentDefinition() { Type = "my-type-unknown", Name = "my-name-1", Description = "my-description-1", Instructions = "my-instructions-1" }; + var kernel = new Kernel(); + var target = new AggregatorKernelAgentFactory(new MyKernelAgentFactory1(), new MyKernelAgentFactory2()); + + // Act + var result = await target.CreateAsync(kernel, agentDefinition); + + // Assert + Assert.Null(result); + } + + #region private + private sealed class MyKernelAgentFactory1 : IKernelAgentFactory + { + public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + { + return agentDefinition.Type != "my-type-1" + ? null + : (KernelAgent)await Task.FromResult(new MyKernelAgent1() + { + Name = agentDefinition.Name, + Description = agentDefinition.Description, + Instructions = agentDefinition.Instructions, + Kernel = kernel, + }); + } + } + + private sealed class MyKernelAgentFactory2 : IKernelAgentFactory + { + public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + { + return agentDefinition.Type != "my-type-2" + ? null + : (KernelAgent)await Task.FromResult(new MyKernelAgent2() + { + Name = agentDefinition.Name, + Description = agentDefinition.Description, + Instructions = agentDefinition.Instructions, + Kernel = kernel, + }); + } + } + + private sealed class MyKernelAgent1 : KernelAgent + { + public MyKernelAgent1() + { + } + + protected internal override Task CreateChannelAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + protected internal override IEnumerable GetChannelKeys() + { + throw new NotImplementedException(); + } + + protected internal override Task RestoreChannelAsync(string channelState, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } + + private sealed class MyKernelAgent2 : KernelAgent + { + public MyKernelAgent2() + { + } + + protected internal override Task CreateChannelAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + protected internal override IEnumerable GetChannelKeys() + { + throw new NotImplementedException(); + } + + protected internal override Task RestoreChannelAsync(string channelState, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } + #endregion +} diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs new file mode 100644 index 000000000000..add40ee77216 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.OpenAI; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI.Extensions; + +/// +/// Unit tests for AgentDefinitionExtensions +/// +public class AgentDefinitionExtensionsTests +{ + /// + /// Verify CreateAssistantCreationOptions + /// + [Fact] + public void VerifyCreateAssistantCreationOptions() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Type = OpenAIAssistantAgentFactory.OpenAIAssistantAgentType, + Name = "OpenAIAssistantAgent", + Description = "OpenAIAssistantAgent Description", + Instructions = "OpenAIAssistantAgent Instructions", + Model = new() + { + Id = "gpt-4o-mini" + }, + Tools = [ + new AgentToolDefinition() + { + Name = "tool1", + Type = AgentToolDefinition.CodeInterpreter, + }, + ] + }; + + // Act + var creationOptions = agentDefinition.CreateAssistantCreationOptions(); + + // Assert + Assert.NotNull(creationOptions); + Assert.Equal(agentDefinition.Name, creationOptions.Name); + Assert.Equal(agentDefinition.Description, creationOptions.Description); + Assert.Equal(agentDefinition.Instructions, creationOptions.Instructions); + Assert.Single(creationOptions.Tools); + } + + /// + /// Verify GetCodeInterpreterFileIds + /// + [Fact] + public void VerifyGetCodeInterpreterFileIds() + { + // Arrange + var fileIds = new List(["file1", "file2"]); + var configuration = new Dictionary + { + { "file_ids", fileIds } + }; + AgentDefinition agentDefinition = new() + { + Tools = [ + new AgentToolDefinition() + { + Name = "tool1", + Type = AgentToolDefinition.CodeInterpreter, + Configuration = configuration, + }, + ] + }; + + // Act + var interpreterFileIds = agentDefinition.GetCodeInterpreterFileIds(); + + // Assert + Assert.NotNull(interpreterFileIds); + Assert.Equal(2, interpreterFileIds.Count); + } + + /// + /// Verify GetVectorStoreId + /// + [Fact] + public void VerifyGetVectorStoreId() + { + // Arrange + AgentDefinition agentDefinition = new() + { + }; + + // Act + var vectorId = agentDefinition.GetVectorStoreId(); + + // Assert + Assert.Null(vectorId); + } + + /// + /// Verify GetMetadata + /// + [Fact] + public void VerifyGetMetadata() + { + // Arrange + AgentDefinition agentDefinition = new() + { + }; + + // Act + var metadata = agentDefinition.GetMetadata(); + + // Assert + Assert.Null(metadata); + } +} diff --git a/dotnet/src/Agents/Yaml/KernelAgentYaml.cs b/dotnet/src/Agents/Yaml/KernelAgentYaml.cs index b69c449a477e..d0865fe35911 100644 --- a/dotnet/src/Agents/Yaml/KernelAgentYaml.cs +++ b/dotnet/src/Agents/Yaml/KernelAgentYaml.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Agents.Factory; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; From 30e633b82a2fcc472abd0996397d9fcd05859357 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:37:04 +0000 Subject: [PATCH 21/30] Address initial code review feedback --- .../Definition/AgentDefinition.cs | 2 +- ...lAgentFactory.cs => KernelAgentFactory.cs} | 19 +++++++- .../AzureAI/Definition/AzureAIAgentFactory.cs | 14 ++++-- .../AggregatorKernelAgentFactory.cs | 16 ++++--- .../Definition/ChatCompletionAgentFactory.cs | 14 ++++-- dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj | 1 + .../Definition/OpenAIAssistantAgentFactory.cs | 14 ++++-- .../OpenAI/Extensions/KernelExtensions.cs | 13 ++++-- .../AggregatorKernelAgentFactoryTests.cs | 16 +++++-- .../Extensions/KernelExtensionsTests.cs | 41 +++++++++++++++++ .../UnitTests/Yaml/KernelAgentYamlTests.cs | 8 ++-- dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs | 26 +++++++++++ .../Yaml/KernelAgentFactoryYamlExtensions.cs | 31 +++++++++++++ dotnet/src/Agents/Yaml/KernelAgentYaml.cs | 45 ------------------- .../PromptTemplate/ModelConfiguration.cs | 14 ++++++ 15 files changed, 199 insertions(+), 75 deletions(-) rename dotnet/src/Agents/Abstractions/Definition/{IKernelAgentFactory.cs => KernelAgentFactory.cs} (53%) create mode 100644 dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs create mode 100644 dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs create mode 100644 dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs delete mode 100644 dotnet/src/Agents/Yaml/KernelAgentYaml.cs diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs index ca6dc2602d1c..65774cc96943 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentDefinition.cs @@ -11,7 +11,7 @@ namespace Microsoft.SemanticKernel.Agents; public sealed class AgentDefinition { /// - /// Gets or sets the version of the schema. + /// Gets or sets the version of the schema being used. /// public string? Version { diff --git a/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs b/dotnet/src/Agents/Abstractions/Definition/KernelAgentFactory.cs similarity index 53% rename from dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs rename to dotnet/src/Agents/Abstractions/Definition/KernelAgentFactory.cs index 65a30570892e..536ece6e9eab 100644 --- a/dotnet/src/Agents/Abstractions/Definition/IKernelAgentFactory.cs +++ b/dotnet/src/Agents/Abstractions/Definition/KernelAgentFactory.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,8 +9,22 @@ namespace Microsoft.SemanticKernel.Agents; /// /// Represents a factory for creating instances. /// -public interface IKernelAgentFactory +public abstract class KernelAgentFactory { + /// + /// Gets the types of agents this factory can create. + /// + public IReadOnlyList Types { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Types of agent this factory can create + protected KernelAgentFactory(string[] types) + { + this.Types = types; + } + /// /// Tries to create a from the specified . /// @@ -17,5 +32,5 @@ public interface IKernelAgentFactory /// Definition of the agent to create. /// Optional cancellation token. /// The created , if null the agent type is not supported. - Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default); + public abstract Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs index ca43122aee94..f7a8837bdcee 100644 --- a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs +++ b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs @@ -7,17 +7,25 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI; /// -/// Provides a which creates instances of . +/// Provides a which creates instances of . /// -public sealed class AzureAIAgentFactory : IKernelAgentFactory +public sealed class AzureAIAgentFactory : KernelAgentFactory { /// /// Gets the type of the Azure AI agent. /// public static string AzureAIAgentType => "azureai_agent"; + /// + /// Initializes a new instance of the class. + /// + public AzureAIAgentFactory() + : base([AzureAIAgentType]) + { + } + /// - public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); Verify.NotNull(agentDefinition.Model); diff --git a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs index b7b3a501a9f5..147a6822f498 100644 --- a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs @@ -1,23 +1,25 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Microsoft.SemanticKernel.Agents; /// -/// Provides a which aggregates multiple kernel agent factories. +/// Provides a which aggregates multiple kernel agent factories. /// -public sealed class AggregatorKernelAgentFactory : IKernelAgentFactory +public sealed class AggregatorKernelAgentFactory : KernelAgentFactory { - private readonly IKernelAgentFactory?[] _kernelAgentFactories; + private readonly KernelAgentFactory?[] _kernelAgentFactories; /// Initializes the instance. /// Ordered instances to aggregate. - public AggregatorKernelAgentFactory(params IKernelAgentFactory[] kernelAgentFactories) + public AggregatorKernelAgentFactory(params KernelAgentFactory[] kernelAgentFactories) : base(kernelAgentFactories.SelectMany(f => f.Types).ToArray()) { Verify.NotNullOrEmpty(kernelAgentFactories); - foreach (IKernelAgentFactory kernelAgentFactory in kernelAgentFactories) + + foreach (KernelAgentFactory kernelAgentFactory in kernelAgentFactories) { Verify.NotNull(kernelAgentFactory, nameof(kernelAgentFactories)); } @@ -26,7 +28,7 @@ public AggregatorKernelAgentFactory(params IKernelAgentFactory[] kernelAgentFact } /// - public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); @@ -42,6 +44,6 @@ public AggregatorKernelAgentFactory(params IKernelAgentFactory[] kernelAgentFact } } - return Task.FromResult(null).Result; + return null; } } diff --git a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs index 22671a5453c6..c70a6812bca6 100644 --- a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs @@ -6,17 +6,25 @@ namespace Microsoft.SemanticKernel.Agents; /// -/// Provides a which creates instances of . +/// Provides a which creates instances of . /// -public sealed class ChatCompletionAgentFactory : IKernelAgentFactory +public sealed class ChatCompletionAgentFactory : KernelAgentFactory { /// /// Gets the type of the chat completion agent. /// public static string ChatCompletionAgentType => "chat_completion_agent"; + /// + /// Initializes a new instance of the class. + /// + public ChatCompletionAgentFactory() + : base([ChatCompletionAgentType]) + { + } + /// - public Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); diff --git a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj index 5f7d8c11b80f..de1d4230c227 100644 --- a/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj +++ b/dotnet/src/Agents/OpenAI/Agents.OpenAI.csproj @@ -39,6 +39,7 @@ + diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs index faeeb63a74e2..84a414d87ec1 100644 --- a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -7,17 +7,25 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// -/// Provides a which creates instances of . +/// Provides a which creates instances of . /// -public sealed class OpenAIAssistantAgentFactory : IKernelAgentFactory +public sealed class OpenAIAssistantAgentFactory : KernelAgentFactory { /// /// Gets the type of the OpenAI assistant agent. /// public static string OpenAIAssistantAgentType => "openai_assistant"; + /// + /// Initializes a new instance of the class. + /// + public OpenAIAssistantAgentFactory() + : base([OpenAIAssistantAgentType]) + { + } + /// - public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); Verify.NotNull(agentDefinition.Model); diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs index b1cc867e4717..56914a5e3bf5 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs @@ -2,6 +2,8 @@ using System; using System.ClientModel; using System.Linq; +using Azure.Identity; +using OpenAI; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -36,15 +38,20 @@ public static OpenAIClientProvider GetOpenAIClientProvider(this Kernel kernel, A { return OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(apiKey!.ToString()!)); } - /* else if (!hasApiKey && hasEndpoint) { return OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(endpoint!.ToString()!)); } - */ } - // Return the service registered on the kernel + // Use the client registered on the kernel + var client = kernel.GetAllServices().FirstOrDefault(); + if (client is not null) + { + return OpenAIClientProvider.FromClient(client); + } + + // Use the provider registered on the kernel var clientProvider = kernel.GetAllServices().FirstOrDefault(); return (OpenAIClientProvider?)clientProvider ?? throw new InvalidOperationException("OpenAI client provider not found."); } diff --git a/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs index 54c42de597d9..d3864e222bfc 100644 --- a/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs @@ -57,9 +57,13 @@ public async Task ItReturnsNullForUnknowAgentTypeAsync() } #region private - private sealed class MyKernelAgentFactory1 : IKernelAgentFactory + private sealed class MyKernelAgentFactory1 : KernelAgentFactory { - public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public MyKernelAgentFactory1() : base(["my-type-1"]) + { + } + + public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { return agentDefinition.Type != "my-type-1" ? null @@ -73,9 +77,13 @@ private sealed class MyKernelAgentFactory1 : IKernelAgentFactory } } - private sealed class MyKernelAgentFactory2 : IKernelAgentFactory + private sealed class MyKernelAgentFactory2 : KernelAgentFactory { - public async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public MyKernelAgentFactory2() : base(["my-type-2"]) + { + } + + public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { return agentDefinition.Type != "my-type-2" ? null diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs new file mode 100644 index 000000000000..075ca650fc43 --- /dev/null +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/KernelExtensionsTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using Microsoft.SemanticKernel.Agents; +using Xunit; + +namespace SemanticKernel.Agents.UnitTests.OpenAI.Extensions; + +/// +/// Tests for KernelExtensions. +/// +public class KernelExtensionsTests +{ + /// + /// Verify GetOpenAIClientProvider for AzureOpenAI + /// + [Fact] + public void VerifyGetOpenAIClientProviderForAzureOpenAIWithApiKey() + { + // Arrange + AgentDefinition agentDefinition = new() + { + Model = new() + { + Id = "gpt-4o-mini", + Configuration = new() + { + ExtensionData = new Dictionary() + { + ["endpoint"] = "https://contosoo.openai.azure.com", + ["api_key"] = "api_key", + } + } + } + }; + + // Act + + // Assert + } +} diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs index d074100e7b7b..d2676da45ec6 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs @@ -20,7 +20,7 @@ namespace SemanticKernel.Agents.UnitTests.Yaml; /// -/// Unit tests for . +/// Unit tests for . /// public class KernelAgentYamlTests : IDisposable { @@ -83,7 +83,7 @@ public async Task VerifyCanCreateChatCompletionAgentAsync() ChatCompletionAgentFactory factory = new(); // Act - var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); + var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text); // Assert Assert.NotNull(agent); @@ -117,7 +117,7 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantAgentFactoryTests.OpenAIAssistantResponse); // Act - var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); + var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text); // Assert Assert.NotNull(agent); @@ -151,7 +151,7 @@ public async Task VerifyCanCreateAzureAIAgentAsync() this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentResponse); // Act - var agent = await KernelAgentYaml.FromAgentYamlAsync(this._kernel, text, factory); + var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text); // Assert Assert.NotNull(agent); diff --git a/dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs b/dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs new file mode 100644 index 000000000000..27bc1699b4c2 --- /dev/null +++ b/dotnet/src/Agents/Yaml/AgentDefinitionYaml.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. + +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Microsoft.SemanticKernel.Agents; + +/// +/// Helper methods for creating from YAML. +/// +public static class AgentDefinitionYaml +{ + /// + /// Convert the given YAML text to a model. + /// + /// YAML representation of the to use to create the prompt function. + public static AgentDefinition FromYaml(string text) + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .WithTypeConverter(new PromptExecutionSettingsTypeConverter()) + .Build(); + + return deserializer.Deserialize(text); + } +} diff --git a/dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs b/dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs new file mode 100644 index 000000000000..aa56778e220c --- /dev/null +++ b/dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Agents; + +/// +/// Extension methods for to create agents from YAML. +/// +public static class KernelAgentFactoryYamlExtensions +{ + /// + /// Create a from the given YAML text. + /// + /// + /// + /// + /// + public static async Task CreateAgentFromYamlAsync(this KernelAgentFactory kernelAgentFactory, Kernel kernel, string text, CancellationToken cancellationToken = default) + { + var agentDefinition = AgentDefinitionYaml.FromYaml(text); + agentDefinition.Type = agentDefinition.Type ?? kernelAgentFactory.Types.FirstOrDefault(); + + return await kernelAgentFactory.CreateAsync( + kernel, + agentDefinition, + cancellationToken).ConfigureAwait(false); + } +} diff --git a/dotnet/src/Agents/Yaml/KernelAgentYaml.cs b/dotnet/src/Agents/Yaml/KernelAgentYaml.cs deleted file mode 100644 index d0865fe35911..000000000000 --- a/dotnet/src/Agents/Yaml/KernelAgentYaml.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Threading; -using System.Threading.Tasks; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace Microsoft.SemanticKernel.Agents; - -/// -/// Factory methods for creating instances. -/// -public static class KernelAgentYaml -{ - /// - /// Create a from the given YAML text. - /// - /// - /// - /// - /// - public static async Task FromAgentYamlAsync(Kernel kernel, string text, IKernelAgentFactory kernelAgentFactory, CancellationToken cancellationToken = default) - { - var agentDefinition = ToAgentDefinition(text); - - return await kernelAgentFactory.CreateAsync( - kernel, - agentDefinition, - cancellationToken).ConfigureAwait(false); - } - - /// - /// Convert the given YAML text to a model. - /// - /// YAML representation of the to use to create the prompt function. - public static AgentDefinition ToAgentDefinition(string text) - { - var deserializer = new DeserializerBuilder() - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .WithTypeConverter(new PromptExecutionSettingsTypeConverter()) - .Build(); - - return deserializer.Deserialize(text); - } -} diff --git a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs index 4d08367e01c1..a0b5c8c1bd61 100644 --- a/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs +++ b/dotnet/src/SemanticKernel.Abstractions/PromptTemplate/ModelConfiguration.cs @@ -26,6 +26,19 @@ public string? Type } } + /// + /// Gets or sets the Service ID of the model configuration. + /// + public string? ServiceId + { + get => this._serviceId; + set + { + Verify.NotNull(value); + this._serviceId = value; + } + } + /// /// Extra properties that may be included in the serialized model configuration. /// @@ -56,6 +69,7 @@ public bool TryGetValue(string key, out object? value) #region private private string? _type; + private string? _serviceId; private IDictionary? _extensionData; #endregion } From adbe914313e5b7735684d3d7005e28b9cec6522e Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 20:46:51 +0000 Subject: [PATCH 22/30] Fix warning --- .../Agents/Yaml/KernelAgentFactoryYamlExtensions.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs b/dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs index aa56778e220c..9f8842c78c96 100644 --- a/dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs +++ b/dotnet/src/Agents/Yaml/KernelAgentFactoryYamlExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,14 +13,14 @@ public static class KernelAgentFactoryYamlExtensions /// /// Create a from the given YAML text. /// - /// - /// - /// - /// + /// Kernel agent factory which will be used to create the agent + /// Kernel instance + /// YAML in text format + /// Optional cancellation token public static async Task CreateAgentFromYamlAsync(this KernelAgentFactory kernelAgentFactory, Kernel kernel, string text, CancellationToken cancellationToken = default) { var agentDefinition = AgentDefinitionYaml.FromYaml(text); - agentDefinition.Type = agentDefinition.Type ?? kernelAgentFactory.Types.FirstOrDefault(); + agentDefinition.Type = agentDefinition.Type ?? (kernelAgentFactory.Types.Count > 0 ? kernelAgentFactory.Types[0] : null); return await kernelAgentFactory.CreateAsync( kernel, From 431c6eef2ab8f926d5e388edba21f589684c3a7f Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 21:13:59 +0000 Subject: [PATCH 23/30] Remove agents.openai from code coverage --- .../UnitTests/Yaml/KernelAgentYamlTests.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs index d2676da45ec6..c15cd02f7aa0 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs @@ -60,6 +60,55 @@ public void Dispose() this._httpClient.Dispose(); } + /// + /// Verify can create an instance of from YAML text. + /// + [Fact] + public void VerifyAgentDefinitionFromYaml() + { + // Arrange + var text = + """ + version: 1.0.0 + type: chat_completion_agent + name: ChatCompletionAgent + description: ChatCompletionAgent Description + instructions: ChatCompletionAgent Instructions + metadata: + author: Microsoft + created: 2025-02-21 + model: + id: gpt-4o-mini + options: + temperature: 0.4 + function_choice_behavior: + type: auto + configuration: + type: azureai + inputs: + - name: input1 + description: input1 description + - name: input2 + description: input2 description + outputs: + - description: output1 description + template: + format: liquid + parser: semantic-kernel + tools: + - name: tool1 + type: code_interpreter + - name: tool2 + type: file_search + """; + + // Act + var agentDefinition = AgentDefinitionYaml.FromYaml(text); + + // Assert + Assert.NotNull(agentDefinition); + } + /// /// Verify can create an instance of using /// From 4e684a823ad009f57b4877e49734dc28a843f586 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 21:29:30 +0000 Subject: [PATCH 24/30] Exclude some stuff from code coveregae --- .../src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs | 2 ++ .../src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs | 2 ++ dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs | 2 ++ .../OpenAI/Extensions/PromptExecutionSettingsExtensions.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs index 84a414d87ec1..afeddaa2a0b2 100644 --- a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using OpenAI.Assistants; @@ -9,6 +10,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Provides a which creates instances of . /// +[ExcludeFromCodeCoverage] public sealed class OpenAIAssistantAgentFactory : KernelAgentFactory { /// diff --git a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs index 06afa479f987..782775de32f4 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using OpenAI.Assistants; namespace Microsoft.SemanticKernel.Agents.OpenAI; @@ -8,6 +9,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Provides extension methods for . /// +[ExcludeFromCodeCoverage] internal static class AgentDefinitionExtensions { /// diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs index 56914a5e3bf5..b247ebc0f593 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; using System.ClientModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Azure.Identity; using OpenAI; @@ -10,6 +11,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Provides extension methods for . /// +[ExcludeFromCodeCoverage] internal static class KernelExtensions { private const string Endpoint = "endpoint"; diff --git a/dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs index 8159b3a5215b..827e631a571e 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/PromptExecutionSettingsExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Diagnostics.CodeAnalysis; using Microsoft.SemanticKernel.Connectors.OpenAI; namespace Microsoft.SemanticKernel; @@ -7,6 +8,7 @@ namespace Microsoft.SemanticKernel; /// /// Provides extension methods for interacting with /// +[ExcludeFromCodeCoverage] public static class PromptExecutionSettingsExtensions { /// From cec323d207576cc3e2615ff21eb46c6199d1a88f Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Fri, 21 Feb 2025 21:54:35 +0000 Subject: [PATCH 25/30] Run dotnet format --- dotnet/src/IntegrationTests/Agents/BedrockAgentTests.cs | 4 ++-- .../TestSettings/BedrockAgentConfiguration.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/src/IntegrationTests/Agents/BedrockAgentTests.cs b/dotnet/src/IntegrationTests/Agents/BedrockAgentTests.cs index 417fb17c72b2..1e4363f21ce8 100644 --- a/dotnet/src/IntegrationTests/Agents/BedrockAgentTests.cs +++ b/dotnet/src/IntegrationTests/Agents/BedrockAgentTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; @@ -235,4 +235,4 @@ public string Forecast([Description("The location to get the weather for.")] str } } #pragma warning restore CA1812 // Avoid uninstantiated internal classes -} \ No newline at end of file +} diff --git a/dotnet/src/IntegrationTests/TestSettings/BedrockAgentConfiguration.cs b/dotnet/src/IntegrationTests/TestSettings/BedrockAgentConfiguration.cs index 7e7f28456c2a..19476f4d72b4 100644 --- a/dotnet/src/IntegrationTests/TestSettings/BedrockAgentConfiguration.cs +++ b/dotnet/src/IntegrationTests/TestSettings/BedrockAgentConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System.Diagnostics.CodeAnalysis; From d970c6b7ce2263c28a6d1e1354221048719fc369 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:43:34 +0000 Subject: [PATCH 26/30] Address first round of code review feedback --- .../Definition/AgentToolDefinition.cs | 10 ---- .../Definition/KernelAgentFactory.cs | 35 +++++++++++++- .../Extensions/AgentDefinitionExtensions.cs | 18 ++----- .../Agents/AzureAI/AzureAIClientProvider.cs | 2 +- .../AzureAI/Definition/AzureAIAgentFactory.cs | 11 ++--- .../AzureAI/Extensions/KernelExtensions.cs | 15 +++--- .../AggregatorKernelAgentFactory.cs | 4 +- .../Definition/ChatCompletionAgentFactory.cs | 4 +- .../Definition/OpenAIAssistantAgentFactory.cs | 14 +++--- .../Extensions/AgentDefinitionExtensions.cs | 18 +++++-- .../OpenAI/Extensions/KernelExtensions.cs | 47 ++++++++++++------- .../ModelConfigurationExtensions.cs | 43 +++++++++++++++++ .../src/Agents/OpenAI/OpenAIClientProvider.cs | 4 +- .../Definition/AzureAIAgentFactoryTests.cs | 4 +- .../AgentDefinitionExtensionsTests.cs | 4 +- .../AgentDefinitionExtensionsTests.cs | 6 +-- .../AggregatorKernelAgentFactoryTests.cs | 4 +- .../OpenAIAssistantAgentFactoryTests.cs | 8 +++- .../AgentDefinitionExtensionsTests.cs | 4 +- .../UnitTests/Yaml/KernelAgentYamlTests.cs | 11 +++-- .../SemanticKernel.Abstractions.csproj | 4 -- 21 files changed, 175 insertions(+), 95 deletions(-) create mode 100644 dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs diff --git a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs index 695ad310d9ad..7c18ae624496 100644 --- a/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs +++ b/dotnet/src/Agents/Abstractions/Definition/AgentToolDefinition.cs @@ -10,16 +10,6 @@ namespace Microsoft.SemanticKernel.Agents; /// public class AgentToolDefinition { - /// - /// Tool definition type for code interpreter. - /// - public const string CodeInterpreter = "code_interpreter"; - - /// - /// Tool definition type for file search. - /// - public const string FileSearch = "file_search"; - /// /// The type of the tool. /// diff --git a/dotnet/src/Agents/Abstractions/Definition/KernelAgentFactory.cs b/dotnet/src/Agents/Abstractions/Definition/KernelAgentFactory.cs index 536ece6e9eab..4b7c40fef774 100644 --- a/dotnet/src/Agents/Abstractions/Definition/KernelAgentFactory.cs +++ b/dotnet/src/Agents/Abstractions/Definition/KernelAgentFactory.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -25,6 +27,37 @@ protected KernelAgentFactory(string[] types) this.Types = types; } + /// + /// Return true if this instance of supports creating agents from the provided + /// + /// + /// + public bool IsSupported(AgentDefinition agentDefinition) + { + return this.Types.Any(s => string.Equals(s, agentDefinition.Type, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Create a from the specified . + /// + /// Kernel instance to associate with the agent. + /// Definition of the agent to create. + /// Optional cancellation token. + /// The created , if null the agent type is not supported. + public Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + { + Verify.NotNull(kernel); + Verify.NotNull(agentDefinition); + + var kernelAgent = this.TryCreateAsync(kernel, agentDefinition, cancellationToken); + if (kernelAgent is not null) + { + return kernelAgent!; + } + + throw new KernelException($"Agent type {agentDefinition.Type} is not supported."); + } + /// /// Tries to create a from the specified . /// @@ -32,5 +65,5 @@ protected KernelAgentFactory(string[] types) /// Definition of the agent to create. /// Optional cancellation token. /// The created , if null the agent type is not supported. - public abstract Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default); + public abstract Task TryCreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs index c9b20d63278c..db9f5c5c1185 100644 --- a/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/Abstractions/Extensions/AgentDefinitionExtensions.cs @@ -46,24 +46,14 @@ public static KernelArguments GetDefaultKernelArguments(this AgentDefinition age } /// - /// Determines if the agent definition has a code interpreter tool. + /// Determines if the agent definition has a tool of the specified type. /// /// Agent definition - public static bool IsEnableCodeInterpreter(this AgentDefinition agentDefinition) - { - Verify.NotNull(agentDefinition); - - return agentDefinition.Tools?.Where(tool => tool.Type == AgentToolDefinition.CodeInterpreter).Any() ?? false; - } - - /// - /// Determines if the agent definition has a file search tool. - /// - /// Agent definition - public static bool IsEnableFileSearch(this AgentDefinition agentDefinition) + /// Tool type + public static bool HasToolType(this AgentDefinition agentDefinition, string toolType) { Verify.NotNull(agentDefinition); - return agentDefinition.Tools?.Where(tool => tool.Type == AgentToolDefinition.FileSearch).Any() ?? false; + return agentDefinition.Tools?.Where(tool => tool?.Type?.Equals(toolType, System.StringComparison.Ordinal) ?? false).Any() ?? false; } } diff --git a/dotnet/src/Agents/AzureAI/AzureAIClientProvider.cs b/dotnet/src/Agents/AzureAI/AzureAIClientProvider.cs index 9082225ef698..4d9b55515192 100644 --- a/dotnet/src/Agents/AzureAI/AzureAIClientProvider.cs +++ b/dotnet/src/Agents/AzureAI/AzureAIClientProvider.cs @@ -64,7 +64,7 @@ public static AzureAIClientProvider FromClient(AIProjectClient client) return new(client, [client.GetType().FullName!, client.GetHashCode().ToString()]); } - private static AIProjectClientOptions CreateAzureClientOptions(HttpClient? httpClient) + internal static AIProjectClientOptions CreateAzureClientOptions(HttpClient? httpClient) { AIProjectClientOptions options = new() diff --git a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs index f7a8837bdcee..b9f147f8bff0 100644 --- a/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs +++ b/dotnet/src/Agents/AzureAI/Definition/AzureAIAgentFactory.cs @@ -25,18 +25,17 @@ public AzureAIAgentFactory() } /// - public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task TryCreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); Verify.NotNull(agentDefinition.Model); Verify.NotNull(agentDefinition.Model.Id); - KernelAgent? kernelAgent = null; if (agentDefinition.Type?.Equals(AzureAIAgentType, System.StringComparison.Ordinal) ?? false) { - var clientProvider = kernel.GetAzureAIClientProvider(agentDefinition); + var projectClient = kernel.GetAIProjectClient(agentDefinition); - AgentsClient client = clientProvider.Client.GetAgentsClient(); + AgentsClient client = projectClient.GetAgentsClient(); Azure.AI.Projects.Agent agent = await client.CreateAgentAsync( model: agentDefinition.Model.Id, name: agentDefinition.Name, @@ -46,12 +45,12 @@ public AzureAIAgentFactory() metadata: agentDefinition.GetMetadata(), cancellationToken: cancellationToken).ConfigureAwait(false); - kernelAgent = new AzureAIAgent(agent, client) + return new AzureAIAgent(agent, client) { Kernel = kernel, }; } - return Task.FromResult(kernelAgent).Result; + return null; } } diff --git a/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs index a68ce0a7daef..afec2e310445 100644 --- a/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/AzureAI/Extensions/KernelExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Net.Http; using Azure.AI.Projects; using Azure.Identity; @@ -15,11 +16,11 @@ internal static class KernelExtensions private const string ConnectionString = "connection_string"; /// - /// Return the to be used with the specified . + /// Return the to be used with the specified . /// /// Kernel instance which will be used to resolve a default . /// Agent definition which will be used to provide configuration for the . - public static AzureAIClientProvider GetAzureAIClientProvider(this Kernel kernel, AgentDefinition agentDefinition) + public static AIProjectClient GetAIProjectClient(this Kernel kernel, AgentDefinition agentDefinition) { Verify.NotNull(agentDefinition); @@ -30,7 +31,9 @@ public static AzureAIClientProvider GetAzureAIClientProvider(this Kernel kernel, var hasConnectionString = configuration.TryGetValue(ConnectionString, out var connectionString) && connectionString is not null; if (hasConnectionString) { - return AzureAIClientProvider.FromConnectionString(connectionString!.ToString()!, new AzureCliCredential()); + var httpClient = kernel.GetAllServices().FirstOrDefault(); + AIProjectClientOptions clientOptions = AzureAIClientProvider.CreateAzureClientOptions(httpClient); + return new(connectionString!.ToString()!, new AzureCliCredential(), clientOptions); } } @@ -38,11 +41,9 @@ public static AzureAIClientProvider GetAzureAIClientProvider(this Kernel kernel, var client = kernel.GetAllServices().FirstOrDefault(); if (client is not null) { - return AzureAIClientProvider.FromClient(client); + return client; } - // Return the service registered on the kernel - var clientProvider = kernel.GetAllServices().FirstOrDefault(); - return (AzureAIClientProvider?)clientProvider ?? throw new InvalidOperationException("AzureAI client provider not found."); + throw new InvalidOperationException("AzureAI project client not found."); } } diff --git a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs index 147a6822f498..56b44c75fecc 100644 --- a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs @@ -28,13 +28,13 @@ public AggregatorKernelAgentFactory(params KernelAgentFactory[] kernelAgentFacto } /// - public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task TryCreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); foreach (var kernelAgentFactory in this._kernelAgentFactories) { - if (kernelAgentFactory is not null) + if (kernelAgentFactory is not null && kernelAgentFactory.IsSupported(agentDefinition)) { var kernelAgent = await kernelAgentFactory.CreateAsync(kernel, agentDefinition, cancellationToken).ConfigureAwait(false); if (kernelAgent is not null) diff --git a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs index c70a6812bca6..64de8a7d2c84 100644 --- a/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/ChatCompletionAgentFactory.cs @@ -24,14 +24,14 @@ public ChatCompletionAgentFactory() } /// - public override Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override Task TryCreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); // TODO Implement template handling ChatCompletionAgent? kernelAgent = null; - if (agentDefinition.Type?.Equals(ChatCompletionAgentType, System.StringComparison.Ordinal) ?? false) + if (this.IsSupported(agentDefinition)) { kernelAgent = new ChatCompletionAgent() { diff --git a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs index afeddaa2a0b2..4f39f8df2987 100644 --- a/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs +++ b/dotnet/src/Agents/OpenAI/Definition/OpenAIAssistantAgentFactory.cs @@ -27,28 +27,28 @@ public OpenAIAssistantAgentFactory() } /// - public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task TryCreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { Verify.NotNull(agentDefinition); Verify.NotNull(agentDefinition.Model); Verify.NotNull(agentDefinition.Model.Id); KernelAgent? kernelAgent = null; - if (agentDefinition.Type?.Equals(OpenAIAssistantAgentType, System.StringComparison.Ordinal) ?? false) + if (this.IsSupported(agentDefinition)) { - var clientProvider = kernel.GetOpenAIClientProvider(agentDefinition); - AssistantClient client = clientProvider.Client.GetAssistantClient(); + var client = kernel.GetOpenAIClient(agentDefinition); + AssistantClient assistantClient = client.GetAssistantClient(); var assistantCreationOptions = agentDefinition.CreateAssistantCreationOptions(); - Assistant model = await client.CreateAssistantAsync(agentDefinition.Model.Id, assistantCreationOptions, cancellationToken).ConfigureAwait(false); + Assistant model = await assistantClient.CreateAssistantAsync(agentDefinition.Model.Id, assistantCreationOptions, cancellationToken).ConfigureAwait(false); - kernelAgent = new OpenAIAssistantAgent(model, clientProvider.AssistantClient) + kernelAgent = new OpenAIAssistantAgent(model, assistantClient) { Kernel = kernel, Arguments = agentDefinition.GetDefaultKernelArguments() ?? [], }; } - return Task.FromResult(kernelAgent).Result; + return kernelAgent; } } diff --git a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs index 782775de32f4..c83713fd058e 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/AgentDefinitionExtensions.cs @@ -12,10 +12,20 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; [ExcludeFromCodeCoverage] internal static class AgentDefinitionExtensions { + /// + /// Tool definition type for code interpreter. + /// + internal const string CodeInterpreter = "code_interpreter"; + + /// + /// Tool definition type for file search. + /// + internal const string FileSearch = "file_search"; + /// /// Property name for the file ids configuration. /// - public const string FileIds = "file_ids"; + internal const string FileIds = "file_ids"; /// /// Create the which corresponds with the provided . @@ -42,12 +52,12 @@ public static AssistantCreationOptions CreateAssistantCreationOptions(this Agent // Metadata // ExecutionOptions - if (agentDefinition?.IsEnableCodeInterpreter() ?? false) + if (agentDefinition?.HasToolType(CodeInterpreter) ?? false) { assistantCreationOptions.Tools.Add(ToolDefinition.CreateCodeInterpreter()); } - if (agentDefinition?.IsEnableFileSearch() ?? false) + if (agentDefinition?.HasToolType(FileSearch) ?? false) { assistantCreationOptions.Tools.Add(ToolDefinition.CreateFileSearch()); } @@ -63,7 +73,7 @@ public static AssistantCreationOptions CreateAssistantCreationOptions(this Agent { Verify.NotNull(agentDefinition); - var toolDefinition = agentDefinition.GetFirstToolDefinition(AgentToolDefinition.CodeInterpreter); + var toolDefinition = agentDefinition.GetFirstToolDefinition(CodeInterpreter); if (toolDefinition?.Configuration?.TryGetValue(FileIds, out var fileIds) ?? false) { // TODO: Verify that the fileIds are strings diff --git a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs index b247ebc0f593..5d8436108686 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/KernelExtensions.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.ClientModel; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Net.Http; +using Azure.AI.OpenAI; using Azure.Identity; using OpenAI; @@ -14,15 +15,17 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI; [ExcludeFromCodeCoverage] internal static class KernelExtensions { - private const string Endpoint = "endpoint"; private const string ApiKey = "api_key"; + private const string OpenAI = "openai"; + private const string AzureOpenAI = "azure_openai"; + /// - /// Return the to be used with the specified . + /// Return the to be used with the specified . /// - /// Kernel instance which will be used to resolve a default . - /// Agent definition which will be used to provide configuration for the . - public static OpenAIClientProvider GetOpenAIClientProvider(this Kernel kernel, AgentDefinition agentDefinition) + /// Kernel instance which will be used to resolve a default . + /// Agent definition which will be used to provide configuration for the . + public static OpenAIClient GetOpenAIClient(this Kernel kernel, AgentDefinition agentDefinition) { Verify.NotNull(agentDefinition); @@ -30,31 +33,39 @@ public static OpenAIClientProvider GetOpenAIClientProvider(this Kernel kernel, A var configuration = agentDefinition?.Model?.Configuration; if (configuration is not null) { - var hasEndpoint = configuration.TryGetValue(Endpoint, out var endpoint) && endpoint is not null; - var hasApiKey = configuration.TryGetValue(ApiKey, out var apiKey) && apiKey is not null; - if (hasApiKey && hasEndpoint) + if (configuration.Type is null) { - return OpenAIClientProvider.ForAzureOpenAI(new ApiKeyCredential(apiKey!.ToString()!), new Uri(endpoint!.ToString()!)); + throw new InvalidOperationException("OpenAI client type was not specified."); } - else if (hasApiKey && !hasEndpoint) + + var httpClient = kernel.GetAllServices().FirstOrDefault(); + if (configuration.Type.Equals(OpenAI, StringComparison.OrdinalIgnoreCase)) { - return OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(apiKey!.ToString()!)); + OpenAIClientOptions clientOptions = OpenAIClientProvider.CreateOpenAIClientOptions(configuration.GetEndpointUri(), httpClient); + return new OpenAIClient(configuration.GetApiKeyCredential(), clientOptions); } - else if (!hasApiKey && hasEndpoint) + else if (configuration.Type.Equals(AzureOpenAI, StringComparison.OrdinalIgnoreCase)) { - return OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(endpoint!.ToString()!)); + AzureOpenAIClientOptions clientOptions = OpenAIClientProvider.CreateAzureClientOptions(httpClient); + var endpoint = configuration.GetEndpointUri(); + if (configuration.TryGetValue(ApiKey, out var apiKey) && apiKey is not null) + { + return new AzureOpenAIClient(endpoint, configuration.GetApiKeyCredential(), clientOptions); + } + + return new AzureOpenAIClient(endpoint, new AzureCliCredential(), clientOptions); } + + throw new InvalidOperationException($"Invalid OpenAI client type '{configuration.Type}' was specified."); } // Use the client registered on the kernel var client = kernel.GetAllServices().FirstOrDefault(); if (client is not null) { - return OpenAIClientProvider.FromClient(client); + return client; } - // Use the provider registered on the kernel - var clientProvider = kernel.GetAllServices().FirstOrDefault(); - return (OpenAIClientProvider?)clientProvider ?? throw new InvalidOperationException("OpenAI client provider not found."); + throw new InvalidOperationException("OpenAI client not found."); } } diff --git a/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs new file mode 100644 index 000000000000..d2e8a4ef6ac0 --- /dev/null +++ b/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ClientModel; + +namespace Microsoft.SemanticKernel.Agents.OpenAI; + +/// +/// Provides extension methods for . +/// +internal static class ModelConfigurationExtensions +{ + /// + /// Gets the endpoint property as a from the specified . + /// + /// Model configuration + internal static Uri GetEndpointUri(this ModelConfiguration configuration) + { + Verify.NotNull(configuration); + + if (!configuration.TryGetValue("endpoint", out var endpoint) || endpoint is null) + { + throw new InvalidOperationException("Endpoint was not specified."); + } + return new Uri(endpoint.ToString()!); + } + + /// + /// Gets the API key property as an from the specified . + /// + /// Model configuration + internal static ApiKeyCredential GetApiKeyCredential(this ModelConfiguration configuration) + { + Verify.NotNull(configuration); + + if (!configuration.TryGetValue("api_key", out var apiKey) || apiKey is null) + { + throw new InvalidOperationException("API key was not specified."); + } + + return new ApiKeyCredential(apiKey.ToString()!); + } +} diff --git a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs index ab4f542eb49b..109bebbe85d0 100644 --- a/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs +++ b/dotnet/src/Agents/OpenAI/OpenAIClientProvider.cs @@ -109,7 +109,7 @@ public static OpenAIClientProvider FromClient(OpenAIClient client) return new(client, [client.GetType().FullName!, client.GetHashCode().ToString()]); } - private static AzureOpenAIClientOptions CreateAzureClientOptions(HttpClient? httpClient) + internal static AzureOpenAIClientOptions CreateAzureClientOptions(HttpClient? httpClient) { AzureOpenAIClientOptions options = new() { @@ -121,7 +121,7 @@ private static AzureOpenAIClientOptions CreateAzureClientOptions(HttpClient? htt return options; } - private static OpenAIClientOptions CreateOpenAIClientOptions(Uri? endpoint, HttpClient? httpClient) + internal static OpenAIClientOptions CreateOpenAIClientOptions(Uri? endpoint, HttpClient? httpClient) { OpenAIClientOptions options = new() { diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs index b6b264bea2c1..97f4679e45ee 100644 --- a/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/AzureAI/Definition/AzureAIAgentFactoryTests.cs @@ -37,7 +37,7 @@ public AzureAIAgentFactoryTests() new FakeTokenCredential(), new AIProjectClientOptions() { Transport = new HttpClientTransport(this._httpClient) }); - builder.Services.AddSingleton(AzureAIClientProvider.FromClient(client)); + builder.Services.AddSingleton(client); this._kernel = builder.Build(); } @@ -70,7 +70,7 @@ public async Task VerifyCanCreateAzureAIAgentAsync() new Microsoft.SemanticKernel.Agents.AgentToolDefinition() { Name = "tool1", - Type = Microsoft.SemanticKernel.Agents.AgentToolDefinition.CodeInterpreter, + Type = "code_interpreter", }, ] }; diff --git a/dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs index 0a053cf5babd..d50dd3ec1350 100644 --- a/dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/AzureAI/Extensions/AgentDefinitionExtensionsTests.cs @@ -24,12 +24,12 @@ public void VerifyGetAzureToolDefinitions() new AgentToolDefinition() { Name = "tool1", - Type = AgentToolDefinition.CodeInterpreter, + Type = "code_interpreter", }, new AgentToolDefinition() { Name = "tool2", - Type = AgentToolDefinition.FileSearch, + Type = "file_search", }, ] }; diff --git a/dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs index 9d02bc03dfd2..afc4c837cfaf 100644 --- a/dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Extensions/AgentDefinitionExtensionsTests.cs @@ -59,7 +59,7 @@ public void VerifyGetFirstToolDefinition() } /// - /// Verify IsEnableCodeInterpreter + /// Verify HasToolType /// [Fact] public void VerifyIsEnableCodeInterpreter() @@ -74,7 +74,7 @@ public void VerifyIsEnableCodeInterpreter() }; // Act & Assert - Assert.True(agentDefinition.IsEnableCodeInterpreter()); + Assert.True(agentDefinition.HasToolType("code_interpreter")); } /// @@ -93,6 +93,6 @@ public void VerifyIsEnableFileSearch() }; // Act & Assert - Assert.True(agentDefinition.IsEnableFileSearch()); + Assert.True(agentDefinition.HasToolType("file_search")); } } diff --git a/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs index d3864e222bfc..576fbd179f3a 100644 --- a/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/Factory/AggregatorKernelAgentFactoryTests.cs @@ -63,7 +63,7 @@ public MyKernelAgentFactory1() : base(["my-type-1"]) { } - public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task TryCreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { return agentDefinition.Type != "my-type-1" ? null @@ -83,7 +83,7 @@ public MyKernelAgentFactory2() : base(["my-type-2"]) { } - public override async Task CreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) + public override async Task TryCreateAsync(Kernel kernel, AgentDefinition agentDefinition, CancellationToken cancellationToken = default) { return agentDefinition.Type != "my-type-2" ? null diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs index 736a265ebc2c..d71ca3e5a62f 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Definition/OpenAIAssistantAgentFactoryTests.cs @@ -9,6 +9,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.OpenAI; +using OpenAI; using Xunit; namespace SemanticKernel.Agents.UnitTests.OpenAI.Definition; @@ -30,8 +31,11 @@ public OpenAIAssistantAgentFactoryTests() this._messageHandlerStub = new HttpMessageHandlerStub(); this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false); + OpenAIClientOptions clientOptions = OpenAIClientProvider.CreateOpenAIClientOptions(endpoint: null, httpClient: this._httpClient); + OpenAIClient openAIClient = new(new ApiKeyCredential("fakekey"), clientOptions); + var builder = Kernel.CreateBuilder(); - builder.Services.AddSingleton(OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient)); + builder.Services.AddSingleton(openAIClient); this._kernel = builder.Build(); } @@ -64,7 +68,7 @@ public async Task VerifyCanCreateOpenAIAssistantAsync() new AgentToolDefinition() { Name = "tool1", - Type = AgentToolDefinition.CodeInterpreter, + Type = "code_interpreter", }, ] }; diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs index add40ee77216..3571e4f92aff 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/AgentDefinitionExtensionsTests.cs @@ -32,7 +32,7 @@ public void VerifyCreateAssistantCreationOptions() new AgentToolDefinition() { Name = "tool1", - Type = AgentToolDefinition.CodeInterpreter, + Type = "code_interpreter", }, ] }; @@ -66,7 +66,7 @@ public void VerifyGetCodeInterpreterFileIds() new AgentToolDefinition() { Name = "tool1", - Type = AgentToolDefinition.CodeInterpreter, + Type = "code_interpreter", Configuration = configuration, }, ] diff --git a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs index c15cd02f7aa0..fe43da2657cb 100644 --- a/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs +++ b/dotnet/src/Agents/UnitTests/Yaml/KernelAgentYamlTests.cs @@ -12,6 +12,7 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.AzureAI; using Microsoft.SemanticKernel.Agents.OpenAI; +using OpenAI; using SemanticKernel.Agents.UnitTests.AzureAI.Definition; using SemanticKernel.Agents.UnitTests.OpenAI; using SemanticKernel.Agents.UnitTests.OpenAI.Definition; @@ -38,16 +39,18 @@ public KernelAgentYamlTests() var builder = Kernel.CreateBuilder(); - // Add OpenAI client provider - builder.Services.AddSingleton(OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient)); + // Add OpenAI client + OpenAIClientOptions clientOptions = OpenAIClientProvider.CreateOpenAIClientOptions(endpoint: null, httpClient: this._httpClient); + OpenAIClient openAIClient = new(new ApiKeyCredential("fakekey"), clientOptions); + builder.Services.AddSingleton(openAIClient); - // Add Azure AI client provider + // Add Azure AI agents client var client = new AIProjectClient( "endpoint;subscription_id;resource_group_name;project_name", new FakeTokenCredential(), new AIProjectClientOptions() { Transport = new HttpClientTransport(this._httpClient) }); - builder.Services.AddSingleton(AzureAIClientProvider.FromClient(client)); + builder.Services.AddSingleton(client); this._kernel = builder.Build(); } diff --git a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj index 8cc7e60512b9..235c08e4d52b 100644 --- a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj +++ b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj @@ -58,9 +58,5 @@ - - - - From bd15c546471eabb484d87d7d5730d2b9a3431271 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:49:30 +0000 Subject: [PATCH 27/30] Add getting started sample --- .../Step08_AzureAIAgent_Declarative.cs | 81 +++++++++++++++++++ .../GettingStartedWithAgents.csproj | 1 + 2 files changed, 82 insertions(+) create mode 100644 dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs new file mode 100644 index 000000000000..d42172675277 --- /dev/null +++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft. All rights reserved. +using Azure.AI.Projects; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Agents.AzureAI; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace GettingStarted.AzureAgents; + +/// +/// This example demonstrates how to declaratively create instances of . +/// +public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest +{ + public Step08_AzureAIAgent_Declarative(ITestOutputHelper output) : base(output) + { + var builder = Kernel.CreateBuilder(); + builder.Services.AddSingleton(this.Client); + this._kernel = builder.Build(); + } + + [Fact] + public async Task AzureAIAgentWithCodeInterpreterAsync() + { + var text = + """ + type: azureai_agent + name: AzureAIAgent + description: AzureAIAgent Description + instructions: AzureAIAgent Instructions + model: + id: gpt-4o-mini + tools: + - name: tool1 + type: code_interpreter + """; + AzureAIAgentFactory factory = new(); + + var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text) as AzureAIAgent; + + await InvokeAgentAsync(agent, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); + } + + #region private + private readonly Kernel _kernel; + + /// + /// Invoke the agent with the user input. + /// + private async Task InvokeAgentAsync(AzureAIAgent? agent, string input) + { + // Create a thread for the agent conversation. + AgentThread thread = await this.AgentsClient.CreateThreadAsync(metadata: SampleMetadata); + + // Respond to user input + try + { + await InvokeAsync(input); + } + finally + { + await this.AgentsClient.DeleteThreadAsync(thread.Id); + await this.AgentsClient.DeleteAgentAsync(agent!.Id); + } + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAsync(string input) + { + ChatMessageContent message = new(AuthorRole.User, input); + await agent!.AddChatMessageAsync(thread.Id, message); + this.WriteAgentChatMessage(message); + + await foreach (ChatMessageContent response in agent.InvokeAsync(thread.Id)) + { + this.WriteAgentChatMessage(response); + } + } + } + #endregion +} diff --git a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj index ffc4734e10d6..733ca83417c8 100644 --- a/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj +++ b/dotnet/samples/GettingStartedWithAgents/GettingStartedWithAgents.csproj @@ -46,6 +46,7 @@ + From 90e611f4dfcfa8ce4b6863fe04ddf59ffacdc3b7 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 24 Feb 2025 12:16:29 +0000 Subject: [PATCH 28/30] Exclude ModelConfigurationExtensions from code coverage --- .../Step08_AzureAIAgent_Declarative.cs | 54 +++++++++++++++++++ .../ModelConfigurationExtensions.cs | 2 + 2 files changed, 56 insertions(+) diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs index d42172675277..acb868d26972 100644 --- a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs +++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs @@ -5,6 +5,7 @@ using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.AzureAI; using Microsoft.SemanticKernel.ChatCompletion; +using Plugins; namespace GettingStarted.AzureAgents; @@ -42,6 +43,59 @@ public async Task AzureAIAgentWithCodeInterpreterAsync() await InvokeAgentAsync(agent, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); } + [Fact] + public async Task AzureAIAgentWithOpenApiAsync() + { + var text = + """ + type: azureai_agent + name: AzureAIAgent + description: AzureAIAgent Description + instructions: AzureAIAgent Instructions + model: + id: gpt-4o-mini + tools: + - type: openapi + name: RestCountriesAPI + description: Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com. + schema: '{"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}' + """; + AzureAIAgentFactory factory = new(); + + var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text) as AzureAIAgent; + + await InvokeAgentAsync(agent, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); + } + + [Fact] + public async Task AzureAIAgentWithLocalFunctionsAsync() + { + var text = + """ + type: azureai_agent + name: RestaurantHost + instructions: Answer questions about the menu. + description: This agent answers questions about the menu. + model: + id: gpt-4o-mini + options: + temperature: 0.4 + function_choice_behavior: + type: auto + functions: + - MenuPlugin.GetSpecials + - MenuPlugin.GetItemPrice + """; + AzureAIAgentFactory factory = new(); + + KernelPlugin plugin = KernelPluginFactory.CreateFromType(); + this._kernel.Plugins.Add(plugin); + + var agent = await factory.CreateAgentFromYamlAsync(this._kernel, text) as AzureAIAgent; + + await InvokeAgentAsync(agent, "What is the special soup and how much does it cost?"); + } + #region private private readonly Kernel _kernel; diff --git a/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs index d2e8a4ef6ac0..5ce92acdfd58 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/ModelConfigurationExtensions.cs @@ -2,12 +2,14 @@ using System; using System.ClientModel; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.SemanticKernel.Agents.OpenAI; /// /// Provides extension methods for . /// +[ExcludeFromCodeCoverage] internal static class ModelConfigurationExtensions { /// From e8c8609b1a616c9f7c4944fd0d2722db59303773 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:15:46 +0000 Subject: [PATCH 29/30] Use the correct Open API specification --- .../AzureAIAgent/Step08_AzureAIAgent_Declarative.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs index acb868d26972..c8c848a44240 100644 --- a/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs +++ b/dotnet/samples/GettingStartedWithAgents/AzureAIAgent/Step08_AzureAIAgent_Declarative.cs @@ -43,7 +43,7 @@ public async Task AzureAIAgentWithCodeInterpreterAsync() await InvokeAgentAsync(agent, "Use code to determine the values in the Fibonacci sequence that that are less then the value of 101?"); } - [Fact] + [Fact(Skip = "Support for tool configuration will be added in a later PR")] public async Task AzureAIAgentWithOpenApiAsync() { var text = @@ -58,7 +58,7 @@ public async Task AzureAIAgentWithOpenApiAsync() - type: openapi name: RestCountriesAPI description: Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com. - schema: '{"openapi":"3.1.0","info":{"title":"Get Weather Data","description":"Retrieves current weather data for a location based on wttr.in.","version":"v1.0.0"},"servers":[{"url":"https://wttr.in"}],"auth":[],"paths":{"/{location}":{"get":{"description":"Get weather information for a specific location","operationId":"GetCurrentWeather","parameters":[{"name":"location","in":"path","description":"City or location to retrieve the weather for","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","description":"Always use j1 value for this parameter","required":true,"schema":{"type":"string","default":"j1"}}],"responses":{"200":{"description":"Successful response","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"Location not found"}},"deprecated":false}}},"components":{"schemes":{}}}' + schema: '{"openapi":"3.1.0","info":{"title":"RestCountries.NET API","description":"Web API version 3.1 for managing country items, based on previous implementations from restcountries.eu and restcountries.com.","version":"v3.1"},"servers":[{"url":"https://restcountries.net"}],"auth":[],"paths":{"/v3.1/currency":{"get":{"description":"Search by currency.","operationId":"LookupCountryByCurrency","parameters":[{"name":"currency","in":"query","description":"The currency to search for.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"text/plain":{"schema":{"type":"string"}}}}}}}},"components":{"schemes":{}}}' """; AzureAIAgentFactory factory = new(); From 76dff5a076808b1a609543d3d45f4b9700651d4f Mon Sep 17 00:00:00 2001 From: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:29:37 +0000 Subject: [PATCH 30/30] More code review feedback --- .../src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs index 56b44c75fecc..3c56f00cc732 100644 --- a/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs +++ b/dotnet/src/Agents/Core/Definition/AggregatorKernelAgentFactory.cs @@ -36,7 +36,7 @@ public AggregatorKernelAgentFactory(params KernelAgentFactory[] kernelAgentFacto { if (kernelAgentFactory is not null && kernelAgentFactory.IsSupported(agentDefinition)) { - var kernelAgent = await kernelAgentFactory.CreateAsync(kernel, agentDefinition, cancellationToken).ConfigureAwait(false); + var kernelAgent = await kernelAgentFactory.TryCreateAsync(kernel, agentDefinition, cancellationToken).ConfigureAwait(false); if (kernelAgent is not null) { return Task.FromResult(kernelAgent).Result;