mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Add Conformance Integration Tests for AzureAI Package (#2237)
* Conformance tests added and passing * Correct namespace * Update Azure.AI.Project to latest public nuget version
This commit is contained in:
committed by
GitHub
Unverified
parent
480b7fb084
commit
100a1e753e
@@ -17,8 +17,8 @@
|
||||
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
|
||||
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0-beta.435" />
|
||||
<!-- Azure.* -->
|
||||
<PackageVersion Include="Azure.AI.Projects" Version="1.2.0-alpha.20251113.7" />
|
||||
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="1.0.0-alpha.20251113.7" />
|
||||
<PackageVersion Include="Azure.AI.Projects" Version="1.2.0-beta.1" />
|
||||
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="1.0.0-beta.1" />
|
||||
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.7" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="2.5.0-beta.1" />
|
||||
<PackageVersion Include="Azure.Identity" Version="1.17.0" />
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
|
||||
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/Purview/" />
|
||||
<Folder Name="/Samples/Purview/AgentWithPurview/">
|
||||
<Project Path="samples/Purview/AgentWithPurview/AgentWithPurview.csproj" />
|
||||
</Folder>
|
||||
@@ -342,8 +343,8 @@
|
||||
<Project Path="src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
|
||||
<Project Path="src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj" />
|
||||
@@ -351,6 +352,7 @@
|
||||
<Folder Name="/Tests/" />
|
||||
<Folder Name="/Tests/IntegrationTests/">
|
||||
<Project Path="tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj" />
|
||||
<Project Path="tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj" />
|
||||
<Project Path="tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj" />
|
||||
<Project Path="tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj" />
|
||||
|
||||
@@ -3,14 +3,10 @@
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="azure-sdk-for-net" value="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
<packageSourceMapping>
|
||||
<packageSource key="nuget.org">
|
||||
<package pattern="*" />
|
||||
</packageSource>
|
||||
<packageSource key="azure-sdk-for-net">
|
||||
<package pattern="Azure.AI.Project*" />
|
||||
</packageSource>
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace AgentConformance.IntegrationTests;
|
||||
@@ -16,6 +17,8 @@ namespace AgentConformance.IntegrationTests;
|
||||
public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> createAgentFixture) : AgentTests<TAgentFixture>(createAgentFixture)
|
||||
where TAgentFixture : IAgentFixture
|
||||
{
|
||||
public virtual Func<Task<AgentRunOptions?>> AgentRunOptionsFactory { get; set; } = () => Task.FromResult(default(AgentRunOptions));
|
||||
|
||||
[RetryFact(Constants.RetryCount, Constants.RetryDelay)]
|
||||
public virtual async Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
@@ -25,7 +28,7 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var chatResponses = await agent.RunStreamingAsync(thread).ToListAsync();
|
||||
var chatResponses = await agent.RunStreamingAsync(thread, await this.AgentRunOptionsFactory.Invoke()).ToListAsync();
|
||||
}
|
||||
|
||||
[RetryFact(Constants.RetryCount, Constants.RetryDelay)]
|
||||
@@ -37,7 +40,7 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var responseUpdates = await agent.RunStreamingAsync("What is the capital of France.", thread).ToListAsync();
|
||||
var responseUpdates = await agent.RunStreamingAsync("What is the capital of France.", thread, await this.AgentRunOptionsFactory.Invoke()).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Concat(responseUpdates.Select(x => x.Text));
|
||||
@@ -53,7 +56,7 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var responseUpdates = await agent.RunStreamingAsync(new ChatMessage(ChatRole.User, "What is the capital of France."), thread).ToListAsync();
|
||||
var responseUpdates = await agent.RunStreamingAsync(new ChatMessage(ChatRole.User, "What is the capital of France."), thread, await this.AgentRunOptionsFactory.Invoke()).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Concat(responseUpdates.Select(x => x.Text));
|
||||
@@ -74,7 +77,8 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
new ChatMessage(ChatRole.User, "Hello."),
|
||||
new ChatMessage(ChatRole.User, "What is the capital of France.")
|
||||
],
|
||||
thread).ToListAsync();
|
||||
thread,
|
||||
await this.AgentRunOptionsFactory.Invoke()).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var chatResponseText = string.Concat(responseUpdates.Select(x => x.Text));
|
||||
@@ -92,8 +96,9 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var responseUpdates1 = await agent.RunStreamingAsync(Q1, thread).ToListAsync();
|
||||
var responseUpdates2 = await agent.RunStreamingAsync(Q2, thread).ToListAsync();
|
||||
var options = await this.AgentRunOptionsFactory.Invoke();
|
||||
var responseUpdates1 = await agent.RunStreamingAsync(Q1, thread, options).ToListAsync();
|
||||
var responseUpdates2 = await agent.RunStreamingAsync(Q2, thread, options).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var response1Text = string.Concat(responseUpdates1.Select(x => x.Text));
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace AgentConformance.IntegrationTests;
|
||||
@@ -16,6 +17,8 @@ namespace AgentConformance.IntegrationTests;
|
||||
public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFixture) : AgentTests<TAgentFixture>(createAgentFixture)
|
||||
where TAgentFixture : IAgentFixture
|
||||
{
|
||||
public virtual Func<Task<AgentRunOptions?>> AgentRunOptionsFactory { get; set; } = () => Task.FromResult(default(AgentRunOptions));
|
||||
|
||||
[RetryFact(Constants.RetryCount, Constants.RetryDelay)]
|
||||
public virtual async Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
@@ -40,7 +43,7 @@ public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFix
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var response = await agent.RunAsync("What is the capital of France.", thread);
|
||||
var response = await agent.RunAsync("What is the capital of France.", thread, await this.AgentRunOptionsFactory.Invoke());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -58,7 +61,7 @@ public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFix
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var response = await agent.RunAsync(new ChatMessage(ChatRole.User, "What is the capital of France."), thread);
|
||||
var response = await agent.RunAsync(new ChatMessage(ChatRole.User, "What is the capital of France."), thread, await this.AgentRunOptionsFactory.Invoke());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -80,7 +83,8 @@ public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFix
|
||||
new ChatMessage(ChatRole.User, "Hello."),
|
||||
new ChatMessage(ChatRole.User, "What is the capital of France.")
|
||||
],
|
||||
thread);
|
||||
thread,
|
||||
await this.AgentRunOptionsFactory.Invoke());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(response);
|
||||
@@ -99,8 +103,9 @@ public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFix
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var result1 = await agent.RunAsync(Q1, thread);
|
||||
var result2 = await agent.RunAsync(Q2, thread);
|
||||
var options = await this.AgentRunOptionsFactory.Invoke();
|
||||
var result1 = await agent.RunAsync(Q1, thread, options);
|
||||
var result2 = await agent.RunAsync(Q2, thread, options);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Paris", result1.Text);
|
||||
@@ -111,8 +116,8 @@ public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFix
|
||||
Assert.Equal(2, chatHistory.Count(x => x.Role == ChatRole.User));
|
||||
Assert.Equal(2, chatHistory.Count(x => x.Role == ChatRole.Assistant));
|
||||
Assert.Equal(Q1, chatHistory[0].Text);
|
||||
Assert.Equal(Q2, chatHistory[2].Text);
|
||||
Assert.Contains("Paris", chatHistory[1].Text);
|
||||
Assert.Equal(Q2, chatHistory[2].Text);
|
||||
Assert.Contains("Vienna", chatHistory[3].Text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
namespace AzureAI.IntegrationTests;
|
||||
|
||||
public class AIProjectClientAgentRunStreamingPreviousResponseTests() : RunStreamingTests<AIProjectClientFixture>(() => new())
|
||||
{
|
||||
[Fact(Skip = "No messages is not supported")]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class AIProjectClientAgentRunStreamingConversationTests() : RunTests<AIProjectClientFixture>(() => new())
|
||||
{
|
||||
public override Func<Task<AgentRunOptions?>> AgentRunOptionsFactory => async () =>
|
||||
{
|
||||
var conversationId = await this.Fixture.CreateConversationAsync();
|
||||
return new ChatClientAgentRunOptions(new() { ConversationId = conversationId });
|
||||
};
|
||||
|
||||
[Fact(Skip = "No messages is not supported")]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
namespace AzureAI.IntegrationTests;
|
||||
|
||||
public class AIProjectClientAgentRunPreviousResponseTests() : RunTests<AIProjectClientFixture>(() => new())
|
||||
{
|
||||
[Fact(Skip = "No messages is not supported")]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class AIProjectClientAgentRunConversationTests() : RunTests<AIProjectClientFixture>(() => new())
|
||||
{
|
||||
public override Func<Task<AgentRunOptions?>> AgentRunOptionsFactory => async () =>
|
||||
{
|
||||
var conversationId = await this.Fixture.CreateConversationAsync();
|
||||
return new ChatClientAgentRunOptions(new() { ConversationId = conversationId });
|
||||
};
|
||||
|
||||
[Fact(Skip = "No messages is not supported")]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace AzureAI.IntegrationTests;
|
||||
|
||||
public class AIProjectClientChatClientAgentRunStreamingTests() : ChatClientAgentRunStreamingTests<AIProjectClientFixture>(() => new())
|
||||
{
|
||||
[Fact(Skip = "No messages is not supported")]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace AzureAI.IntegrationTests;
|
||||
|
||||
public class AIProjectClientChatClientAgentRunTests() : ChatClientAgentRunTests<AIProjectClientFixture>(() => new())
|
||||
{
|
||||
[Fact(Skip = "No messages is not supported")]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.AI.Projects.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Files;
|
||||
using OpenAI.Responses;
|
||||
using Shared.IntegrationTests;
|
||||
|
||||
namespace AzureAI.IntegrationTests;
|
||||
|
||||
public class AIProjectClientCreateTests
|
||||
{
|
||||
private static readonly AzureAIConfiguration s_config = TestConfiguration.LoadSection<AzureAIConfiguration>();
|
||||
private readonly AIProjectClient _client = new(new Uri(s_config.Endpoint), new AzureCliCredential());
|
||||
|
||||
[Theory]
|
||||
[InlineData("CreateWithChatClientAgentOptionsAsync")]
|
||||
[InlineData("CreateWithChatClientAgentOptionsSync")]
|
||||
[InlineData("CreateWithFoundryOptionsAsync")]
|
||||
[InlineData("CreateWithFoundryOptionsSync")]
|
||||
public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync(string createMechanism)
|
||||
{
|
||||
// Arrange.
|
||||
const string AgentName = "IntegrationTestAgent";
|
||||
const string AgentDescription = "An agent created during integration tests";
|
||||
const string AgentInstructions = "You are an integration test agent";
|
||||
|
||||
// Act.
|
||||
var agent = createMechanism switch
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
description: AgentDescription)),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
|
||||
model: s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
instructions: AgentInstructions,
|
||||
name: AgentName,
|
||||
description: AgentDescription)),
|
||||
"CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
name: AgentName,
|
||||
creationOptions: new AgentVersionCreationOptions(new PromptAgentDefinition(s_config.DeploymentName) { Instructions = AgentInstructions }) { Description = AgentDescription }),
|
||||
"CreateWithFoundryOptionsSync" => this._client.CreateAIAgent(
|
||||
name: AgentName,
|
||||
creationOptions: new AgentVersionCreationOptions(new PromptAgentDefinition(s_config.DeploymentName) { Instructions = AgentInstructions }) { Description = AgentDescription }),
|
||||
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Assert.
|
||||
Assert.NotNull(agent);
|
||||
Assert.Equal(AgentName, agent.Name);
|
||||
Assert.Equal(AgentDescription, agent.Description);
|
||||
Assert.Equal(AgentInstructions, agent.Instructions);
|
||||
|
||||
var agentRecord = await this._client.Agents.GetAgentAsync(agent.Name);
|
||||
Assert.NotNull(agentRecord);
|
||||
Assert.Equal(AgentName, agentRecord.Value.Name);
|
||||
var definition = Assert.IsType<PromptAgentDefinition>(agentRecord.Value.Versions.Latest.Definition);
|
||||
Assert.Equal(AgentDescription, agentRecord.Value.Versions.Latest.Description);
|
||||
Assert.Equal(AgentInstructions, definition.Instructions);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory(Skip = "For manual testing only")]
|
||||
[InlineData("CreateWithChatClientAgentOptionsAsync")]
|
||||
[InlineData("CreateWithChatClientAgentOptionsSync")]
|
||||
[InlineData("CreateWithFoundryOptionsAsync")]
|
||||
[InlineData("CreateWithFoundryOptionsSync")]
|
||||
public async Task CreateAgent_CreatesAgentWithVectorStoresAsync(string createMechanism)
|
||||
{
|
||||
// Arrange.
|
||||
const string AgentName = "VectorStoreAgent";
|
||||
const string AgentInstructions = """
|
||||
You are a helpful agent that can help fetch data from files you know about.
|
||||
Use the File Search Tool to look up codes for words.
|
||||
Do not answer a question unless you can find the answer using the File Search Tool.
|
||||
""";
|
||||
|
||||
// Get the project OpenAI client.
|
||||
var projectOpenAIClient = this._client.GetProjectOpenAIClient();
|
||||
|
||||
// Create a vector store.
|
||||
var searchFilePath = Path.GetTempFileName() + "wordcodelookup.txt";
|
||||
File.WriteAllText(
|
||||
path: searchFilePath,
|
||||
contents: "The word 'apple' uses the code 442345, while the word 'banana' uses the code 673457."
|
||||
);
|
||||
OpenAIFile uploadedAgentFile = projectOpenAIClient.GetProjectFilesClient().UploadFile(
|
||||
filePath: searchFilePath,
|
||||
purpose: FileUploadPurpose.Assistants
|
||||
);
|
||||
var vectorStoreMetadata = await projectOpenAIClient.GetProjectVectorStoresClient().CreateVectorStoreAsync(options: new() { FileIds = { uploadedAgentFile.Id }, Name = "WordCodeLookup_VectorStore" });
|
||||
|
||||
// Act.
|
||||
var agent = createMechanism switch
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]),
|
||||
"CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [ResponseTool.CreateFileSearchTool(vectorStoreIds: [vectorStoreMetadata.Value.Id]).AsAITool()]),
|
||||
"CreateWithFoundryOptionsSync" => this._client.CreateAIAgent(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [ResponseTool.CreateFileSearchTool(vectorStoreIds: [vectorStoreMetadata.Value.Id]).AsAITool()]),
|
||||
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Assert.
|
||||
// Verify that the agent can use the vector store to answer a question.
|
||||
var result = await agent.RunAsync("Can you give me the documented code for 'banana'?");
|
||||
Assert.Contains("673457", result.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
await projectOpenAIClient.GetProjectVectorStoresClient().DeleteVectorStoreAsync(vectorStoreMetadata.Value.Id);
|
||||
await projectOpenAIClient.GetProjectFilesClient().DeleteFileAsync(uploadedAgentFile.Id);
|
||||
File.Delete(searchFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("CreateWithChatClientAgentOptionsAsync")]
|
||||
[InlineData("CreateWithChatClientAgentOptionsSync")]
|
||||
[InlineData("CreateWithFoundryOptionsAsync")]
|
||||
[InlineData("CreateWithFoundryOptionsSync")]
|
||||
public async Task CreateAgent_CreatesAgentWithCodeInterpreterAsync(string createMechanism)
|
||||
{
|
||||
// Arrange.
|
||||
const string AgentName = "CodeInterpreterAgent";
|
||||
const string AgentInstructions = """
|
||||
You are a helpful coding agent. A Python file is provided. Use the Code Interpreter Tool to run the file
|
||||
and report the SECRET_NUMBER value it prints. Respond only with the number.
|
||||
""";
|
||||
|
||||
// Get the project OpenAI client.
|
||||
var projectOpenAIClient = this._client.GetProjectOpenAIClient();
|
||||
|
||||
// Create a python file that prints a known value.
|
||||
var codeFilePath = Path.GetTempFileName() + "secret_number.py";
|
||||
File.WriteAllText(
|
||||
path: codeFilePath,
|
||||
contents: "print(\"SECRET_NUMBER=24601\")" // Deterministic output we will look for.
|
||||
);
|
||||
OpenAIFile uploadedCodeFile = projectOpenAIClient.GetProjectFilesClient().UploadFile(
|
||||
filePath: codeFilePath,
|
||||
purpose: FileUploadPurpose.Assistants
|
||||
);
|
||||
|
||||
// Act.
|
||||
var agent = createMechanism switch
|
||||
{
|
||||
// Hosted tool path (tools supplied via ChatClientAgentOptions)
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]),
|
||||
// Foundry (definitions + resources provided directly)
|
||||
"CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [ResponseTool.CreateCodeInterpreterTool(new CodeInterpreterToolContainer(CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration([uploadedCodeFile.Id]))).AsAITool()]),
|
||||
"CreateWithFoundryOptionsSync" => this._client.CreateAIAgent(
|
||||
model: s_config.DeploymentName,
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [ResponseTool.CreateCodeInterpreterTool(new CodeInterpreterToolContainer(CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration([uploadedCodeFile.Id]))).AsAITool()]),
|
||||
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Assert.
|
||||
var result = await agent.RunAsync("What is the SECRET_NUMBER?");
|
||||
// We expect the model to run the code and surface the number.
|
||||
Assert.Contains("24601", result.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup.
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
await projectOpenAIClient.GetProjectFilesClient().DeleteFileAsync(uploadedCodeFile.Id);
|
||||
File.Delete(codeFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("CreateWithChatClientAgentOptionsAsync")]
|
||||
[InlineData("CreateWithChatClientAgentOptionsSync")]
|
||||
public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync(string createMechanism)
|
||||
{
|
||||
// Arrange.
|
||||
const string AgentName = "WeatherAgent";
|
||||
const string AgentInstructions = "You are a helpful weather assistant. Always call the GetWeather function to answer questions about weather.";
|
||||
|
||||
static string GetWeather(string location) => $"The weather in {location} is sunny with a high of 23C.";
|
||||
var weatherFunction = AIFunctionFactory.Create(GetWeather);
|
||||
|
||||
ChatClientAgent agent = createMechanism switch
|
||||
{
|
||||
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
|
||||
model: s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
|
||||
s_config.DeploymentName,
|
||||
options: new ChatClientAgentOptions(
|
||||
name: AgentName,
|
||||
instructions: AgentInstructions,
|
||||
tools: [weatherFunction])),
|
||||
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Act.
|
||||
var response = await agent.RunAsync("What is the weather like in Amsterdam?");
|
||||
|
||||
// Assert - ensure function was invoked and its output surfaced.
|
||||
var text = response.Text;
|
||||
Assert.Contains("Amsterdam", text, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("sunny", text, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("23", text, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.AI.Projects.OpenAI;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using OpenAI.Responses;
|
||||
using Shared.IntegrationTests;
|
||||
|
||||
namespace AzureAI.IntegrationTests;
|
||||
|
||||
public class AIProjectClientFixture : IChatClientAgentFixture
|
||||
{
|
||||
private static readonly AzureAIConfiguration s_config = TestConfiguration.LoadSection<AzureAIConfiguration>();
|
||||
|
||||
private ChatClientAgent _agent = null!;
|
||||
private AIProjectClient _client = null!;
|
||||
|
||||
public IChatClient ChatClient => this._agent.ChatClient;
|
||||
|
||||
public AIAgent Agent => this._agent;
|
||||
|
||||
public async Task<string> CreateConversationAsync()
|
||||
{
|
||||
var response = await this._client.GetProjectOpenAIClient().GetProjectConversationsClient().CreateProjectConversationAsync();
|
||||
return response.Value.Id;
|
||||
}
|
||||
|
||||
public async Task<List<ChatMessage>> GetChatHistoryAsync(AgentThread thread)
|
||||
{
|
||||
var chatClientThread = (ChatClientAgentThread)thread;
|
||||
|
||||
if (chatClientThread.ConversationId?.StartsWith("conv_", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
// Conversation threads do not persist message history.
|
||||
return await this.GetChatHistoryFromConversationAsync(chatClientThread.ConversationId);
|
||||
}
|
||||
|
||||
if (chatClientThread.ConversationId?.StartsWith("resp_", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
return await this.GetChatHistoryFromResponsesChainAsync(chatClientThread.ConversationId);
|
||||
}
|
||||
|
||||
return chatClientThread.MessageStore is null ? [] : (await chatClientThread.MessageStore.GetMessagesAsync()).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<ChatMessage>> GetChatHistoryFromResponsesChainAsync(string conversationId)
|
||||
{
|
||||
var openAIResponseClient = this._client.GetProjectOpenAIClient().GetProjectResponsesClient();
|
||||
var inputItems = await openAIResponseClient.GetResponseInputItemsAsync(conversationId).ToListAsync();
|
||||
var response = await openAIResponseClient.GetResponseAsync(conversationId);
|
||||
var responseItem = response.Value.OutputItems.FirstOrDefault()!;
|
||||
|
||||
// Take the messages that were the chat history leading up to the current response
|
||||
// remove the instruction messages, and reverse the order so that the most recent message is last.
|
||||
var previousMessages = inputItems
|
||||
.Select(ConvertToChatMessage)
|
||||
.Where(x => x.Text != "You are a helpful assistant.")
|
||||
.Reverse();
|
||||
|
||||
// Convert the response item to a chat message.
|
||||
var responseMessage = ConvertToChatMessage(responseItem);
|
||||
|
||||
// Concatenate the previous messages with the response message to get a full chat history
|
||||
// that includes the current response.
|
||||
return [.. previousMessages, responseMessage];
|
||||
}
|
||||
|
||||
private static ChatMessage ConvertToChatMessage(ResponseItem item)
|
||||
{
|
||||
if (item is MessageResponseItem messageResponseItem)
|
||||
{
|
||||
var role = messageResponseItem.Role == MessageRole.User ? ChatRole.User : ChatRole.Assistant;
|
||||
return new ChatMessage(role, messageResponseItem.Content.FirstOrDefault()?.Text);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("This test currently only supports text messages");
|
||||
}
|
||||
|
||||
private async Task<List<ChatMessage>> GetChatHistoryFromConversationAsync(string conversationId)
|
||||
{
|
||||
List<ChatMessage> messages = [];
|
||||
await foreach (AgentResponseItem item in this._client.GetProjectOpenAIClient().GetProjectConversationsClient().GetProjectConversationItemsAsync(conversationId, order: "asc"))
|
||||
{
|
||||
var openAIItem = item.AsOpenAIResponseItem();
|
||||
if (openAIItem is MessageResponseItem messageItem)
|
||||
{
|
||||
messages.Add(new ChatMessage
|
||||
{
|
||||
Role = new ChatRole(messageItem.Role.ToString()),
|
||||
Contents = messageItem.Content
|
||||
.Where(c => c.Kind is ResponseContentPartKind.OutputText or ResponseContentPartKind.InputText)
|
||||
.Select(c => new TextContent(c.Text))
|
||||
.ToList<AIContent>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public async Task<ChatClientAgent> CreateChatClientAgentAsync(
|
||||
string name = "HelpfulAssistant",
|
||||
string instructions = "You are a helpful assistant.",
|
||||
IList<AITool>? aiTools = null)
|
||||
{
|
||||
return await this._client.CreateAIAgentAsync(GenerateUniqueAgentName(name), model: s_config.DeploymentName, instructions: instructions, tools: aiTools);
|
||||
}
|
||||
|
||||
private static string GenerateUniqueAgentName(string baseName) =>
|
||||
$"{baseName}-{Guid.NewGuid().ToString("N").Substring(0, 8)}";
|
||||
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent) =>
|
||||
this._client.Agents.DeleteAgentAsync(agent.Name);
|
||||
|
||||
public async Task DeleteThreadAsync(AgentThread thread)
|
||||
{
|
||||
var typedThread = (ChatClientAgentThread)thread;
|
||||
if (typedThread.ConversationId?.StartsWith("conv_", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
await this._client.GetProjectOpenAIClient().GetProjectConversationsClient().DeleteConversationAsync(typedThread.ConversationId);
|
||||
}
|
||||
else if (typedThread.ConversationId?.StartsWith("resp_", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
await this.DeleteResponseChainAsync(typedThread.ConversationId!);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteResponseChainAsync(string lastResponseId)
|
||||
{
|
||||
var response = await this._client.GetProjectOpenAIClient().GetProjectResponsesClient().GetResponseAsync(lastResponseId);
|
||||
await this._client.GetProjectOpenAIClient().GetProjectResponsesClient().DeleteResponseAsync(lastResponseId);
|
||||
|
||||
if (response.Value.PreviousResponseId is not null)
|
||||
{
|
||||
await this.DeleteResponseChainAsync(response.Value.PreviousResponseId);
|
||||
}
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
if (this._client is not null && this._agent is not null)
|
||||
{
|
||||
return this._client.Agents.DeleteAgentAsync(this._agent.Name);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
this._client = new(new Uri(s_config.Endpoint), new AzureCliCredential());
|
||||
this._agent = await this.CreateChatClientAgentAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsDebugTargetFrameworks)</TargetFrameworks>
|
||||
<InjectSharedIntegrationTestCode>True</InjectSharedIntegrationTestCode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.AzureAI\Microsoft.Agents.AI.AzureAI.csproj" />
|
||||
<ProjectReference Include="..\AgentConformance.IntegrationTests\AgentConformance.IntegrationTests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user