.Net Agents - Update Azure Foundry Agent SDK (#12027)

### Motivation and Context

As part of going GA with Foundry Agents, Azure team has split
`Azure.AI.Projects` into two packages: `Azure.AI.Projects` &
`Azure.AI.Agents.Persistent`

In addition, connecting to a foundry project will be based on
endpoint/credential and connection-string will not be supported.

### Description

- Updated package dependency to include: `Azure.AI.Projects.OneDP` &
`Azure.AI.Agents.Persistent`
- Updated declarative agents to support `endpoint` instead of
`connection-string`
- Updated tests and samples
- Deprecated `OpenAIClientProvider` 
- Removed `AzureAIClientProvider`


![image](https://github.com/user-attachments/assets/16ba9e4d-86b0-4bbb-a817-dc8bd346ddb0)



### Contribution Checklist
<!-- Before submitting this PR, please make sure: -->

- [X] The code builds clean without any errors or warnings
- [X] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [X] All unit tests pass, and I have added new tests where possible
- [X] I didn't break anyone 😄
This commit is contained in:
Chris
2025-05-19 23:12:21 +00:00
committed by GitHub
parent 54519f0fb3
commit f97664329b
57 changed files with 476 additions and 793 deletions
+2 -2
View File
@@ -17,10 +17,10 @@
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.0-preview.16" />
<PackageVersion Include="AWSSDK.Extensions.NETCore.Setup" Version="4.0.0" />
<PackageVersion Include="AWSSDK.SecurityToken" Version="4.0.0.2" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.0.0" />
<PackageVersion Include="Azure.AI.ContentSafety" Version="1.0.0" />
<PackageVersion Include="Azure.AI.Inference" Version="1.0.0-beta.2" />
<PackageVersion Include="Azure.AI.Inference" Version="1.0.0-beta.5" />
<PackageVersion Include="Azure.AI.OpenAI" Version="[2.2.0-beta.4]" />
<PackageVersion Include="Azure.AI.Projects" Version="[1.0.0-beta.8]" />
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.3.0" />
<PackageVersion Include="Azure.Search.Documents" Version="11.6.0" />
+1 -1
View File
@@ -3,7 +3,7 @@
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
@@ -1,10 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Resources;
using Agent = Azure.AI.Projects.Agent;
namespace Agents;
@@ -17,10 +16,10 @@ public class AzureAIAgent_FileManipulation(ITestOutputHelper output) : BaseAzure
public async Task AnalyzeCSVFileUsingAzureAIAgentAsync()
{
await using Stream stream = EmbeddedResource.ReadStream("sales.csv")!;
AgentFile fileInfo = await this.AgentsClient.UploadFileAsync(stream, AgentFilePurpose.Agents, "sales.csv");
PersistentAgentFileInfo fileInfo = await this.Client.Files.UploadFileAsync(stream, PersistentAgentFilePurpose.Agents, "sales.csv");
// Define the agent
Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new CodeInterpreterToolDefinition()],
toolResources:
@@ -31,8 +30,8 @@ public class AzureAIAgent_FileManipulation(ITestOutputHelper output) : BaseAzure
FileIds = { fileInfo.Id },
}
});
AzureAIAgent agent = new(definition, this.AgentsClient);
AzureAIAgentThread thread = new(this.AgentsClient);
AzureAIAgent agent = new(definition, this.Client);
AzureAIAgentThread thread = new(this.Client);
// Respond to user input
try
@@ -44,8 +43,8 @@ public class AzureAIAgent_FileManipulation(ITestOutputHelper output) : BaseAzure
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.AgentsClient.DeleteFileAsync(fileInfo.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
await this.Client.Files.DeleteFileAsync(fileInfo.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -1,11 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Agent = Azure.AI.Projects.Agent;
namespace Agents;
@@ -21,23 +20,30 @@ public class AzureAIAgent_Streaming(ITestOutputHelper output) : BaseAzureAgentTe
const string AgentInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound.";
// Define the agent
Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
AgentName,
null,
AgentInstructions);
AzureAIAgent agent = new(definition, this.AgentsClient);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread agentThread = new(this.AgentsClient, metadata: SampleMetadata);
try
{
// Create a thread for the agent conversation.
AzureAIAgentThread agentThread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Fortune favors the bold.");
await InvokeAgentAsync(agent, agentThread, "I came, I saw, I conquered.");
await InvokeAgentAsync(agent, agentThread, "Practice makes perfect.");
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Fortune favors the bold.");
await InvokeAgentAsync(agent, agentThread, "I came, I saw, I conquered.");
await InvokeAgentAsync(agent, agentThread, "Practice makes perfect.");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
finally
{
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
[Fact]
@@ -47,26 +53,34 @@ public class AzureAIAgent_Streaming(ITestOutputHelper output) : BaseAzureAgentTe
const string AgentInstructions = "Answer questions about the menu.";
// Define the agent
Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
AgentName,
null,
AgentInstructions);
AzureAIAgent agent = new(definition, this.AgentsClient);
AzureAIAgent agent = new(definition, this.Client);
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
agent.Kernel.Plugins.Add(plugin);
// Create a thread for the agent conversation.
AzureAIAgentThread agentThread = new(this.AgentsClient, metadata: SampleMetadata);
AzureAIAgentThread agentThread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, agentThread, "What is the special drink and its price?");
try
{
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "What is the special soup and its price?");
await InvokeAgentAsync(agent, agentThread, "What is the special drink and its price?");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
finally
{
await this.Client.Threads.DeleteThreadAsync(agentThread.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
[Fact]
@@ -76,23 +90,31 @@ public class AzureAIAgent_Streaming(ITestOutputHelper output) : BaseAzureAgentTe
const string AgentInstructions = "Solve math problems with code.";
// Define the agent
Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
AgentName,
null,
AgentInstructions,
[new CodeInterpreterToolDefinition()]);
AzureAIAgent agent = new(definition, this.AgentsClient);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread agentThread = new(this.AgentsClient, metadata: SampleMetadata);
AzureAIAgentThread agentThread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Is 191 a prime number?");
await InvokeAgentAsync(agent, agentThread, "Determine the values in the Fibonacci sequence that that are less then the value of 101");
try
{
// Respond to user input
await InvokeAgentAsync(agent, agentThread, "Is 191 a prime number?");
await InvokeAgentAsync(agent, agentThread, "Determine the values in the Fibonacci sequence that that are less then the value of 101");
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
// Output the entire chat history
await DisplayChatHistoryAsync(agentThread);
}
finally
{
await this.Client.Threads.DeleteThreadAsync(agentThread.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
// Local function to invoke agent and display the conversation messages.
@@ -4,7 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;
@@ -68,7 +68,7 @@ internal sealed class AzureAIAgentWithMCPToolsSample : BaseSample
await response!.Thread.DeleteAsync();
// Delete the agent after use
await agent.Client.DeleteAgentAsync(agent.Id);
await agent.Client.Administration.DeleteAgentAsync(agent.Id);
}
/// <summary>
@@ -86,7 +86,7 @@ internal sealed class AzureAIAgentWithMCPToolsSample : BaseSample
.AddEnvironmentVariables()
.Build();
if (config["AzureAI:ConnectionString"] is not { } connectionString)
if (config["AzureAI:Endpoint"] is not { } endpoint)
{
const string Message = "Please provide a valid `AzureAI:ConnectionString` secret to run this sample. See the associated README.md for more details.";
Console.Error.WriteLine(Message);
@@ -96,11 +96,9 @@ internal sealed class AzureAIAgentWithMCPToolsSample : BaseSample
string modelId = config["AzureAI:ChatModelId"] ?? "gpt-4o-mini";
// Create the Azure AI Agent
AIProjectClient projectClient = AzureAIAgent.CreateAzureAIClient(connectionString, new AzureCliCredential());
PersistentAgentsClient agentsClient = AzureAIAgent.CreateAgentsClient(endpoint, new AzureCliCredential());
AgentsClient agentsClient = projectClient.GetAgentsClient();
Azure.AI.Projects.Agent agent = await agentsClient.CreateAgentAsync(modelId, name, null, instructions);
PersistentAgent agent = await agentsClient.Administration.CreateAgentAsync(modelId, name, null, instructions);
return new AzureAIAgent(agent, agentsClient)
{
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
@@ -19,11 +20,10 @@ public class Step01_AzureAIAgent(ITestOutputHelper output) : BaseAzureAgentTest(
string generateStoryYaml = EmbeddedResource.Read("GenerateStory.yaml");
PromptTemplateConfig templateConfig = KernelFunctionYaml.ToPromptTemplateConfig(generateStoryYaml);
// Instructions, Name and Description properties defined via the PromptTemplateConfig.
Azure.AI.Projects.Agent definition = await this.AgentsClient.CreateAgentAsync(TestConfiguration.AzureAI.ChatModelId, templateConfig.Name, templateConfig.Description, templateConfig.Template);
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(TestConfiguration.AzureAI.ChatModelId, templateConfig.Name, templateConfig.Description, templateConfig.Template);
AzureAIAgent agent = new(
definition,
this.AgentsClient,
this.Client,
templateFactory: new KernelPromptTemplateFactory(),
templateFormat: PromptTemplateConfig.SemanticKernelTemplateFormat)
{
@@ -35,7 +35,7 @@ public class Step01_AzureAIAgent(ITestOutputHelper output) : BaseAzureAgentTest(
};
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.AgentsClient, metadata: SampleMetadata);
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
try
{
@@ -53,7 +53,7 @@ public class Step01_AzureAIAgent(ITestOutputHelper output) : BaseAzureAgentTest(
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the response.
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
@@ -23,7 +24,7 @@ public class Step02_AzureAIAgent_Plugins(ITestOutputHelper output) : BaseAzureAg
name: "Host");
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.AgentsClient, metadata: SampleMetadata);
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -36,7 +37,7 @@ public class Step02_AzureAIAgent_Plugins(ITestOutputHelper output) : BaseAzureAg
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
@@ -47,7 +48,7 @@ public class Step02_AzureAIAgent_Plugins(ITestOutputHelper output) : BaseAzureAg
AzureAIAgent agent = await CreateAzureAgentAsync(plugin: KernelPluginFactory.CreateFromType<WidgetFactory>());
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.AgentsClient, metadata: SampleMetadata);
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -57,20 +58,20 @@ public class Step02_AzureAIAgent_Plugins(ITestOutputHelper output) : BaseAzureAg
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
}
private async Task<AzureAIAgent> CreateAzureAgentAsync(KernelPlugin plugin, string? instructions = null, string? name = null)
{
// Define the agent
Azure.AI.Projects.Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
name,
null,
instructions);
AzureAIAgent agent = new(definition, this.AgentsClient);
AzureAIAgent agent = new(definition, this.Client);
// Add to the agent's Kernel
if (plugin != null)
@@ -1,10 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
using Agent = Azure.AI.Projects.Agent;
namespace GettingStarted.AzureAgents;
@@ -39,18 +39,18 @@ public class Step03_AzureAIAgent_Chat(ITestOutputHelper output) : BaseAzureAgent
public async Task UseGroupChatWithTwoAgents()
{
// Define the agents
Agent reviewerModel = await this.AgentsClient.CreateAgentAsync(
PersistentAgent reviewerModel = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
ReviewerName,
null,
ReviewerInstructions);
AzureAIAgent agentReviewer = new(reviewerModel, this.AgentsClient);
Agent writerModel = await this.AgentsClient.CreateAgentAsync(
AzureAIAgent agentReviewer = new(reviewerModel, this.Client);
PersistentAgent writerModel = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
CopyWriterName,
null,
CopyWriterInstructions);
AzureAIAgent agentWriter = new(writerModel, this.AgentsClient);
AzureAIAgent agentWriter = new(writerModel, this.Client);
// Create a chat for agent interaction.
AgentGroupChat chat =
@@ -89,6 +89,8 @@ public class Step03_AzureAIAgent_Chat(ITestOutputHelper output) : BaseAzureAgent
finally
{
await chat.ResetAsync();
await agentReviewer.Client.Administration.DeleteAgentAsync(agentReviewer.Id);
await agentWriter.Client.Administration.DeleteAgentAsync(agentWriter.Id);
}
}
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
@@ -15,13 +16,13 @@ public class Step04_AzureAIAgent_CodeInterpreter(ITestOutputHelper output) : Bas
public async Task UseCodeInterpreterToolWithAgent()
{
// Define the agent
Azure.AI.Projects.Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new Azure.AI.Projects.CodeInterpreterToolDefinition()]);
AzureAIAgent agent = new(definition, this.AgentsClient);
tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.AgentsClient, metadata: SampleMetadata);
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -31,7 +32,7 @@ public class Step04_AzureAIAgent_CodeInterpreter(ITestOutputHelper output) : Bas
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -1,5 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
@@ -18,12 +18,12 @@ public class Step05_AzureAIAgent_FileSearch(ITestOutputHelper output) : BaseAzur
// Define the agent
await using Stream stream = EmbeddedResource.ReadStream("employees.pdf")!;
AgentFile fileInfo = await this.AgentsClient.UploadFileAsync(stream, AgentFilePurpose.Agents, "employees.pdf");
VectorStore fileStore =
await this.AgentsClient.CreateVectorStoreAsync(
PersistentAgentFileInfo fileInfo = await this.Client.Files.UploadFileAsync(stream, PersistentAgentFilePurpose.Agents, "employees.pdf");
PersistentAgentsVectorStore fileStore =
await this.Client.VectorStores.CreateVectorStoreAsync(
[fileInfo.Id],
metadata: new Dictionary<string, string>() { { SampleMetadataKey, bool.TrueString } });
Agent agentModel = await this.AgentsClient.CreateAgentAsync(
PersistentAgent agentModel = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new FileSearchToolDefinition()],
toolResources: new()
@@ -34,10 +34,10 @@ public class Step05_AzureAIAgent_FileSearch(ITestOutputHelper output) : BaseAzur
}
},
metadata: new Dictionary<string, string>() { { SampleMetadataKey, bool.TrueString } });
AzureAIAgent agent = new(agentModel, this.AgentsClient);
AzureAIAgent agent = new(agentModel, this.Client);
// Create a thread associated for the agent conversation.
Microsoft.SemanticKernel.Agents.AgentThread thread = new AzureAIAgentThread(this.AgentsClient, metadata: SampleMetadata);
Microsoft.SemanticKernel.Agents.AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -49,9 +49,9 @@ public class Step05_AzureAIAgent_FileSearch(ITestOutputHelper output) : BaseAzur
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.AgentsClient.DeleteVectorStoreAsync(fileStore.Id);
await this.AgentsClient.DeleteFileAsync(fileInfo.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
await this.Client.VectorStores.DeleteVectorStoreAsync(fileStore.Id);
await this.Client.Files.DeleteFileAsync(fileInfo.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -1,5 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
@@ -24,17 +24,17 @@ public class Step06_AzureAIAgent_OpenAPI(ITestOutputHelper output) : BaseAzureAg
string apiWeather = EmbeddedResource.Read("weather.json");
// Define the agent
Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools:
[
new OpenApiToolDefinition("RestCountries", "Retrieve country information", BinaryData.FromString(apiCountries), new OpenApiAnonymousAuthDetails()),
new OpenApiToolDefinition("Weather", "Retrieve weather by location", BinaryData.FromString(apiWeather), new OpenApiAnonymousAuthDetails())
]);
AzureAIAgent agent = new(definition, this.AgentsClient);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
Microsoft.SemanticKernel.Agents.AgentThread thread = new AzureAIAgentThread(this.AgentsClient, metadata: SampleMetadata);
Microsoft.SemanticKernel.Agents.AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -45,7 +45,7 @@ public class Step06_AzureAIAgent_OpenAPI(ITestOutputHelper output) : BaseAzureAg
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -1,4 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
@@ -27,19 +28,19 @@ public class Step07_AzureAIAgent_Functions(ITestOutputHelper output) : BaseAzure
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
var tools = plugin.Select(f => f.ToToolDefinition(plugin.Name));
Azure.AI.Projects.Agent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
model: TestConfiguration.AzureAI.ChatModelId,
name: HostName,
description: null,
instructions: HostInstructions,
tools: tools);
AzureAIAgent agent = new(definition, this.AgentsClient);
AzureAIAgent agent = new(definition, this.Client);
// Add plugin to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(plugin);
// Create a thread for the agent conversation.
AgentThread thread = new AzureAIAgentThread(this.AgentsClient, metadata: SampleMetadata);
AgentThread thread = new AzureAIAgentThread(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -52,7 +53,7 @@ public class Step07_AzureAIAgent_Functions(ITestOutputHelper output) : BaseAzure
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -1,5 +1,4 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
@@ -8,7 +7,6 @@ using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Plugins;
using Agent = Microsoft.SemanticKernel.Agents.Agent;
namespace GettingStarted.AzureAgents;
@@ -37,6 +35,7 @@ public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
AzureAIAgentFactory factory = new();
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton(this.Client);
builder.Services.AddSingleton<TokenCredential>(new AzureCliCredential());
var kernel = builder.Build();
@@ -54,6 +53,8 @@ public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
name: MyAgent
description: My helpful agent.
instructions: You are helpful agent.
model:
id: ${AzureOpenAI:ChatModelId}
""";
AzureAIAgentFactory factory = new();
@@ -190,9 +191,6 @@ public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
""";
AzureAIAgentFactory factory = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
this._kernel.Plugins.Add(plugin);
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
await InvokeAgentAsync(agent!, "What are the key features of the Semantic Kernel?");
@@ -382,7 +380,9 @@ public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
AzureAIAgentFactory factory = new();
var promptTemplateFactory = new KernelPromptTemplateFactory();
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot);
var agent =
await factory.CreateAgentFromYamlAsync(text, new() { Kernel = this._kernel }, TestConfiguration.ConfigurationRoot) ??
throw new InvalidOperationException("Unable to create agent");
var options = new AgentInvokeOptions()
{
@@ -404,8 +404,8 @@ public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
}
finally
{
var azureaiAgent = agent as AzureAIAgent;
await azureaiAgent!.Client.DeleteAgentAsync(azureaiAgent.Id);
var azureaiAgent = (AzureAIAgent)agent;
await azureaiAgent.Client.Administration.DeleteAgentAsync(azureaiAgent.Id);
if (agentThread is not null)
{
@@ -417,7 +417,7 @@ public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
public Step08_AzureAIAgent_Declarative(ITestOutputHelper output) : base(output)
{
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<AIProjectClient>(this.Client);
builder.Services.AddSingleton(this.Client);
this._kernel = builder.Build();
}
@@ -448,7 +448,7 @@ public class Step08_AzureAIAgent_Declarative : BaseAzureAgentTest
{
var azureaiAgent = agent as AzureAIAgent;
Assert.NotNull(azureaiAgent);
await azureaiAgent.Client.DeleteAgentAsync(azureaiAgent.Id);
await azureaiAgent.Client.Administration.DeleteAgentAsync(azureaiAgent.Id);
if (agentThread is not null)
{
@@ -1,10 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using FoundryAgent = Azure.AI.Projects.Agent;
namespace GettingStarted.AzureAgents;
@@ -17,21 +16,15 @@ public class Step09_AzureAIAgent_BingGrounding(ITestOutputHelper output) : BaseA
public async Task UseBingGroundingToolWithAgent()
{
// Access the BingGrounding connection
ConnectionsClient connectionsClient = this.Client.GetConnectionsClient();
ConnectionResponse bingConnection = await connectionsClient.GetConnectionAsync(TestConfiguration.AzureAI.BingConnectionId);
// Define the agent
ToolConnectionList toolConnections = new()
{
ConnectionList = { new ToolConnection(bingConnection.Id) }
};
FoundryAgent definition = await this.AgentsClient.CreateAgentAsync(
BingGroundingSearchConfiguration bingToolConfiguration = new(TestConfiguration.AzureAI.BingConnectionId);
BingGroundingSearchToolParameters bingToolParameters = new([bingToolConfiguration]);
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new BingGroundingToolDefinition(toolConnections)]);
AzureAIAgent agent = new(definition, this.AgentsClient);
tools: [new BingGroundingToolDefinition(bingToolParameters)]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread thread = new(this.AgentsClient, metadata: SampleMetadata);
AzureAIAgentThread thread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -42,7 +35,7 @@ public class Step09_AzureAIAgent_BingGrounding(ITestOutputHelper output) : BaseA
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -62,21 +55,17 @@ public class Step09_AzureAIAgent_BingGrounding(ITestOutputHelper output) : BaseA
public async Task UseBingGroundingToolWithStreaming()
{
// Access the BingGrounding connection
ConnectionsClient connectionClient = this.Client.GetConnectionsClient();
ConnectionResponse bingConnectionResponse = await connectionClient.GetConnectionAsync(TestConfiguration.AzureAI.BingConnectionId);
BingGroundingSearchConfiguration bingToolConfiguration = new(TestConfiguration.AzureAI.BingConnectionId);
BingGroundingSearchToolParameters bingToolParameters = new([bingToolConfiguration]);
// Define the agent
ToolConnectionList toolConnections = new()
{
ConnectionList = { new ToolConnection(bingConnectionResponse.Id) }
};
FoundryAgent definition = await this.AgentsClient.CreateAgentAsync(
PersistentAgent definition = await this.Client.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
tools: [new BingGroundingToolDefinition(toolConnections)]);
AzureAIAgent agent = new(definition, this.AgentsClient);
tools: [new BingGroundingToolDefinition(bingToolParameters)]);
AzureAIAgent agent = new(definition, this.Client);
// Create a thread for the agent conversation.
AzureAIAgentThread thread = new(this.AgentsClient, metadata: SampleMetadata);
AzureAIAgentThread thread = new(this.Client, metadata: SampleMetadata);
// Respond to user input
try
@@ -96,7 +85,7 @@ public class Step09_AzureAIAgent_BingGrounding(ITestOutputHelper output) : BaseA
finally
{
await thread.DeleteAsync();
await this.AgentsClient.DeleteAgentAsync(agent.Id);
await this.Client.Administration.DeleteAgentAsync(agent.Id);
}
// Local function to invoke agent and display the conversation messages.
@@ -209,8 +209,8 @@ public class Step07_Assistant_Declarative : BaseAssistantTest
{
if (deleteAgent)
{
var openaiAgent = agent as OpenAIAssistantAgent;
await openaiAgent!.Client.DeleteAssistantAsync(openaiAgent.Id);
var openaiAgent = (OpenAIAssistantAgent)agent;
await openaiAgent.Client.DeleteAssistantAsync(openaiAgent.Id);
}
if (agentThread is not null)
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
@@ -9,7 +10,6 @@ using Microsoft.SemanticKernel.Agents.Orchestration;
using Microsoft.SemanticKernel.Agents.Runtime.InProcess;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using AAIP = Azure.AI.Projects;
namespace GettingStarted.Orchestration;
@@ -40,15 +40,14 @@ public class Step05_Magentic(ITestOutputHelper output) : BaseOrchestrationTest(o
instructions: "You are a Researcher. You find information without additional computation or quantitative analysis.",
kernel: researchKernel);
AAIP.AIProjectClient projectClient = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
AAIP.AgentsClient agentsClient = projectClient.GetAgentsClient();
AAIP.Agent definition =
await agentsClient.CreateAgentAsync(
PersistentAgentsClient agentsClient = AzureAIAgent.CreateAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
PersistentAgent definition =
await agentsClient.Administration.CreateAgentAsync(
TestConfiguration.AzureAI.ChatModelId,
name: "CoderAgent",
description: "Write and executes code to process and analyze data.",
instructions: "You solve questions using code. Please provide detailed analysis and computation process.",
tools: [new Azure.AI.Projects.CodeInterpreterToolDefinition()]);
tools: [new CodeInterpreterToolDefinition()]);
AzureAIAgent coderAgent = new(definition, agentsClient);
// Create a monitor to capturing agent responses (via ResponseCallback)
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
@@ -79,7 +79,7 @@ public class Step10_MultiAgent_Declarative : BaseAgentsTest
{
var azureaiAgent = agent as AzureAIAgent;
Assert.NotNull(azureaiAgent);
await azureaiAgent.Client.DeleteAgentAsync(azureaiAgent.Id);
await azureaiAgent.Client.Administration.DeleteAgentAsync(azureaiAgent.Id);
if (agentThread is not null)
{
@@ -97,19 +97,19 @@ public class Step10_MultiAgent_Declarative : BaseAgentsTest
OpenAIAssistantAgent.CreateAzureOpenAIClient(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(this.Endpoint!));
var aiProjectClient = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
var agentsClient = AzureAIAgent.CreateAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<OpenAIClient>(openaiClient);
builder.Services.AddSingleton<AIProjectClient>(aiProjectClient);
builder.Services.AddSingleton<PersistentAgentsClient>(agentsClient);
AddChatCompletionToKernel(builder);
this._kernel = builder.Build();
this._kernelAgentFactory = new AggregatorKernelAgentFactory(
new ChatCompletionAgentFactory(),
new OpenAIAssistantAgentFactory(),
new AzureAIAgentFactory()
);
this._kernelAgentFactory =
new AggregatorKernelAgentFactory(
new ChatCompletionAgentFactory(),
new OpenAIAssistantAgentFactory(),
new AzureAIAgentFactory());
}
#region private
@@ -37,7 +37,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Net.Http;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Core;
using Azure.Core.Pipeline;
using Microsoft.SemanticKernel.Http;
@@ -8,32 +8,32 @@ using Microsoft.SemanticKernel.Http;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
/// <summary>
/// Provides an <see cref="AIProjectClient"/> for use by <see cref="AzureAIAgent"/>.
/// Provides an <see cref="PersistentAgentsClient"/> for use by <see cref="AzureAIAgent"/>.
/// </summary>
public sealed partial class AzureAIAgent : Agent
{
/// <summary>
/// Produces a <see cref="AIProjectClient"/>.
/// Produces a <see cref="PersistentAgentsClient"/>.
/// </summary>
/// <param name="connectionString">The Azure AI Foundry project connection string, in the form `endpoint;subscription_id;resource_group_name;project_name`.</param>
/// <param name="endpoint">The Azure AI Foundry project endpoint.</param>
/// <param name="credential"> A credential used to authenticate to an Azure Service.</param>
/// <param name="httpClient">A custom <see cref="HttpClient"/> for HTTP requests.</param>
public static AIProjectClient CreateAzureAIClient(
string connectionString,
public static PersistentAgentsClient CreateAgentsClient(
string endpoint,
TokenCredential credential,
HttpClient? httpClient = null)
{
Verify.NotNullOrWhiteSpace(connectionString, nameof(connectionString));
Verify.NotNull(endpoint, nameof(endpoint));
Verify.NotNull(credential, nameof(credential));
AIProjectClientOptions clientOptions = CreateAzureClientOptions(httpClient);
PersistentAgentsAdministrationClientOptions clientOptions = CreateAzureClientOptions(httpClient);
return new AIProjectClient(connectionString, credential, clientOptions);
return new PersistentAgentsClient(endpoint, credential, clientOptions);
}
private static AIProjectClientOptions CreateAzureClientOptions(HttpClient? httpClient)
private static PersistentAgentsAdministrationClientOptions CreateAzureClientOptions(HttpClient? httpClient)
{
AIProjectClientOptions options = new();
PersistentAgentsAdministrationClientOptions options = new();
options.AddPolicy(new SemanticKernelHeadersPolicy(), HttpPipelinePosition.PerCall);
+7 -8
View File
@@ -5,13 +5,12 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Agents.AzureAI.Internal;
using Microsoft.SemanticKernel.Agents.Extensions;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Diagnostics;
using AAIP = Azure.AI.Projects;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
@@ -45,7 +44,7 @@ public sealed partial class AzureAIAgent : Agent
/// <summary>
/// Gets the assistant definition.
/// </summary>
public Azure.AI.Projects.Agent Definition { get; private init; }
public PersistentAgent Definition { get; private init; }
/// <summary>
/// Gets the polling behavior for run processing.
@@ -56,13 +55,13 @@ public sealed partial class AzureAIAgent : Agent
/// Initializes a new instance of the <see cref="AzureAIAgent"/> class.
/// </summary>
/// <param name="model">The agent model definition.</param>
/// <param name="client">An <see cref="AgentsClient"/> instance.</param>
/// <param name="client">An <see cref="PersistentAgentsClient"/> instance.</param>
/// <param name="plugins">Optional collection of plugins to add to the kernel.</param>
/// <param name="templateFactory">An optional factory to produce the <see cref="IPromptTemplate"/> for the agent.</param>
/// <param name="templateFormat">The format of the prompt template used when "templateFactory" parameter is supplied.</param>
public AzureAIAgent(
Azure.AI.Projects.Agent model,
AgentsClient client,
PersistentAgent model,
PersistentAgentsClient client,
IEnumerable<KernelPlugin>? plugins = null,
IPromptTemplateFactory? templateFactory = null,
string? templateFormat = null)
@@ -95,7 +94,7 @@ public sealed partial class AzureAIAgent : Agent
/// <summary>
/// The associated client.
/// </summary>
public AgentsClient Client { get; }
public PersistentAgentsClient Client { get; }
/// <summary>
/// Adds a message to the specified thread.
@@ -431,7 +430,7 @@ public sealed partial class AzureAIAgent : Agent
this.Logger.LogAzureAIAgentRestoringChannel(nameof(RestoreChannelAsync), nameof(AzureAIChannel), threadId);
AAIP.AgentThread thread = await this.Client.GetThreadAsync(threadId, cancellationToken).ConfigureAwait(false);
PersistentAgentThread thread = await this.Client.Threads.GetThreadAsync(threadId, cancellationToken).ConfigureAwait(false);
this.Logger.LogAzureAIAgentRestoredChannel(nameof(RestoreChannelAsync), nameof(AzureAIChannel), threadId);
@@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel.Agents.AzureAI.Internal;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
@@ -17,7 +17,7 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI;
/// </summary>
public sealed class AzureAIAgentThread : AgentThread
{
private readonly AgentsClient _client;
private readonly PersistentAgentsClient _client;
private readonly IEnumerable<ThreadMessageOptions>? _messages;
private readonly ToolResources? _toolResources;
private readonly IReadOnlyDictionary<string, string>? _metadata;
@@ -34,7 +34,7 @@ public sealed class AzureAIAgentThread : AgentThread
/// </param>
/// <param name="metadata">Metadata to attach to the underlying thread when it is created..</param>
public AzureAIAgentThread(
AgentsClient client,
PersistentAgentsClient client,
IEnumerable<ThreadMessageOptions>? messages = null,
ToolResources? toolResources = null,
IReadOnlyDictionary<string, string>? metadata = null)
@@ -52,7 +52,7 @@ public sealed class AzureAIAgentThread : AgentThread
/// </summary>
/// <param name="client">The agents client to use for interacting with threads.</param>
/// <param name="id">The ID of an existing thread to resume.</param>
public AzureAIAgentThread(AgentsClient client, string id)
public AzureAIAgentThread(PersistentAgentsClient client, string id)
{
Verify.NotNull(client);
Verify.NotNull(id);
@@ -78,7 +78,7 @@ public sealed class AzureAIAgentThread : AgentThread
try
{
var agentThreadResponse = await this._client.CreateThreadAsync(
var agentThreadResponse = await this._client.Threads.CreateThreadAsync(
this._messages,
this._toolResources,
this._metadata,
@@ -102,7 +102,7 @@ public sealed class AzureAIAgentThread : AgentThread
try
{
await this._client.DeleteThreadAsync(this.Id, cancellationToken).ConfigureAwait(false);
await this._client.Threads.DeleteThreadAsync(this.Id, cancellationToken).ConfigureAwait(false);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
+3 -3
View File
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel.Agents.AzureAI.Internal;
using Microsoft.SemanticKernel.Agents.Extensions;
using Microsoft.SemanticKernel.Diagnostics;
@@ -12,7 +12,7 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI;
/// <summary>
/// A <see cref="AgentChannel"/> specialization for use with <see cref="AzureAIAgent"/>.
/// </summary>
internal sealed class AzureAIChannel(AgentsClient client, string threadId)
internal sealed class AzureAIChannel(PersistentAgentsClient client, string threadId)
: AgentChannel<AzureAIAgent>
{
/// <inheritdoc/>
@@ -53,7 +53,7 @@ internal sealed class AzureAIChannel(AgentsClient client, string threadId)
/// <inheritdoc/>
protected override Task ResetAsync(CancellationToken cancellationToken = default)
{
return client.DeleteThreadAsync(threadId, cancellationToken);
return client.Threads.DeleteThreadAsync(threadId, cancellationToken);
}
/// <inheritdoc/>
@@ -1,116 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Azure.AI.Projects;
using Azure.Core;
using Azure.Core.Pipeline;
using Microsoft.SemanticKernel.Http;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
/// <summary>
/// Provides an <see cref="AIProjectClient"/> for use by <see cref="AzureAIAgent"/>.
/// </summary>
public sealed class AzureAIClientProvider
{
private AgentsClient? _agentsClient;
/// <summary>
/// Gets an active client instance.
/// </summary>
public AIProjectClient Client { get; }
/// <summary>
/// Gets an active assistant client instance.
/// </summary>
public AgentsClient AgentsClient => this._agentsClient ??= this.Client.GetAgentsClient();
/// <summary>
/// Configuration keys required for <see cref="AgentChannel"/> management.
/// </summary>
internal IReadOnlyList<string> ConfigurationKeys { get; }
private AzureAIClientProvider(AIProjectClient client, IEnumerable<string> keys)
{
this.Client = client;
this.ConfigurationKeys = keys.ToArray();
}
/// <summary>
/// Produces a <see cref="AzureAIClientProvider"/>.
/// </summary>
/// <param name="connectionString">The Azure AI Foundry project connection string, in the form `endpoint;subscription_id;resource_group_name;project_name`.</param>
/// <param name="credential"> A credential used to authenticate to an Azure Service.</param>
/// <param name="httpClient">A custom <see cref="HttpClient"/> for HTTP requests.</param>
public static AzureAIClientProvider FromConnectionString(
string connectionString,
TokenCredential credential,
HttpClient? httpClient = null)
{
Verify.NotNullOrWhiteSpace(connectionString, nameof(connectionString));
Verify.NotNull(credential, nameof(credential));
AIProjectClientOptions clientOptions = CreateAzureClientOptions(httpClient);
return new(new AIProjectClient(connectionString, credential, clientOptions), CreateConfigurationKeys(connectionString, httpClient));
}
/// <summary>
/// Provides a client instance directly.
/// </summary>
public static AzureAIClientProvider FromClient(AIProjectClient client)
{
return new(client, [client.GetType().FullName!, client.GetHashCode().ToString()]);
}
internal static AIProjectClientOptions CreateAzureClientOptions(HttpClient? httpClient)
{
AIProjectClientOptions options =
new()
{
Diagnostics = {
ApplicationId = HttpHeaderConstant.Values.UserAgent,
}
};
options.AddPolicy(new SemanticKernelHeadersPolicy(), HttpPipelinePosition.PerCall);
if (httpClient is not null)
{
options.Transport = new HttpClientTransport(httpClient);
// Disable retry policy if and only if a custom HttpClient is provided.
options.RetryPolicy = new RetryPolicy(maxRetries: 0);
}
return options;
}
private static IEnumerable<string> CreateConfigurationKeys(string connectionString, HttpClient? httpClient)
{
yield return connectionString;
if (httpClient is not null)
{
if (httpClient.BaseAddress is not null)
{
yield return httpClient.BaseAddress.AbsoluteUri;
}
foreach (string header in httpClient.DefaultRequestHeaders.SelectMany(h => h.Value))
{
yield return header;
}
}
}
private class SemanticKernelHeadersPolicy : HttpPipelineSynchronousPolicy
{
public override void OnSendingRequest(HttpMessage message)
{
message.Request.Headers.Add(
HttpHeaderConstant.Names.SemanticKernelVersion,
HttpHeaderConstant.Values.GetAssemblyVersion(typeof(AzureAIAgent)));
}
}
}
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel.Agents.AzureAI.Internal;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
@@ -3,7 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
@@ -33,14 +33,13 @@ public sealed class AzureAIAgentFactory : AgentFactory
if (agentDefinition.Type?.Equals(AzureAIAgentType, System.StringComparison.Ordinal) ?? false)
{
var projectClient = agentDefinition.GetAIProjectClient(kernel);
var client = agentDefinition.GetAgentsClient(kernel);
AgentsClient client = projectClient.GetAgentsClient();
Azure.AI.Projects.Agent agent;
PersistentAgent agent;
if (!string.IsNullOrEmpty(agentDefinition.Id))
{
// Get an existing agent
agent = await client.GetAgentAsync(
agent = await client.Administration.GetAgentAsync(
agentDefinition.Id,
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -57,7 +56,7 @@ public sealed class AzureAIAgentFactory : AgentFactory
Verify.NotNull(agentDefinition.Model);
Verify.NotNull(agentDefinition.Model.Id);
agent = await client.CreateAgentAsync(
agent = await client.Administration.CreateAgentAsync(
model: agentDefinition.Model.Id,
name: agentDefinition.Name,
description: agentDefinition.Description,
@@ -2,7 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel.Http;
@@ -20,24 +20,20 @@ internal static class AgentDefinitionExtensions
private const string CodeInterpreterType = "code_interpreter";
private const string FileSearchType = "file_search";
private const string FunctionType = "function";
private const string MicrosoftFabricType = "fabric_aiskill";
private const string OpenApiType = "openapi";
private const string SharepointGroundingType = "sharepoint_grounding";
private static readonly string[] s_validToolTypes = new string[]
{
private static readonly string[] s_validToolTypes =
[
AzureAISearchType,
AzureFunctionType,
BingGroundingType,
CodeInterpreterType,
FileSearchType,
FunctionType,
MicrosoftFabricType,
OpenApiType,
SharepointGroundingType
};
];
private const string ConnectionString = "connection_string";
private const string Endpoint = "endpoint";
/// <summary>
/// Return the Azure AI tool definitions which corresponds with the provided <see cref="AgentDefinition"/>.
@@ -58,9 +54,7 @@ internal static class AgentDefinitionExtensions
CodeInterpreterType => CreateCodeInterpreterToolDefinition(tool),
FileSearchType => CreateFileSearchToolDefinition(tool),
FunctionType => CreateFunctionToolDefinition(tool),
MicrosoftFabricType => CreateMicrosoftFabricToolDefinition(tool),
OpenApiType => CreateOpenApiToolDefinition(tool),
SharepointGroundingType => CreateSharepointGroundingToolDefinition(tool),
_ => throw new NotSupportedException($"Unable to create Azure AI tool definition because of unsupported tool type: {tool.Type}, supported tool types are: {string.Join(",", s_validToolTypes)}"),
};
}) ?? [];
@@ -108,11 +102,11 @@ internal static class AgentDefinitionExtensions
}
/// <summary>
/// Return the <see cref="AIProjectClient"/> to be used with the specified <see cref="AgentDefinition"/>.
/// Return the <see cref="PersistentAgentsClient"/> to be used with the specified <see cref="AgentDefinition"/>.
/// </summary>
/// <param name="agentDefinition">Agent definition which will be used to provide connection for the <see cref="AIProjectClient"/>.</param>
/// <param name="kernel">Kernel instance which will be used to resolve a default <see cref="AIProjectClient"/>.</param>
public static AIProjectClient GetAIProjectClient(this AgentDefinition agentDefinition, Kernel kernel)
/// <param name="agentDefinition">Agent definition which will be used to provide connection for the <see cref="PersistentAgentsClient"/>.</param>
/// <param name="kernel">Kernel instance which will be used to resolve a default <see cref="PersistentAgentsClient"/>.</param>
public static PersistentAgentsClient GetAgentsClient(this AgentDefinition agentDefinition, Kernel kernel)
{
Verify.NotNull(agentDefinition);
@@ -120,21 +114,20 @@ internal static class AgentDefinitionExtensions
var connection = agentDefinition?.Model?.Connection;
if (connection is not null)
{
if (connection.ExtensionData.TryGetValue(ConnectionString, out var value) && value is string connectionString)
if (connection.ExtensionData.TryGetValue(Endpoint, out var value) && value is string endpoint)
{
#pragma warning disable CA2000 // Dispose objects before losing scope, not relevant because the HttpClient is created and may be used elsewhere
var httpClient = HttpClientProvider.GetHttpClient(kernel.Services);
#pragma warning restore CA2000 // Dispose objects before losing scope
AIProjectClientOptions clientOptions = AzureAIClientProvider.CreateAzureClientOptions(httpClient);
var tokenCredential = kernel.Services.GetRequiredService<TokenCredential>();
return new(connectionString, tokenCredential, clientOptions);
return AzureAIAgent.CreateAgentsClient(endpoint, tokenCredential, httpClient);
}
}
// Return the client registered on the kernel
var client = kernel.GetAllServices<AIProjectClient>().FirstOrDefault();
return (AIProjectClient?)client ?? throw new InvalidOperationException("AzureAI project client not found.");
var client = kernel.GetAllServices<PersistentAgentsClient>().FirstOrDefault();
return (PersistentAgentsClient?)client ?? throw new InvalidOperationException("AzureAI project client not found.");
}
#region private
@@ -184,7 +177,7 @@ internal static class AgentDefinitionExtensions
return null;
}
private static AzureAISearchResource? GetAzureAISearchResource(this AgentDefinition agentDefinition)
private static AzureAISearchToolResource? GetAzureAISearchResource(this AgentDefinition agentDefinition)
{
Verify.NotNull(agentDefinition);
@@ -205,7 +198,7 @@ internal static class AgentDefinitionExtensions
string filter = azureAISearch.GetFilter() ?? string.Empty;
AzureAISearchQueryType? queryType = azureAISearch.GetAzureAISearchQueryType();
return new AzureAISearchResource(indexConnectionId, indexName, topK, filter, queryType);
return new AzureAISearchToolResource(indexConnectionId, indexName, topK, filter, queryType);
}
return null;
@@ -237,9 +230,9 @@ internal static class AgentDefinitionExtensions
{
Verify.NotNull(tool);
ToolConnectionList bingGrounding = tool.GetToolConnectionList();
BingGroundingSearchToolParameters bingToolParameters = new(tool.GetToolConnections().Select(connectionId => new BingGroundingSearchConfiguration(connectionId)));
return new BingGroundingToolDefinition(bingGrounding);
return new BingGroundingToolDefinition(bingToolParameters);
}
private static CodeInterpreterToolDefinition CreateCodeInterpreterToolDefinition(AgentToolDefinition tool)
@@ -270,15 +263,6 @@ internal static class AgentDefinitionExtensions
return new FunctionToolDefinition(name, description, parameters);
}
private static MicrosoftFabricToolDefinition CreateMicrosoftFabricToolDefinition(AgentToolDefinition tool)
{
Verify.NotNull(tool);
ToolConnectionList fabricAiSkill = tool.GetToolConnectionList();
return new MicrosoftFabricToolDefinition(fabricAiSkill);
}
private static OpenApiToolDefinition CreateOpenApiToolDefinition(AgentToolDefinition tool)
{
Verify.NotNull(tool);
@@ -292,14 +276,5 @@ internal static class AgentDefinitionExtensions
return new OpenApiToolDefinition(name, description, spec, auth);
}
private static SharepointToolDefinition CreateSharepointGroundingToolDefinition(AgentToolDefinition tool)
{
Verify.NotNull(tool);
ToolConnectionList sharepointGrounding = tool.GetToolConnectionList();
return new SharepointToolDefinition(sharepointGrounding);
}
#endregion
}
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel.Agents.AzureAI.Internal;
namespace Microsoft.SemanticKernel.Agents.AzureAI.Extensions;
@@ -19,24 +19,19 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI.Extensions;
internal static class AgentRunExtensions
{
public static async IAsyncEnumerable<RunStep> GetStepsAsync(
this AgentsClient client,
ThreadRun run,
[EnumeratorCancellation] CancellationToken cancellationToken)
this PersistentAgentsClient client,
ThreadRun run,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
PageableList<RunStep>? steps = null;
do
AsyncPageable<RunStep>? steps = client.Runs.GetRunStepsAsync(run, cancellationToken: cancellationToken);
await foreach (RunStep step in steps.ConfigureAwait(false))
{
steps = await client.GetRunStepsAsync(run, after: steps?.LastId, cancellationToken: cancellationToken).ConfigureAwait(false);
foreach (RunStep step in steps)
{
yield return step;
}
yield return step;
}
while (steps?.HasMore ?? false);
}
public static async Task<ThreadRun> CreateAsync(
this AgentsClient client,
this PersistentAgentsClient client,
string threadId,
AzureAIAgent agent,
string? instructions,
@@ -44,16 +39,16 @@ internal static class AgentRunExtensions
AzureAIInvocationOptions? invocationOptions,
CancellationToken cancellationToken)
{
TruncationObject? truncationStrategy = GetTruncationStrategy(invocationOptions);
Truncation? truncationStrategy = GetTruncationStrategy(invocationOptions);
BinaryData? responseFormat = GetResponseFormat(invocationOptions);
return
await client.CreateRunAsync(
await client.Runs.CreateRunAsync(
threadId,
agent.Definition.Id,
agent.Id,
overrideModelName: invocationOptions?.ModelName,
overrideInstructions: invocationOptions?.OverrideInstructions ?? instructions,
additionalInstructions: invocationOptions?.AdditionalInstructions,
additionalMessages: AgentMessageFactory.GetThreadMessages(invocationOptions?.AdditionalMessages).ToArray(),
additionalMessages: [.. AgentMessageFactory.GetThreadMessages(invocationOptions?.AdditionalMessages)],
overrideTools: tools,
stream: false,
temperature: invocationOptions?.Temperature,
@@ -72,11 +67,12 @@ internal static class AgentRunExtensions
private static BinaryData? GetResponseFormat(AzureAIInvocationOptions? invocationOptions)
{
return invocationOptions?.EnableJsonResponse == true ?
BinaryData.FromString(ResponseFormat.JsonObject.ToString()) :
//BinaryData.FromString(ResponseFormat.JsonObject.ToString()) :
BinaryData.FromString("// %%% TODO") :
null;
}
private static TruncationObject? GetTruncationStrategy(AzureAIInvocationOptions? invocationOptions)
private static Truncation? GetTruncationStrategy(AzureAIInvocationOptions? invocationOptions)
{
return invocationOptions?.TruncationMessageCount == null ?
null :
@@ -87,7 +83,7 @@ internal static class AgentRunExtensions
}
public static IAsyncEnumerable<StreamingUpdate> CreateStreamingAsync(
this AgentsClient client,
this PersistentAgentsClient client,
string threadId,
AzureAIAgent agent,
string? instructions,
@@ -95,16 +91,16 @@ internal static class AgentRunExtensions
AzureAIInvocationOptions? invocationOptions,
CancellationToken cancellationToken)
{
TruncationObject? truncationStrategy = GetTruncationStrategy(invocationOptions);
Truncation? truncationStrategy = GetTruncationStrategy(invocationOptions);
BinaryData? responseFormat = GetResponseFormat(invocationOptions);
return
client.CreateRunStreamingAsync(
client.Runs.CreateRunStreamingAsync(
threadId,
agent.Definition.Id,
agent.Id,
overrideModelName: invocationOptions?.ModelName,
overrideInstructions: invocationOptions?.OverrideInstructions ?? instructions,
additionalInstructions: invocationOptions?.AdditionalInstructions,
additionalMessages: AgentMessageFactory.GetThreadMessages(invocationOptions?.AdditionalMessages).ToArray(),
additionalMessages: [.. AgentMessageFactory.GetThreadMessages(invocationOptions?.AdditionalMessages)],
overrideTools: tools,
temperature: invocationOptions?.Temperature,
topP: invocationOptions?.TopP,
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
@@ -75,20 +75,6 @@ internal static class AgentToolDefinitionExtensions
return details;
}
internal static ToolConnectionList GetToolConnectionList(this AgentToolDefinition agentToolDefinition)
{
Verify.NotNull(agentToolDefinition.Options);
var toolConnections = agentToolDefinition.GetToolConnections();
var toolConnectionList = new ToolConnectionList();
if (toolConnections is not null)
{
toolConnectionList.ConnectionList.AddRange(toolConnections);
}
return toolConnectionList;
}
internal static BinaryData GetSpecification(this AgentToolDefinition agentToolDefinition)
{
Verify.NotNull(agentToolDefinition.Options);
@@ -121,7 +107,7 @@ internal static class AgentToolDefinitionExtensions
internal static List<string>? GetVectorStoreIds(this AgentToolDefinition agentToolDefinition)
{
return agentToolDefinition.GetOption<List<object>>("vector_store_ids")?.Select(id => id.ToString()!).ToList();
return agentToolDefinition.GetOption<List<object>>("vector_store_ids")?.Select(id => $"{id}").ToList();
}
internal static List<string>? GetFileIds(this AgentToolDefinition agentToolDefinition)
@@ -236,13 +222,13 @@ internal static class AgentToolDefinitionExtensions
return null;
}
private static List<ToolConnection> GetToolConnections(this AgentToolDefinition agentToolDefinition)
internal static List<string> GetToolConnections(this AgentToolDefinition agentToolDefinition)
{
Verify.NotNull(agentToolDefinition.Options);
var toolConnections = agentToolDefinition.GetRequiredOption<List<object>>("tool_connections");
List<object> toolConnections = agentToolDefinition.GetRequiredOption<List<object>>("tool_connections");
return [.. toolConnections.Select(connectionId => new ToolConnection(connectionId.ToString()))];
return [.. toolConnections.Select(connectionId => $"{connectionId}")];
}
private static T GetRequiredOption<T>(this AgentToolDefinition agentToolDefinition, string key)
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
namespace Microsoft.SemanticKernel.Agents.AzureAI;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel.ChatCompletion;
namespace Microsoft.SemanticKernel.Agents.AzureAI.Internal;
@@ -15,7 +15,7 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI.Internal;
internal static class AgentMessageFactory
{
/// <summary>
/// Translate metadata from a <see cref="ChatMessageContent"/> to be used for a <see cref="ThreadMessage"/> or
/// Translate metadata from a <see cref="ChatMessageContent"/> to be used for a <see cref="PersistentThreadMessage"/> or
/// <see cref="ThreadMessageOptions"/>.
/// </summary>
/// <param name="message">The message content.</param>
@@ -25,7 +25,7 @@ internal static class AgentMessageFactory
}
/// <summary>
/// Translate attachments from a <see cref="ChatMessageContent"/> to be used for a <see cref="ThreadMessage"/> or
/// Translate attachments from a <see cref="ChatMessageContent"/> to be used for a <see cref="PersistentThreadMessage"/> or
/// </summary>
/// <param name="message">The message content.</param>
public static IEnumerable<MessageAttachment> GetAttachments(ChatMessageContent message)
@@ -35,7 +35,7 @@ internal static class AgentMessageFactory
.OfType<FileReferenceContent>()
.Select(
fileContent =>
new MessageAttachment(fileContent.FileId, GetToolDefinition(fileContent.Tools).ToList()));
new MessageAttachment(fileContent.FileId, [.. GetToolDefinition(fileContent.Tools)]));
}
/// <summary>
@@ -58,7 +58,7 @@ internal static class AgentMessageFactory
role: message.Role == AuthorRole.User ? MessageRole.User : MessageRole.Agent,
content: message.Content)
{
Attachments = GetAttachments(message).ToArray(),
Attachments = [.. GetAttachments(message)],
};
if (message.Metadata != null)
@@ -9,13 +9,12 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Agents.AzureAI.Extensions;
using Microsoft.SemanticKernel.Agents.Extensions;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.FunctionCalling;
using AAIP = Azure.AI.Projects;
namespace Microsoft.SemanticKernel.Agents.AzureAI.Internal;
@@ -44,9 +43,9 @@ internal static class AgentThreadActions
/// <param name="client">The assistant client</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The thread identifier</returns>
public static async Task<string> CreateThreadAsync(AgentsClient client, CancellationToken cancellationToken = default)
public static async Task<string> CreateThreadAsync(PersistentAgentsClient client, CancellationToken cancellationToken = default)
{
AAIP.AgentThread thread = await client.CreateThreadAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
PersistentAgentThread thread = await client.Threads.CreateThreadAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
return thread.Id;
}
@@ -59,7 +58,7 @@ internal static class AgentThreadActions
/// <param name="message">The message to add</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <throws><see cref="KernelException"/> if a system message is present, without taking any other action</throws>
public static async Task CreateMessageAsync(AgentsClient client, string threadId, ChatMessageContent message, CancellationToken cancellationToken)
public static async Task CreateMessageAsync(PersistentAgentsClient client, string threadId, ChatMessageContent message, CancellationToken cancellationToken)
{
if (message.Items.Any(i => i is FunctionCallContent))
{
@@ -72,7 +71,7 @@ internal static class AgentThreadActions
return;
}
await client.CreateMessageAsync(
await client.Messages.CreateMessageAsync(
threadId,
role: message.Role == AuthorRole.User ? MessageRole.User : MessageRole.Agent,
content,
@@ -89,39 +88,35 @@ internal static class AgentThreadActions
/// <param name="messageOrder">The order to return messages in.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>Asynchronous enumeration of messages.</returns>
public static async IAsyncEnumerable<ChatMessageContent> GetMessagesAsync(AgentsClient client, string threadId, ListSortOrder? messageOrder, [EnumeratorCancellation] CancellationToken cancellationToken)
public static async IAsyncEnumerable<ChatMessageContent> GetMessagesAsync(PersistentAgentsClient client, string threadId, ListSortOrder? messageOrder, [EnumeratorCancellation] CancellationToken cancellationToken)
{
Dictionary<string, string?> agentNames = []; // Cache agent names by their identifier
string? lastId = null;
PageableList<ThreadMessage>? messages = null;
do
AsyncPageable<PersistentThreadMessage>? messages = client.Messages.GetMessagesAsync(threadId, runId: null, limit: null, messageOrder ?? ListSortOrder.Descending, after: lastId, before: null, cancellationToken);
await foreach (PersistentThreadMessage message in messages.ConfigureAwait(false))
{
messages = await client.GetMessagesAsync(threadId, runId: null, limit: null, messageOrder ?? ListSortOrder.Descending, after: lastId, before: null, cancellationToken).ConfigureAwait(false);
foreach (ThreadMessage message in messages)
lastId = message.Id;
string? assistantName = null;
if (!string.IsNullOrWhiteSpace(message.AssistantId) &&
!agentNames.TryGetValue(message.AssistantId, out assistantName))
{
lastId = message.Id;
string? assistantName = null;
if (!string.IsNullOrWhiteSpace(message.AssistantId) &&
!agentNames.TryGetValue(message.AssistantId, out assistantName))
PersistentAgent assistant = await client.Administration.GetAgentAsync(message.AssistantId, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(assistant.Name))
{
Azure.AI.Projects.Agent assistant = await client.GetAgentAsync(message.AssistantId, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(assistant.Name))
{
agentNames.Add(assistant.Id, assistant.Name);
}
}
assistantName ??= message.AssistantId;
ChatMessageContent content = GenerateMessageContent(assistantName, message);
if (content.Items.Count > 0)
{
yield return content;
agentNames.Add(assistant.Id, assistant.Name);
}
}
} while (messages?.HasMore ?? false);
assistantName ??= message.AssistantId;
ChatMessageContent content = GenerateMessageContent(assistantName, message);
if (content.Items.Count > 0)
{
yield return content;
}
}
}
/// <summary>
@@ -140,7 +135,7 @@ internal static class AgentThreadActions
/// <returns>Asynchronous enumeration of messages.</returns>
public static async IAsyncEnumerable<(bool IsVisible, ChatMessageContent Message)> InvokeAsync(
AzureAIAgent agent,
AgentsClient client,
PersistentAgentsClient client,
string threadId,
AzureAIInvocationOptions? invocationOptions,
ILogger logger,
@@ -186,7 +181,7 @@ internal static class AgentThreadActions
throw new KernelException($"Agent Failure - Run terminated: {run.Status} [{run.Id}]: {run.LastError?.Message ?? "Unknown"}");
}
RunStep[] steps = await client.GetStepsAsync(run, cancellationToken).ToArrayAsync(cancellationToken).ConfigureAwait(false);
RunStep[] steps = await client.GetStepsAsync(run, cancellationToken: cancellationToken).ToArrayAsync(cancellationToken).ConfigureAwait(false);
// Is tool action required?
if (run.Status == RunStatus.RequiresAction)
@@ -220,7 +215,7 @@ internal static class AgentThreadActions
// Process tool output
ToolOutput[] toolOutputs = GenerateToolOutputs(functionResults);
await client.SubmitToolOutputsToRunAsync(run, toolOutputs, cancellationToken).ConfigureAwait(false);
await client.Runs.SubmitToolOutputsToRunAsync(run, toolOutputs, cancellationToken).ConfigureAwait(false);
}
logger.LogAzureAIAgentProcessedRunSteps(nameof(InvokeAsync), functionCalls.Length, run.Id, threadId);
@@ -270,7 +265,7 @@ internal static class AgentThreadActions
{
// Retrieve the message
RunStepMessageCreationDetails messageDetails = (RunStepMessageCreationDetails)completedStep.StepDetails;
ThreadMessage? message = await RetrieveMessageAsync(client, threadId, messageDetails.MessageCreation.MessageId, agent.PollingOptions.MessageSynchronizationDelay, cancellationToken).ConfigureAwait(false);
PersistentThreadMessage? message = await RetrieveMessageAsync(client, threadId, messageDetails.MessageCreation.MessageId, agent.PollingOptions.MessageSynchronizationDelay, cancellationToken).ConfigureAwait(false);
if (message is not null)
{
@@ -315,7 +310,7 @@ internal static class AgentThreadActions
try
{
run = await client.GetRunAsync(threadId, run.Id, cancellationToken).ConfigureAwait(false);
run = await client.Runs.GetRunAsync(threadId, run.Id, cancellationToken).ConfigureAwait(false);
}
// The presence of a `Status` code means the server responded with error...always fail in that case
catch (ClientResultException clientException) when (clientException.Status <= 0)
@@ -371,7 +366,7 @@ internal static class AgentThreadActions
/// </remarks>
public static async IAsyncEnumerable<StreamingChatMessageContent> InvokeStreamingAsync(
AzureAIAgent agent,
AgentsClient client,
PersistentAgentsClient client,
string threadId,
IList<ChatMessageContent>? messages,
AzureAIInvocationOptions? invocationOptions,
@@ -497,7 +492,7 @@ internal static class AgentThreadActions
// Process tool output
ToolOutput[] toolOutputs = GenerateToolOutputs(functionResults);
asyncUpdates = client.SubmitToolOutputsToStreamAsync(run, toolOutputs, cancellationToken);
asyncUpdates = client.Runs.SubmitToolOutputsToStreamAsync(run, toolOutputs, cancellationToken);
foreach (RunStep step in activeSteps)
{
@@ -514,7 +509,7 @@ internal static class AgentThreadActions
{
if (step.StepDetails is RunStepMessageCreationDetails messageDetails)
{
ThreadMessage? message =
PersistentThreadMessage? message =
await RetrieveMessageAsync(
client,
threadId,
@@ -555,17 +550,17 @@ internal static class AgentThreadActions
logger.LogAzureAIAgentCompletedRun(nameof(InvokeAsync), run?.Id ?? "Failed", threadId);
}
private static ChatMessageContent GenerateMessageContent(string? assistantName, ThreadMessage message, RunStep? completedStep = null, ILogger? logger = null)
private static ChatMessageContent GenerateMessageContent(string? assistantName, PersistentThreadMessage message, RunStep? completedStep = null, ILogger? logger = null)
{
AuthorRole role = new(message.Role.ToString());
Dictionary<string, object?>? metadata =
new()
{
{ nameof(ThreadMessage.CreatedAt), message.CreatedAt },
{ nameof(ThreadMessage.AssistantId), message.AssistantId },
{ nameof(ThreadMessage.ThreadId), message.ThreadId },
{ nameof(ThreadMessage.RunId), message.RunId },
{ nameof(PersistentThreadMessage.CreatedAt), message.CreatedAt },
{ nameof(PersistentThreadMessage.AssistantId), message.AssistantId },
{ nameof(PersistentThreadMessage.ThreadId), message.ThreadId },
{ nameof(PersistentThreadMessage.RunId), message.RunId },
{ nameof(MessageContentUpdate.MessageId), message.Id },
};
@@ -696,16 +691,16 @@ internal static class AgentThreadActions
EndIndex = fileCitationAnnotation.EndIndex,
};
}
if (annotation is MessageTextUrlCitationAnnotation urlCitationAnnotation)
if (annotation is MessageTextUriCitationAnnotation urlCitationAnnotation)
{
return
new AnnotationContent(
kind: AnnotationKind.UrlCitation,
label: annotation.Text,
referenceId: urlCitationAnnotation.UrlCitation.Url)
referenceId: urlCitationAnnotation.UriCitation.Uri)
{
InnerContent = annotation,
Title = urlCitationAnnotation.UrlCitation.Title,
Title = urlCitationAnnotation.UriCitation.Title,
StartIndex = urlCitationAnnotation.StartIndex,
EndIndex = urlCitationAnnotation.EndIndex,
};
@@ -883,9 +878,9 @@ internal static class AgentThreadActions
return toolOutputs;
}
private static async Task<ThreadMessage?> RetrieveMessageAsync(AgentsClient client, string threadId, string messageId, TimeSpan syncDelay, CancellationToken cancellationToken)
private static async Task<PersistentThreadMessage?> RetrieveMessageAsync(PersistentAgentsClient client, string threadId, string messageId, TimeSpan syncDelay, CancellationToken cancellationToken)
{
ThreadMessage? message = null;
PersistentThreadMessage? message = null;
bool retry = false;
int count = 0;
@@ -893,7 +888,7 @@ internal static class AgentThreadActions
{
try
{
message = await client.GetMessageAsync(threadId, messageId, cancellationToken).ConfigureAwait(false);
message = await client.Messages.GetMessageAsync(threadId, messageId, cancellationToken).ConfigureAwait(false);
}
catch (RequestFailedException exception)
{
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Diagnostics.CodeAnalysis;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.Agents.AzureAI.Internal;
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Azure.AI.OpenAI;
using Azure.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel.Http;
@@ -140,22 +139,20 @@ internal static class AgentDefinitionExtensions
if (connection.Type.Equals(OpenAI, StringComparison.OrdinalIgnoreCase))
{
OpenAIClientOptions clientOptions = OpenAIClientProvider.CreateOpenAIClientOptions(connection.TryGetEndpoint(), httpClient);
return new OpenAIClient(connection.GetApiKeyCredential(), clientOptions);
return OpenAIAssistantAgent.CreateOpenAIClient(connection.GetApiKeyCredential(), connection.TryGetEndpoint(), httpClient);
}
else if (connection.Type.Equals(AzureOpenAI, StringComparison.OrdinalIgnoreCase))
{
var endpoint = connection.TryGetEndpoint();
Verify.NotNull(endpoint, "Endpoint must be specified when using Azure OpenAI.");
AzureOpenAIClientOptions clientOptions = OpenAIClientProvider.CreateAzureClientOptions(httpClient);
if (connection.ExtensionData.TryGetValue(ApiKey, out var apiKey) && apiKey is not null)
{
return new AzureOpenAIClient(endpoint, connection.GetApiKeyCredential(), clientOptions);
return OpenAIAssistantAgent.CreateAzureOpenAIClient(connection.GetApiKeyCredential(), endpoint, httpClient);
}
var tokenCredential = kernel.Services.GetRequiredService<TokenCredential>();
return new AzureOpenAIClient(endpoint, tokenCredential, clientOptions);
return OpenAIAssistantAgent.CreateAzureOpenAIClient(tokenCredential, endpoint, httpClient);
}
throw new InvalidOperationException($"Invalid OpenAI client type '{connection.Type}' was specified.");
@@ -19,6 +19,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI;
/// Provides an <see cref="OpenAIClient"/> for use by <see cref="OpenAIAssistantAgent"/>.
/// </summary>
[Experimental("SKEXP0110")]
[Obsolete("Use OpenAIAssistantAgent.CreateAzureOpenAIClient(...) or OpenAIAssistantAgent.CreateOpenAIClient(...)")]
public sealed class OpenAIClientProvider
{
/// <summary>
@@ -1,72 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Net.Http;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Moq;
using Xunit;
namespace SemanticKernel.Agents.UnitTests.AzureAI;
/// <summary>
/// Unit testing of <see cref="AzureAIClientProvider"/>.
/// </summary>
public class AzureAIClientProviderTests
{
/// <summary>
/// Verify that provisioning of client for Azure OpenAI.
/// </summary>
[Fact]
public void VerifyAzureAIClientProviderDefault()
{
// Act
AzureAIClientProvider provider = AzureAIClientProvider.FromConnectionString("test;test;test;test", new AzureCliCredential());
// Assert
Assert.NotNull(provider.Client);
}
/// <summary>
/// Verify that the factory can create a client with http proxy.
/// </summary>
[Fact]
public void VerifyAzureAIClientProviderWithHttpClient()
{
// Arrange
using HttpClient httpClient = new() { BaseAddress = new Uri("http://myproxy:9819") };
// Act
AzureAIClientProvider provider = AzureAIClientProvider.FromConnectionString("test;test;test;test", new AzureCliCredential(), httpClient);
// Assert
Assert.NotNull(provider.Client);
// Arrange
using HttpClient httpClientWithHeaders = new() { BaseAddress = new Uri("http://myproxy:9819") };
httpClientWithHeaders.DefaultRequestHeaders.Add("X-Test", "Test");
// Act
AzureAIClientProvider providerWithHeaders = AzureAIClientProvider.FromConnectionString("test;test;test;test", new AzureCliCredential(), httpClientWithHeaders);
// Assert
Assert.NotNull(providerWithHeaders.Client);
Assert.NotEqual(provider.ConfigurationKeys.Count, providerWithHeaders.ConfigurationKeys.Count);
}
/// <summary>
/// Verify that the factory can accept an client that already exists.
/// </summary>
[Fact]
public void VerifyAzureAIClientProviderFromClient()
{
// Arrange
Mock<AIProjectClient> mockClient = new();
AzureAIClientProvider provider = AzureAIClientProvider.FromClient(mockClient.Object);
// Assert
Assert.NotNull(provider.Client);
Assert.Equal(mockClient.Object, provider.Client);
}
}
@@ -4,7 +4,7 @@ using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Core.Pipeline;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
@@ -32,12 +32,14 @@ public class AzureAIAgentFactoryTests : IDisposable
this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false);
var builder = Kernel.CreateBuilder();
var client = new AIProjectClient(
"endpoint;subscription_id;resource_group_name;project_name",
var client = new PersistentAgentsClient(
"https://test",
new FakeTokenCredential(),
new AIProjectClientOptions()
{ Transport = new HttpClientTransport(this._httpClient) });
builder.Services.AddSingleton<AIProjectClient>(client);
new PersistentAgentsAdministrationClientOptions
{
Transport = new HttpClientTransport(this._httpClient)
});
builder.Services.AddSingleton(client);
this._kernel = builder.Build();
}
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ComponentModel;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Xunit;
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.AzureAI.Internal;
using Microsoft.SemanticKernel.ChatCompletion;
@@ -31,8 +31,7 @@ public class OpenAIAssistantAgentFactoryTests : IDisposable
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);
OpenAIClient openAIClient = OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential("fakekey"), httpClient: this._httpClient);
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<OpenAIClient>(openAIClient);
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI;
using OpenAI.Assistants;
using Xunit;
@@ -22,7 +23,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
private readonly HttpMessageHandlerStub _messageHandlerStub;
private readonly HttpClient _httpClient;
private readonly OpenAIClientProvider _clientProvider;
private readonly OpenAIClient _client;
/// <summary>
/// Verify the assistant creation with default values.
@@ -34,7 +35,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.AssistantDefinition(ModelValue));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(modelId: ModelValue);
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(modelId: ModelValue);
// Assert
Assert.NotNull(definition);
@@ -61,7 +62,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
description: DescriptionValue));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: ModelValue,
name: NameValue,
instructions: InstructionsValue,
@@ -99,7 +100,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
description: DescriptionValue));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantFromTemplateAsync(modelId: ModelValue, templateConfig);
Assistant definition = await this._client.GetAssistantClient().CreateAssistantFromTemplateAsync(modelId: ModelValue, templateConfig);
// Assert
Assert.NotNull(definition);
@@ -120,7 +121,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
OpenAIAssistantResponseContent.AssistantDefinition(ModelValue, enableCodeInterpreter: true));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: ModelValue,
enableCodeInterpreter: true);
@@ -143,7 +144,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
OpenAIAssistantResponseContent.AssistantDefinition(ModelValue, codeInterpreterFileIds: fileIds));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: ModelValue,
codeInterpreterFileIds: fileIds);
@@ -167,7 +168,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
OpenAIAssistantResponseContent.AssistantDefinition(ModelValue, enableFileSearch: true));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: ModelValue,
enableFileSearch: true);
@@ -190,7 +191,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
OpenAIAssistantResponseContent.AssistantDefinition(ModelValue, vectorStoreId: VectorStoreValue));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: ModelValue,
vectorStoreId: VectorStoreValue);
@@ -214,7 +215,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.AssistantDefinition("testmodel", temperature: TemperatureValue));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: "testmodel",
temperature: TemperatureValue);
@@ -235,7 +236,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.AssistantDefinition("testmodel", topP: TopPValue));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: "testmodel",
topP: TopPValue);
@@ -261,7 +262,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.AssistantDefinition("testmodel", metadata: metadata));
// Act
Assistant definition = await this._clientProvider.AssistantClient.CreateAssistantAsync(
Assistant definition = await this._client.GetAssistantClient().CreateAssistantAsync(
modelId: "testmodel",
metadata: metadata);
@@ -280,7 +281,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.DeleteAgent);
// Act
AssistantDeletionResult result = await this._clientProvider.AssistantClient.DeleteAssistantAsync("testid");
AssistantDeletionResult result = await this._client.GetAssistantClient().DeleteAssistantAsync("testid");
// Assert
Assert.True(result.Deleted);
@@ -296,7 +297,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateThread);
// Act
string threadId = await this._clientProvider.AssistantClient.CreateThreadAsync(messages: null);
string threadId = await this._client.GetAssistantClient().CreateThreadAsync(messages: null);
// Assert
Assert.NotNull(threadId);
@@ -312,7 +313,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateThread);
// Act
string threadId = await this._clientProvider.AssistantClient.CreateThreadAsync(messages: [new ChatMessageContent(AuthorRole.User, "test")]);
string threadId = await this._client.GetAssistantClient().CreateThreadAsync(messages: [new ChatMessageContent(AuthorRole.User, "test")]);
// Assert
Assert.NotNull(threadId);
@@ -329,7 +330,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
Dictionary<string, string> metadata = new() { { "a", "1" }, { "b", "2" } };
// Act
string threadId = await this._clientProvider.AssistantClient.CreateThreadAsync(metadata: metadata);
string threadId = await this._client.GetAssistantClient().CreateThreadAsync(metadata: metadata);
// Assert
Assert.NotNull(threadId);
@@ -349,7 +350,7 @@ public sealed class AssistantClientExtensionsTests : IDisposable
{
this._messageHandlerStub = new HttpMessageHandlerStub();
this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false);
this._clientProvider = OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient);
this._client = OpenAIAssistantAgent.CreateOpenAIClient(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient);
}
private void SetupResponse(HttpStatusCode statusCode, string content) =>
@@ -8,6 +8,7 @@ using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI;
using OpenAI.VectorStores;
using Xunit;
@@ -20,7 +21,7 @@ public sealed class OpenAIClientExtensionsTests : IDisposable
{
private readonly HttpMessageHandlerStub _messageHandlerStub;
private readonly HttpClient _httpClient;
private readonly OpenAIClientProvider _clientProvider;
private readonly OpenAIClient _client;
/// <summary>
/// Verify the default creation of vector-store.
@@ -33,7 +34,7 @@ public sealed class OpenAIClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateVectorStore);
// Act
string storeId = await this._clientProvider.Client.CreateVectorStoreAsync(fileIds, waitUntilCompleted: false);
string storeId = await this._client.CreateVectorStoreAsync(fileIds, waitUntilCompleted: false);
// Assert
Assert.NotNull(storeId);
@@ -56,7 +57,7 @@ public sealed class OpenAIClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateVectorStore);
// Act
string storeId = await this._clientProvider.Client.CreateVectorStoreAsync(
string storeId = await this._client.CreateVectorStoreAsync(
fileIds,
waitUntilCompleted: false,
storeName: "test-store",
@@ -79,7 +80,7 @@ public sealed class OpenAIClientExtensionsTests : IDisposable
// Act
await using MemoryStream stream = new(Encoding.UTF8.GetBytes("test"));
string fileId = await this._clientProvider.Client.UploadAssistantFileAsync(stream, "text.txt");
string fileId = await this._client.UploadAssistantFileAsync(stream, "text.txt");
// Assert
Assert.NotNull(fileId);
@@ -95,7 +96,7 @@ public sealed class OpenAIClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.DeleteFile);
// Act
bool isDeleted = await this._clientProvider.Client.DeleteFileAsync("file-id");
bool isDeleted = await this._client.DeleteFileAsync("file-id");
// Assert
Assert.True(isDeleted);
@@ -111,7 +112,7 @@ public sealed class OpenAIClientExtensionsTests : IDisposable
this.SetupResponse(HttpStatusCode.OK, OpenAIAssistantResponseContent.DeleteVectorStore);
// Act
bool isDeleted = await this._clientProvider.Client.DeleteVectorStoreAsync("store-id");
bool isDeleted = await this._client.DeleteVectorStoreAsync("store-id");
// Assert
Assert.True(isDeleted);
@@ -131,7 +132,7 @@ public sealed class OpenAIClientExtensionsTests : IDisposable
{
this._messageHandlerStub = new HttpMessageHandlerStub();
this._httpClient = new HttpClient(this._messageHandlerStub, disposeHandler: false);
this._clientProvider = OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient);
this._client = OpenAIAssistantAgent.CreateOpenAIClient(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient);
}
private void SetupResponse(HttpStatusCode statusCode, string content) =>
@@ -309,7 +309,7 @@ public sealed class OpenAIAssistantAgentTests : IDisposable
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.RetrieveAsync(
this.CreateTestProvider(),
this.CreateTestClient(),
"#id",
this._emptyKernel);
@@ -330,7 +330,7 @@ public sealed class OpenAIAssistantAgentTests : IDisposable
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.RetrieveAsync(
this.CreateTestProvider(),
this.CreateTestClient(),
"#id",
this._emptyKernel,
[],
@@ -748,7 +748,7 @@ public sealed class OpenAIAssistantAgentTests : IDisposable
// Act
var messages =
await OpenAIAssistantAgent.ListDefinitionsAsync(
this.CreateTestProvider()).ToArrayAsync();
this.CreateTestClient()).ToArrayAsync();
// Assert
Assert.Equal(7, messages.Length);
@@ -761,7 +761,7 @@ public sealed class OpenAIAssistantAgentTests : IDisposable
// Act
messages =
await OpenAIAssistantAgent.ListDefinitionsAsync(
this.CreateTestProvider()).ToArrayAsync();
this.CreateTestClient()).ToArrayAsync();
// Assert
Assert.Equal(4, messages.Length);
}
@@ -823,7 +823,7 @@ public sealed class OpenAIAssistantAgentTests : IDisposable
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateAsync(
this.CreateTestProvider(),
this.CreateTestClient(),
definition,
this._emptyKernel);
@@ -839,7 +839,7 @@ public sealed class OpenAIAssistantAgentTests : IDisposable
OpenAIAssistantAgent agent =
await OpenAIAssistantAgent.CreateFromTemplateAsync(
this.CreateTestProvider(),
this.CreateTestClient(),
capabilities,
this._emptyKernel,
[],
@@ -938,12 +938,12 @@ public sealed class OpenAIAssistantAgentTests : IDisposable
return
OpenAIAssistantAgent.CreateAsync(
this.CreateTestProvider(),
this.CreateTestClient(),
definition,
this._emptyKernel);
}
private OpenAIClientProvider CreateTestProvider(bool targetAzure = false)
private OpenAIClientProvider CreateTestClient(bool targetAzure = false)
=> targetAzure ?
OpenAIClientProvider.ForAzureOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: new Uri("https://localhost"), this._httpClient) :
OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient);
@@ -9,6 +9,7 @@ using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Moq;
using OpenAI;
using OpenAI.Assistants;
using Xunit;
@@ -75,10 +76,9 @@ public class OpenAIAssistantAgentThreadTests : IDisposable
// Arrange
this._messageHandlerStub.SetupResponses(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateThread);
var provider = this.CreateTestProvider();
var assistantClient = provider.AssistantClient;
var client = this.CreateTestClient();
var thread = new OpenAIAssistantAgentThread(assistantClient);
var thread = new OpenAIAssistantAgentThread(client.GetAssistantClient());
// Act
await thread.CreateAsync();
@@ -97,10 +97,9 @@ public class OpenAIAssistantAgentThreadTests : IDisposable
// Arrange
this._messageHandlerStub.SetupResponses(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateThread);
var provider = this.CreateTestProvider();
var assistantClient = provider.AssistantClient;
var client = this.CreateTestClient();
var thread = new OpenAIAssistantAgentThread(assistantClient, new ThreadCreationOptions());
var thread = new OpenAIAssistantAgentThread(client.GetAssistantClient(), new ThreadCreationOptions());
// Act
await thread.CreateAsync();
@@ -119,10 +118,9 @@ public class OpenAIAssistantAgentThreadTests : IDisposable
// Arrange
this._messageHandlerStub.SetupResponses(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateThread);
var provider = this.CreateTestProvider();
var assistantClient = provider.AssistantClient;
var client = this.CreateTestClient();
var thread = new OpenAIAssistantAgentThread(assistantClient, [new ChatMessageContent(AuthorRole.User, "Hello")]);
var thread = new OpenAIAssistantAgentThread(client.GetAssistantClient(), [new ChatMessageContent(AuthorRole.User, "Hello")]);
// Act
await thread.CreateAsync();
@@ -142,10 +140,9 @@ public class OpenAIAssistantAgentThreadTests : IDisposable
this._messageHandlerStub.SetupResponses(HttpStatusCode.OK, OpenAIAssistantResponseContent.CreateThread);
this._messageHandlerStub.SetupResponses(HttpStatusCode.OK, OpenAIAssistantResponseContent.DeleteThread);
var provider = this.CreateTestProvider();
var assistantClient = provider.AssistantClient;
var client = this.CreateTestClient();
var thread = new OpenAIAssistantAgentThread(assistantClient);
var thread = new OpenAIAssistantAgentThread(client.GetAssistantClient());
await thread.CreateAsync();
// Act
@@ -164,6 +161,6 @@ public class OpenAIAssistantAgentThreadTests : IDisposable
GC.SuppressFinalize(this);
}
private OpenAIClientProvider CreateTestProvider()
=> OpenAIClientProvider.ForOpenAI(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient);
private OpenAIClient CreateTestClient()
=> OpenAIAssistantAgent.CreateOpenAIClient(apiKey: new ApiKeyCredential("fakekey"), endpoint: null, this._httpClient);
}
@@ -1,11 +1,10 @@
// 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.AI.Agents.Persistent;
using Azure.Core.Pipeline;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
@@ -40,17 +39,18 @@ public class AgentYamlTests : IDisposable
var builder = Kernel.CreateBuilder();
// Add OpenAI client
OpenAIClientOptions clientOptions = OpenAIClientProvider.CreateOpenAIClientOptions(endpoint: null, httpClient: this._httpClient);
OpenAIClient openAIClient = new(new ApiKeyCredential("fakekey"), clientOptions);
builder.Services.AddSingleton<OpenAIClient>(openAIClient);
OpenAIClient openAIClient = OpenAIAssistantAgent.CreateOpenAIClient(new System.ClientModel.ApiKeyCredential("fakekey"), httpClient: this._httpClient);
builder.Services.AddSingleton(openAIClient);
// Add Azure AI agents client
var client = new AIProjectClient(
"endpoint;subscription_id;resource_group_name;project_name",
var client = new PersistentAgentsClient(
"https://endpoint",
new FakeTokenCredential(),
new AIProjectClientOptions()
{ Transport = new HttpClientTransport(this._httpClient) });
builder.Services.AddSingleton<AIProjectClient>(client);
new PersistentAgentsAdministrationClientOptions
{
Transport = new HttpClientTransport(this._httpClient)
});
builder.Services.AddSingleton(client);
this._kernel = builder.Build();
}
@@ -7,7 +7,7 @@ using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Core.Pipeline;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
@@ -38,12 +38,14 @@ public class AzureAIKernelAgentYamlTests : IDisposable
var builder = Kernel.CreateBuilder();
// Add Azure AI agents client
var client = new AIProjectClient(
"endpoint;subscription_id;resource_group_name;project_name",
var client = new PersistentAgentsClient(
"https://test",
new FakeTokenCredential(),
new AIProjectClientOptions()
{ Transport = new HttpClientTransport(this._httpClient) });
builder.Services.AddSingleton<AIProjectClient>(client);
new PersistentAgentsAdministrationClientOptions
{
Transport = new HttpClientTransport(this._httpClient)
});
builder.Services.AddSingleton(client);
this._kernel = builder.Build();
this._kernel.Plugins.Add(KernelPluginFactory.CreateFromType<WeatherPlugin>());
@@ -217,42 +219,6 @@ public class AzureAIKernelAgentYamlTests : IDisposable
Assert.Equal("bing_grounding", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
}
/// <summary>
/// Verify the request includes a Microsoft Fabric tool when creating an Azure AI agent.
/// </summary>
[Fact]
public async Task VerifyRequestIncludesMicrosoftFabricAsync()
{
// Arrange
const string Text =
"""
type: foundry_agent
name: FoundryAgent
description: AzureAIAgent Description
instructions: AzureAIAgent Instructions
model:
id: gpt-4o-mini
tools:
- type: fabric_aiskill
options:
tool_connections:
- connection_string
""";
AzureAIAgentFactory factory = new();
this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentCreateResponse);
// Act
var agent = await factory.CreateAgentFromYamlAsync(Text, new() { Kernel = this._kernel });
// Assert
Assert.NotNull(agent);
var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
Assert.NotNull(requestContent);
var requestJson = JsonSerializer.Deserialize<JsonElement>(requestContent);
Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
Assert.Equal("fabric_dataagent", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
}
/// <summary>
/// Verify the request includes a Open API tool when creating an Azure AI agent.
/// </summary>
@@ -306,42 +272,6 @@ public class AzureAIKernelAgentYamlTests : IDisposable
Assert.Equal("openapi", requestJson.GetProperty("tools")[2].GetProperty("type").GetString());
}
/// <summary>
/// Verify the request includes a Sharepoint tool when creating an Azure AI agent.
/// </summary>
[Fact]
public async Task VerifyRequestIncludesSharepointGroundingAsync()
{
// Arrange
const string Text =
"""
type: foundry_agent
name: FoundryAgent
description: AzureAIAgent Description
instructions: AzureAIAgent Instructions
model:
id: gpt-4o-mini
tools:
- type: sharepoint_grounding
options:
tool_connections:
- connection_string
""";
AzureAIAgentFactory factory = new();
this.SetupResponse(HttpStatusCode.OK, AzureAIAgentFactoryTests.AzureAIAgentCreateResponse);
// Act
var agent = await factory.CreateAgentFromYamlAsync(Text, new() { Kernel = this._kernel });
// Assert
Assert.NotNull(agent);
var requestContent = Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent!);
Assert.NotNull(requestContent);
var requestJson = JsonSerializer.Deserialize<JsonElement>(requestContent);
Assert.Equal(1, requestJson.GetProperty("tools").GetArrayLength());
Assert.Equal("sharepoint_grounding", requestJson.GetProperty("tools")[0].GetProperty("type").GetString());
}
/// <summary>
/// Verify the request includes a code interpreter tool and associated resource when creating an Azure AI agent.
/// </summary>
@@ -40,7 +40,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
@@ -41,7 +41,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
@@ -1,8 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -21,16 +22,14 @@ public class AzureAIAgentTests
{
private readonly Kernel _kernel;
private readonly AzureAIConfiguration _configuration;
private readonly AIProjectClient _client;
private readonly AgentsClient _agentsClient;
private readonly PersistentAgentsClient _client;
public AzureAIAgentTests()
{
var kernelBuilder = Kernel.CreateBuilder();
this._kernel = kernelBuilder.Build();
this._configuration = this.ReadAzureConfiguration();
this._client = AzureAIAgent.CreateAzureAIClient(this._configuration.ConnectionString!, new AzureCliCredential());
this._agentsClient = this._client.GetAgentsClient();
this._client = AzureAIAgent.CreateAgentsClient(this._configuration.Endpoint, new AzureCliCredential());
}
/// <summary>
@@ -40,14 +39,14 @@ public class AzureAIAgentTests
public async Task AzureAIAgentWithThreadCustomOptionsAsync()
{
var aiAgent =
await this._agentsClient.CreateAgentAsync(
await this._client.Administration.CreateAgentAsync(
this._configuration.ChatModelId,
name: "HelpfulAssistant",
description: "Helpful Assistant",
instructions: "You are a helpful assistant.");
var agent = new AzureAIAgent(aiAgent, this._agentsClient) { Kernel = this._kernel };
var agent = new AzureAIAgent(aiAgent, this._client) { Kernel = this._kernel };
AzureAIAgentThread agentThread = new(this._agentsClient);
AzureAIAgentThread agentThread = new(this._client);
try
{
@@ -63,7 +62,7 @@ public class AzureAIAgentTests
finally
{
await agentThread.DeleteAsync();
await this._agentsClient.DeleteAgentAsync(agent.Id);
await this._client.Administration.DeleteAgentAsync(agent.Id);
}
}
@@ -74,14 +73,14 @@ public class AzureAIAgentTests
public async Task AzureAIAgentWithThreadCustomOptionsStreamingAsync()
{
var aiAgent =
await this._agentsClient.CreateAgentAsync(
await this._client.Administration.CreateAgentAsync(
this._configuration.ChatModelId,
name: "HelpfulAssistant",
description: "Helpful Assistant",
instructions: "You are a helpful assistant.");
var agent = new AzureAIAgent(aiAgent, this._agentsClient) { Kernel = this._kernel };
var agent = new AzureAIAgent(aiAgent, this._client) { Kernel = this._kernel };
AzureAIAgentThread agentThread = new(this._agentsClient);
AzureAIAgentThread agentThread = new(this._client);
try
{
@@ -97,7 +96,7 @@ public class AzureAIAgentTests
finally
{
await agentThread.DeleteAsync();
await this._agentsClient.DeleteAgentAsync(agent.Id);
await this._client.Administration.DeleteAgentAsync(agent.Id);
}
}
@@ -108,7 +107,7 @@ public class AzureAIAgentTests
public async Task AzureAIAgentDeclarativeAsync()
{
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton<AIProjectClient>(this._client);
builder.Services.AddSingleton<PersistentAgentsClient>(this._client);
var kernel = builder.Build();
var text =
@@ -125,7 +124,7 @@ public class AzureAIAgentTests
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel });
Assert.NotNull(agent);
AzureAIAgentThread agentThread = new(this._agentsClient);
AzureAIAgentThread agentThread = new(this._client);
try
{
var response = await agent.InvokeAsync("What is the capital of France?", agentThread).FirstAsync();
@@ -135,7 +134,7 @@ public class AzureAIAgentTests
finally
{
await agentThread.DeleteAsync();
await this._agentsClient.DeleteAgentAsync(agent.Id);
await this._client.Administration.DeleteAgentAsync(agent.Id);
}
}
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Azure;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;
@@ -9,7 +11,6 @@ using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.ChatCompletion;
using SemanticKernel.IntegrationTests.TestSettings;
using AAIP = Azure.AI.Projects;
namespace SemanticKernel.IntegrationTests.Agents.CommonInterfaceConformance;
@@ -22,8 +23,8 @@ public class AzureAIAgentFixture : AgentFixture
.AddUserSecrets<AzureAIAgentFixture>()
.Build();
private AAIP.AgentsClient? _agentsClient;
private AAIP.Agent? _aiAgent;
private PersistentAgentsClient? _agentsClient;
private PersistentAgent? _aiAgent;
private AzureAIAgent? _agent;
private AzureAIAgentThread? _thread;
private AzureAIAgentThread? _createdThread;
@@ -43,7 +44,7 @@ public class AzureAIAgentFixture : AgentFixture
public override async Task<ChatHistory> GetChatHistory()
{
var chatHistory = new ChatHistory();
await foreach (var existingMessage in this._thread!.GetMessagesAsync(AAIP.ListSortOrder.Ascending).ConfigureAwait(false))
await foreach (var existingMessage in this._thread!.GetMessagesAsync(ListSortOrder.Ascending).ConfigureAwait(false))
{
chatHistory.Add(existingMessage);
}
@@ -52,7 +53,7 @@ public class AzureAIAgentFixture : AgentFixture
public override Task DeleteThread(AgentThread thread)
{
return this._agentsClient!.DeleteThreadAsync(thread.Id);
return this._agentsClient!.Threads.DeleteThreadAsync(thread.Id);
}
public override async Task DisposeAsync()
@@ -61,7 +62,7 @@ public class AzureAIAgentFixture : AgentFixture
{
try
{
await this._agentsClient!.DeleteThreadAsync(this._thread!.Id);
await this._agentsClient!.Threads.DeleteThreadAsync(this._thread!.Id);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
@@ -70,7 +71,7 @@ public class AzureAIAgentFixture : AgentFixture
try
{
await this._agentsClient!.DeleteThreadAsync(this._createdThread!.Id);
await this._agentsClient!.Threads.DeleteThreadAsync(this._createdThread!.Id);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
@@ -78,23 +79,22 @@ public class AzureAIAgentFixture : AgentFixture
try
{
await this._agentsClient!.DeleteThreadAsync(this._createdServiceFailingAgentThread!.Id);
await this._agentsClient!.Threads.DeleteThreadAsync(this._createdServiceFailingAgentThread!.Id);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
}
await this._agentsClient!.DeleteAgentAsync(this._aiAgent!.Id);
await this._agentsClient!.Administration.DeleteAgentAsync(this._aiAgent!.Id);
}
public override async Task InitializeAsync()
{
AzureAIConfiguration configuration = this._configuration.GetSection("AzureAI").Get<AzureAIConfiguration>()!;
var client = AzureAIAgent.CreateAzureAIClient(configuration.ConnectionString!, new AzureCliCredential());
this._agentsClient = client.GetAgentsClient();
this._agentsClient = AzureAIAgent.CreateAgentsClient(configuration.Endpoint, new AzureCliCredential());
this._aiAgent =
await this._agentsClient.CreateAgentAsync(
await this._agentsClient.Administration.CreateAgentAsync(
configuration.ChatModelId,
name: "HelpfulAssistant",
description: "Helpful Assistant",
@@ -109,10 +109,10 @@ public class AzureAIAgentFixture : AgentFixture
this._createdThread = new AzureAIAgentThread(this._agentsClient);
await this._createdThread.CreateAsync();
var serviceFailingClient = AzureAIAgent.CreateAzureAIClient("swedencentral.api.azureml.ms;<subscription_id>;<resource_group_name>;<project_name>", new AzureCliCredential());
this._serviceFailingAgentThread = new AzureAIAgentThread(serviceFailingClient.GetAgentsClient());
var serviceFailingClient = AzureAIAgent.CreateAgentsClient("https://invalid", new AzureCliCredential());
this._serviceFailingAgentThread = new AzureAIAgentThread(serviceFailingClient);
var createdFailingThreadResponse = await this._agentsClient.CreateThreadAsync();
this._createdServiceFailingAgentThread = new AzureAIAgentThread(serviceFailingClient.GetAgentsClient(), createdFailingThreadResponse.Value.Id);
var createdFailingThreadResponse = await this._agentsClient.Threads.CreateThreadAsync();
this._createdServiceFailingAgentThread = new AzureAIAgentThread(serviceFailingClient, createdFailingThreadResponse.Value.Id);
}
}
@@ -12,6 +12,7 @@ using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using OpenAI;
using OpenAI.Assistants;
using SemanticKernel.IntegrationTests.TestSettings;
using xRetry;
@@ -45,7 +46,7 @@ public sealed class MixedAgentTests
// Arrange, Act & Assert
await this.VerifyAgentExecutionAsync(
this.CreateChatCompletionKernel(openAISettings),
OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(openAISettings.ApiKey)),
OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(openAISettings.ApiKey)),
openAISettings.ChatModelId!,
useNewFunctionCallingModel);
}
@@ -65,14 +66,14 @@ public sealed class MixedAgentTests
// Arrange, Act & Assert
await this.VerifyAgentExecutionAsync(
this.CreateChatCompletionKernel(azureOpenAISettings),
OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(azureOpenAISettings.Endpoint)),
OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(azureOpenAISettings.Endpoint)),
azureOpenAISettings.ChatDeploymentName!,
useNewFunctionCallingModel);
}
private async Task VerifyAgentExecutionAsync(
Kernel chatCompletionKernel,
OpenAIClientProvider clientProvider,
OpenAIClient client,
string modelName,
bool useNewFunctionCallingModel)
{
@@ -95,8 +96,9 @@ public sealed class MixedAgentTests
chatAgent.Kernel.Plugins.Add(plugin);
// Configure assistant agent with the plugin.
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(modelName, instructions: "Answer questions about the menu.");
OpenAIAssistantAgent assistantAgent = new(definition, clientProvider.AssistantClient, [plugin]);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(modelName, instructions: "Answer questions about the menu.");
OpenAIAssistantAgent assistantAgent = new(definition, assistantClient, [plugin]);
// Act & Assert
try
@@ -107,7 +109,7 @@ public sealed class MixedAgentTests
}
finally
{
await clientProvider.AssistantClient.DeleteAssistantAsync(assistantAgent.Id);
await assistantClient.DeleteAssistantAsync(assistantAgent.Id);
}
}
@@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
@@ -14,6 +15,7 @@ using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI;
using OpenAI.Assistants;
using OpenAI.Files;
using OpenAI.VectorStores;
@@ -46,7 +48,7 @@ public sealed class OpenAIAssistantAgentTests
Assert.NotNull(openAISettings);
await this.ExecuteAgentAsync(
OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(openAISettings.ApiKey)),
OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(openAISettings.ApiKey)),
openAISettings.ChatModelId!,
input,
expectedAnswerContains);
@@ -63,8 +65,11 @@ public sealed class OpenAIAssistantAgentTests
var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get<AzureOpenAIConfiguration>();
Assert.NotNull(azureOpenAIConfiguration);
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
await this.ExecuteAgentAsync(
OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(azureOpenAIConfiguration.Endpoint)),
CreateClient(azureOpenAIConfiguration),
azureOpenAIConfiguration.ChatDeploymentName!,
input,
expectedAnswerContains);
@@ -82,7 +87,7 @@ public sealed class OpenAIAssistantAgentTests
Assert.NotNull(openAISettings);
await this.ExecuteStreamingAgentAsync(
OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(openAISettings.ApiKey)),
OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(openAISettings.ApiKey)),
openAISettings.ModelId,
input,
expectedAnswerContains);
@@ -99,7 +104,7 @@ public sealed class OpenAIAssistantAgentTests
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
await this.ExecuteStreamingAgentAsync(
CreateClientProvider(azureOpenAIConfiguration),
CreateClient(azureOpenAIConfiguration),
azureOpenAIConfiguration.ChatDeploymentName!,
input,
expectedAnswerContains);
@@ -114,11 +119,12 @@ public sealed class OpenAIAssistantAgentTests
public async Task AzureOpenAIAssistantAgentFunctionCallResultAsync()
{
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
OpenAIClientProvider clientProvider = CreateClientProvider(azureOpenAIConfiguration);
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient);
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, assistantClient);
AssistantThread thread = await clientProvider.AssistantClient.CreateThreadAsync();
AssistantThread thread = await assistantClient.CreateThreadAsync();
ChatMessageContent functionResultMessage = new(AuthorRole.Assistant, [new FunctionResultContent("mock-function", result: "A result value")]);
try
{
@@ -128,8 +134,8 @@ public sealed class OpenAIAssistantAgentTests
}
finally
{
await clientProvider.AssistantClient.DeleteThreadAsync(thread.Id);
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteThreadAsync(thread.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
}
}
@@ -142,9 +148,10 @@ public sealed class OpenAIAssistantAgentTests
public async Task AzureOpenAIAssistantAgentTokensAsync()
{
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
OpenAIClientProvider clientProvider = CreateClientProvider(azureOpenAIConfiguration);
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!, instructions: "Repeat the user all of the user messages");
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient)
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!, instructions: "Repeat the user all of the user messages");
OpenAIAssistantAgent agent = new(definition, assistantClient)
{
RunOptions = new()
{
@@ -152,7 +159,7 @@ public sealed class OpenAIAssistantAgentTests
}
};
AssistantThread thread = await clientProvider.AssistantClient.CreateThreadAsync();
AssistantThread thread = await assistantClient.CreateThreadAsync();
ChatMessageContent functionResultMessage = new(AuthorRole.User, "A long time ago there lived a king who was famed for his wisdom through all the land. Nothing was hidden from him, and it seemed as if news of the most secret things was brought to him through the air. But he had a strange custom; every day after dinner, when the table was cleared, and no one else was present, a trusty servant had to bring him one more dish. It was covered, however, and even the servant did not know what was in it, neither did anyone know, for the king never took off the cover to eat of it until he was quite alone.");
try
{
@@ -161,8 +168,8 @@ public sealed class OpenAIAssistantAgentTests
}
finally
{
await clientProvider.AssistantClient.DeleteThreadAsync(thread.Id);
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteThreadAsync(thread.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
}
}
@@ -173,9 +180,10 @@ public sealed class OpenAIAssistantAgentTests
public async Task AzureOpenAIAssistantAgentWithThreadCustomOptionsAsync()
{
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
OpenAIClientProvider clientProvider = CreateClientProvider(azureOpenAIConfiguration);
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient);
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, assistantClient);
ThreadCreationOptions threadOptions = new()
{
@@ -185,7 +193,7 @@ public sealed class OpenAIAssistantAgentTests
new ChatMessageContent(AuthorRole.User, "How may I help you?").ToThreadInitializationMessage(),
}
};
OpenAIAssistantAgentThread agentThread = new(clientProvider.AssistantClient, threadOptions);
OpenAIAssistantAgentThread agentThread = new(assistantClient, threadOptions);
try
{
@@ -211,7 +219,7 @@ public sealed class OpenAIAssistantAgentTests
finally
{
await agentThread.DeleteAsync();
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
}
}
@@ -224,9 +232,10 @@ public sealed class OpenAIAssistantAgentTests
public async Task AzureOpenAIAssistantAgentAdditionalMessagesAsync()
{
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
OpenAIClientProvider clientProvider = CreateClientProvider(azureOpenAIConfiguration);
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient);
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, assistantClient);
ThreadCreationOptions threadOptions = new()
{
@@ -236,7 +245,7 @@ public sealed class OpenAIAssistantAgentTests
new ChatMessageContent(AuthorRole.User, "How may I help you?").ToThreadInitializationMessage(),
}
};
AssistantThread thread = await clientProvider.AssistantClient.CreateThreadAsync(threadOptions);
AssistantThread thread = await assistantClient.CreateThreadAsync(threadOptions);
try
{
var messages = await agent.GetThreadMessagesAsync(thread.Id).ToArrayAsync();
@@ -260,8 +269,8 @@ public sealed class OpenAIAssistantAgentTests
}
finally
{
await clientProvider.AssistantClient.DeleteThreadAsync(thread.Id);
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteThreadAsync(thread.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
}
}
@@ -274,17 +283,18 @@ public sealed class OpenAIAssistantAgentTests
public async Task AzureOpenAIAssistantAgentStreamingFileSearchAsync()
{
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
OpenAIClientProvider clientProvider = CreateClientProvider(azureOpenAIConfiguration);
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient);
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, assistantClient);
// Upload file - Using a table of fictional employees.
OpenAIFileClient fileClient = clientProvider.Client.GetOpenAIFileClient();
OpenAIFileClient fileClient = client.GetOpenAIFileClient();
await using Stream stream = File.OpenRead("TestData/employees.pdf")!;
OpenAIFile fileInfo = await fileClient.UploadFileAsync(stream, "employees.pdf", FileUploadPurpose.Assistants);
// Create a vector-store
VectorStoreClient vectorStoreClient = clientProvider.Client.GetVectorStoreClient();
VectorStoreClient vectorStoreClient = client.GetVectorStoreClient();
CreateVectorStoreOperation result =
await vectorStoreClient.CreateVectorStoreAsync(waitUntilCompleted: false,
new VectorStoreCreationOptions()
@@ -292,7 +302,7 @@ public sealed class OpenAIAssistantAgentTests
FileIds = { fileInfo.Id }
});
AssistantThread thread = await clientProvider.AssistantClient.CreateThreadAsync();
AssistantThread thread = await assistantClient.CreateThreadAsync();
try
{
await agent.AddChatMessageAsync(thread.Id, new(AuthorRole.User, "Who works in sales?"));
@@ -303,8 +313,8 @@ public sealed class OpenAIAssistantAgentTests
}
finally
{
await clientProvider.AssistantClient.DeleteThreadAsync(thread.Id);
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteThreadAsync(thread.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
await vectorStoreClient.DeleteVectorStoreAsync(result.VectorStoreId);
await fileClient.DeleteFileAsync(fileInfo.Id);
}
@@ -317,11 +327,12 @@ public sealed class OpenAIAssistantAgentTests
public async Task AzureOpenAIAssistantAgentWithThreadCustomOptionsStreamingAsync()
{
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
OpenAIClientProvider clientProvider = CreateClientProvider(azureOpenAIConfiguration);
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient);
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(azureOpenAIConfiguration.ChatDeploymentName!);
OpenAIAssistantAgent agent = new(definition, assistantClient);
OpenAIAssistantAgentThread agentThread = new(clientProvider.AssistantClient);
OpenAIAssistantAgentThread agentThread = new(assistantClient);
try
{
@@ -342,7 +353,7 @@ public sealed class OpenAIAssistantAgentTests
finally
{
await agentThread.DeleteAsync();
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
}
}
@@ -353,7 +364,8 @@ public sealed class OpenAIAssistantAgentTests
public async Task AzureOpenAIAssistantAgentDeclarativeAsync()
{
AzureOpenAIConfiguration azureOpenAIConfiguration = this.ReadAzureConfiguration();
OpenAIClientProvider clientProvider = CreateClientProvider(azureOpenAIConfiguration);
OpenAIClient client = CreateClient(azureOpenAIConfiguration);
AssistantClient assistantClient = client.GetAssistantClient();
var text =
$"""
@@ -376,7 +388,7 @@ public sealed class OpenAIAssistantAgentTests
var agent = await factory.CreateAgentFromYamlAsync(text, new() { Kernel = kernel });
Assert.NotNull(agent);
OpenAIAssistantAgentThread agentThread = new(clientProvider.AssistantClient);
OpenAIAssistantAgentThread agentThread = new(assistantClient);
try
{
RunCreationOptions invocationOptions = new()
@@ -394,22 +406,22 @@ public sealed class OpenAIAssistantAgentTests
finally
{
await agentThread.DeleteAsync();
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
}
}
private async Task ExecuteAgentAsync(
OpenAIClientProvider clientProvider,
OpenAIClient client,
string modelName,
string input,
string expected)
{
// Arrange
Kernel kernel = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(modelName, instructions: "Answer questions about the menu.");
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient, [plugin]);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await client.GetAssistantClient().CreateAssistantAsync(modelName, instructions: "Answer questions about the menu.");
OpenAIAssistantAgent agent = new(definition, assistantClient, [plugin]);
try
{
@@ -432,12 +444,12 @@ public sealed class OpenAIAssistantAgentTests
}
finally
{
await clientProvider.AssistantClient.DeleteAssistantAsync(agent.Id);
await assistantClient.DeleteAssistantAsync(agent.Id);
}
}
private async Task ExecuteStreamingAgentAsync(
OpenAIClientProvider clientProvider,
OpenAIClient client,
string modelName,
string input,
string expected)
@@ -446,8 +458,9 @@ public sealed class OpenAIAssistantAgentTests
Kernel kernel = new();
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
Assistant definition = await clientProvider.AssistantClient.CreateAssistantAsync(modelName, instructions: "Answer questions about the menu.");
OpenAIAssistantAgent agent = new(definition, clientProvider.AssistantClient, [plugin]);
AssistantClient assistantClient = client.GetAssistantClient();
Assistant definition = await assistantClient.CreateAssistantAsync(modelName, instructions: "Answer questions about the menu.");
OpenAIAssistantAgent agent = new(definition, assistantClient, [plugin]);
AgentGroupChat chat = new();
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));
@@ -489,9 +502,9 @@ public sealed class OpenAIAssistantAgentTests
return azureOpenAIConfiguration;
}
private static OpenAIClientProvider CreateClientProvider(AzureOpenAIConfiguration azureOpenAIConfiguration)
private static AzureOpenAIClient CreateClient(AzureOpenAIConfiguration azureOpenAIConfiguration)
{
return OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(azureOpenAIConfiguration.Endpoint));
return OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(azureOpenAIConfiguration.Endpoint));
}
public sealed class MenuPlugin
@@ -6,9 +6,9 @@ namespace SemanticKernel.IntegrationTests.TestSettings;
[SuppressMessage("Performance", "CA1812:Internal class that is apparently never instantiated",
Justification = "Configuration classes are instantiated through IConfiguration.")]
internal sealed class AzureAIConfiguration(string connectionString, string chatModelId)
internal sealed class AzureAIConfiguration(string endpoint, string chatModelId)
{
public string ConnectionString { get; set; } = connectionString;
public string Endpoint { get; set; } = endpoint;
public string ChatModelId { get; set; } = chatModelId;
}
@@ -39,13 +39,13 @@ public static class AgentThreadFactory
private static async Task<AgentThread> CreateAzureAIThreadAsync(string? id, Kernel kernel)
{
const string ErrorMessage = "The thread could not be created due to an error response from the service.";
var client = kernel.Services.GetService<Azure.AI.Projects.AgentsClient>() ?? throw new KernelException("The AzureAI thread type requires an AgentsClient to be registered in the kernel.");
var client = kernel.Services.GetService<Azure.AI.Agents.Persistent.PersistentAgentsClient>() ?? throw new KernelException("The AzureAI thread type requires an AgentsClient to be registered in the kernel.");
if (string.IsNullOrWhiteSpace(id))
{
try
{
var threadResponse = await client.CreateThreadAsync().ConfigureAwait(false);
var threadResponse = await client.Threads.CreateThreadAsync().ConfigureAwait(false);
id = threadResponse.Value.Id;
}
catch (RequestFailedException ex)
@@ -2,7 +2,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
@@ -1,13 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Azure.AI.Projects;
using Azure.Identity;
using Azure.AI.Agents.Persistent;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.Agents.OpenAI;
using Microsoft.SemanticKernel.ChatCompletion;
using OpenAI.Assistants;
@@ -41,27 +38,6 @@ public abstract class BaseAzureTest(ITestOutputHelper output) : BaseTest(output,
{ AssistantSampleMetadataKey, bool.TrueString }
});
/// <summary>
/// Provide a <see cref="OpenAIClientProvider"/> according to the configuration settings.
/// </summary>
protected AzureAIClientProvider GetAzureProvider()
{
return AzureAIClientProvider.FromConnectionString(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
}
/// <summary>
/// Provide a <see cref="OpenAIClientProvider"/> according to the configuration settings.
/// </summary>
protected OpenAIClientProvider GetClientProvider()
{
return
this.UseOpenAIConfig ?
OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) :
!string.IsNullOrWhiteSpace(this.ApiKey) ?
OpenAIClientProvider.ForAzureOpenAI(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(this.Endpoint!));
}
/// <summary>
/// Common method to write formatted agent chat content to the console.
/// </summary>
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Azure.AI.Projects;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
@@ -10,21 +10,14 @@ using Microsoft.SemanticKernel.Agents.AzureAI;
/// <summary>
/// Base class for samples that demonstrate the usage of <see cref="AzureAIAgent"/>.
/// </summary>
public abstract class BaseAzureAgentTest : BaseAgentsTest<AIProjectClient>
public abstract class BaseAzureAgentTest : BaseAgentsTest<PersistentAgentsClient>
{
protected BaseAzureAgentTest(ITestOutputHelper output) : base(output)
{
this.Client = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
this.AgentsClient = this.Client.GetAgentsClient();
this.Client = AzureAIAgent.CreateAgentsClient(TestConfiguration.AzureAI.Endpoint, new AzureCliCredential());
}
/// <inheritdoc/>
protected override AIProjectClient Client { get; }
/// <summary>
/// Gets the <see cref="AgentsClient"/>.
/// </summary>
protected AgentsClient AgentsClient { get; }
protected override PersistentAgentsClient Client { get; }
protected async Task DownloadContentAsync(ChatMessageContent message)
{
@@ -39,8 +32,8 @@ public abstract class BaseAzureAgentTest : BaseAgentsTest<AIProjectClient>
protected async Task DownloadFileAsync(string fileId, bool launchViewer = false)
{
AgentFile fileInfo = this.AgentsClient.GetFile(fileId);
if (fileInfo.Purpose == AgentFilePurpose.AgentsOutput)
PersistentAgentFileInfo fileInfo = this.Client.Files.GetFile(fileId);
if (fileInfo.Purpose == PersistentAgentFilePurpose.AgentsOutput)
{
string filePath = Path.Combine(Path.GetTempPath(), Path.GetFileName(fileInfo.Filename));
if (launchViewer)
@@ -48,7 +41,7 @@ public abstract class BaseAzureAgentTest : BaseAgentsTest<AIProjectClient>
filePath = Path.ChangeExtension(filePath, ".png");
}
BinaryData content = await this.AgentsClient.GetFileContentAsync(fileId);
BinaryData content = await this.Client.Files.GetFileContentAsync(fileId);
File.WriteAllBytes(filePath, content.ToArray());
Console.WriteLine($" File #{fileId} saved to: {filePath}");
@@ -106,9 +106,9 @@ public sealed class TestConfiguration
public class AzureAIConfig
{
public string WorkflowEndpoint { get; set; }
public string ConnectionString { get; set; }
public string ChatModelId { get; set; }
public string Endpoint { get; set; }
public string WorkflowEndpoint { get; set; }
public string BingConnectionId { get; set; }
public string VectorStoreId { get; set; }
public string AgentId { get; set; }